From 85a72c5cf4be9b6a9f08fc6bce31de29dbfdb255 Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Wed, 1 Mar 2023 19:19:15 +0100
Subject: [PATCH 1/5] Activate linting for frontend files

---
 .eslintrc.js    | 216 ++++++++++++++++++++++++++++++++++++++++++++++++
 .prettierignore |  91 ++++++++++++++++++++
 tox.ini         |   8 +-
 3 files changed, 312 insertions(+), 3 deletions(-)
 create mode 100644 .eslintrc.js
 create mode 100644 .prettierignore

diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..996cd5a
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,216 @@
+module.exports = {
+  extends: [
+    "eslint:recommended",
+    "plugin:vue/strongly-recommended",
+    //    "plugin:prettier/recommended",
+    "plugin:@intlify/vue-i18n/recommended",
+  ],
+  rules: {
+    "no-unused-vars": "warn",
+    "vue/no-unused-vars": "off",
+    "vue/multi-word-component-names": "off",
+    "@intlify/vue-i18n/key-format-style": [
+      "error",
+      "snake_case",
+      {
+        splitByDots: false,
+      },
+    ],
+    // "@intlify/vue-i18n/no-unused-keys": ["warn", {}],
+    "@intlify/vue-i18n/no-raw-text": [
+      "error",
+      {
+        ignoreNodes: ["v-icon"],
+        ignorePattern: "^[-–—·#:()\\[\\]&\\.\\s]+$",
+      },
+    ],
+    // Fixes for prettier (avoid eslint-config-prettier)
+    // The following rules can be used in some cases. See the README for more
+    // information. (These are marked with `0` instead of `"off"` so that a
+    // script can distinguish them.)
+    curly: 0,
+    "lines-around-comment": 0,
+    "max-len": 0,
+    "no-confusing-arrow": 0,
+    "no-mixed-operators": 0,
+    "no-tabs": 0,
+    "no-unexpected-multiline": 0,
+    quotes: 0,
+    "@typescript-eslint/quotes": 0,
+    "babel/quotes": 0,
+    "vue/html-self-closing": 0,
+    "vue/max-len": 0,
+
+    // The rest are rules that you never need to enable when using Prettier.
+    "array-bracket-newline": "off",
+    "array-bracket-spacing": "off",
+    "array-element-newline": "off",
+    "arrow-parens": "off",
+    "arrow-spacing": "off",
+    "block-spacing": "off",
+    "brace-style": "off",
+    "comma-dangle": "off",
+    "comma-spacing": "off",
+    "comma-style": "off",
+    "computed-property-spacing": "off",
+    "dot-location": "off",
+    "eol-last": "off",
+    "func-call-spacing": "off",
+    "function-call-argument-newline": "off",
+    "function-paren-newline": "off",
+    "generator-star": "off",
+    "generator-star-spacing": "off",
+    "implicit-arrow-linebreak": "off",
+    indent: "off",
+    "jsx-quotes": "off",
+    "key-spacing": "off",
+    "keyword-spacing": "off",
+    "linebreak-style": "off",
+    "multiline-ternary": "off",
+    "newline-per-chained-call": "off",
+    "new-parens": "off",
+    "no-arrow-condition": "off",
+    "no-comma-dangle": "off",
+    "no-extra-parens": "off",
+    "no-extra-semi": "off",
+    "no-floating-decimal": "off",
+    "no-mixed-spaces-and-tabs": "off",
+    "no-multi-spaces": "off",
+    "no-multiple-empty-lines": "off",
+    "no-reserved-keys": "off",
+    "no-space-before-semi": "off",
+    "no-trailing-spaces": "off",
+    "no-whitespace-before-property": "off",
+    "no-wrap-func": "off",
+    "nonblock-statement-body-position": "off",
+    "object-curly-newline": "off",
+    "object-curly-spacing": "off",
+    "object-property-newline": "off",
+    "one-var-declaration-per-line": "off",
+    "operator-linebreak": "off",
+    "padded-blocks": "off",
+    "quote-props": "off",
+    "rest-spread-spacing": "off",
+    semi: "off",
+    "semi-spacing": "off",
+    "semi-style": "off",
+    "space-after-function-name": "off",
+    "space-after-keywords": "off",
+    "space-before-blocks": "off",
+    "space-before-function-paren": "off",
+    "space-before-function-parentheses": "off",
+    "space-before-keywords": "off",
+    "space-in-brackets": "off",
+    "space-in-parens": "off",
+    "space-infix-ops": "off",
+    "space-return-throw-case": "off",
+    "space-unary-ops": "off",
+    "space-unary-word-ops": "off",
+    "switch-colon-spacing": "off",
+    "template-curly-spacing": "off",
+    "template-tag-spacing": "off",
+    "unicode-bom": "off",
+    "wrap-iife": "off",
+    "wrap-regex": "off",
+    "yield-star-spacing": "off",
+    "@babel/object-curly-spacing": "off",
+    "@babel/semi": "off",
+    "@typescript-eslint/brace-style": "off",
+    "@typescript-eslint/comma-dangle": "off",
+    "@typescript-eslint/comma-spacing": "off",
+    "@typescript-eslint/func-call-spacing": "off",
+    "@typescript-eslint/indent": "off",
+    "@typescript-eslint/keyword-spacing": "off",
+    "@typescript-eslint/member-delimiter-style": "off",
+    "@typescript-eslint/no-extra-parens": "off",
+    "@typescript-eslint/no-extra-semi": "off",
+    "@typescript-eslint/object-curly-spacing": "off",
+    "@typescript-eslint/semi": "off",
+    "@typescript-eslint/space-before-blocks": "off",
+    "@typescript-eslint/space-before-function-paren": "off",
+    "@typescript-eslint/space-infix-ops": "off",
+    "@typescript-eslint/type-annotation-spacing": "off",
+    "babel/object-curly-spacing": "off",
+    "babel/semi": "off",
+    "flowtype/boolean-style": "off",
+    "flowtype/delimiter-dangle": "off",
+    "flowtype/generic-spacing": "off",
+    "flowtype/object-type-curly-spacing": "off",
+    "flowtype/object-type-delimiter": "off",
+    "flowtype/quotes": "off",
+    "flowtype/semi": "off",
+    "flowtype/space-after-type-colon": "off",
+    "flowtype/space-before-generic-bracket": "off",
+    "flowtype/space-before-type-colon": "off",
+    "flowtype/union-intersection-spacing": "off",
+    "react/jsx-child-element-spacing": "off",
+    "react/jsx-closing-bracket-location": "off",
+    "react/jsx-closing-tag-location": "off",
+    "react/jsx-curly-newline": "off",
+    "react/jsx-curly-spacing": "off",
+    "react/jsx-equals-spacing": "off",
+    "react/jsx-first-prop-new-line": "off",
+    "react/jsx-indent": "off",
+    "react/jsx-indent-props": "off",
+    "react/jsx-max-props-per-line": "off",
+    "react/jsx-newline": "off",
+    "react/jsx-one-expression-per-line": "off",
+    "react/jsx-props-no-multi-spaces": "off",
+    "react/jsx-tag-spacing": "off",
+    "react/jsx-wrap-multilines": "off",
+    "standard/array-bracket-even-spacing": "off",
+    "standard/computed-property-even-spacing": "off",
+    "standard/object-curly-even-spacing": "off",
+    "unicorn/empty-brace-spaces": "off",
+    "unicorn/no-nested-ternary": "off",
+    "unicorn/number-literal-case": "off",
+    "vue/array-bracket-newline": "off",
+    "vue/array-bracket-spacing": "off",
+    "vue/arrow-spacing": "off",
+    "vue/block-spacing": "off",
+    "vue/block-tag-newline": "off",
+    "vue/brace-style": "off",
+    "vue/comma-dangle": "off",
+    "vue/comma-spacing": "off",
+    "vue/comma-style": "off",
+    "vue/dot-location": "off",
+    "vue/func-call-spacing": "off",
+    "vue/html-closing-bracket-newline": "off",
+    "vue/html-closing-bracket-spacing": "off",
+    "vue/html-end-tags": "off",
+    "vue/html-indent": "off",
+    "vue/html-quotes": "off",
+    "vue/key-spacing": "off",
+    "vue/keyword-spacing": "off",
+    "vue/max-attributes-per-line": "off",
+    "vue/multiline-html-element-content-newline": "off",
+    "vue/multiline-ternary": "off",
+    "vue/mustache-interpolation-spacing": "off",
+    "vue/no-extra-parens": "off",
+    "vue/no-multi-spaces": "off",
+    "vue/no-spaces-around-equal-signs-in-attribute": "off",
+    "vue/object-curly-newline": "off",
+    "vue/object-curly-spacing": "off",
+    "vue/object-property-newline": "off",
+    "vue/operator-linebreak": "off",
+    "vue/quote-props": "off",
+    "vue/script-indent": "off",
+    "vue/singleline-html-element-content-newline": "off",
+    "vue/space-in-parens": "off",
+    "vue/space-infix-ops": "off",
+    "vue/space-unary-ops": "off",
+    "vue/template-curly-spacing": "off",
+  },
+  settings: {
+    "vue-i18n": {
+      localeDir: "./aleksis/core/frontend/messages/*.{json}",
+      messageSyntaxVersion: "^8.0.0",
+    },
+  },
+  env: {
+    es2021: true,
+  },
+  parserOptions: {
+    ecmaVersion: "latest",
+  },
+};
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..38d141b
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,91 @@
+# Byte-compiled / optimized / DLL files
+*$py.class
+*.py[cod]
+__pycache__/
+
+# Distribution / packaging
+*.egg
+*.egg-info/
+.Python
+.eggs/
+.installed.cfg
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+
+# Installer logs
+pip-delete-this-directory.txt
+pip-log.txt
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+
+# pyenv
+.python-version
+
+# Environments
+.env
+.venv
+ENV/
+env/
+venv/
+
+# Editors
+*~
+DEADJOE
+\#*#
+
+# IntelliJ
+.idea
+.idea/
+
+# Database
+db.sqlite3
+
+# Sphinx
+docs/_build/
+
+# TeX
+*.aux
+
+# Generated files
+/node_modules/
+/static/
+/whoosh_index/
+poetry.lock
+
+.coverage
+.mypy_cache/
+.tox/
+htmlcov/
+maintenance_mode_state.txt
+media/
+package-lock.json
+yarn.lock
+
+# VSCode
+.vscode/
+.history/
+*.code-workspace
+
+/cache
+
+# Add HTML files to avoid problems with unsupported Django templates
+*.html
+
+# Do not check/reformat generated files
+aleksis/core/util/licenses.json
+.vite/
diff --git a/tox.ini b/tox.ini
index 749e060..6e4b77a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,16 +1,15 @@
 [tox]
 skipsdist = True
 skip_missing_interpreters = true
