From 920dea1b2dc1fdcd4c757b915c74a5c4b08a2044 Mon Sep 17 00:00:00 2001
From: "Y.D.X" <73375426+YDX-2147483647@users.noreply.github.com>
Date: Thu, 10 Aug 2023 11:01:02 +0800
Subject: [PATCH 1/3] ci: Check migrations when `just check-all`
---
justfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/justfile b/justfile
index 1de7274..455b741 100644
--- a/justfile
+++ b/justfile
@@ -32,7 +32,7 @@ mypy:
test: (manage "test" "quiz")
# 检查所有
-check-all: mypy test (manage "check")
+check-all: mypy test (manage "check") (manage "makemigrations" "--check")
# 更新依赖、数据库等(拉取他人提交后建议运行)
update: && (manage "migrate")
From c6aa11fa3c81e356b97e871886b84f3a7b24e4aa Mon Sep 17 00:00:00 2001
From: "Y.D.X" <73375426+YDX-2147483647@users.noreply.github.com>
Date: Thu, 10 Aug 2023 12:48:15 +0800
Subject: [PATCH 2/3] lint, doc: More rules for ruff
---
.pre-commit-config.yaml | 2 +-
contest/contest/__init__.py | 1 +
contest/contest/asgi.py | 3 +-
contest/contest/settings.py | 4 +-
contest/contest/urls.py | 5 ++-
contest/contest/wsgi.py | 3 +-
contest/js/__init__.py | 4 ++
contest/quiz/__init__.py | 4 ++
contest/quiz/auth_backends.py | 7 ++--
contest/quiz/constants.py | 14 +++++++
contest/quiz/models.py | 48 ++++++++++-----------
contest/quiz/templatetags/current_page.py | 7 +++-
contest/quiz/templatetags/my_humanize.py | 9 +++-
contest/quiz/tests.py | 14 ++++---
contest/quiz/util.py | 51 +++++++++++++++++++----
contest/quiz/views.py | 29 +++++++++----
contest/theme/__init__.py | 4 ++
pyproject.toml | 47 ++++++++++++++++++---
18 files changed, 194 insertions(+), 62 deletions(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 0691cb6..63485a4 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -9,7 +9,7 @@ repos:
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/charliermarsh/ruff-pre-commit
- rev: "v0.0.264"
+ rev: "v0.0.284"
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix, --show-fixes]
diff --git a/contest/contest/__init__.py b/contest/contest/__init__.py
index e69de29..5bd9c4d 100644
--- a/contest/contest/__init__.py
+++ b/contest/contest/__init__.py
@@ -0,0 +1 @@
+""""项目容器"""
diff --git a/contest/contest/asgi.py b/contest/contest/asgi.py
index da68a83..8f0d437 100644
--- a/contest/contest/asgi.py
+++ b/contest/contest/asgi.py
@@ -1,5 +1,4 @@
-"""
-ASGI config for contest project.
+"""ASGI config for contest project.
It exposes the ASGI callable as a module-level variable named ``application``.
diff --git a/contest/contest/settings.py b/contest/contest/settings.py
index 4109b28..cdb7111 100644
--- a/contest/contest/settings.py
+++ b/contest/contest/settings.py
@@ -1,5 +1,4 @@
-"""
-Django settings for contest project.
+"""Django settings for contest project.
Generated by 'django-admin startproject' using Django 4.1.2.
@@ -9,6 +8,7 @@
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.1/ref/settings/
"""
+from __future__ import annotations
from pathlib import Path
from shutil import which
diff --git a/contest/contest/urls.py b/contest/contest/urls.py
index 95b2c96..e6cf952 100644
--- a/contest/contest/urls.py
+++ b/contest/contest/urls.py
@@ -2,7 +2,9 @@
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.1/topics/http/urls/
-Examples:
+
+Examples
+--------
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
@@ -12,6 +14,7 @@
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
+
"""
from django.contrib import admin
from django.urls import include, path
diff --git a/contest/contest/wsgi.py b/contest/contest/wsgi.py
index d30625d..5d68bf0 100644
--- a/contest/contest/wsgi.py
+++ b/contest/contest/wsgi.py
@@ -1,5 +1,4 @@
-"""
-WSGI config for contest project.
+"""WSGI config for contest project.
It exposes the WSGI callable as a module-level variable named ``application``.
diff --git a/contest/js/__init__.py b/contest/js/__init__.py
index e69de29..76cc848 100644
--- a/contest/js/__init__.py
+++ b/contest/js/__init__.py
@@ -0,0 +1,4 @@
+"""JavaScript 程序及构建系统
+
+请参考 doc/js.md。
+"""
diff --git a/contest/quiz/__init__.py b/contest/quiz/__init__.py
index e69de29..820756a 100644
--- a/contest/quiz/__init__.py
+++ b/contest/quiz/__init__.py
@@ -0,0 +1,4 @@
+"""答题
+
+主应用。
+"""
diff --git a/contest/quiz/auth_backends.py b/contest/quiz/auth_backends.py
index 08a04e9..2f0728b 100644
--- a/contest/quiz/auth_backends.py
+++ b/contest/quiz/auth_backends.py
@@ -12,14 +12,15 @@
class CASBackend(AbstractCASBackend):
+ """Authentication backend for CAS"""
+
def configure_user(self, user: User) -> User:
"""CAS 自动创建 User 后,继续建立 Student"""
-
# todo: Not all users are students.
Student.objects.create(
- user=user,
+ user=user, # type: ignore[misc]
name=user.first_name + user.last_name or user.username,
- ) # type: ignore
+ )
# `AbstractCASBackend`中`User`被标注为标准`User`,
# 而`Student`需要自定义的,导致虚警。
# `AbstractCASBackend`实际会用`django.contrib.auth.get_user_model`,
diff --git a/contest/quiz/constants.py b/contest/quiz/constants.py
index 68d03c5..a3cd4d4 100644
--- a/contest/quiz/constants.py
+++ b/contest/quiz/constants.py
@@ -1,9 +1,18 @@
+"""常量
+
+视图、模板中的所有常量。
+"""
from dataclasses import dataclass
from datetime import timedelta
from typing import NamedTuple
class PageMeta(NamedTuple):
+ """页面的元数据
+
+ 用于 ROUTES。
+ """
+
title: str
"""标题用于`
`、``等"""
@@ -13,6 +22,11 @@ class PageMeta(NamedTuple):
@dataclass(frozen=True)
class ConstantsNamespace:
+ """常量的命名空间
+
+ 为了避免修改,使用 dataclass 而非 dict。
+ """
+
DEADLINE_DURATION = timedelta(minutes=15)
"""作答限时"""
diff --git a/contest/quiz/models.py b/contest/quiz/models.py
index 1397a38..5af48c2 100644
--- a/contest/quiz/models.py
+++ b/contest/quiz/models.py
@@ -20,12 +20,12 @@ class User(AbstractUser):
class Question(models.Model):
content = models.CharField("题干内容", max_length=200)
- def __str__(self) -> str:
- return self.content
-
class Meta:
verbose_name_plural = verbose_name = "题目"
+ def __str__(self) -> str:
+ return self.content
+
class Choice(models.Model):
content = models.CharField("选项内容", max_length=200)
@@ -33,12 +33,12 @@ class Choice(models.Model):
question = models.ForeignKey(Question, verbose_name="题干", on_delete=models.CASCADE)
- def __str__(self) -> str:
- return self.content
-
class Meta:
verbose_name_plural = verbose_name = "选项"
+ def __str__(self) -> str:
+ return self.content
+
class Student(models.Model):
user = models.OneToOneField(
@@ -48,23 +48,26 @@ class Student(models.Model):
verbose_name="用户",
)
- @admin.display(description="最终得分")
- def final_score(self) -> float:
- return max([0] + [r.score() for r in self.response_set.all()])
-
name = models.CharField("姓名", max_length=50)
+ class Meta:
+ verbose_name_plural = verbose_name = "学生"
+
def __str__(self) -> str:
return self.name
- class Meta:
- verbose_name_plural = verbose_name = "学生"
+ @admin.display(description="最终得分")
+ def final_score(self) -> float:
+ return max([0] + [r.score() for r in self.response_set.all()])
class Response(models.Model):
submit_at = models.DateTimeField("提交时刻")
student = models.ForeignKey(Student, verbose_name="作答者", on_delete=models.CASCADE)
+ class Meta:
+ verbose_name_plural = verbose_name = "答卷"
+
def __str__(self) -> str:
return f"{self.student.name} 在 {self.submit_at} 提交的答卷"
@@ -78,9 +81,6 @@ def score(self) -> float:
constants.SCORE * len(self.answer_set.filter(choice__correct=True)) / n_answers
)
- class Meta:
- verbose_name_plural = verbose_name = "答卷"
-
class Answer(models.Model):
response = models.ForeignKey(Response, verbose_name="答卷", on_delete=models.CASCADE)
@@ -89,12 +89,12 @@ class Answer(models.Model):
Choice, verbose_name="所选选项", blank=True, null=True, on_delete=models.CASCADE
)
- def __str__(self) -> str:
- return f"“{self.question}” → “{self.choice}”"
-
class Meta:
verbose_name_plural = verbose_name = "回答"
+ def __str__(self) -> str:
+ return f"“{self.question}” → “{self.choice}”"
+
class DraftResponse(models.Model):
student = models.OneToOneField(
@@ -105,12 +105,12 @@ class DraftResponse(models.Model):
)
deadline = models.DateTimeField("截止时刻")
- def __str__(self) -> str:
- return f"{self.student.name} 的答卷草稿"
-
class Meta:
verbose_name_plural = verbose_name = "答卷草稿"
+ def __str__(self) -> str:
+ return f"{self.student.name} 的答卷草稿"
+
def finalize(self, submit_at: datetime) -> tuple[Response, list[Answer]]:
"""转换为正式 Response
@@ -142,12 +142,12 @@ class DraftAnswer(models.Model):
Choice, verbose_name="所选选项", blank=True, null=True, on_delete=models.CASCADE
)
- def __str__(self) -> str:
- return f"“{self.question}” → “{self.choice}”"
-
class Meta:
verbose_name_plural = verbose_name = "回答草稿"
+ def __str__(self) -> str:
+ return f"“{self.question}” → “{self.choice}”"
+
def finalize(self, response: Response) -> Answer:
"""转换为正式 Answer
diff --git a/contest/quiz/templatetags/current_page.py b/contest/quiz/templatetags/current_page.py
index da3fc9b..244e16d 100644
--- a/contest/quiz/templatetags/current_page.py
+++ b/contest/quiz/templatetags/current_page.py
@@ -1,3 +1,7 @@
+"""检测当前页面
+
+需要`constants.ROUTES`。
+"""
from __future__ import annotations
from typing import TYPE_CHECKING
@@ -8,7 +12,7 @@
if TYPE_CHECKING:
from django.http import HttpRequest
- from ..constants import ConstantsNamespace
+ from quiz.constants import ConstantsNamespace
register = template.Library()
@@ -16,6 +20,7 @@
@register.simple_tag(takes_context=True)
def current_page_title(context: dict, default="") -> str:
+ """当前页面的标题"""
request: HttpRequest = context["request"]
constants: ConstantsNamespace = context["constants"]
diff --git a/contest/quiz/templatetags/my_humanize.py b/contest/quiz/templatetags/my_humanize.py
index e5d629e..c988664 100644
--- a/contest/quiz/templatetags/my_humanize.py
+++ b/contest/quiz/templatetags/my_humanize.py
@@ -1,3 +1,8 @@
+"""人性化地显示数据
+
+补充`django.contrib.humanize`。
+https://docs.djangoproject.com/en/4.2/ref/contrib/humanize/
+"""
from __future__ import annotations
from typing import TYPE_CHECKING
@@ -14,10 +19,12 @@
@register.filter
def natural_delta(value: timedelta) -> str:
+ """时间间隔"""
humanize.i18n.activate("zh_CN")
return humanize.naturaldelta(value)
@register.filter
-def as_score(value: float | int) -> str:
+def as_score(value: float) -> str:
+ """答题得分"""
return f"{value:.0f}"
diff --git a/contest/quiz/tests.py b/contest/quiz/tests.py
index aa96f24..fc82480 100644
--- a/contest/quiz/tests.py
+++ b/contest/quiz/tests.py
@@ -19,7 +19,10 @@
class ResponseModelTests(TestCase):
+ """答卷等模型"""
+
def setUp(self):
+ """初始化"""
self.question = Question.objects.create(
content="The ultimate question of life, the universe, and everything."
)
@@ -32,7 +35,6 @@ def setUp(self):
def test_finalize_answer(self):
"""回答草稿可以转换为回答"""
-
draft = DraftAnswer(question=self.question, choice=self.choice)
final = draft.finalize(Response())
@@ -42,14 +44,16 @@ def test_finalize_answer(self):
def test_finalize_response(self):
"""答卷草稿可以转换为答卷"""
-
draft = DraftResponse.objects.create(deadline=timezone.now(), student=self.student)
final, answers = draft.finalize(submit_at=timezone.now())
self.assertIsInstance(final, Response)
class ContestViewTests(TestCase):
+ """竞赛等视图"""
+
def setUp(self):
+ """初始化"""
contents_map = {
"Angel Attack": [
"Emergency in Tokai",
@@ -82,7 +86,6 @@ def setUp(self):
def test_info_view(self):
"""访问个人中心"""
-
self.client.force_login(self.user)
response = self.client.get(reverse("quiz:info"))
@@ -90,7 +93,6 @@ def test_info_view(self):
def test_contest_view(self):
"""访问首页,登录,然后开始作答,再原地刷新"""
-
response = self.client.get(reverse("quiz:index"))
self.assertEqual(response.status_code, HTTPStatus.OK)
@@ -259,13 +261,15 @@ def test_non_student_user(self):
class EmptyDataTests(TestCase):
+ """空题库"""
+
def setUp(self):
+ """初始化"""
self.user = User.objects.create_user(username="Asuka")
self.student = Student.objects.create(user=self.user)
def test_contest_without_any_question(self):
"""空题库时尝试答题"""
-
self.client.force_login(self.user)
with self.assertRaisesMessage(ValueError, "Sample larger than population"):
diff --git a/contest/quiz/util.py b/contest/quiz/util.py
index 397371d..f2d2183 100644
--- a/contest/quiz/util.py
+++ b/contest/quiz/util.py
@@ -1,29 +1,50 @@
+"""装饰器等小工具"""
from __future__ import annotations
from functools import wraps
from http import HTTPStatus
from typing import TYPE_CHECKING
-from django.http import (
- HttpResponse,
-)
from django.shortcuts import render
from .constants import constants
if TYPE_CHECKING:
- from typing import Callable
+ from collections.abc import Callable
from django.contrib.auth.models import AbstractBaseUser, AnonymousUser
- from django.http import HttpRequest
+ from django.http import (
+ HttpRequest,
+ HttpResponse,
+ )
from .models import User
class AuthenticatedHttpRequest(HttpRequest):
+ """登录者发送的网络请求
+
+ 仅用于标注类型。
+
+ 网络请求默认的`request.user`是`User | AnonymousUser`,这一子类排除了后一种可能。
+
+ 请参考 django-stubs 文档。
+ https://github.com/typeddjango/django-stubs/blob/325006ccd72a9b838cf841b1c61ba4092b084f99/README.md#how-can-i-create-a-httprequest-thats-guaranteed-to-have-an-authenticated-user
+ """
+
user: User
def is_student(user: AbstractBaseUser | AnonymousUser) -> bool:
+ """是否为学生
+
+ Examples
+ --------
+ ```
+ @user_passes_test(is_student)
+ def my_view(request: HttpRequest) -> HttpResponse:
+ ...
+ ```
+ """
return hasattr(user, "student")
@@ -32,14 +53,16 @@ def student_only(
) -> Callable[[AuthenticatedHttpRequest], HttpResponse]:
"""If not student, render `not_student.html`
- # Example
-
+ Examples
+ --------
```
@login_required
@student_only
def my_view(request: AuthenticatedHttpRequest) -> HttpResponse:
...
```
+
+ 如果去掉`@login_required`,则不区分“未登录者”和“登录但非学生者”,回应模糊。
"""
@wraps(view_func)
@@ -60,4 +83,18 @@ def wrapper(request):
def is_student_taking_contest(user: AbstractBaseUser | AnonymousUser) -> bool:
+ """是否为正在参赛的学生
+
+ Examples
+ --------
+ ```
+ @login_required
+ @student_only
+ @user_passes_test(is_student_taking_contest)
+ def my_view(request: AuthenticatedHttpRequest) -> HttpResponse:
+ ...
+ ```
+
+ 如果去掉`@student_only`,则非学生使用者访问时会转到登录页面,莫名奇妙;现在这样则会提示必须是学生才行。
+ """
return hasattr(user, "student") and hasattr(user.student, "draft_response")
diff --git a/contest/quiz/views.py b/contest/quiz/views.py
index c43b9ea..7c0cdbf 100644
--- a/contest/quiz/views.py
+++ b/contest/quiz/views.py
@@ -44,7 +44,6 @@ def continue_or_finalize(draft: DraftResponse) -> bool:
https://docs.djangoproject.com/en/4.2/ref/models/instances/#django.db.models.Model.delete
https://docs.djangoproject.com/en/4.2/ref/models/instances/#refreshing-objects-from-database
"""
-
if draft.outdated():
# 提交之前的草稿
response, answers = draft.finalize(submit_at=draft.deadline)
@@ -61,14 +60,8 @@ def continue_or_finalize(draft: DraftResponse) -> bool:
def manage_status(
user: User | AnonymousUser,
-) -> (
- Literal["not taking"]
- | Literal["deadline passed"]
- | Literal["taking contest"]
- | Literal[""]
-):
+) -> Literal["not taking", "deadline passed", "taking contest", ""]:
"""检查状态及自动提交"""
-
if user.is_authenticated and is_student(user):
student = user.student
@@ -85,9 +78,17 @@ def manage_status(
class IndexView(TemplateView):
+ """首页"""
+
template_name = "index.html"
def get_context_data(self, **kwargs) -> dict[str, Any]:
+ """补充上下文数据
+
+ status: Literal["not taking", "deadline passed", "taking contest", ""] — 自动提交状态
+ constants: ConstantsNamespace — 常量
+
+ """
context = super().get_context_data(**kwargs)
context["status"] = manage_status(self.request.user)
@@ -98,6 +99,8 @@ def get_context_data(self, **kwargs) -> dict[str, Any]:
@method_decorator(student_only, name="dispatch")
class InfoView(LoginRequiredMixin, IndexView):
+ """个人中心"""
+
template_name = "info.html"
@@ -105,6 +108,7 @@ class InfoView(LoginRequiredMixin, IndexView):
@student_only
@require_GET
def contest(request: AuthenticatedHttpRequest) -> HttpResponse:
+ """发卷"""
student: Student = request.user.student
if hasattr(student, "draft_response"):
@@ -147,6 +151,11 @@ def contest(request: AuthenticatedHttpRequest) -> HttpResponse:
@user_passes_test(is_student_taking_contest)
@require_POST
def contest_update(request: AuthenticatedHttpRequest) -> HttpResponse:
+ """暂存答卷
+
+ 正常暂存则回复 200 OK,超时则回复 403 Forbidden。
+ 请求非法会视情况回复 400 Bad Request 或 404 Not Found。
+ """
student: Student = request.user.student
draft_response: DraftResponse = student.draft_response
@@ -187,6 +196,10 @@ def contest_update(request: AuthenticatedHttpRequest) -> HttpResponse:
@user_passes_test(is_student_taking_contest)
@require_POST
def contest_submit(request: AuthenticatedHttpRequest) -> HttpResponse:
+ """交卷
+
+ 只是将之前暂存的草稿归档,而本次发送的数据完全无用。
+ """
student: Student = request.user.student
# 1. Convert from draft
diff --git a/contest/theme/__init__.py b/contest/theme/__init__.py
index e69de29..a485a52 100644
--- a/contest/theme/__init__.py
+++ b/contest/theme/__init__.py
@@ -0,0 +1,4 @@
+"""主题样式及构建系统
+
+请参考 doc/theme.md。
+"""
diff --git a/pyproject.toml b/pyproject.toml
index 6351145..b625c78 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -41,15 +41,52 @@ priority = "default"
[tool.ruff]
select = [
- "E", # pycodestyle errors
- "W", # pycodestyle warnings
- "F", # Pyflakes
- "I", # isort
- "N", # pep8-naming
+ "E", # pycodestyle errors
+ "W", # pycodestyle warnings
+ "F", # Pyflakes
+ "I", # isort
+ "N", # pep8-naming
+ "D", # pydocstyle
+ "UP", # pyupgrade
+ "B", # flake8-bugbear
+ "A", # flake8-builtins
+ "C4", # flake8-comprehensions
+ "DTZ", # flake8-datetimez
+ "DJ", # flake8-django
+ "FA", # flake8-future-annotations
+ "PYI", # flake8-pyi
+ "TID", # flake8-tidy-imports
+ "TCH", # flake8-type-checking
+ "PGH", # pygrep-hooks
+]
+ignore = [
+ "D105", # undocumented-magic-method
+ "D203", # in favor of blank-line-before-class (D211)
+ "D213", # in favor of multi-line-summary-first-line (D212)
+ "D400", # ends-in-period, because of Chinese
+ "D415", # ends-in-punctuation, because of Chinese
]
line-length = 95
ignore-init-module-imports = true
+[tool.ruff.per-file-ignores]
+# No need to document Django defaults
+# - D100: Missing docstring in public module
+# - D101: Missing docstring in public class
+# - D104: Missing docstring in public package
+"apps.py" = ["D100", "D101"]
+"admin.py" = ["D100", "D101"]
+"models.py" = [
+ "D100",
+ "D101", # 有`Meta`即可
+ "D102", # undocumented-public-method,有`@admin.display`即可
+ "D106", # undocumented-public-nested-class,例如`Meta`
+]
+"tests.py" = ["D100"]
+"urls.py" = ["D100"]
+"views.py" = ["D100"]
+"**/templatetags/__init__.py" = ["D104"]
+
[tool.black]
line-length = 95
From 744c4e9668f54b7807ba78a29d82c2edfe27a162 Mon Sep 17 00:00:00 2001
From: "Y.D.X" <73375426+YDX-2147483647@users.noreply.github.com>
Date: Thu, 10 Aug 2023 13:11:34 +0800
Subject: [PATCH 3/3] lint: ESLint
---
.eslintrc.yml | 15 +++++++++++
.pre-commit-config.yaml | 12 +++++++++
contest/js/static_src/rollup.config.dev.mjs | 28 ++++++++++----------
contest/js/static_src/rollup.config.prod.mjs | 22 +++++++--------
contest/js/static_src/src/contest.js | 18 ++++++-------
contest/js/static_src/src/status.js | 2 +-
contest/theme/static_src/postcss.config.js | 10 +++----
7 files changed, 67 insertions(+), 40 deletions(-)
create mode 100644 .eslintrc.yml
diff --git a/.eslintrc.yml b/.eslintrc.yml
new file mode 100644
index 0000000..256f37a
--- /dev/null
+++ b/.eslintrc.yml
@@ -0,0 +1,15 @@
+env:
+ browser: true
+ es2021: true
+extends: standard
+parserOptions:
+ ecmaVersion: latest
+ sourceType: module
+rules:
+ indent:
+ - error
+ - 4
+ camelcase: 0
+ comma-dangle:
+ - error
+ - only-multiline
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 63485a4..3d6cdb4 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,6 +24,18 @@ repos:
args:
- --quiet
- id: djlint-django
+ - repo: https://github.com/pre-commit/mirrors-eslint
+ rev: v8.46.0
+ hooks:
+ - id: eslint
+ args:
+ - --fix
+ additional_dependencies:
+ - eslint@^8.0.1
+ - eslint-config-standard@latest
+ - eslint-plugin-import@^2.25.2
+ - eslint-plugin-n@^15.0.0 || ^16.0.0
+ - eslint-plugin-promise@^6.0.0
- repo: https://github.com/crate-ci/typos
rev: v1.15.0
hooks:
diff --git a/contest/js/static_src/rollup.config.dev.mjs b/contest/js/static_src/rollup.config.dev.mjs
index e5d494c..d959d9f 100644
--- a/contest/js/static_src/rollup.config.dev.mjs
+++ b/contest/js/static_src/rollup.config.dev.mjs
@@ -2,18 +2,18 @@ import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
export default {
- input: {
- status: './src/status.js',
- contest: './src/contest.js',
- toggle_mobile_menu: './src/toggle_mobile_menu.js',
- },
- output: {
- dir: '../static/js/dist/',
- format: 'es',
- entryFileNames: '[name].js',
- },
- plugins: [
- commonjs(),
- resolve(),
- ],
+ input: {
+ status: './src/status.js',
+ contest: './src/contest.js',
+ toggle_mobile_menu: './src/toggle_mobile_menu.js',
+ },
+ output: {
+ dir: '../static/js/dist/',
+ format: 'es',
+ entryFileNames: '[name].js',
+ },
+ plugins: [
+ commonjs(),
+ resolve(),
+ ],
}
diff --git a/contest/js/static_src/rollup.config.prod.mjs b/contest/js/static_src/rollup.config.prod.mjs
index f8e777d..9f27564 100644
--- a/contest/js/static_src/rollup.config.prod.mjs
+++ b/contest/js/static_src/rollup.config.prod.mjs
@@ -6,15 +6,15 @@ import terser from '@rollup/plugin-terser'
import dev_config from './rollup.config.dev.mjs'
export default {
- input: dev_config.input,
- output: {
- ...dev_config.output,
- compact: true,
- },
- plugins: [
- commonjs(),
- resolve(),
- babel({ babelHelpers: 'bundled' }),
- terser(),
- ],
+ input: dev_config.input,
+ output: {
+ ...dev_config.output,
+ compact: true,
+ },
+ plugins: [
+ commonjs(),
+ resolve(),
+ babel({ babelHelpers: 'bundled' }),
+ terser(),
+ ],
}
diff --git a/contest/js/static_src/src/contest.js b/contest/js/static_src/src/contest.js
index 6627ca6..11e2e6a 100644
--- a/contest/js/static_src/src/contest.js
+++ b/contest/js/static_src/src/contest.js
@@ -1,17 +1,17 @@
import Swal from 'sweetalert2'
-import { get_data } from "./util"
+import { get_data } from './util'
/** 时间允差,毫秒 */
const TIME_MARGIN = 10 * 1e3
-const update_url = get_data("update-url")
-const deadline = Date.parse(get_data("deadline"))
-const deadline_duration_seconds = get_data("deadline-duration")
+const update_url = get_data('update-url')
+const deadline = Date.parse(get_data('deadline'))
+const deadline_duration_seconds = get_data('deadline-duration')
//! 作答
-const form = document.querySelector("form")
+const form = document.querySelector('form')
const filed_sets = Array.from(form.querySelectorAll('fieldset'))
const inputs = filed_sets.map(set => Array.from(set.querySelectorAll('input')))
/** @type {HTMLDivElement} */
@@ -31,9 +31,9 @@ async function update_contest_progress () {
method: 'POST',
body: new FormData(form)
})
- if (!response.ok && response.status == 403) {
+ if (!response.ok && response.status === 403) {
// 锁定表单
- filed_sets.forEach(set => set.disabled = true)
+ filed_sets.forEach(set => { set.disabled = true })
// 提示提交
const result = await Swal.fire({
@@ -75,7 +75,7 @@ setTimeout(update_contest_progress_periodically, deadline - Date.now() - TIME_MA
/** 时间进度 */
class TimeProgress {
- constructor() {
+ constructor () {
/** @type {HTMLDivElement} */
this.bar = document.querySelector('div#time-progress-bar')
/** @type {HTMLSpanElement} */
@@ -112,7 +112,7 @@ class TimeProgress {
setTimeout(() => { this.update() }, 100)
}
} else {
- this.bar.style.width = `100%`
+ this.bar.style.width = '100%'
this.text.textContent = '0秒'
}
diff --git a/contest/js/static_src/src/status.js b/contest/js/static_src/src/status.js
index ff7e8d1..2069d7c 100644
--- a/contest/js/static_src/src/status.js
+++ b/contest/js/static_src/src/status.js
@@ -5,7 +5,7 @@ import { get_data } from './util'
/** @type {"not taking" | "taking contest" | "deadline passed" | ""} */
const status = get_data('status')
-if (status == 'deadline passed') {
+if (status === 'deadline passed') {
Swal.fire({
title: '上次作答已超时',
text: '截止前作答部分已尽量保存。建议下次早些提交。',
diff --git a/contest/theme/static_src/postcss.config.js b/contest/theme/static_src/postcss.config.js
index 0b09b34..78179be 100644
--- a/contest/theme/static_src/postcss.config.js
+++ b/contest/theme/static_src/postcss.config.js
@@ -1,7 +1,7 @@
module.exports = {
- plugins: {
- "postcss-import": {},
- "postcss-simple-vars": {},
- "postcss-nested": {}
- },
+ plugins: {
+ 'postcss-import': {},
+ 'postcss-simple-vars': {},
+ 'postcss-nested': {}
+ },
}