-envlist = py37,py38,py39
+envlist = py39,py310,py311
 
 [testenv]
 whitelist_externals = poetry
-		      sudo
 skip_install = true
 envdir = {toxworkdir}/globalenv
 commands_pre =
      poetry install
-     poetry run aleksis-admin yarn install
+     poetry run aleksis-admin vite build
      poetry run aleksis-admin collectstatic --no-input
 commands =
     poetry run pytest --cov=. {posargs} aleksis/
@@ -27,6 +26,8 @@ commands =
     poetry run black --check --diff aleksis/
     poetry run isort -c --diff --stdout aleksis/
     poetry run flake8 {posargs} aleksis/
+    poetry run sh -c "aleksis-admin yarn run prettier --check --ignore-path={toxinidir}/.prettierignore {toxinidir}"
+    poetry run sh -c "aleksis-admin yarn run eslint {toxinidir}/aleksis/**/*/frontend/**/*.{js,vue} --config={toxinidir}/.eslintrc.js --resolve-plugins-relative-to=."
 
 [testenv:security]
 commands =
@@ -46,6 +47,7 @@ commands = poetry run make -C docs/ html {posargs}
 commands =
     poetry run isort aleksis/
     poetry run black aleksis/
+    poetry run sh -c "aleksis-admin yarn run prettier --write --ignore-path={toxinidir}/.prettierignore {toxinidir}"
 
 [testenv:makemessages]
 commands =
-- 
GitLab


From 9bf54b3d041940f88fe370ef6d5b952a3023b98d Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Wed, 1 Mar 2023 19:25:01 +0100
Subject: [PATCH 2/5] Reformat

---
 .gitlab-ci.yml                                |  36 ++---
 aleksis/apps/stoelindeling/frontend/index.js  | 124 +++++++--------
 .../stoelindeling/frontend/messages/de.json   |   1 -
 .../stoelindeling/frontend/messages/en.json   |   1 -
 .../stoelindeling/frontend/messages/ru.json   |   6 +-
 .../stoelindeling/frontend/messages/uk.json   |   6 +-
 .../static/css/stoelindeling/seating_plan.css |  80 +++++-----
 .../js/stoelindeling/edit_seating_plan.js     | 149 ++++++++++--------
 aleksis/apps/stoelindeling/views.py           |   2 +-
 9 files changed, 213 insertions(+), 192 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2b53733..8beab2e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,19 +1,19 @@
 include:
-    - project: "AlekSIS/official/AlekSIS"
-      file: /ci/general.yml
-    - project: "AlekSIS/official/AlekSIS"
-      file: /ci/prepare/lock.yml
-    - project: "AlekSIS/official/AlekSIS"
-      file: /ci/test/lint.yml
-    - project: "AlekSIS/official/AlekSIS"
-      file: /ci/test/security.yml
-    - project: "AlekSIS/official/AlekSIS"
-      file: /ci/build/dist.yml
-    - project: "AlekSIS/official/AlekSIS"
-      file: /ci/build/docs.yml
-    - project: "AlekSIS/official/AlekSIS"
-      file: "/ci/deploy/trigger_dist.yml"
-    - project: "AlekSIS/official/AlekSIS"
-      file: "/ci/docker/image.yml"
-    - project: "AlekSIS/official/AlekSIS"
-      file: /ci/publish/pypi.yml
+  - project: "AlekSIS/official/AlekSIS"
+    file: /ci/general.yml
+  - project: "AlekSIS/official/AlekSIS"
+    file: /ci/prepare/lock.yml
+  - project: "AlekSIS/official/AlekSIS"
+    file: /ci/test/lint.yml
+  - project: "AlekSIS/official/AlekSIS"
+    file: /ci/test/security.yml
+  - project: "AlekSIS/official/AlekSIS"
+    file: /ci/build/dist.yml
+  - project: "AlekSIS/official/AlekSIS"
+    file: /ci/build/docs.yml
+  - project: "AlekSIS/official/AlekSIS"
+    file: "/ci/deploy/trigger_dist.yml"
+  - project: "AlekSIS/official/AlekSIS"
+    file: "/ci/docker/image.yml"
+  - project: "AlekSIS/official/AlekSIS"
+    file: /ci/publish/pypi.yml
diff --git a/aleksis/apps/stoelindeling/frontend/index.js b/aleksis/apps/stoelindeling/frontend/index.js
index 367dad9..cf576c5 100644
--- a/aleksis/apps/stoelindeling/frontend/index.js
+++ b/aleksis/apps/stoelindeling/frontend/index.js
@@ -1,69 +1,67 @@
-export default
-  {
-    meta: {
-      inMenu: true,
-      titleKey: "stoelindeling.menu_title",
-      icon: "mdi-view-list-outline",
-      permission: "stoelindeling.view_seatingplans_rule",
-    },
-    props: {
-      byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
-    },
-    children: [
-      {
-        path: "seating_plans/",
-        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
-        name: "stoelindeling.seatingPlans",
-        meta: {
-          inMenu: true,
-          titleKey: "stoelindeling.menu_title",
-          icon: "mdi-view-list-outline",
-          permission: "stoelindeling.view_seatingplans_rule",
-        },
-        props: {
-          byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
-        },
+export default {
+  meta: {
+    inMenu: true,
+    titleKey: "stoelindeling.menu_title",
+    icon: "mdi-view-list-outline",
+    permission: "stoelindeling.view_seatingplans_rule",
+  },
+  props: {
+    byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
+  },
+  children: [
+    {
+      path: "seating_plans/",
+      component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+      name: "stoelindeling.seatingPlans",
+      meta: {
+        inMenu: true,
+        titleKey: "stoelindeling.menu_title",
+        icon: "mdi-view-list-outline",
+        permission: "stoelindeling.view_seatingplans_rule",
       },
-      {
-        path: "seating_plans/create/",
-        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
-        name: "stoelindeling.createSeatingPlan",
-        props: {
-          byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
-        },
+      props: {
+        byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
       },
-      {
-        path: "seating_plans/:pk/",
-        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
-        name: "stoelindeling.seatingPlan",
-        props: {
-          byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
-        },
+    },
+    {
+      path: "seating_plans/create/",
+      component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+      name: "stoelindeling.createSeatingPlan",
+      props: {
+        byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
       },
-      {
-        path: "seating_plans/:pk/edit/",
-        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
-        name: "stoelindeling.editSeatingPlan",
-        props: {
-          byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
-        },
+    },
+    {
+      path: "seating_plans/:pk/",
+      component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+      name: "stoelindeling.seatingPlan",
+      props: {
+        byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
       },
-      {
-        path: "seating_plans/:pk/copy/",
-        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
-        name: "stoelindeling.copySeatingPlan",
-        props: {
-          byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
-        },
+    },
+    {
+      path: "seating_plans/:pk/edit/",
+      component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+      name: "stoelindeling.editSeatingPlan",
+      props: {
+        byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
       },
-      {
-        path: "seating_plans/:pk/delete/",
-        component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
-        name: "stoelindeling.deleteSeatingPlan",
-        props: {
-          byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
-        },
+    },
+    {
+      path: "seating_plans/:pk/copy/",
+      component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+      name: "stoelindeling.copySeatingPlan",
+      props: {
+        byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
+      },
+    },
+    {
+      path: "seating_plans/:pk/delete/",
+      component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"),
+      name: "stoelindeling.deleteSeatingPlan",
+      props: {
+        byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true,
       },
-    ],
-  }
-
+    },
+  ],
+};
diff --git a/aleksis/apps/stoelindeling/frontend/messages/de.json b/aleksis/apps/stoelindeling/frontend/messages/de.json
index 4d4542c..2422e7d 100644
--- a/aleksis/apps/stoelindeling/frontend/messages/de.json
+++ b/aleksis/apps/stoelindeling/frontend/messages/de.json
@@ -3,4 +3,3 @@
     "menu_title": "Sitzpläne"
   }
 }
-
diff --git a/aleksis/apps/stoelindeling/frontend/messages/en.json b/aleksis/apps/stoelindeling/frontend/messages/en.json
index a95f9cc..ff661f5 100644
--- a/aleksis/apps/stoelindeling/frontend/messages/en.json
+++ b/aleksis/apps/stoelindeling/frontend/messages/en.json
@@ -3,4 +3,3 @@
     "menu_title": "Seating plans"
   }
 }
-
diff --git a/aleksis/apps/stoelindeling/frontend/messages/ru.json b/aleksis/apps/stoelindeling/frontend/messages/ru.json
index b58b25b..6b23606 100644
--- a/aleksis/apps/stoelindeling/frontend/messages/ru.json
+++ b/aleksis/apps/stoelindeling/frontend/messages/ru.json
@@ -1,5 +1,5 @@
 {
-    "stoelindeling": {
-        "menu_title": "Планы рассадки"
-    }
+  "stoelindeling": {
+    "menu_title": "Планы рассадки"
+  }
 }
diff --git a/aleksis/apps/stoelindeling/frontend/messages/uk.json b/aleksis/apps/stoelindeling/frontend/messages/uk.json
index 64657a7..3c089f2 100644
--- a/aleksis/apps/stoelindeling/frontend/messages/uk.json
+++ b/aleksis/apps/stoelindeling/frontend/messages/uk.json
@@ -1,5 +1,5 @@
 {
-    "stoelindeling": {
-        "menu_title": "Плани розсаджень"
-    }
+  "stoelindeling": {
+    "menu_title": "Плани розсаджень"
+  }
 }
diff --git a/aleksis/apps/stoelindeling/static/css/stoelindeling/seating_plan.css b/aleksis/apps/stoelindeling/static/css/stoelindeling/seating_plan.css
index 7a7891f..7446b6a 100644
--- a/aleksis/apps/stoelindeling/static/css/stoelindeling/seating_plan.css
+++ b/aleksis/apps/stoelindeling/static/css/stoelindeling/seating_plan.css
@@ -1,78 +1,78 @@
 .seat {
-    width: 80px;
-    height: 80px;
-    margin: 2px;
-    padding: 2px;
-    display: flex;
-    flex-direction: column;
-    justify-content: center;
-    text-align: center;
-    align-items: center;
-    font-size: 0.8em;
-    word-break: break-word;
-    position: relative;
+  width: 80px;
+  height: 80px;
+  margin: 2px;
+  padding: 2px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  text-align: center;
+  align-items: center;
+  font-size: 0.8em;
+  word-break: break-word;
+  position: relative;
 }
 
 .drag-area .seat {
-    cursor: grab;
+  cursor: grab;
 }
 
 #seats {
-    overflow: auto;
-    width: auto;
+  overflow: auto;
+  width: auto;
 }
 
 .seat-grid {
-    display: flex;
-    overflow-x: scroll;
+  display: flex;
+  overflow-x: scroll;
 }
 
 .seat-grid-col {
-    margin: 0;
+  margin: 0;
 }
 
 .seat-grid-cell {
-    width: 86px;
-    height: 86px;
-    border: 1px dashed darkgrey;
+  width: 86px;
+  height: 86px;
+  border: 1px dashed darkgrey;
 }
 
 #not-used-seats {
-    display: flex;
-    flex-direction: row;
-    flex-wrap: wrap;
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
 }
 
 .seat-grid-add-col {
-    padding: 0 5px;
+  padding: 0 5px;
 }
 
 .seat-grid-add-col button {
-    height: 100%;
-    padding: 0 5px;
+  height: 100%;
+  padding: 0 5px;
 }
 
 .seat-grid-add-row {
-    padding: 5px 5px;
-    width: 100%;
+  padding: 5px 5px;
+  width: 100%;
 }
 
 .seat-grid-add-row button {
-    width: 100%;
+  width: 100%;
 }
 
 .seat .clip-circle {
-    width: auto;
-    height: 60%;
-    position: absolute;
-    left: 16px;
-    right: 16px;
-    top: 5px;
+  width: auto;
+  height: 60%;
+  position: absolute;
+  left: 16px;
+  right: 16px;
+  top: 5px;
 }
 
 .seat-name {
-    position: absolute;
-    bottom: 2px;
-    left: 2px;
-    right: 2px;
+  position: absolute;
+  bottom: 2px;
+  left: 2px;
+  right: 2px;
 }
diff --git a/aleksis/apps/stoelindeling/static/js/stoelindeling/edit_seating_plan.js b/aleksis/apps/stoelindeling/static/js/stoelindeling/edit_seating_plan.js
index d1dd2d9..e515556 100644
--- a/aleksis/apps/stoelindeling/static/js/stoelindeling/edit_seating_plan.js
+++ b/aleksis/apps/stoelindeling/static/js/stoelindeling/edit_seating_plan.js
@@ -1,90 +1,115 @@
 function setPosition(event) {
-    const parent = $(event.to);
-    const xPos = parent.data("x");
-    const yPos = parent.data("y");
+  const parent = $(event.to);
+  const xPos = parent.data("x");
+  const yPos = parent.data("y");
 
-    let pk = $(event.item).attr("data-pk");
-    let sel = $("#seating-plan-form input[value=" + pk + "].pk-input");
-    let x = sel.nextAll("input.x-input").first();
-    let y = sel.nextAll("input.y-input").first();
-    let seated = sel.nextAll("input.seated-input").first();
+  let pk = $(event.item).attr("data-pk");
+  let sel = $("#seating-plan-form input[value=" + pk + "].pk-input");
+  let x = sel.nextAll("input.x-input").first();
+  let y = sel.nextAll("input.y-input").first();
+  let seated = sel.nextAll("input.seated-input").first();
 
-    if (parent.hasClass("seat-grid-cell")) {
-        x.val(xPos);
-        y.val(yPos);
-        seated.val("True");
-    } else {
-        x.val("0");
-        y.val("0");
-        seated.val("False");
-    }
+  if (parent.hasClass("seat-grid-cell")) {
+    x.val(xPos);
+    y.val(yPos);
+    seated.val("True");
+  } else {
+    x.val("0");
+    y.val("0");
+    seated.val("False");
+  }
 }
 
 function enableSeatGridCells() {
-    $('.seat-grid-cell').sortable({
-        group: 'seats',
-        animation: 150,
-        onEnd: setPosition
-    });
+  $(".seat-grid-cell").sortable({
+    group: "seats",
+    animation: 150,
+    onEnd: setPosition,
+  });
 }
 
 function getStartY() {
-    return Number.parseInt($(".seat-grid .seat-grid-col .seat-grid-cell").first().data("y"))
+  return Number.parseInt(
+    $(".seat-grid .seat-grid-col .seat-grid-cell").first().data("y")
+  );
 }
 
 function getEndY() {
-    return Number.parseInt($(".seat-grid .seat-grid-col .seat-grid-cell").last().data("y"))
+  return Number.parseInt(
+    $(".seat-grid .seat-grid-col .seat-grid-cell").last().data("y")
+  );
 }
 
 function getStartX() {
-    return Number.parseInt($(".seat-grid .seat-grid-col .seat-grid-cell").first().data("x"))
+  return Number.parseInt(
+    $(".seat-grid .seat-grid-col .seat-grid-cell").first().data("x")
+  );
 }
 
 function getEndX() {
-    return Number.parseInt($(".seat-grid .seat-grid-col .seat-grid-cell").last().data("x"))
+  return Number.parseInt(
+    $(".seat-grid .seat-grid-col .seat-grid-cell").last().data("x")
+  );
 }
 
 function buildRow(x) {
-    const el = $("<div class='seat-grid-col'></div>");
-    for (let y = getStartY(); y <= getEndY(); y++) {
-        el.append("<div class='seat-grid-cell' data-x='" + x + "' data-y='" + y + "'></div>");
-    }
-    return el;
+  const el = $("<div class='seat-grid-col'></div>");
+  for (let y = getStartY(); y <= getEndY(); y++) {
+    el.append(
+      "<div class='seat-grid-cell' data-x='" + x + "' data-y='" + y + "'></div>"
+    );
+  }
+  return el;
 }
 
-
 $(document).ready(function () {
-    $('#not-used-seats').sortable({
-        group: 'seats',
-        animation: 150,
-        onEnd: setPosition
-    });
-    enableSeatGridCells();
+  $("#not-used-seats").sortable({
+    group: "seats",
+    animation: 150,
+    onEnd: setPosition,
+  });
+  enableSeatGridCells();
 
-    $("#seat-row-add-top").click(function () {
-        const y = getStartY() - 1;
-        $(".seat-grid .seat-grid-col").each(function (idx, el) {
-            const x = Number.parseInt($(el).children(".seat-grid-cell").first().data("x"));
-            $(el).prepend("<div class='seat-grid-cell' data-x='" + x + "' data-y='" + y + "'></div>");
-        });
-        enableSeatGridCells();
-    });
-    $("#seat-row-add-bottom").click(function () {
-        const y = getEndY() + 1;
-        $(".seat-grid .seat-grid-col").each(function (idx, el) {
-            const x = Number.parseInt($(el).children(".seat-grid-cell").first().data("x"));
-            $(el).prepend("<div class='seat-grid-cell' data-x='" + x + "' data-y='" + y + "'></div>");
-        });
-        enableSeatGridCells();
+  $("#seat-row-add-top").click(function () {
+    const y = getStartY() - 1;
+    $(".seat-grid .seat-grid-col").each(function (idx, el) {
+      const x = Number.parseInt(
+        $(el).children(".seat-grid-cell").first().data("x")
+      );
+      $(el).prepend(
+        "<div class='seat-grid-cell' data-x='" +
+          x +
+          "' data-y='" +
+          y +
+          "'></div>"
+      );
     });
-    $("#seat-col-add-left").click(function () {
-        const el = buildRow(getStartX() - 1);
-        el.insertBefore(".seat-grid-col:first");
-        enableSeatGridCells();
-    });
-    $("#seat-col-add-right").click(function () {
-        const el = buildRow(getEndX() + 1);
-        el.insertAfter(".seat-grid-col:last");
-        enableSeatGridCells();
+    enableSeatGridCells();
+  });
+  $("#seat-row-add-bottom").click(function () {
+    const y = getEndY() + 1;
+    $(".seat-grid .seat-grid-col").each(function (idx, el) {
+      const x = Number.parseInt(
+        $(el).children(".seat-grid-cell").first().data("x")
+      );
+      $(el).prepend(
+        "<div class='seat-grid-cell' data-x='" +
+          x +
+          "' data-y='" +
+          y +
+          "'></div>"
+      );
     });
+    enableSeatGridCells();
+  });
+  $("#seat-col-add-left").click(function () {
+    const el = buildRow(getStartX() - 1);
+    el.insertBefore(".seat-grid-col:first");
+    enableSeatGridCells();
+  });
+  $("#seat-col-add-right").click(function () {
+    const el = buildRow(getEndX() + 1);
+    el.insertAfter(".seat-grid-col:last");
+    enableSeatGridCells();
+  });
 });
diff --git a/aleksis/apps/stoelindeling/views.py b/aleksis/apps/stoelindeling/views.py
index b3a9db1..f2e95ec 100644
--- a/aleksis/apps/stoelindeling/views.py
+++ b/aleksis/apps/stoelindeling/views.py
@@ -1,6 +1,6 @@
 from django.contrib import messages
 from django.shortcuts import redirect
-from django.urls import reverse, reverse_lazy
+from django.urls import reverse
 from django.utils.decorators import method_decorator
 from django.utils.translation import gettext as _
 from django.views.decorators.cache import never_cache
-- 
GitLab


From d1867295d1edfc14000477d0c8febb6a76e71f64 Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Wed, 1 Mar 2023 19:26:14 +0100
Subject: [PATCH 3/5] Bump version to 2.0b0 and update changelog

---
 CHANGELOG.rst  | 21 +++++++++++----------
 docs/conf.py   |  2 +-
 pyproject.toml |  8 ++++----
 3 files changed, 16 insertions(+), 15 deletions(-)

diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index ad28d55..670c5e1 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -6,21 +6,21 @@ All notable changes to this project will be documented in this file.
 The format is based on `Keep a Changelog`_,
 and this project adheres to `Semantic Versioning`_.
 
-Breaking changes
-----------------
+`2.0b0` - 2023-03-01
+--------------------
+
+This version requires AlekSIS-Core 3.0. It is incompatible with any previous
+version.
 
 Removed
 ~~~~~~~
 
-* Remove legacy menu entries.
-
-Unreleased
-----------
+* Legacy menu integration for AlekSIS-Core pre-3.0
 
 Added
 ~~~~~
 
-* Support for usage with new AlekSIS SPA.
+* Add SPA support for AlekSIS-Core 3.0
 
 Fixed
 ~~~~~
@@ -58,6 +58,7 @@ Added
 .. _Semantic Versioning: https://semver.org/spec/v2.0.0.html
 
 
-.. _1.0: https://edugit.org/AlekSIS/official//AlekSIS-App-Stoelindeling/-/tags/1.0
-.. _1.0.1: https://edugit.org/AlekSIS/official//AlekSIS-App-Stoelindeling/-/tags/1.0.1
-.. _1.0.2: https://edugit.org/AlekSIS/official//AlekSIS-App-Stoelindeling/-/tags/1.0.2
+.. _1.0: https://edugit.org/AlekSIS/official/AlekSIS-App-Stoelindeling/-/tags/1.0
+.. _1.0.1: https://edugit.org/AlekSIS/official/AlekSIS-App-Stoelindeling/-/tags/1.0.1
+.. _1.0.2: https://edugit.org/AlekSIS/official/AlekSIS-App-Stoelindeling/-/tags/1.0.2
+.. _2.0b0: https://edugit.org/AlekSIS/official/AlekSIS-App-Stoelindeling/-/tags/2.0b0
diff --git a/docs/conf.py b/docs/conf.py
index dd57a50..cd65ba3 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -31,7 +31,7 @@ author = "The AlekSIS Team"
 # The short X.Y version
 version = "2.0"
 # The full version, including alpha/beta/rc tags
-release = "2.0.0.dev0"
+release = "2.0b0"
 
 
 # -- General configuration ---------------------------------------------------
diff --git a/pyproject.toml b/pyproject.toml
index f8b75e6..daa48da 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "AlekSIS-App-Stoelindeling"
-version = "2.0.dev0"
+version = "2.0b0"
 packages = [
     { include = "aleksis" }
 ]
@@ -15,7 +15,7 @@ description = "AlekSIS (School Information System) — App Stoelindeling (Cr
 authors = ["Jonathan Weth <dev@jonathanweth.de>"]
 license = "EUPL-1.2-or-later"
 homepage = "https://aleksis.org"
-repository = "https://edugit.org/AlekSIS/official//AlekSIS-App-Stoelindeling"
+repository = "https://edugit.org/AlekSIS/official/AlekSIS-App-Stoelindeling"
 documentation = "https://aleksis.org/official/AlekSIS/docs/html/"
 classifiers = [
     "Environment :: Web Environment",
@@ -30,8 +30,8 @@ secondary = true
 
 [tool.poetry.dependencies]
 python = "^3.9"
-aleksis-core = "^3.0.dev3"
-aleksis-app-chronos = "^3.0.dev1"
+aleksis-core = "^3.0b0"
+aleksis-app-chronos = "^3.0b0"
 
 [tool.poetry.dev-dependencies]
 aleksis-builddeps = "*"
-- 
GitLab


From 3c16e69b1e882b64b318b14f13bc01c35f2b05f0 Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Mon, 15 May 2023 17:59:08 +0200
Subject: [PATCH 4/5] Bump version to 2.0

---
 CHANGELOG.rst  | 10 ++++++++--
 docs/conf.py   |  2 +-
 pyproject.toml |  6 +++---
 3 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 670c5e1..bc2693f 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -6,8 +6,13 @@ All notable changes to this project will be documented in this file.
 The format is based on `Keep a Changelog`_,
 and this project adheres to `Semantic Versioning`_.
 
-`2.0b0` - 2023-03-01
---------------------
+`2.0`_ - 2023-05-15
+-------------------
+
+Nothing changed.
+
+`2.0b0`_ - 2023-03-01
+---------------------
 
 This version requires AlekSIS-Core 3.0. It is incompatible with any previous
 version.
@@ -62,3 +67,4 @@ Added
 .. _1.0.1: https://edugit.org/AlekSIS/official/AlekSIS-App-Stoelindeling/-/tags/1.0.1
 .. _1.0.2: https://edugit.org/AlekSIS/official/AlekSIS-App-Stoelindeling/-/tags/1.0.2
 .. _2.0b0: https://edugit.org/AlekSIS/official/AlekSIS-App-Stoelindeling/-/tags/2.0b0
+.. _2.0: https://edugit.org/AlekSIS/official/AlekSIS-App-Stoelindeling/-/tags/2.0
diff --git a/docs/conf.py b/docs/conf.py
index cd65ba3..4753bf4 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -31,7 +31,7 @@ author = "The AlekSIS Team"
 # The short X.Y version
 version = "2.0"
 # The full version, including alpha/beta/rc tags
-release = "2.0b0"
+release = "2.0"
 
 
 # -- General configuration ---------------------------------------------------
diff --git a/pyproject.toml b/pyproject.toml
index daa48da..49ec693 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "AlekSIS-App-Stoelindeling"
-version = "2.0b0"
+version = "2.0"
 packages = [
     { include = "aleksis" }
 ]
@@ -30,8 +30,8 @@ secondary = true
 
 [tool.poetry.dependencies]
 python = "^3.9"
-aleksis-core = "^3.0b0"
-aleksis-app-chronos = "^3.0b0"
+aleksis-core = "^3.0"
+aleksis-app-chronos = "^3.0"
 
 [tool.poetry.dev-dependencies]
 aleksis-builddeps = "*"
-- 
GitLab


From c6ebded4a48750bf30a9329a641e3024a5c10b21 Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Mon, 15 May 2023 20:08:32 +0200
Subject: [PATCH 5/5] Bump version to 2.0.1.dev0

---
 CHANGELOG.rst  | 3 +++
 docs/conf.py   | 2 +-
 pyproject.toml | 2 +-
 3 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index bc2693f..3057df7 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -6,6 +6,9 @@ All notable changes to this project will be documented in this file.
 The format is based on `Keep a Changelog`_,
 and this project adheres to `Semantic Versioning`_.
 
+Unreleased
+----------
+
 `2.0`_ - 2023-05-15
 -------------------
 
diff --git a/docs/conf.py b/docs/conf.py
index 4753bf4..5afcc39 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -31,7 +31,7 @@ author = "The AlekSIS Team"
 # The short X.Y version
 version = "2.0"
 # The full version, including alpha/beta/rc tags
-release = "2.0"
+release = "2.0.1.dev0"
 
 
 # -- General configuration ---------------------------------------------------
diff --git a/pyproject.toml b/pyproject.toml
index 49ec693..389751f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "AlekSIS-App-Stoelindeling"
-version = "2.0"
+version = "2.0.1.dev0"
 packages = [
     { include = "aleksis" }
 ]
-- 
GitLab