Compare commits

..

1 Commits

8
.idea/.gitignore vendored

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.12" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12" project-jdk-type="Python SDK" />
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/zyd2025.iml" filepath="$PROJECT_DIR$/.idea/zyd2025.iml" />
</modules>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="FacetManager">
<facet type="django" name="Django">
<configuration>
<option name="rootFolder" value="$MODULE_DIR$/src" />
<option name="settingsModule" value="settings.py" />
<option name="manageScript" value="$MODULE_DIR$/src/manage.py" />
<option name="environment" value="&lt;map/&gt;" />
<option name="doNotUseTestRunner" value="false" />
<option name="trackFilePattern" value="migrations" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="jdk" jdkName="Python 3.12" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/src/templates" />
</list>
</option>
</component>
</module>

Binary file not shown.

@ -0,0 +1,10 @@
[run]
source = .
include = *.py
omit =
*migrations*
*tests*
*.html
*whoosh_cn_backend*
*settings.py*
*venv*

@ -1 +0,0 @@
DJANGO_SECRET_KEY='f)jnngb81u_)*5!e%zclyr=zoh^61268qhm!kebtl8_c-pp_d^'

@ -90,7 +90,6 @@ python manage.py migrate
# 创建一个超级管理员账户
python manage.py createsuperuser
--zyd202020 zbzydzfy123456
```
### 5. 运行项目

@ -58,21 +58,3 @@ class BlogUserAdmin(UserAdmin):
list_display_links = ('id', 'username')
ordering = ('-id',)
search_fields = ('username', 'nickname', 'email')
# 修复字段集配置
fieldsets = (
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name', 'email', 'nickname')}),
(_('Permissions'), {
'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions'),
}),
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
(_('Additional info'), {'fields': ('source', 'creation_time', 'last_modify_time')}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('username', 'email', 'password1', 'password2'),
}),
)

@ -56,7 +56,7 @@ class AccountTest(TestCase):
self.assertEqual(response.status_code, 200)
def test_validate_register(self):
self.assertEqual(
self.assertEquals(
0, len(
BlogUser.objects.filter(
email='user123@user.com')))
@ -66,7 +66,7 @@ class AccountTest(TestCase):
'password1': 'password123!q@wE#R$T',
'password2': 'password123!q@wE#R$T',
})
self.assertEqual(
self.assertEquals(
1, len(
BlogUser.objects.filter(
email='user123@user.com')))
@ -205,31 +205,3 @@ class AccountTest(TestCase):
self.assertEqual(resp.status_code, 200)
def test_favorites_page(self):
user = BlogUser.objects.create_superuser(
email="fav@user.com",
username="favuser",
password="favpass")
category = Category()
category.name = "favcategory"
category.save()
article = Article()
article.title = "favorite title"
article.body = "favorite body"
article.author = user
article.category = category
article.type = 'a'
article.status = 'p'
article.save()
# 收藏
article.favorite_users.add(user)
# 登录并访问收藏页
self.client.login(username='favuser', password='favpass')
resp = self.client.get(reverse('account:favorites'))
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, "favorite title")

@ -25,5 +25,4 @@ urlpatterns = [re_path(r'^login/$',
re_path(r'^forget_password_code/$',
views.ForgetPasswordEmailCode.as_view(),
name='forget_password_code'),
path('favorites/', views.FavoriteArticlesView.as_view(), name='favorites'),
]

@ -20,8 +20,6 @@ from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import FormView, RedirectView
from django.views.generic.list import ListView
from django.contrib.auth.mixins import LoginRequiredMixin
from djangoblog.utils import send_email, get_sha256, get_current_site, generate_code, delete_sidebar_cache
from . import utils
@ -204,20 +202,3 @@ class ForgetPasswordEmailCode(View):
utils.set_code(to_email, code)
return HttpResponse("ok")
class FavoriteArticlesView(LoginRequiredMixin, ListView):
template_name = 'blog/article_index.html'
context_object_name = 'article_list'
paginate_by = settings.PAGINATE_BY if hasattr(settings, 'PAGINATE_BY') else 10
def get_queryset(self):
return self.request.user.favorite_articles.filter(status='p').order_by('-pub_time')
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['page_type'] = '我的收藏'
ctx['tag_name'] = None
ctx['linktype'] = 'i'
ctx['sort'] = 'latest'
return ctx

@ -9,16 +9,8 @@ class Command(BaseCommand):
help = 'create test datas'
def handle(self, *args, **options):
# 先尝试通过用户名查找用户
try:
user = get_user_model().objects.get(username='测试用户')
except get_user_model().DoesNotExist:
# 如果用户不存在,则创建新用户
user = get_user_model().objects.create(
email='test@test.com',
username='测试用户',
password=make_password('test!q@w#eTYU')
)
user = get_user_model().objects.get_or_create(
email='test@test.com', username='测试用户', password=make_password('test!q@w#eTYU'))[0]
pcategory = Category.objects.get_or_create(
name='我是父类目', parent_category=None)[0]
@ -27,26 +19,21 @@ class Command(BaseCommand):
name='子类目', parent_category=pcategory)[0]
category.save()
# 使用 get_or_create 处理基础标签
basetag, created = Tag.objects.get_or_create(name="标签")
basetag = Tag()
basetag.name = "标签"
basetag.save()
for i in range(1, 20):
article, article_created = Article.objects.get_or_create(
article = Article.objects.get_or_create(
category=category,
title='nice title ' + str(i),
body='nice content ' + str(i),
author=user
)
# 使用 get_or_create 处理每个标签
tag, tag_created = Tag.objects.get_or_create(name="标签" + str(i))
# 只有在文章是新创建的时候才添加标签
if article_created:
article.tags.add(tag)
article.tags.add(basetag)
article.save()
author=user)[0]
tag = Tag()
tag.name = "标签" + str(i)
tag.save()
article.tags.add(tag)
article.tags.add(basetag)
article.save()
from djangoblog.utils import cache
cache.clear()

@ -1,33 +0,0 @@
from django.db import migrations, models
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('blog', '0006_alter_blogsettings_options'),
]
operations = [
migrations.AddField(
model_name='article',
name='like_count',
field=models.PositiveIntegerField(default=0, verbose_name='likes'),
),
migrations.AddField(
model_name='article',
name='favorite_count',
field=models.PositiveIntegerField(default=0, verbose_name='favorites'),
),
migrations.AddField(
model_name='article',
name='like_users',
field=models.ManyToManyField(blank=True, related_name='liked_articles', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='article',
name='favorite_users',
field=models.ManyToManyField(blank=True, related_name='favorite_articles', to=settings.AUTH_USER_MODEL),
),
]

@ -88,18 +88,6 @@ class Article(BaseModel):
default='o')
type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a')
views = models.PositiveIntegerField(_('views'), default=0)
like_count = models.PositiveIntegerField('likes', default=0)
favorite_count = models.PositiveIntegerField('favorites', default=0)
like_users = models.ManyToManyField(
settings.AUTH_USER_MODEL,
related_name='liked_articles',
blank=True
)
favorite_users = models.ManyToManyField(
settings.AUTH_USER_MODEL,
related_name='favorite_articles',
blank=True
)
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_('author'),
@ -151,26 +139,6 @@ class Article(BaseModel):
self.views += 1
self.save(update_fields=['views'])
def like_once_by_user(self, user):
if not user.is_authenticated:
return False
if self.like_users.filter(pk=user.pk).exists():
return False
self.like_users.add(user)
self.like_count += 1
self.save(update_fields=['like_count'])
return True
def favorite_once_by_user(self, user):
if not user.is_authenticated:
return False
if self.favorite_users.filter(pk=user.pk).exists():
return False
self.favorite_users.add(user)
self.favorite_count += 1
self.save(update_fields=['favorite_count'])
return True
def comment_list(self):
cache_key = 'article_comments_{id}'.format(id=self.id)
value = cache.get(cache_key)

@ -273,7 +273,7 @@ def load_article_metas(article, user):
@register.inclusion_tag('blog/tags/article_pagination.html')
def load_pagination_info(page_obj, page_type, tag_name, sort=None):
def load_pagination_info(page_obj, page_type, tag_name):
previous_url = ''
next_url = ''
if page_type == '':
@ -334,12 +334,6 @@ def load_pagination_info(page_obj, page_type, tag_name, sort=None):
'page': previous_number,
'category_name': category.slug})
if sort:
if next_url:
next_url = f"{next_url}?sort={sort}"
if previous_url:
previous_url = f"{previous_url}?sort={sort}"
return {
'previous_url': previous_url,
'next_url': next_url,
@ -347,8 +341,8 @@ def load_pagination_info(page_obj, page_type, tag_name, sort=None):
}
@register.inclusion_tag('blog/tags/article_info.html', takes_context=True)
def load_article_detail(context, article, isindex, user):
@register.inclusion_tag('blog/tags/article_info.html')
def load_article_detail(article, isindex, user):
"""
加载文章详情
:param article:
@ -358,25 +352,11 @@ def load_article_detail(context, article, isindex, user):
from djangoblog.utils import get_blog_setting
blogsetting = get_blog_setting()
request = context.get('request')
liked_ids = request.session.get('liked_articles', []) if request else []
favorited_ids = request.session.get('favorited_articles', []) if request else []
has_liked = False
has_favorited = False
if request and hasattr(request, 'user') and request.user.is_authenticated:
has_liked = article.like_users.filter(pk=request.user.pk).exists()
has_favorited = article.favorite_users.filter(pk=request.user.pk).exists()
else:
has_liked = article.id in liked_ids
has_favorited = article.id in favorited_ids
return {
'article': article,
'isindex': isindex,
'user': user,
'open_site_comment': blogsetting.open_site_comment,
'has_liked': has_liked,
'has_favorited': has_favorited,
}

@ -230,57 +230,3 @@ class ArticleTest(TestCase):
call_command("clear_cache")
call_command("sync_user_avatar")
call_command("build_search_words")
def test_like_and_favorite(self):
user = BlogUser.objects.get_or_create(
email="user@test.com",
username="user1")[0]
user.set_password("pwd12345")
user.save()
category = Category()
category.name = "likecat"
category.save()
article = Article()
article.title = "likefavtitle"
article.body = "body"
article.author = user
article.category = category
article.type = 'a'
article.status = 'p'
article.save()
like_url = reverse('blog:like_article', kwargs={'article_id': article.id})
fav_url = reverse('blog:favorite_article', kwargs={'article_id': article.id})
rsp = self.client.post(like_url)
self.assertEqual(rsp.status_code, 200)
article.refresh_from_db()
self.assertEqual(article.like_count, 1)
rsp = self.client.post(like_url)
article.refresh_from_db()
self.assertEqual(article.like_count, 1)
rsp = self.client.post(fav_url)
self.assertEqual(rsp.status_code, 200)
article.refresh_from_db()
self.assertEqual(article.favorite_count, 1)
rsp = self.client.post(fav_url)
article.refresh_from_db()
self.assertEqual(article.favorite_count, 1)
self.client.login(username='user1', password='pwd12345')
rsp = self.client.post(like_url)
article.refresh_from_db()
self.assertEqual(article.like_count, 2)
rsp = self.client.post(like_url)
article.refresh_from_db()
self.assertEqual(article.like_count, 2)
rsp = self.client.post(fav_url)
article.refresh_from_db()
self.assertEqual(article.favorite_count, 2)
rsp = self.client.post(fav_url)
article.refresh_from_db()
self.assertEqual(article.favorite_count, 2)

@ -41,14 +41,6 @@ urlpatterns = [
r'tag/<slug:tag_name>/<int:page>.html',
views.TagDetailView.as_view(),
name='tag_detail_page'),
path(
r'article/<int:article_id>/like/',
views.like_article,
name='like_article'),
path(
r'article/<int:article_id>/favorite/',
views.favorite_article,
name='favorite_article'),
path(
'archives.html',
cache_page(

@ -5,7 +5,6 @@ import uuid
from django.conf import settings
from django.core.paginator import Paginator
from django.http import HttpResponse, HttpResponseForbidden
from django.http import JsonResponse, HttpResponseBadRequest
from django.shortcuts import get_object_or_404
from django.shortcuts import render
from django.templatetags.static import static
@ -60,20 +59,6 @@ class ArticleListView(ListView):
"""
raise NotImplementedError()
def get_sort(self):
sort = self.request.GET.get('sort', 'latest')
return sort if sort in ('latest', 'hot') else 'latest'
def order_queryset(self, qs):
sort = self.get_sort()
if sort == 'hot':
from django.utils import timezone
from datetime import timedelta
week_ago = timezone.now() - timedelta(days=7)
qs = qs.filter(pub_time__gte=week_ago)
return qs.order_by('-article_order', '-views', '-pub_time')
return qs.order_by('-article_order', '-pub_time')
def get_queryset_from_cache(self, cache_key):
'''
缓存页面数据
@ -101,7 +86,6 @@ class ArticleListView(ListView):
def get_context_data(self, **kwargs):
kwargs['linktype'] = self.link_type
kwargs['sort'] = self.get_sort()
return super(ArticleListView, self).get_context_data(**kwargs)
@ -113,11 +97,11 @@ class IndexView(ArticleListView):
link_type = LinkShowType.I
def get_queryset_data(self):
qs = Article.objects.filter(type='a', status='p')
return self.order_queryset(qs)
article_list = Article.objects.filter(type='a', status='p')
return article_list
def get_queryset_cache_key(self):
cache_key = 'index_{page}_sort_{sort}'.format(page=self.page_number, sort=self.get_sort())
cache_key = 'index_{page}'.format(page=self.page_number)
return cache_key
@ -175,14 +159,6 @@ class ArticleDetailView(DetailView):
# Action Hook, 通知插件"文章详情已获取"
hooks.run_action('after_article_body_get', article=article, request=self.request)
liked_ids = self.request.session.get('liked_articles', [])
favorited_ids = self.request.session.get('favorited_articles', [])
context['has_liked'] = (
self.request.user.is_authenticated and article.like_users.filter(pk=self.request.user.pk).exists()
) or (article.id in liked_ids)
context['has_favorited'] = (
self.request.user.is_authenticated and article.favorite_users.filter(pk=self.request.user.pk).exists()
) or (article.id in favorited_ids)
return context
@ -200,17 +176,17 @@ class CategoryDetailView(ArticleListView):
self.categoryname = categoryname
categorynames = list(
map(lambda c: c.name, category.get_sub_categorys()))
qs = Article.objects.filter(
article_list = Article.objects.filter(
category__name__in=categorynames, status='p')
return self.order_queryset(qs)
return article_list
def get_queryset_cache_key(self):
slug = self.kwargs['category_name']
category = get_object_or_404(Category, slug=slug)
categoryname = category.name
self.categoryname = categoryname
cache_key = 'category_list_{categoryname}_{page}_sort_{sort}'.format(
categoryname=categoryname, page=self.page_number, sort=self.get_sort())
cache_key = 'category_list_{categoryname}_{page}'.format(
categoryname=categoryname, page=self.page_number)
return cache_key
def get_context_data(self, **kwargs):
@ -234,15 +210,15 @@ class AuthorDetailView(ArticleListView):
def get_queryset_cache_key(self):
from uuslug import slugify
author_name = slugify(self.kwargs['author_name'])
cache_key = 'author_{author_name}_{page}_sort_{sort}'.format(
author_name=author_name, page=self.page_number, sort=self.get_sort())
cache_key = 'author_{author_name}_{page}'.format(
author_name=author_name, page=self.page_number)
return cache_key
def get_queryset_data(self):
author_name = self.kwargs['author_name']
qs = Article.objects.filter(
article_list = Article.objects.filter(
author__username=author_name, type='a', status='p')
return self.order_queryset(qs)
return article_list
def get_context_data(self, **kwargs):
author_name = self.kwargs['author_name']
@ -262,17 +238,17 @@ class TagDetailView(ArticleListView):
tag = get_object_or_404(Tag, slug=slug)
tag_name = tag.name
self.name = tag_name
qs = Article.objects.filter(
article_list = Article.objects.filter(
tags__name=tag_name, type='a', status='p')
return self.order_queryset(qs)
return article_list
def get_queryset_cache_key(self):
slug = self.kwargs['tag_name']
tag = get_object_or_404(Tag, slug=slug)
tag_name = tag.name
self.name = tag_name
cache_key = 'tag_{tag_name}_{page}_sort_{sort}'.format(
tag_name=tag_name, page=self.page_number, sort=self.get_sort())
cache_key = 'tag_{tag_name}_{page}'.format(
tag_name=tag_name, page=self.page_number)
return cache_key
def get_context_data(self, **kwargs):
@ -379,50 +355,6 @@ def page_not_found_view(
status=404)
@csrf_exempt
def like_article(request, article_id):
if request.method != 'POST':
return HttpResponseBadRequest('only post')
article = get_object_or_404(Article, pk=article_id)
if request.user.is_authenticated:
if article.like_users.filter(pk=request.user.pk).exists():
return JsonResponse({'ok': False, 'message': '已点赞', 'like_count': article.like_count})
article.like_users.add(request.user)
article.like_count += 1
article.save(update_fields=['like_count'])
return JsonResponse({'ok': True, 'message': '点赞成功', 'like_count': article.like_count})
liked = request.session.get('liked_articles', [])
if article.id in liked:
return JsonResponse({'ok': False, 'message': '已点赞', 'like_count': article.like_count})
liked.append(article.id)
request.session['liked_articles'] = liked
article.like_count += 1
article.save(update_fields=['like_count'])
return JsonResponse({'ok': True, 'message': '点赞成功', 'like_count': article.like_count})
@csrf_exempt
def favorite_article(request, article_id):
if request.method != 'POST':
return HttpResponseBadRequest('only post')
article = get_object_or_404(Article, pk=article_id)
if request.user.is_authenticated:
if article.favorite_users.filter(pk=request.user.pk).exists():
return JsonResponse({'ok': False, 'message': '已收藏', 'favorite_count': article.favorite_count})
article.favorite_users.add(request.user)
article.favorite_count += 1
article.save(update_fields=['favorite_count'])
return JsonResponse({'ok': True, 'message': '收藏成功', 'favorite_count': article.favorite_count})
favorited = request.session.get('favorited_articles', [])
if article.id in favorited:
return JsonResponse({'ok': False, 'message': '已收藏', 'favorite_count': article.favorite_count})
favorited.append(article.id)
request.session['favorited_articles'] = favorited
article.favorite_count += 1
article.save(update_fields=['favorite_count'])
return JsonResponse({'ok': True, 'message': '收藏成功', 'favorite_count': article.favorite_count})
def server_error_view(request, template_name='blog/error_page.html'):
return render(request,
template_name,

File diff suppressed because one or more lines are too long

@ -0,0 +1,688 @@
/*
*
*/
/* Google 图标样式 */
.icon-sn-google {
background-position: 0 -28px;
}
.icon-sn-bg-google {
background-color: #4285f4;
background-position: 0 0;
}
.fa-sn-google {
color: #4285f4;
}
/* GitHub 图标样式 */
.icon-sn-github {
background-position: -28px -28px;
}
.icon-sn-bg-github {
background-color: #333;
background-position: -28px 0;
}
.fa-sn-github {
color: #333;
}
/* 微博图标样式 */
.icon-sn-weibo {
background-position: -56px -28px;
}
.icon-sn-bg-weibo {
background-color: #e90d24;
background-position: -56px 0;
}
.fa-sn-weibo {
color: #e90d24;
}
/* QQ 图标样式 */
.icon-sn-qq {
background-position: -84px -28px;
}
.icon-sn-bg-qq {
background-color: #0098e6;
background-position: -84px 0;
}
.fa-sn-qq {
color: #0098e6;
}
/* Twitter 图标样式 */
.icon-sn-twitter {
background-position: -112px -28px;
}
.icon-sn-bg-twitter {
background-color: #50abf1;
background-position: -112px 0;
}
.fa-sn-twitter {
color: #50abf1;
}
/* Facebook 图标样式 */
.icon-sn-facebook {
background-position: -140px -28px;
}
.icon-sn-bg-facebook {
background-color: #4862a3;
background-position: -140px 0;
}
.fa-sn-facebook {
color: #4862a3;
}
/* 人人网图标样式 */
.icon-sn-renren {
background-position: -168px -28px;
}
.icon-sn-bg-renren {
background-color: #197bc8;
background-position: -168px 0;
}
.fa-sn-renren {
color: #197bc8;
}
/* 腾讯微博图标样式 */
.icon-sn-tqq {
background-position: -196px -28px;
}
.icon-sn-bg-tqq {
background-color: #1f9ed2;
background-position: -196px 0;
}
.fa-sn-tqq {
color: #1f9ed2;
}
/* 豆瓣图标样式 */
.icon-sn-douban {
background-position: -224px -28px;
}
.icon-sn-bg-douban {
background-color: #279738;
background-position: -224px 0;
}
.fa-sn-douban {
color: #279738;
}
/* 微信图标样式 */
.icon-sn-weixin {
background-position: -252px -28px;
}
.icon-sn-bg-weixin {
background-color: #00b500;
background-position: -252px 0;
}
.fa-sn-weixin {
color: #00b500;
}
/* 虚线图标样式 */
.icon-sn-dotted {
background-position: -280px -28px;
}
.icon-sn-bg-dotted {
background-color: #eee;
background-position: -280px 0;
}
.fa-sn-dotted {
color: #eee;
}
/* 站点图标样式 */
.icon-sn-site {
background-position: -308px -28px;
}
.icon-sn-bg-site {
background-color: #00b500;
background-position: -308px 0;
}
.fa-sn-site {
color: #00b500;
}
/* LinkedIn 图标样式 */
.icon-sn-linkedin {
background-position: -336px -28px;
}
.icon-sn-bg-linkedin {
background-color: #0077b9;
background-position: -336px 0;
}
.fa-sn-linkedin {
color: #0077b9;
}
/* 通用图标样式 */
[class*=icon-sn-] {
display: inline-block;
background-image: url('/static/blog/img/icon-sn.svg?56272f05e520');
background-repeat: no-repeat;
width: 28px;
height: 28px;
vertical-align: middle;
background-size: auto 56px;
}
[class*=icon-sn-]:hover {
opacity: .8;
filter: alpha(opacity=80);
}
/*
*
*/
/* Google 按钮样式 */
.btn-sn-google {
background: #4285f4;
}
.btn-sn-google:active,
.btn-sn-google:focus,
.btn-sn-google:hover {
background: #2a75f3;
}
/* GitHub 按钮样式 */
.btn-sn-github {
background: #333;
}
.btn-sn-github:active,
.btn-sn-github:focus,
.btn-sn-github:hover {
background: #262626;
}
/* 微博按钮样式 */
.btn-sn-weibo {
background: #e90d24;
}
.btn-sn-weibo:active,
.btn-sn-weibo:focus,
.btn-sn-weibo:hover {
background: #d10c20;
}
/* QQ 按钮样式 */
.btn-sn-qq {
background: #0098e6;
}
.btn-sn-qq:active,
.btn-sn-qq:focus,
.btn-sn-qq:hover {
background: #0087cd;
}
/* Twitter 按钮样式 */
.btn-sn-twitter {
background: #50abf1;
}
.btn-sn-twitter:active,
.btn-sn-twitter:focus,
.btn-sn-twitter:hover {
background: #38a0ef;
}
/* Facebook 按钮样式 */
.btn-sn-facebook {
background: #4862a3;
}
.btn-sn-facebook:active,
.btn-sn-facebook:focus,
.btn-sn-facebook:hover {
background: #405791;
}
/* 人人网按钮样式 */
.btn-sn-renren {
background: #197bc8;
}
.btn-sn-renren:active,
.btn-sn-renren:focus,
.btn-sn-renren:hover {
background: #166db1;
}
/* 腾讯微博按钮样式 */
.btn-sn-tqq {
background: #1f9ed2;
}
.btn-sn-tqq:active,
.btn-sn-tqq:focus,
.btn-sn-tqq:hover {
background: #1c8dbc;
}
/* 豆瓣按钮样式 */
.btn-sn-douban {
background: #279738;
}
.btn-sn-douban:active,
.btn-sn-douban:focus,
.btn-sn-douban:hover {
background: #228330;
}
/* 微信按钮样式 */
.btn-sn-weixin {
background: #00b500;
}
.btn-sn-weixin:active,
.btn-sn-weixin:focus,
.btn-sn-weixin:hover {
background: #009c00;
}
/* 虚线按钮样式 */
.btn-sn-dotted {
background: #eee;
}
.btn-sn-dotted:active,
.btn-sn-dotted:focus,
.btn-sn-dotted:hover {
background: #e1e1e1;
}
/* 站点按钮样式 */
.btn-sn-site {
background: #00b500;
}
.btn-sn-site:active,
.btn-sn-site:focus,
.btn-sn-site:hover {
background: #009c00;
}
/* LinkedIn 按钮样式 */
.btn-sn-linkedin {
background: #0077b9;
}
.btn-sn-linkedin:active,
.btn-sn-linkedin:focus,
.btn-sn-linkedin:hover {
background: #0067a0;
}
/* 通用按钮样式 */
[class*=btn-sn-],
[class*=btn-sn-]:active,
[class*=btn-sn-]:focus,
[class*=btn-sn-]:hover {
border: none;
color: #fff;
}
.btn-sn-more {
padding: 0;
}
.btn-sn-more,
.btn-sn-more:active,
.btn-sn-more:hover {
box-shadow: none;
}
[class*=btn-sn-] [class*=icon-sn-] {
background-color: transparent;
}
/*
*
*/
.codehilite .hll {
background-color: #ffffcc;
}
.codehilite {
background: #ffffff;
}
.codehilite .c {
color: #177500;
}
.codehilite .err {
color: #000000;
}
.codehilite .k {
color: #A90D91;
}
.codehilite .l {
color: #1C01CE;
}
.codehilite .n {
color: #000000;
}
.codehilite .o {
color: #000000;
}
.codehilite .ch {
color: #177500;
}
.codehilite .cm {
color: #177500;
}
.codehilite .cp {
color: #633820;
}
.codehilite .cpf {
color: #177500;
}
.codehilite .c1 {
color: #177500;
}
.codehilite .cs {
color: #177500;
}
.codehilite .kc {
color: #A90D91;
}
.codehilite .kd {
color: #A90D91;
}
.codehilite .kn {
color: #A90D91;
}
.codehilite .kp {
color: #A90D91;
}
.codehilite .kr {
color: #A90D91;
}
.codehilite .kt {
color: #A90D91;
}
.codehilite .ld {
color: #1C01CE;
}
.codehilite .m {
color: #1C01CE;
}
.codehilite .s {
color: #C41A16;
}
.codehilite .na {
color: #836C28;
}
.codehilite .nb {
color: #A90D91;
}
.codehilite .nc {
color: #3F6E75;
}
.codehilite .no {
color: #000000;
}
.codehilite .nd {
color: #000000;
}
.codehilite .ni {
color: #000000;
}
.codehilite .ne {
color: #000000;
}
.codehilite .nf {
color: #000000;
}
.codehilite .nl {
color: #000000;
}
.codehilite .nn {
color: #000000;
}
.codehilite .nx {
color: #000000;
}
.codehilite .py {
color: #000000;
}
.codehilite .nt {
color: #000000;
}
.codehilite .nv {
color: #000000;
}
.codehilite .ow {
color: #000000;
}
.codehilite .mb {
color: #1C01CE;
}
.codehilite .mf {
color: #1C01CE;
}
.codehilite .mh {
color: #1C01CE;
}
.codehilite .mi {
color: #1C01CE;
}
.codehilite .mo {
color: #1C01CE;
}
.codehilite .sb {
color: #C41A16;
}
.codehilite .sc {
color: #2300CE;
}
.codehilite .sd {
color: #C41A16;
}
.codehilite .s2 {
color: #C41A16;
}
.codehilite .se {
color: #C41A16;
}
.codehilite .sh {
color: #C41A16;
}
.codehilite .si {
color: #C41A16;
}
.codehilite .sx {
color: #C41A16;
}
.codehilite .sr {
color: #C41A16;
}
.codehilite .s1 {
color: #C41A16;
}
.codehilite .ss {
color: #C41A16;
}
.codehilite .bp {
color: #5B269A;
}
.codehilite .vc {
color: #000000;
}
.codehilite .vg {
color: #000000;
}
.codehilite .vi {
color: #000000;
}
.codehilite .il {
color: #1C01CE;
}
/*
* (NProgress)
*/
#nprogress {
pointer-events: none;
}
#nprogress .bar {
background: red;
position: fixed;
z-index: 1031;
top: 0;
left: 0;
width: 100%;
height: 2px;
}
#nprogress .peg {
display: block;
position: absolute;
right: 0px;
width: 100px;
height: 100%;
box-shadow: 0 0 10px #29d, 0 0 5px #29d;
opacity: 1.0;
-webkit-transform: rotate(3deg) translate(0px, -4px);
-ms-transform: rotate(3deg) translate(0px, -4px);
transform: rotate(3deg) translate(0px, -4px);
}
#nprogress .spinner {
display: block;
position: fixed;
z-index: 1031;
top: 15px;
right: 15px;
}
#nprogress .spinner-icon {
width: 18px;
height: 18px;
box-sizing: border-box;
border: solid 2px transparent;
border-top-color: red;
border-left-color: red;
border-radius: 50%;
-webkit-animation: nprogress-spinner 400ms linear infinite;
animation: nprogress-spinner 400ms linear infinite;
}
.nprogress-custom-parent {
overflow: hidden;
position: relative;
}
.nprogress-custom-parent #nprogress .spinner,
.nprogress-custom-parent #nprogress .bar {
position: absolute;
}
@-webkit-keyframes nprogress-spinner {
0% {
-webkit-transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
}
}
@keyframes nprogress-spinner {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,26 @@
/*!
* IE10 viewport hack for Surface/desktop Windows 8 bug
* Copyright 2014-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/(function(){'use strict';if(navigator.userAgent.match(/IEMobile\/10\.0/)){var msViewportStyle=document.createElement('style')
msViewportStyle.appendChild(document.createTextNode('@-ms-viewport{width:auto!important}'))
document.querySelector('head').appendChild(msViewportStyle)}})();;/*!
* Copyright 2014-2015 Twitter, Inc.
*
* Licensed under the Creative Commons Attribution 3.0 Unported License. For
* details, see https://creativecommons.org/licenses/by/3.0/.
*/(function(){'use strict';function emulatedIEMajorVersion(){var groups=/MSIE ([0-9.]+)/.exec(window.navigator.userAgent)
if(groups===null){return null}
var ieVersionNum=parseInt(groups[1],10)
var ieMajorVersion=Math.floor(ieVersionNum)
return ieMajorVersion}
function actualNonEmulatedIEMajorVersion(){var jscriptVersion=new Function('/*@cc_on return @_jscript_version; @*/')()
if(jscriptVersion===undefined){return 11}
if(jscriptVersion<9){return 8}
return jscriptVersion}
var ua=window.navigator.userAgent
if(ua.indexOf('Opera')>-1||ua.indexOf('Presto')>-1){return}
var emulated=emulatedIEMajorVersion()
if(emulated===null){return}
var nonEmulated=actualNonEmulatedIEMajorVersion()
if(emulated!==nonEmulated){window.alert('WARNING: You appear to be using IE'+nonEmulated+' in IE'+emulated+' emulation mode.\nIE emulation modes can behave significantly differently from ACTUAL older versions of IE.\nPLEASE DON\'T FILE BOOTSTRAP BUGS based on testing in IE emulation modes!')}})();;

@ -0,0 +1,6 @@
{
"e33c03e5cececc24214f8c70d7bfaf0079144308b8384c2b99378574a030b9a3": "<link rel=\"stylesheet\" href=\"/__compressor_url_placeholder__/CACHE/css/output.758e876fbda7.css\" type=\"text/css\" media=\"all\"><link rel=\"stylesheet\" href=\"/__compressor_url_placeholder__/CACHE/css/output.20f74afba408.css\" type=\"text/css\">",
"8efb09bae7df3f7f0c86c3573cd92622e620c23d356cee88d602cc06cd8ba0a5": "<link rel=\"stylesheet\" href=\"/__compressor_url_placeholder__/CACHE/css/output.5ee9ff3cb1a7.css\" type=\"text/css\">",
"602f536ee15494b2c004d9caae6d729f444aeae603707972c22cb7085ef69aa8": "<script src=\"/__compressor_url_placeholder__/CACHE/js/output.bc55ccd28723.js\"></script>",
"2e8e3574500075700cfa894c402675e08c87a3b71b31ed7d0002d493a526bcc8": "<script src=\"/__compressor_url_placeholder__/CACHE/js/output.de188198a436.js\"></script>"
}

@ -0,0 +1,25 @@
/* 按钮样式 */
/* Button styles */
.button {
/* 移除边框 */
/* Remove border */
border: none;
/* 设置内边距 */
/* Set padding */
padding: 4px 80px;
/* 设置文本对齐方式 */
/* Set text alignment */
text-align: center;
/* 移除文本装饰 */
/* Remove text decoration */
text-decoration: none;
/* 设置为行内块元素 */
/* Set as inline-block element */
display: inline-block;
/* 设置字体大小 */
/* Set font size */
font-size: 16px;
/* 设置外边距 */
/* Set margin */
margin: 4px 2px;
}

@ -0,0 +1,101 @@
// 设置倒计时初始值为60秒
// Set initial countdown value to 60 seconds
let wait = 60;
// 倒计时函数,用于在发送验证码按钮上显示倒计时
// Countdown function to display countdown on the send verification code button
function time(o) {
// 如果倒计时结束
// If countdown is over
if (wait == 0) {
// 移除按钮的禁用状态
// Remove button disabled state
o.removeAttribute("disabled");
// 恢复按钮文本
// Restore button text
o.value = "获取验证码";
// 重置倒计时
// Reset countdown
wait = 60
return false
} else {
// 设置按钮为禁用状态
// Set button to disabled state
o.setAttribute("disabled", true);
// 更新按钮文本显示倒计时
// Update button text to show countdown
o.value = "重新发送(" + wait + ")";
// 倒计时减1
// Decrease countdown by 1
wait--;
// 1秒后递归调用此函数
// Recursively call this function after 1 second
setTimeout(function () {
time(o)
},
1000)
}
}
// 获取验证码按钮点击事件处理
// Verification code button click event handler
document.getElementById("btn").onclick = function () {
// 获取邮箱输入框元素
// Get email input element
let id_email = $("#id_email")
// 获取CSRF令牌
// Get CSRF token
let token = $("*[name='csrfmiddlewaretoken']").val()
// 获取当前按钮元素
// Get current button element
let ts = this
// 获取错误信息显示元素
// Get error message display element
let myErr = $("#myErr")
// 发送AJAX请求
// Send AJAX request
$.ajax(
{
// 请求URL
// Request URL
url: "/forget_password_code/",
// 请求类型
// Request type
type: "POST",
// 请求数据
// Request data
data: {
"email": id_email.val(),
"csrfmiddlewaretoken": token
},
// 请求成功回调函数
// Success callback function
success: function (result) {
// 如果返回结果不是"ok"
// If the returned result is not "ok"
if (result != "ok") {
// 移除旧的错误信息
// Remove old error message
myErr.remove()
// 在邮箱输入框后添加新的错误信息
// Add new error message after email input
id_email.after("<ul className='errorlist' id='myErr'><li>" + result + "</li></ul>")
return
}
// 移除错误信息
// Remove error message
myErr.remove()
// 启动倒计时
// Start countdown
time(ts)
},
// 请求失败回调函数
// Error callback function
error: function (e) {
// 弹出发送失败提示
// Show send failure alert
alert("发送失败,请重试")
}
}
);
}

@ -0,0 +1,332 @@
/* 管理界面自动补全样式 */
/* Admin autocomplete styles */
/* 选择器宽度设置 */
select.admin-autocomplete {
width: 20em;
}
/* 自动补全容器最小高度设置 */
.select2-container--admin-autocomplete.select2-container {
min-height: 30px;
}
/* 单选和多选容器最小高度设置 */
.select2-container--admin-autocomplete .select2-selection--single,
.select2-container--admin-autocomplete .select2-selection--multiple {
min-height: 30px;
padding: 0;
}
/* 聚焦或打开状态下的选择器边框颜色 */
.select2-container--admin-autocomplete.select2-container--focus .select2-selection,
.select2-container--admin-autocomplete.select2-container--open .select2-selection {
border-color: var(--body-quiet-color);
min-height: 30px;
}
/* 聚焦或打开状态下单选选择器内边距 */
.select2-container--admin-autocomplete.select2-container--focus .select2-selection.select2-selection--single,
.select2-container--admin-autocomplete.select2-container--open .select2-selection.select2-selection--single {
padding: 0;
}
/* 聚焦或打开状态下多选选择器内边距 */
.select2-container--admin-autocomplete.select2-container--focus .select2-selection.select2-selection--multiple,
.select2-container--admin-autocomplete.select2-container--open .select2-selection.select2-selection--multiple {
padding: 0;
}
/* 单选选择器背景、边框和圆角设置 */
.select2-container--admin-autocomplete .select2-selection--single {
background-color: var(--body-bg);
border: 1px solid var(--border-color);
border-radius: 4px;
}
/* 单选选择器渲染内容颜色和行高设置 */
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__rendered {
color: var(--body-fg);
line-height: 30px;
}
/* 单选选择器清除按钮光标和浮动设置 */
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__clear {
cursor: pointer;
float: right;
font-weight: bold;
}
/* 单选选择器占位符颜色设置 */
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__placeholder {
color: var(--body-quiet-color);
}
/* 单选选择器箭头高度、位置和宽度设置 */
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow {
height: 26px;
position: absolute;
top: 1px;
right: 1px;
width: 20px;
}
/* 单选选择器箭头图标样式设置 */
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow b {
border-color: #888 transparent transparent transparent;
border-style: solid;
border-width: 5px 4px 0 4px;
height: 0;
left: 50%;
margin-left: -4px;
margin-top: -2px;
position: absolute;
top: 50%;
width: 0;
}
/* 从右到左语言环境下单选选择器清除按钮浮动设置 */
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--single .select2-selection__clear {
float: left;
}
/* 从右到左语言环境下单选选择器箭头位置设置 */
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--single .select2-selection__arrow {
left: 1px;
right: auto;
}
/* 禁用状态下单选选择器背景和光标设置 */
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--single {
background-color: var(--darkened-bg);
cursor: default;
}
/* 禁用状态下单选选择器清除按钮显示设置 */
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--single .select2-selection__clear {
display: none;
}
/* 打开状态下单选选择器箭头图标样式设置 */
.select2-container--admin-autocomplete.select2-container--open .select2-selection--single .select2-selection__arrow b {
border-color: transparent transparent #888 transparent;
border-width: 0 4px 5px 4px;
}
/* 多选选择器背景、边框、圆角和光标设置 */
.select2-container--admin-autocomplete .select2-selection--multiple {
background-color: var(--body-bg);
border: 1px solid var(--border-color);
border-radius: 4px;
cursor: text;
}
/* 多选选择器渲染内容盒模型、列表样式、外边距、内边距和宽度设置 */
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__rendered {
box-sizing: border-box;
list-style: none;
margin: 0;
padding: 0 10px 5px 5px;
width: 100%;
display: flex;
flex-wrap: wrap;
}
/* 多选选择器渲染内容列表项样式设置 */
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__rendered li {
list-style: none;
}
/* 多选选择器占位符颜色、上外边距和浮动设置 */
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__placeholder {
color: var(--body-quiet-color);
margin-top: 5px;
float: left;
}
/* 多选选择器清除按钮光标、浮动、字体粗细、外边距、位置和右边距设置 */
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__clear {
cursor: pointer;
float: right;
font-weight: bold;
margin: 5px;
position: absolute;
right: 0;
}
/* 多选选择器选项背景、边框、圆角、光标、浮动、右边距、上外边距和内边距设置 */
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice {
background-color: var(--darkened-bg);
border: 1px solid var(--border-color);
border-radius: 4px;
cursor: default;
float: left;
margin-right: 5px;
margin-top: 5px;
padding: 0 5px;
}
/* 多选选择器选项移除按钮颜色、光标、显示、字体粗细和右边距设置 */
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove {
color: var(--body-quiet-color);
cursor: pointer;
display: inline-block;
font-weight: bold;
margin-right: 2px;
}
/* 多选选择器选项移除按钮悬停状态颜色设置 */
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove:hover {
color: var(--body-fg);
}
/* 从右到左语言环境下多选选择器选项、占位符和内联搜索浮动设置 */
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-search--inline {
float: right;
}
/* 从右到左语言环境下多选选择器选项左边距和右边距设置 */
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
margin-left: 5px;
margin-right: auto;
}
/* 从右到左语言环境下多选选择器选项移除按钮左边距和右边距设置 */
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
margin-left: 2px;
margin-right: auto;
}
/* 聚焦状态下多选选择器边框设置 */
.select2-container--admin-autocomplete.select2-container--focus .select2-selection--multiple {
border: solid var(--body-quiet-color) 1px;
outline: 0;
}
/* 禁用状态下多选选择器背景和光标设置 */
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--multiple {
background-color: var(--darkened-bg);
cursor: default;
}
/* 禁用状态下多选选择器选项移除按钮显示设置 */
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection__choice__remove {
display: none;
}
/* 打开状态下选择器上边框圆角设置 */
.select2-container--admin-autocomplete.select2-container--open.select2-container--above .select2-selection--single, .select2-container--admin-autocomplete.select2-container--open.select2-container--above .select2-selection--multiple {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
/* 打开状态下选择器下边框圆角设置 */
.select2-container--admin-autocomplete.select2-container--open.select2-container--below .select2-selection--single, .select2-container--admin-autocomplete.select2-container--open.select2-container--below .select2-selection--multiple {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
/* 下拉搜索框背景设置 */
.select2-container--admin-autocomplete .select2-search--dropdown {
background: var(--darkened-bg);
}
/* 下拉搜索框字段背景、颜色、边框、圆角设置 */
.select2-container--admin-autocomplete .select2-search--dropdown .select2-search__field {
background: var(--body-bg);
color: var(--body-fg);
border: 1px solid var(--border-color);
border-radius: 4px;
}
/* 内联搜索框字段背景、颜色、边框、轮廓和外观设置 */
.select2-container--admin-autocomplete .select2-search--inline .select2-search__field {
background: transparent;
color: var(--body-fg);
border: none;
outline: 0;
box-shadow: none;
-webkit-appearance: textfield;
}
/* 结果选项最大高度、溢出和颜色设置 */
.select2-container--admin-autocomplete .select2-results > .select2-results__options {
max-height: 200px;
overflow-y: auto;
color: var(--body-fg);
background: var(--body-bg);
}
/* 分组结果选项内边距设置 */
.select2-container--admin-autocomplete .select2-results__option[role=group] {
padding: 0;
}
/* 禁用结果选项颜色设置 */
.select2-container--admin-autocomplete .select2-results__option[aria-disabled=true] {
color: var(--body-quiet-color);
}
/* 选中结果选项背景和颜色设置 */
.select2-container--admin-autocomplete .select2-results__option[aria-selected=true] {
background-color: var(--selected-bg);
color: var(--body-fg);
}
/* 结果选项嵌套内边距设置 */
.select2-container--admin-autocomplete .select2-results__option .select2-results__option {
padding-left: 1em;
}
/* 结果选项嵌套分组内边距设置 */
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__group {
padding-left: 0;
}
/* 结果选项嵌套第二层内边距和左边距设置 */
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option {
margin-left: -1em;
padding-left: 2em;
}
/* 结果选项嵌套第三层内边距和左边距设置 */
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -2em;
padding-left: 3em;
}
/* 结果选项嵌套第四层内边距和左边距设置 */
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -3em;
padding-left: 4em;
}
/* 结果选项嵌套第五层内边距和左边距设置 */
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -4em;
padding-left: 5em;
}
/* 结果选项嵌套第六层内边距和左边距设置 */
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -5em;
padding-left: 6em;
}
/* 高亮结果选项背景和颜色设置 */
.select2-container--admin-autocomplete .select2-results__option--highlighted[aria-selected] {
background-color: var(--primary);
color: var(--primary-fg);
}
/* 分组结果选项光标、显示、内边距设置 */
.select2-container--admin-autocomplete .select2-results__group {
cursor: default;
display: block;
padding: 6px;
}
/* 错误状态下选择器边框设置 */
.errors .select2-selection {
border: 1px solid var(--error-fg);
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,350 @@
/* 变更列表 */
/* CHANGELISTS */
#changelist {
display: flex;
align-items: flex-start;
justify-content: space-between;
}
#changelist .changelist-form-container {
flex: 1 1 auto;
min-width: 0;
}
#changelist table {
width: 100%;
}
.change-list .hiddenfields { display:none; }
.change-list .filtered table {
border-right: none;
}
.change-list .filtered {
min-height: 400px;
}
.change-list .filtered .results, .change-list .filtered .paginator,
.filtered #toolbar, .filtered div.xfull {
width: auto;
}
.change-list .filtered table tbody th {
padding-right: 1em;
}
#changelist-form .results {
overflow-x: auto;
width: 100%;
}
#changelist .toplinks {
border-bottom: 1px solid var(--hairline-color);
}
#changelist .paginator {
color: var(--body-quiet-color);
border-bottom: 1px solid var(--hairline-color);
background: var(--body-bg);
overflow: hidden;
}
/* 变更列表表格 */
/* CHANGELIST TABLES */
#changelist table thead th {
padding: 0;
white-space: nowrap;
vertical-align: middle;
}
#changelist table thead th.action-checkbox-column {
width: 1.5em;
text-align: center;
}
#changelist table tbody td.action-checkbox {
text-align: center;
}
#changelist table tfoot {
color: var(--body-quiet-color);
}
/* 工具栏 */
/* TOOLBAR */
#toolbar {
padding: 8px 10px;
margin-bottom: 15px;
border-top: 1px solid var(--hairline-color);
border-bottom: 1px solid var(--hairline-color);
background: var(--darkened-bg);
color: var(--body-quiet-color);
}
#toolbar form input {
border-radius: 4px;
font-size: 0.875rem;
padding: 5px;
color: var(--body-fg);
}
#toolbar #searchbar {
height: 1.1875rem;
border: 1px solid var(--border-color);
padding: 2px 5px;
margin: 0;
vertical-align: top;
font-size: 0.8125rem;
max-width: 100%;
}
#toolbar #searchbar:focus {
border-color: var(--body-quiet-color);
}
#toolbar form input[type="submit"] {
border: 1px solid var(--border-color);
font-size: 0.8125rem;
padding: 4px 8px;
margin: 0;
vertical-align: middle;
background: var(--body-bg);
box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
cursor: pointer;
color: var(--body-fg);
}
#toolbar form input[type="submit"]:focus,
#toolbar form input[type="submit"]:hover {
border-color: var(--body-quiet-color);
}
#changelist-search img {
vertical-align: middle;
margin-right: 4px;
}
#changelist-search .help {
word-break: break-word;
}
/* 过滤器列 */
/* FILTER COLUMN */
#changelist-filter {
flex: 0 0 240px;
order: 1;
background: var(--darkened-bg);
border-left: none;
margin: 0 0 0 30px;
}
@media (forced-colors: active) {
#changelist-filter {
border: 1px solid;
}
}
#changelist-filter h2 {
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 5px 15px;
margin-bottom: 12px;
border-bottom: none;
}
#changelist-filter h3,
#changelist-filter details summary {
font-weight: 400;
padding: 0 15px;
margin-bottom: 10px;
}
#changelist-filter details summary > * {
display: inline;
}
#changelist-filter details > summary {
list-style-type: none;
}
#changelist-filter details > summary::-webkit-details-marker {
display: none;
}
#changelist-filter details > summary::before {
content: '→';
font-weight: bold;
color: var(--link-hover-color);
}
#changelist-filter details[open] > summary::before {
content: '↓';
}
#changelist-filter ul {
margin: 5px 0;
padding: 0 15px 15px;
border-bottom: 1px solid var(--hairline-color);
}
#changelist-filter ul:last-child {
border-bottom: none;
}
#changelist-filter li {
list-style-type: none;
margin-left: 0;
padding-left: 0;
}
#changelist-filter a {
display: block;
color: var(--body-quiet-color);
word-break: break-word;
}
#changelist-filter li.selected {
border-left: 5px solid var(--hairline-color);
padding-left: 10px;
margin-left: -15px;
}
#changelist-filter li.selected a {
color: var(--link-selected-fg);
}
#changelist-filter a:focus, #changelist-filter a:hover,
#changelist-filter li.selected a:focus,
#changelist-filter li.selected a:hover {
color: var(--link-hover-color);
}
#changelist-filter #changelist-filter-extra-actions {
font-size: 0.8125rem;
margin-bottom: 10px;
border-bottom: 1px solid var(--hairline-color);
}
/* 日期钻取 */
/* DATE DRILLDOWN */
.change-list .toplinks {
display: flex;
padding-bottom: 5px;
flex-wrap: wrap;
gap: 3px 17px;
font-weight: bold;
}
.change-list .toplinks a {
font-size: 0.8125rem;
}
.change-list .toplinks .date-back {
color: var(--body-quiet-color);
}
.change-list .toplinks .date-back:focus,
.change-list .toplinks .date-back:hover {
color: var(--link-hover-color);
}
/* 操作 */
/* ACTIONS */
.filtered .actions {
border-right: none;
}
#changelist table input {
margin: 0;
vertical-align: baseline;
}
/* 一旦所有浏览器都支持:has()伪类可以移除tr.selected选择器和添加该类的JS代码 */
/* Once the :has() pseudo-class is supported by all browsers, the tr.selected
selector and the JS adding the class can be removed. */
#changelist tbody tr.selected {
background-color: var(--selected-row);
}
#changelist tbody tr:has(.action-select:checked) {
background-color: var(--selected-row);
}
@media (forced-colors: active) {
#changelist tbody tr.selected {
background-color: SelectedItem;
}
#changelist tbody tr:has(.action-select:checked) {
background-color: SelectedItem;
}
}
#changelist .actions {
padding: 10px;
background: var(--body-bg);
border-top: none;
border-bottom: none;
line-height: 1.5rem;
color: var(--body-quiet-color);
width: 100%;
}
#changelist .actions span.all,
#changelist .actions span.action-counter,
#changelist .actions span.clear,
#changelist .actions span.question {
font-size: 0.8125rem;
margin: 0 0.5em;
}
#changelist .actions:last-child {
border-bottom: none;
}
#changelist .actions select {
vertical-align: top;
height: 1.5rem;
color: var(--body-fg);
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 0.875rem;
padding: 0 0 0 4px;
margin: 0;
margin-left: 10px;
}
#changelist .actions select:focus {
border-color: var(--body-quiet-color);
}
#changelist .actions label {
display: inline-block;
vertical-align: middle;
font-size: 0.8125rem;
}
#changelist .actions .button {
font-size: 0.8125rem;
border: 1px solid var(--border-color);
border-radius: 4px;
background: var(--body-bg);
box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
cursor: pointer;
height: 1.5rem;
line-height: 1;
padding: 4px 8px;
margin: 0;
color: var(--body-fg);
}
#changelist .actions .button:focus, #changelist .actions .button:hover {
border-color: var(--body-quiet-color);
}

@ -0,0 +1,221 @@
/* 深色模式媒体查询,当系统偏好设置为深色时应用 */
/* Dark mode media query, applied when system preference is set to dark */
@media (prefers-color-scheme: dark) {
:root {
/* 主色调 */
/* Primary color */
--primary: #264b5d;
/* 主要前景色 */
/* Primary foreground color */
--primary-fg: #f7f7f7;
/* 主体前景色 */
/* Body foreground color */
--body-fg: #eeeeee;
/* 主体背景色 */
/* Body background color */
--body-bg: #121212;
/* 主体安静色(较淡)*/
/* Body quiet color (lighter) */
--body-quiet-color: #d0d0d0;
/* 主体中等颜色 */
/* Body medium color */
--body-medium-color: #e0e0e0;
/* 主体响亮色(较亮)*/
/* Body loud color (brighter) */
--body-loud-color: #ffffff;
/* 面包屑链接颜色 */
/* Breadcrumbs link color */
--breadcrumbs-link-fg: #e0e0e0;
/* 面包屑背景色 */
/* Breadcrumbs background color */
--breadcrumbs-bg: var(--primary);
/* 链接颜色 */
/* Link color */
--link-fg: #81d4fa;
/* 链接悬停颜色 */
/* Link hover color */
--link-hover-color: #4ac1f7;
/* 链接选中颜色 */
/* Link selected color */
--link-selected-fg: #6f94c6;
/* 细线颜色 */
/* Hairline color */
--hairline-color: #272727;
/* 边框颜色 */
/* Border color */
--border-color: #353535;
/* 错误颜色 */
/* Error color */
--error-fg: #e35f5f;
/* 成功消息背景色 */
/* Success message background color */
--message-success-bg: #006b1b;
/* 警告消息背景色 */
/* Warning message background color */
--message-warning-bg: #583305;
/* 错误消息背景色 */
/* Error message background color */
--message-error-bg: #570808;
/* 深色背景 */
/* Darkened background */
--darkened-bg: #212121;
/* 选中背景色 */
/* Selected background color */
--selected-bg: #1b1b1b;
/* 选中行颜色 */
/* Selected row color */
--selected-row: #00363a;
/* 关闭按钮背景色 */
/* Close button background color */
--close-button-bg: #333333;
/* 关闭按钮悬停背景色 */
/* Close button hover background color */
--close-button-hover-bg: #666666;
/* 颜色方案设置为深色 */
/* Color scheme set to dark */
color-scheme: dark;
}
}
/* 当HTML元素的data-theme属性为"dark"时应用的样式 */
/* Styles applied when HTML element's data-theme attribute is "dark" */
html[data-theme="dark"] {
--primary: #264b5d;
--primary-fg: #f7f7f7;
--body-fg: #eeeeee;
--body-bg: #121212;
--body-quiet-color: #d0d0d0;
--body-medium-color: #e0e0e0;
--body-loud-color: #ffffff;
--breadcrumbs-link-fg: #e0e0e0;
--breadcrumbs-bg: var(--primary);
--link-fg: #81d4fa;
--link-hover-color: #4ac1f7;
--link-selected-fg: #6f94c6;
--hairline-color: #272727;
--border-color: #353535;
--error-fg: #e35f5f;
--message-success-bg: #006b1b;
--message-warning-bg: #583305;
--message-error-bg: #570808;
--darkened-bg: #212121;
--selected-bg: #1b1b1b;
--selected-row: #00363a;
--close-button-bg: #333333;
--close-button-hover-bg: #666666;
color-scheme: dark;
}
/* 主题切换按钮样式 */
/* Theme switch button styles */
.theme-toggle {
/* 设置光标为指针 */
/* Set cursor to pointer */
cursor: pointer;
/* 移除边框 */
/* Remove border */
border: none;
/* 设置内边距为0 */
/* Set padding to 0 */
padding: 0;
/* 设置背景为透明 */
/* Set background to transparent */
background: transparent;
/* 设置垂直对齐方式 */
/* Set vertical alignment */
vertical-align: middle;
/* 设置左边距 */
/* Set left margin */
margin-inline-start: 5px;
/* 设置上边距 */
/* Set top margin */
margin-top: -1px;
}
/* 主题切换按钮中的SVG图标样式 */
/* SVG icon styles in theme toggle button */
.theme-toggle svg {
/* 设置垂直对齐方式 */
/* Set vertical alignment */
vertical-align: middle;
/* 设置高度 */
/* Set height */
height: 1.5rem;
/* 设置宽度 */
/* Set width */
width: 1.5rem;
/* 默认不显示 */
/* Hidden by default */
display: none;
}
/*
Fully hide screen reader text so we only show the one matching the current
theme.
*/
.theme-toggle .visually-hidden {
display: none;
}
/* 当data-theme属性为"auto"时显示自动主题标签 */
/* Show auto theme label when data-theme attribute is "auto" */
html[data-theme="auto"] .theme-toggle .theme-label-when-auto {
display: block;
}
/* 当data-theme属性为"dark"时显示深色主题标签 */
/* Show dark theme label when data-theme attribute is "dark" */
html[data-theme="dark"] .theme-toggle .theme-label-when-dark {
display: block;
}
/* 当data-theme属性为"light"时显示浅色主题标签 */
/* Show light theme label when data-theme attribute is "light" */
html[data-theme="light"] .theme-toggle .theme-label-when-light {
display: block;
}
/* 图标样式 */
/* Icon styles */
.theme-toggle svg.theme-icon-when-auto,
.theme-toggle svg.theme-icon-when-dark,
.theme-toggle svg.theme-icon-when-light {
fill: var(--header-link-color);
color: var(--header-bg);
}
/* 当data-theme属性为"auto"时显示自动主题图标 */
/* Show auto theme icon when data-theme attribute is "auto" */
html[data-theme="auto"] .theme-toggle svg.theme-icon-when-auto {
display: block;
}
/* 当data-theme属性为"dark"时显示深色主题图标 */
/* Show dark theme icon when data-theme attribute is "dark" */
html[data-theme="dark"] .theme-toggle svg.theme-icon-when-dark {
display: block;
}
/* 当data-theme属性为"light"时显示浅色主题图标 */
/* Show light theme icon when data-theme attribute is "light" */
html[data-theme="light"] .theme-toggle svg.theme-icon-when-light {
display: block;
}

@ -0,0 +1,31 @@
/* 仪表板 */
/* DASHBOARD */
.dashboard td, .dashboard th {
word-break: break-word;
}
.dashboard .module table th {
width: 100%;
}
.dashboard .module table td {
white-space: nowrap;
}
.dashboard .module table td a {
display: block;
padding-right: .6em;
}
/* 最近操作模块 */
/* RECENT ACTIONS MODULE */
.module ul.actionlist {
margin-left: 0;
}
ul.actionlist li {
list-style-type: none;
overflow: hidden;
text-overflow: ellipsis;
}

@ -0,0 +1,511 @@
/* 导入小部件样式 */
@import url('widgets.css');
/* 表单行 */
/* FORM ROWS */
.form-row {
overflow: hidden;
padding: 10px;
font-size: 0.8125rem;
border-bottom: 1px solid var(--hairline-color);
}
.form-row img, .form-row input {
vertical-align: middle;
}
.form-row label input[type="checkbox"] {
margin-top: 0;
vertical-align: 0;
}
form .form-row p {
padding-left: 0;
}
.flex-container {
display: flex;
}
.form-multiline {
flex-wrap: wrap;
}
.form-multiline > div {
padding-bottom: 10px;
}
/* 表单标签 */
/* FORM LABELS */
label {
font-weight: normal;
color: var(--body-quiet-color);
font-size: 0.8125rem;
}
.required label, label.required {
font-weight: bold;
}
/* 单选按钮 */
/* RADIO BUTTONS */
form div.radiolist div {
padding-right: 7px;
}
form div.radiolist.inline div {
display: inline-block;
}
form div.radiolist label {
width: auto;
}
form div.radiolist input[type="radio"] {
margin: -2px 4px 0 0;
padding: 0;
}
form ul.inline {
margin-left: 0;
padding: 0;
}
form ul.inline li {
float: left;
padding-right: 7px;
}
/* 字段集 */
/* FIELDSETS */
fieldset .fieldset-heading,
fieldset .inline-heading,
:not(.inline-related) .collapse summary {
border: 1px solid var(--header-bg);
margin: 0;
padding: 8px;
font-weight: 400;
font-size: 0.8125rem;
background: var(--header-bg);
color: var(--header-link-color);
}
/* 对齐的字段集 */
/* ALIGNED FIELDSETS */
.aligned label {
display: block;
padding: 4px 10px 0 0;
min-width: 160px;
width: 160px;
word-wrap: break-word;
}
.aligned label:not(.vCheckboxLabel):after {
content: '';
display: inline-block;
vertical-align: middle;
}
.aligned label + p, .aligned .checkbox-row + div.help, .aligned label + div.readonly {
padding: 6px 0;
margin-top: 0;
margin-bottom: 0;
margin-left: 0;
overflow-wrap: break-word;
}
.aligned ul label {
display: inline;
float: none;
width: auto;
}
.aligned .form-row input {
margin-bottom: 0;
}
.colMS .aligned .vLargeTextField, .colMS .aligned .vXMLLargeTextField {
width: 350px;
}
form .aligned ul {
margin-left: 160px;
padding-left: 10px;
}
form .aligned div.radiolist {
display: inline-block;
margin: 0;
padding: 0;
}
form .aligned p.help,
form .aligned div.help {
margin-top: 0;
margin-left: 160px;
padding-left: 10px;
}
form .aligned p.date div.help.timezonewarning,
form .aligned p.datetime div.help.timezonewarning,
form .aligned p.time div.help.timezonewarning {
margin-left: 0;
padding-left: 0;
font-weight: normal;
}
form .aligned p.help:last-child,
form .aligned div.help:last-child {
margin-bottom: 0;
padding-bottom: 0;
}
form .aligned input + p.help,
form .aligned textarea + p.help,
form .aligned select + p.help,
form .aligned input + div.help,
form .aligned textarea + div.help,
form .aligned select + div.help {
margin-left: 160px;
padding-left: 10px;
}
form .aligned select option:checked {
background-color: var(--selected-row);
}
form .aligned ul li {
list-style: none;
}
form .aligned table p {
margin-left: 0;
padding-left: 0;
}
.aligned .vCheckboxLabel {
padding: 1px 0 0 5px;
}
.aligned .vCheckboxLabel + p.help,
.aligned .vCheckboxLabel + div.help {
margin-top: -4px;
}
.colM .aligned .vLargeTextField, .colM .aligned .vXMLLargeTextField {
width: 610px;
}
fieldset .fieldBox {
margin-right: 20px;
}
/* 宽字段集 */
/* WIDE FIELDSETS */
.wide label {
width: 200px;
}
form .wide p.help,
form .wide ul.errorlist,
form .wide div.help {
padding-left: 50px;
}
form div.help ul {
padding-left: 0;
margin-left: 0;
}
.colM fieldset.wide .vLargeTextField, .colM fieldset.wide .vXMLLargeTextField {
width: 450px;
}
/* 可折叠字段集 */
/* COLLAPSIBLE FIELDSETS */
.collapse summary .fieldset-heading,
.collapse summary .inline-heading {
background: transparent;
border: none;
color: currentColor;
display: inline;
margin: 0;
padding: 0;
}
/* 等宽字体文本区域 */
/* MONOSPACE TEXTAREAS */
fieldset.monospace textarea {
font-family: var(--font-family-monospace);
}
/* 提交行 */
/* SUBMIT ROW */
.submit-row {
padding: 12px 14px 12px;
margin: 0 0 20px;
background: var(--darkened-bg);
border: 1px solid var(--hairline-color);
border-radius: 4px;
overflow: hidden;
display: flex;
gap: 10px;
flex-wrap: wrap;
}
body.popup .submit-row {
overflow: auto;
}
.submit-row input {
height: 2.1875rem;
line-height: 0.9375rem;
}
.submit-row input, .submit-row a {
margin: 0;
}
.submit-row input.default {
text-transform: uppercase;
}
.submit-row a.deletelink {
margin-left: auto;
}
.submit-row a.deletelink {
display: block;
background: var(--delete-button-bg);
border-radius: 4px;
padding: 0.625rem 0.9375rem;
height: 0.9375rem;
line-height: 0.9375rem;
color: var(--button-fg);
}
.submit-row a.closelink {
display: inline-block;
background: var(--close-button-bg);
border-radius: 4px;
padding: 10px 15px;
height: 0.9375rem;
line-height: 0.9375rem;
color: var(--button-fg);
}
.submit-row a.deletelink:focus,
.submit-row a.deletelink:hover,
.submit-row a.deletelink:active {
background: var(--delete-button-hover-bg);
text-decoration: none;
}
.submit-row a.closelink:focus,
.submit-row a.closelink:hover,
.submit-row a.closelink:active {
background: var(--close-button-hover-bg);
text-decoration: none;
}
/* 自定义表单字段 */
/* CUSTOM FORM FIELDS */
.vSelectMultipleField {
vertical-align: top;
}
.vCheckboxField {
border: none;
}
.vDateField, .vTimeField {
margin-right: 2px;
margin-bottom: 4px;
}
.vDateField {
min-width: 6.85em;
}
.vTimeField {
min-width: 4.7em;
}
.vURLField {
width: 30em;
}
.vLargeTextField, .vXMLLargeTextField {
width: 48em;
}
.flatpages-flatpage #id_content {
height: 40.2em;
}
.module table .vPositiveSmallIntegerField {
width: 2.2em;
}
.vIntegerField {
width: 5em;
}
.vBigIntegerField {
width: 10em;
}
.vForeignKeyRawIdAdminField {
width: 5em;
}
.vTextField, .vUUIDField {
width: 20em;
}
/* 行内组 */
/* INLINES */
.inline-group {
padding: 0;
margin: 0 0 30px;
}
.inline-group thead th {
padding: 8px 10px;
}
.inline-group .aligned label {
width: 160px;
}
.inline-related {
position: relative;
}
.inline-related h4,
.inline-related:not(.tabular) .collapse summary {
margin: 0;
color: var(--body-medium-color);
padding: 5px;
font-size: 0.8125rem;
background: var(--darkened-bg);
border: 1px solid var(--hairline-color);
border-left-color: var(--darkened-bg);
border-right-color: var(--darkened-bg);
}
.inline-related h3 span.delete {
float: right;
}
.inline-related h3 span.delete label {
margin-left: 2px;
font-size: 0.6875rem;
}
.inline-related fieldset {
margin: 0;
background: var(--body-bg);
border: none;
width: 100%;
}
.inline-group .tabular fieldset.module {
border: none;
}
.inline-related.tabular fieldset.module table {
width: 100%;
overflow-x: scroll;
}
.last-related fieldset {
border: none;
}
.inline-group .tabular tr.has_original td {
padding-top: 2em;
}
.inline-group .tabular tr td.original {
padding: 2px 0 0 0;
width: 0;
_position: relative;
}
.inline-group .tabular th.original {
width: 0px;
padding: 0;
}
.inline-group .tabular td.original p {
position: absolute;
left: 0;
height: 1.1em;
padding: 2px 9px;
overflow: hidden;
font-size: 0.5625rem;
font-weight: bold;
color: var(--body-quiet-color);
_width: 700px;
}
.inline-group div.add-row,
.inline-group .tabular tr.add-row td {
color: var(--body-quiet-color);
background: var(--darkened-bg);
padding: 8px 10px;
border-bottom: 1px solid var(--hairline-color);
}
.inline-group .tabular tr.add-row td {
padding: 8px 10px;
border-bottom: 1px solid var(--hairline-color);
}
.inline-group div.add-row a,
.inline-group .tabular tr.add-row td a {
font-size: 0.75rem;
}
.empty-form {
display: none;
}
/* 相关字段添加一个/查找 */
/* RELATED FIELD ADD ONE / LOOKUP */
.related-lookup {
margin-left: 5px;
display: inline-block;
vertical-align: middle;
background-repeat: no-repeat;
background-size: 14px;
}
.related-lookup {
width: 1rem;
height: 1rem;
background-image: url(../img/search.svg);
}
form .related-widget-wrapper ul {
display: inline-block;
margin-left: 0;
padding-left: 0;
}
.clearable-file-input input {
margin-top: 0;
}

@ -0,0 +1,89 @@
/* 登录表单样式 */
/* LOGIN FORM */
.login {
/* 设置背景色 */
background: var(--darkened-bg);
/* 设置高度为自动 */
height: auto;
}
.login #header {
/* 设置头部高度为自动 */
height: auto;
/* 设置内边距 */
padding: 15px 16px;
/* 设置内容居中对齐 */
justify-content: center;
}
.login #header h1 {
/* 设置标题字体大小 */
font-size: 1.125rem;
/* 设置外边距 */
margin: 0;
}
.login #header h1 a {
/* 设置链接颜色 */
color: var(--header-link-color);
}
.login #content {
/* 设置内容区内边距 */
padding: 20px;
}
.login #container {
/* 设置容器背景色 */
background: var(--body-bg);
/* 设置边框 */
border: 1px solid var(--hairline-color);
/* 设置圆角 */
border-radius: 4px;
/* 设置溢出隐藏 */
overflow: hidden;
/* 设置宽度 */
width: 28em;
/* 设置最小宽度 */
min-width: 300px;
/* 设置外边距居中 */
margin: 100px auto;
/* 设置高度为自动 */
height: auto;
}
.login .form-row {
/* 设置表单行内边距 */
padding: 4px 0;
}
.login .form-row label {
/* 设置标签为块级元素 */
display: block;
/* 设置行高 */
line-height: 2em;
}
.login .form-row #id_username, .login .form-row #id_password {
/* 设置内边距 */
padding: 8px;
/* 设置宽度为100% */
width: 100%;
/* 设置盒模型为border-box */
box-sizing: border-box;
}
.login .submit-row {
/* 设置提交行内边距 */
padding: 1em 0 0 0;
/* 设置外边距 */
margin: 0;
/* 设置文本居中对齐 */
text-align: center;
}
.login .password-reset-link {
/* 设置密码重置链接文本居中对齐 */
text-align: center;
}

@ -0,0 +1,178 @@
/* 粘性定位 - 使侧边栏在滚动时保持在顶部 */
/* sticky */
.sticky {
position: sticky;
top: 0;
max-height: 100vh;
}
/* 切换导航侧边栏按钮 */
/* 切换导航侧边栏按钮 */
.toggle-nav-sidebar {
z-index: 20;
left: 0;
display: flex;
align-items: center;
justify-content: center;
flex: 0 0 23px;
width: 23px;
border: 0;
border-right: 1px solid var(--hairline-color);
background-color: var(--body-bg);
cursor: pointer;
font-size: 1.25rem;
color: var(--link-fg);
padding: 0;
}
/* 从右到左语言环境下的切换按钮边框调整 */
[dir="rtl"] .toggle-nav-sidebar {
border-left: 1px solid var(--hairline-color);
border-right: 0;
}
/* 切换按钮的悬停和焦点状态 */
.toggle-nav-sidebar:hover,
.toggle-nav-sidebar:focus {
background-color: var(--darkened-bg);
}
/* 导航侧边栏 */
#nav-sidebar {
z-index: 15;
flex: 0 0 275px;
left: -276px;
margin-left: -276px;
border-top: 1px solid transparent;
border-right: 1px solid var(--hairline-color);
background-color: var(--body-bg);
overflow: auto;
}
/* 从右到左语言环境下的导航侧边栏调整 */
[dir="rtl"] #nav-sidebar {
border-left: 1px solid var(--hairline-color);
border-right: 0;
left: 0;
margin-left: 0;
right: -276px;
margin-right: -276px;
}
/* 切换导航侧边栏按钮的:before伪元素内容 */
.toggle-nav-sidebar::before {
content: '\00BB';
}
/* 主内容区域移动时切换按钮的:before伪元素内容 */
.main.shifted .toggle-nav-sidebar::before {
content: '\00AB';
}
/* 主内容区域的导航侧边栏可见性 */
.main > #nav-sidebar {
visibility: hidden;
}
/* 主内容区域移动时导航侧边栏的可见性 */
.main.shifted > #nav-sidebar {
margin-left: 0;
visibility: visible;
}
/* 从右到左语言环境下主内容区域移动时导航侧边栏的右边距 */
[dir="rtl"] .main.shifted > #nav-sidebar {
margin-right: 0;
}
/* 导航侧边栏模块标题的宽度和换行处理 */
#nav-sidebar .module th {
width: 100%;
overflow-wrap: anywhere;
}
/* 导航侧边栏模块标题和说明文字的内边距 */
#nav-sidebar .module th,
#nav-sidebar .module caption {
padding-left: 16px;
}
/* 导航侧边栏模块数据单元格的空白处理 */
#nav-sidebar .module td {
white-space: nowrap;
}
/* 从右到左语言环境下导航侧边栏模块标题和说明文字的内边距调整 */
[dir="rtl"] #nav-sidebar .module th,
[dir="rtl"] #nav-sidebar .module caption {
padding-left: 8px;
padding-right: 16px;
}
/* 当前应用的节链接样式 */
#nav-sidebar .current-app .section:link,
#nav-sidebar .current-app .section:visited {
color: var(--header-color);
font-weight: bold;
}
/* 当前模型的背景色 */
#nav-sidebar .current-model {
background: var(--selected-row);
}
/* 强制颜色模式下当前模型的背景色 */
@media (forced-colors: active) {
#nav-sidebar .current-model {
background-color: SelectedItem;
}
}
/* 主内容区域导航侧边栏和内容的最大宽度 */
.main > #nav-sidebar + .content {
max-width: calc(100% - 23px);
}
/* 主内容区域移动时导航侧边栏和内容的最大宽度 */
.main.shifted > #nav-sidebar + .content {
max-width: calc(100% - 299px);
}
/* 移动设备样式 - 隐藏导航侧边栏和切换按钮 */
@media (max-width: 767px) {
#nav-sidebar, #toggle-nav-sidebar {
display: none;
}
/* 移动设备上主内容区域的最大宽度 */
.main > #nav-sidebar + .content,
.main.shifted > #nav-sidebar + .content {
max-width: 100%;
}
}
/* 导航过滤器输入框样式 */
#nav-filter {
width: 100%;
box-sizing: border-box;
padding: 2px 5px;
margin: 5px 0;
border: 1px solid var(--border-color);
background-color: var(--darkened-bg);
color: var(--body-fg);
}
/* 导航过滤器输入框的焦点状态 */
#nav-filter:focus {
border-color: var(--body-quiet-color);
}
/* 导航过滤器无结果时的背景色 */
#nav-filter.no-results {
background: var(--message-error-bg);
}
/* 导航侧边栏表格宽度 */
#nav-sidebar table {
width: 100%;
}

@ -0,0 +1,930 @@
/* 平板设备 */
/* Tablets */
input[type="submit"], button {
-webkit-appearance: none;
appearance: none;
}
@media (max-width: 1024px) {
/* 基础样式 */
/* Basic */
html {
-webkit-text-size-adjust: 100%;
}
td, th {
padding: 10px;
font-size: 0.875rem;
}
.small {
font-size: 0.75rem;
}
/* 布局 */
/* Layout */
#container {
min-width: 0;
}
#content {
padding: 15px 20px 20px;
}
div.breadcrumbs {
padding: 10px 30px;
}
/* 头部 */
/* Header */
#header {
flex-direction: column;
padding: 15px 30px;
justify-content: flex-start;
}
#site-name {
margin: 0 0 8px;
line-height: 1.2;
}
#user-tools {
margin: 0;
font-weight: 400;
line-height: 1.85;
text-align: left;
}
#user-tools a {
display: inline-block;
line-height: 1.4;
}
/* 仪表板 */
/* Dashboard */
.dashboard #content {
width: auto;
}
#content-related {
margin-right: -290px;
}
.colSM #content-related {
margin-left: -290px;
}
.colMS {
margin-right: 290px;
}
.colSM {
margin-left: 290px;
}
.dashboard .module table td a {
padding-right: 0;
}
td .changelink, td .addlink {
font-size: 0.8125rem;
}
/* 变更列表 */
/* Changelist */
#toolbar {
border: none;
padding: 15px;
}
#changelist-search > div {
display: flex;
flex-wrap: nowrap;
max-width: 480px;
}
#changelist-search label {
line-height: 1.375rem;
}
#toolbar form #searchbar {
flex: 1 0 auto;
width: 0;
height: 1.375rem;
margin: 0 10px 0 6px;
}
#toolbar form input[type=submit] {
flex: 0 1 auto;
}
#changelist-search .quiet {
width: 0;
flex: 1 0 auto;
margin: 5px 0 0 25px;
}
#changelist .actions {
display: flex;
flex-wrap: wrap;
padding: 15px 0;
}
#changelist .actions label {
display: flex;
}
#changelist .actions select {
background: var(--body-bg);
}
#changelist .actions .button {
min-width: 48px;
margin: 0 10px;
}
#changelist .actions span.all,
#changelist .actions span.clear,
#changelist .actions span.question,
#changelist .actions span.action-counter {
font-size: 0.6875rem;
margin: 0 10px 0 0;
}
#changelist-filter {
flex-basis: 200px;
}
.change-list .filtered .results,
.change-list .filtered .paginator,
.filtered #toolbar,
.filtered .actions,
#changelist .paginator {
border-top-color: var(--hairline-color); /* XXX Is this used at all? */
}
#changelist .results + .paginator {
border-top: none;
}
/* 表单 */
/* Forms */
label {
font-size: 1rem;
}
/*
"input" HTML"type"
input:not([type])使CSS
*/
.form-row input:not([type]),
.form-row input[type=text],
.form-row input[type=password],
.form-row input[type=email],
.form-row input[type=url],
.form-row input[type=tel],
.form-row input[type=number],
.form-row textarea,
.form-row select,
.form-row .vTextField {
box-sizing: border-box;
margin: 0;
padding: 6px 8px;
min-height: 2.25rem;
font-size: 1rem;
}
.form-row select {
height: 2.25rem;
}
.form-row select[multiple] {
height: auto;
min-height: 0;
}
fieldset .fieldBox + .fieldBox {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid var(--hairline-color);
}
textarea {
max-width: 100%;
max-height: 120px;
}
.aligned label {
padding-top: 6px;
}
.aligned .related-lookup,
.aligned .datetimeshortcuts,
.aligned .related-lookup + strong {
align-self: center;
margin-left: 15px;
}
form .aligned div.radiolist {
margin-left: 2px;
}
.submit-row {
padding: 8px;
}
.submit-row a.deletelink {
padding: 10px 7px;
}
.button, input[type=submit], input[type=button], .submit-row input, a.button {
padding: 7px;
}
/* 选择器 */
/* Selector */
.selector {
display: flex;
width: 100%;
}
.selector .selector-filter {
display: flex;
align-items: center;
}
.selector .selector-filter input {
width: 100%;
min-height: 0;
flex: 1 1;
}
.selector-available, .selector-chosen {
width: auto;
flex: 1 1;
display: flex;
flex-direction: column;
}
.selector select {
width: 100%;
flex: 1 0 auto;
margin-bottom: 5px;
}
.selector-chooseall, .selector-clearall {
align-self: center;
}
.stacked {
flex-direction: column;
max-width: 480px;
}
.stacked > * {
flex: 0 1 auto;
}
.stacked select {
margin-bottom: 0;
}
.stacked .selector-available, .stacked .selector-chosen {
width: auto;
}
.stacked ul.selector-chooser {
padding: 0 2px;
transform: none;
}
.stacked .selector-chooser li {
padding: 3px;
}
.help-tooltip, .selector .help-icon {
display: none;
}
.datetime input {
width: 50%;
max-width: 120px;
}
.datetime span {
font-size: 0.8125rem;
}
.datetime .timezonewarning {
display: block;
font-size: 0.6875rem;
color: var(--body-quiet-color);
}
.datetimeshortcuts {
color: var(--border-color); /* XXX Redundant, .datetime span also sets #ccc */
}
.form-row .datetime input.vDateField, .form-row .datetime input.vTimeField {
width: 75%;
}
.inline-group {
overflow: auto;
}
/* 消息 */
/* Messages */
ul.messagelist li {
padding-left: 55px;
background-position: 30px 12px;
}
ul.messagelist li.error {
background-position: 30px 12px;
}
ul.messagelist li.warning {
background-position: 30px 14px;
}
/* 登录 */
/* Login */
.login #header {
padding: 15px 20px;
}
.login #site-name {
margin: 0;
}
/* GIS */
div.olMap {
max-width: calc(100vw - 30px);
max-height: 300px;
}
.olMap + .clear_features {
display: block;
margin-top: 10px;
}
/* 文档 */
/* Docs */
.module table.xfull {
width: 100%;
}
pre.literal-block {
overflow: auto;
}
}
/* 移动设备 */
/* Mobile */
@media (max-width: 767px) {
/* 布局 */
/* Layout */
#header, #content {
padding: 15px;
}
div.breadcrumbs {
padding: 10px 15px;
}
/* 仪表板 */
/* Dashboard */
.colMS, .colSM {
margin: 0;
}
#content-related, .colSM #content-related {
width: 100%;
margin: 0;
}
#content-related .module {
margin-bottom: 0;
}
#content-related .module h2 {
padding: 10px 15px;
font-size: 1rem;
}
/* 变更列表 */
/* Changelist */
#changelist {
align-items: stretch;
flex-direction: column;
}
#toolbar {
padding: 10px;
}
#changelist-filter {
margin-left: 0;
}
#changelist .actions label {
flex: 1 1;
}
#changelist .actions select {
flex: 1 0;
width: 100%;
}
#changelist .actions span {
flex: 1 0 100%;
}
#changelist-filter {
position: static;
width: auto;
margin-top: 30px;
}
.object-tools {
float: none;
margin: 0 0 15px;
padding: 0;
overflow: hidden;
}
.object-tools li {
height: auto;
margin-left: 0;
}
.object-tools li + li {
margin-left: 15px;
}
/* 表单 */
/* Forms */
.form-row {
padding: 15px 0;
}
.aligned .form-row,
.aligned .form-row > div {
max-width: 100vw;
}
.aligned .form-row > div {
width: calc(100vw - 30px);
}
.flex-container {
flex-flow: column;
}
.flex-container.checkbox-row {
flex-flow: row;
}
textarea {
max-width: none;
}
.vURLField {
width: auto;
}
fieldset .fieldBox + .fieldBox {
margin-top: 15px;
padding-top: 15px;
}
.aligned label {
width: 100%;
min-width: auto;
padding: 0 0 10px;
}
.aligned label:after {
max-height: 0;
}
.aligned .form-row input,
.aligned .form-row select,
.aligned .form-row textarea {
flex: 1 1 auto;
max-width: 100%;
}
.aligned .checkbox-row input {
flex: 0 1 auto;
margin: 0;
}
.aligned .vCheckboxLabel {
flex: 1 0;
padding: 1px 0 0 5px;
}
.aligned label + p,
.aligned label + div.help,
.aligned label + div.readonly {
padding: 0;
margin-left: 0;
}
.aligned p.file-upload {
font-size: 0.8125rem;
}
span.clearable-file-input {
margin-left: 15px;
}
span.clearable-file-input label {
font-size: 0.8125rem;
padding-bottom: 0;
}
.aligned .timezonewarning {
flex: 1 0 100%;
margin-top: 5px;
}
form .aligned .form-row div.help {
width: 100%;
margin: 5px 0 0;
padding: 0;
}
form .aligned ul,
form .aligned ul.errorlist {
margin-left: 0;
padding-left: 0;
}
form .aligned div.radiolist {
margin-top: 5px;
margin-right: 15px;
margin-bottom: -3px;
}
form .aligned div.radiolist:not(.inline) div + div {
margin-top: 5px;
}
/* 相关小部件 */
/* Related widget */
.related-widget-wrapper {
width: 100%;
display: flex;
align-items: flex-start;
}
.related-widget-wrapper .selector {
order: 1;
flex: 1 0 auto;
}
.related-widget-wrapper > a {
order: 2;
}
.related-widget-wrapper .radiolist ~ a {
align-self: flex-end;
}
.related-widget-wrapper > select ~ a {
align-self: center;
}
/* 选择器 */
/* Selector */
.selector {
flex-direction: column;
gap: 10px 0;
}
.selector-available, .selector-chosen {
flex: 1 1 auto;
}
.selector select {
max-height: 96px;
}
.selector ul.selector-chooser {
display: flex;
width: 60px;
height: 30px;
padding: 0 2px;
transform: none;
}
.selector ul.selector-chooser li {
float: left;
}
.selector-remove {
background-position: 0 0;
}
:enabled.selector-remove:focus, :enabled.selector-remove:hover {
background-position: 0 -24px;
}
.selector-add {
background-position: 0 -48px;
}
:enabled.selector-add:focus, :enabled.selector-add:hover {
background-position: 0 -72px;
}
/* 行内组 */
/* Inlines */
.inline-group[data-inline-type="stacked"] .inline-related {
border: 1px solid var(--hairline-color);
border-radius: 4px;
margin-top: 15px;
overflow: auto;
}
.inline-group[data-inline-type="stacked"] .inline-related > * {
box-sizing: border-box;
}
.inline-group[data-inline-type="stacked"] .inline-related .module {
padding: 0 10px;
}
.inline-group[data-inline-type="stacked"] .inline-related .module .form-row {
border-top: 1px solid var(--hairline-color);
border-bottom: none;
}
.inline-group[data-inline-type="stacked"] .inline-related .module .form-row:first-child {
border-top: none;
}
.inline-group[data-inline-type="stacked"] .inline-related h3 {
padding: 10px;
border-top-width: 0;
border-bottom-width: 2px;
display: flex;
flex-wrap: wrap;
align-items: center;
}
.inline-group[data-inline-type="stacked"] .inline-related h3 .inline_label {
margin-right: auto;
}
.inline-group[data-inline-type="stacked"] .inline-related h3 span.delete {
float: none;
flex: 1 1 100%;
margin-top: 5px;
}
.inline-group[data-inline-type="stacked"] .aligned .form-row > div:not([class]) {
width: 100%;
}
.inline-group[data-inline-type="stacked"] .aligned label {
width: 100%;
}
.inline-group[data-inline-type="stacked"] div.add-row {
margin-top: 15px;
border: 1px solid var(--hairline-color);
border-radius: 4px;
}
.inline-group div.add-row,
.inline-group .tabular tr.add-row td {
padding: 0;
}
.inline-group div.add-row a,
.inline-group .tabular tr.add-row td a {
display: block;
padding: 8px 10px 8px 26px;
background-position: 8px 9px;
}
/* 提交行 */
/* Submit row */
.submit-row {
padding: 10px;
margin: 0 0 15px;
flex-direction: column;
gap: 8px;
}
.submit-row input, .submit-row input.default, .submit-row a {
text-align: center;
}
.submit-row a.closelink {
padding: 10px 0;
text-align: center;
}
.submit-row a.deletelink {
margin: 0;
}
/* 消息 */
/* Messages */
ul.messagelist li {
padding-left: 40px;
background-position: 15px 12px;
}
ul.messagelist li.error {
background-position: 15px 12px;
}
ul.messagelist li.warning {
background-position: 15px 14px;
}
/* 分页器 */
/* Paginator */
.paginator .this-page, .paginator a:link, .paginator a:visited {
padding: 4px 10px;
}
/* 登录 */
/* Login */
body.login {
padding: 0 15px;
}
.login #container {
width: auto;
max-width: 480px;
margin: 50px auto;
}
.login #header,
.login #content {
padding: 15px;
}
.login #content-main {
float: none;
}
.login .form-row {
padding: 0;
}
.login .form-row + .form-row {
margin-top: 15px;
}
.login .form-row label {
margin: 0 0 5px;
line-height: 1.2;
}
.login .submit-row {
padding: 15px 0 0;
}
.login br {
display: none;
}
.login .submit-row input {
margin: 0;
text-transform: uppercase;
}
.errornote {
margin: 0 0 20px;
padding: 8px 12px;
font-size: 0.8125rem;
}
/* 日历和时钟 */
/* Calendar and clock */
.calendarbox, .clockbox {
position: fixed !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%);
margin: 0;
border: none;
overflow: visible;
}
.calendarbox:before, .clockbox:before {
content: '';
position: fixed;
top: 50%;
left: 50%;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.75);
transform: translate(-50%, -50%);
}
.calendarbox > *, .clockbox > * {
position: relative;
z-index: 1;
}
.calendarbox > div:first-child {
z-index: 2;
}
.calendarbox .calendar, .clockbox h2 {
border-radius: 4px 4px 0 0;
overflow: hidden;
}
.calendarbox .calendar-cancel, .clockbox .calendar-cancel {
border-radius: 0 0 4px 4px;
overflow: hidden;
}
.calendar-shortcuts {
padding: 10px 0;
font-size: 0.75rem;
line-height: 0.75rem;
}
.calendar-shortcuts a {
margin: 0 4px;
}
.timelist a {
background: var(--body-bg);
padding: 4px;
}
.calendar-cancel {
padding: 8px 10px;
}
.clockbox h2 {
padding: 8px 15px;
}
.calendar caption {
padding: 10px;
}
.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next {
z-index: 1;
top: 10px;
}
/* 历史记录 */
/* History */
table#change-history tbody th, table#change-history tbody td {
font-size: 0.8125rem;
word-break: break-word;
}
table#change-history tbody th {
width: auto;
}
/* 文档 */
/* Docs */
table.model tbody th, table.model tbody td {
font-size: 0.8125rem;
word-break: break-word;
}
}

@ -0,0 +1,109 @@
/* 平板设备样式 */
/* TABLETS */
@media (max-width: 1024px) {
/* 从右到左的语言布局调整 */
[dir="rtl"] .colMS {
margin-right: 0;
}
/* 用户工具栏文本对齐调整 */
[dir="rtl"] #user-tools {
text-align: right;
}
/* 变更列表操作标签内边距调整 */
[dir="rtl"] #changelist .actions label {
padding-left: 10px;
padding-right: 0;
}
/* 变更列表操作选择框边距调整 */
[dir="rtl"] #changelist .actions select {
margin-left: 0;
margin-right: 15px;
}
/* 过滤器相关元素的边距调整 */
[dir="rtl"] .change-list .filtered .results,
[dir="rtl"] .change-list .filtered .paginator,
[dir="rtl"] .filtered #toolbar,
[dir="rtl"] .filtered div.xfull,
[dir="rtl"] .filtered .actions,
[dir="rtl"] #changelist-filter {
margin-left: 0;
}
/* 行内组添加行链接的内边距和背景位置调整 */
[dir="rtl"] .inline-group div.add-row a,
[dir="rtl"] .inline-group .tabular tr.add-row td a {
padding: 8px 26px 8px 10px;
background-position: calc(100% - 8px) 9px;
}
/* 对象工具列表项浮动方向调整 */
[dir="rtl"] .object-tools li {
float: right;
}
/* 对象工具相邻列表项的边距调整 */
[dir="rtl"] .object-tools li + li {
margin-left: 0;
margin-right: 15px;
}
/* 仪表板模块表格链接内边距调整 */
[dir="rtl"] .dashboard .module table td a {
padding-left: 0;
padding-right: 16px;
}
}
/* 移动设备样式 */
/* MOBILE */
@media (max-width: 767px) {
/* 相关查找和日期时间快捷方式的边距调整 */
[dir="rtl"] .aligned .related-lookup,
[dir="rtl"] .aligned .datetimeshortcuts {
margin-left: 0;
margin-right: 15px;
}
/* 列表的右边距调整 */
[dir="rtl"] .aligned ul,
[dir="rtl"] form .aligned ul.errorlist {
margin-right: 0;
}
/* 变更列表过滤器的边距调整 */
[dir="rtl"] #changelist-filter {
margin-left: 0;
margin-right: 0;
}
/* 复选框标签的内边距调整 */
[dir="rtl"] .aligned .vCheckboxLabel {
padding: 1px 5px 0 0;
}
/* 选择器移除按钮的背景位置调整 */
[dir="rtl"] .selector-remove {
background-position: 0 0;
}
/* 选择器移除按钮的焦点和悬停状态背景位置调整 */
[dir="rtl"] :enabled.selector-remove:focus, :enabled.selector-remove:hover {
background-position: 0 -24px;
}
/* 选择器添加按钮的背景位置调整 */
[dir="rtl"] .selector-add {
background-position: 0 -48px;
}
/* 选择器添加按钮的焦点和悬停状态背景位置调整 */
[dir="rtl"] :enabled.selector-add:focus, :enabled.selector-add:hover {
background-position: 0 -72px;
}
}

@ -0,0 +1,356 @@
/* 全局样式 - 适用于从右到左的语言 */
/* GLOBAL */
/* 表格标题文本对齐 */
th {
text-align: right;
}
/* 模块标题和说明文字对齐 */
.module h2, .module caption {
text-align: right;
}
/* 模块列表的左右边距调整 */
.module ul, .module ol {
margin-left: 0;
margin-right: 1.5em;
}
/* 查看、添加、变更、隐藏链接的内边距和背景位置调整 */
.viewlink, .addlink, .changelink, .hidelink {
padding-left: 0;
padding-right: 16px;
background-position: 100% 1px;
}
/* 删除链接的内边距和背景位置调整 */
.deletelink {
padding-left: 0;
padding-right: 16px;
background-position: 100% 1px;
}
/* 对象工具栏浮动方向 */
.object-tools {
float: left;
}
/* 表格首列的边框调整 */
thead th:first-child,
tfoot td:first-child {
border-left: none;
}
/* 布局相关样式 */
/* LAYOUT */
/* 用户工具栏位置和文本对齐调整 */
#user-tools {
right: auto;
left: 0;
text-align: left;
}
/* 面包屑导航文本对齐 */
div.breadcrumbs {
text-align: right;
}
/* 主内容区域浮动方向 */
#content-main {
float: right;
}
/* 相关内容区域浮动方向和边距调整 */
#content-related {
float: left;
margin-left: -300px;
margin-right: auto;
}
/* 主列和侧边栏布局调整 */
.colMS {
margin-left: 300px;
margin-right: 0;
}
/* 可排序表格样式 */
/* SORTABLE TABLES */
/* 排序选项浮动方向 */
table thead th.sorted .sortoptions {
float: left;
}
/* 已排序列文本内边距调整 */
thead th.sorted .text {
padding-right: 0;
padding-left: 42px;
}
/* 仪表板样式 */
/* dashboard styles */
/* 仪表板模块表格链接内边距调整 */
.dashboard .module table td a {
padding-left: .6em;
padding-right: 16px;
}
/* 变更列表样式 */
/* changelists styles */
/* 过滤变更列表的表格边框调整 */
.change-list .filtered table {
border-left: none;
border-right: 0px none;
}
/* 变更列表过滤器边框和边距调整 */
#changelist-filter {
border-left: none;
border-right: none;
margin-left: 0;
margin-right: 30px;
}
/* 已选过滤条件的边框、内边距和边距调整 */
#changelist-filter li.selected {
border-left: none;
padding-left: 10px;
margin-left: 0;
border-right: 5px solid var(--hairline-color);
padding-right: 10px;
margin-right: -15px;
}
/* 变更列表表格首列的边框调整 */
#changelist table tbody td:first-child, #changelist table tbody th:first-child {
border-right: none;
border-left: none;
}
/* 分页器末页链接的边距调整 */
.paginator .end {
margin-left: 6px;
margin-right: 0;
}
/* 分页器输入框的边距调整 */
.paginator input {
margin-left: 0;
margin-right: auto;
}
/* 表单样式 */
/* FORMS */
/* 对齐标签的内边距调整 */
.aligned label {
padding: 0 0 3px 1em;
}
/* 删除链接的边距调整 */
.submit-row a.deletelink {
margin-left: 0;
margin-right: auto;
}
/* 日期和时间字段的边距调整 */
.vDateField, .vTimeField {
margin-left: 2px;
}
/* 对齐表单行输入框的边距调整 */
.aligned .form-row input {
margin-left: 5px;
}
/* 对齐列表的边距和内边距调整 */
form .aligned ul {
margin-right: 163px;
padding-right: 10px;
margin-left: 0;
padding-left: 0;
}
/* 行内列表项的浮动方向和内边距调整 */
form ul.inline li {
float: right;
padding-right: 0;
padding-left: 7px;
}
/* 表单帮助文本的边距和内边距调整 */
form .aligned p.help,
form .aligned div.help {
margin-left: 0;
margin-right: 160px;
padding-right: 10px;
}
/* 帮助文本列表、复选框行和时区警告的边距调整 */
form div.help ul,
form .aligned .checkbox-row + .help,
form .aligned p.date div.help.timezonewarning,
form .aligned p.datetime div.help.timezonewarning,
form .aligned p.time div.help.timezonewarning {
margin-right: 0;
padding-right: 0;
}
/* 宽表单帮助文本、错误列表和帮助文本的内边距调整 */
form .wide p.help,
form .wide ul.errorlist,
form .wide div.help {
padding-left: 0;
padding-right: 50px;
}
/* 提交行文本对齐 */
.submit-row {
text-align: right;
}
/* 字段框的边距调整 */
fieldset .fieldBox {
margin-left: 20px;
margin-right: 0;
}
/* 错误列表项的背景位置和内边距调整 */
.errorlist li {
background-position: 100% 12px;
padding: 0;
}
/* 错误提示的背景位置和内边距调整 */
.errornote {
background-position: 100% 12px;
padding: 10px 12px;
}
/* 小部件样式 */
/* WIDGETS */
/* 日历导航上一个按钮的位置和背景调整 */
.calendarnav-previous {
top: 0;
left: auto;
right: 10px;
background: url(../img/calendar-icons.svg) 0 -15px no-repeat;
}
/* 日历导航下一个按钮的位置和背景调整 */
.calendarnav-next {
top: 0;
right: auto;
left: 10px;
background: url(../img/calendar-icons.svg) 0 0 no-repeat;
}
/* 日历标题和日历框标题文本对齐 */
.calendar caption, .calendarbox h2 {
text-align: center;
}
/* 选择器浮动方向 */
.selector {
float: right;
}
/* 选择器过滤器文本对齐 */
.selector .selector-filter {
text-align: right;
}
/* 选择器添加按钮的背景和背景大小调整 */
.selector-add {
background: url(../img/selector-icons.svg) 0 -96px no-repeat;
background-size: 24px auto;
}
/* 选择器添加按钮的焦点和悬停状态背景位置调整 */
:enabled.selector-add:focus, :enabled.selector-add:hover {
background-position: 0 -120px;
}
/* 选择器移除按钮的背景和背景大小调整 */
.selector-remove {
background: url(../img/selector-icons.svg) 0 -144px no-repeat;
background-size: 24px auto;
}
/* 选择器移除按钮的焦点和悬停状态背景位置调整 */
:enabled.selector-remove:focus, :enabled.selector-remove:hover {
background-position: 0 -168px;
}
/* 全选按钮的背景调整 */
.selector-chooseall {
background: url(../img/selector-icons.svg) right -128px no-repeat;
}
/* 全选按钮的焦点和悬停状态背景位置调整 */
:enabled.selector-chooseall:focus, :enabled.selector-chooseall:hover {
background-position: 100% -144px;
}
/* 全清按钮的背景调整 */
.selector-clearall {
background: url(../img/selector-icons.svg) 0 -160px no-repeat;
}
/* 全清按钮的焦点和悬停状态背景位置调整 */
:enabled.selector-clearall:focus, :enabled.selector-clearall:hover {
background-position: 0 -176px;
}
/* 行内删除链接浮动方向 */
.inline-deletelink {
float: left;
}
/* 日期时间表单行的溢出处理 */
form .form-row p.datetime {
overflow: hidden;
}
/* 相关小部件包装器的浮动方向 */
.related-widget-wrapper {
float: right;
}
/* 其他样式 */
/* MISC */
/* 行内相关标题和行内组标题文本对齐 */
.inline-related h2, .inline-group h2 {
text-align: right
}
/* 行内相关标题删除链接的内边距、位置和浮动方向调整 */
.inline-related h3 span.delete {
padding-right: 20px;
padding-left: inherit;
left: 10px;
right: inherit;
float:left;
}
/* 行内相关标题删除链接标签的边距调整 */
.inline-related h3 span.delete label {
margin-left: inherit;
margin-right: 2px;
}
/* 行内组表格原始列段落的右边距调整 */
.inline-group .tabular td.original p {
right: 0;
}
/* 选择器选择器的边距调整 */
.selector .selector-chooser {
margin: 0;
}

@ -0,0 +1,22 @@
/* 当用户选择可用密码时隐藏警告字段 */
/* Hide warnings fields if usable password is selected */
form:has(#id_usable_password input[value="true"]:checked) .messagelist {
display: none;
}
/* 当用户选择不可用密码时隐藏密码字段 */
/* Hide password fields if unusable password is selected */
form:has(#id_usable_password input[value="false"]:checked) .field-password1,
form:has(#id_usable_password input[value="false"]:checked) .field-password2 {
display: none;
}
/* 选择适当的提交按钮 */
/* Select appropriate submit button */
form:has(#id_usable_password input[value="true"]:checked) input[type="submit"].unset-password {
display: none;
}
form:has(#id_usable_password input[value="false"]:checked) input[type="submit"].set-password {
display: none;
}

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2012-2017 Kevin Brown, Igor Vaynberg, and Select2 contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

@ -0,0 +1,481 @@
.select2-container {
box-sizing: border-box;
display: inline-block;
margin: 0;
position: relative;
vertical-align: middle; }
.select2-container .select2-selection--single {
box-sizing: border-box;
cursor: pointer;
display: block;
height: 28px;
user-select: none;
-webkit-user-select: none; }
.select2-container .select2-selection--single .select2-selection__rendered {
display: block;
padding-left: 8px;
padding-right: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; }
.select2-container .select2-selection--single .select2-selection__clear {
position: relative; }
.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered {
padding-right: 8px;
padding-left: 20px; }
.select2-container .select2-selection--multiple {
box-sizing: border-box;
cursor: pointer;
display: block;
min-height: 32px;
user-select: none;
-webkit-user-select: none; }
.select2-container .select2-selection--multiple .select2-selection__rendered {
display: inline-block;
overflow: hidden;
padding-left: 8px;
text-overflow: ellipsis;
white-space: nowrap; }
.select2-container .select2-search--inline {
float: left; }
.select2-container .select2-search--inline .select2-search__field {
box-sizing: border-box;
border: none;
font-size: 100%;
margin-top: 5px;
padding: 0; }
.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button {
-webkit-appearance: none; }
.select2-dropdown {
background-color: white;
border: 1px solid #aaa;
border-radius: 4px;
box-sizing: border-box;
display: block;
position: absolute;
left: -100000px;
width: 100%;
z-index: 1051; }
.select2-results {
display: block; }
.select2-results__options {
list-style: none;
margin: 0;
padding: 0; }
.select2-results__option {
padding: 6px;
user-select: none;
-webkit-user-select: none; }
.select2-results__option[aria-selected] {
cursor: pointer; }
.select2-container--open .select2-dropdown {
left: 0; }
.select2-container--open .select2-dropdown--above {
border-bottom: none;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0; }
.select2-container--open .select2-dropdown--below {
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0; }
.select2-search--dropdown {
display: block;
padding: 4px; }
.select2-search--dropdown .select2-search__field {
padding: 4px;
width: 100%;
box-sizing: border-box; }
.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button {
-webkit-appearance: none; }
.select2-search--dropdown.select2-search--hide {
display: none; }
.select2-close-mask {
border: 0;
margin: 0;
padding: 0;
display: block;
position: fixed;
left: 0;
top: 0;
min-height: 100%;
min-width: 100%;
height: auto;
width: auto;
opacity: 0;
z-index: 99;
background-color: #fff;
filter: alpha(opacity=0); }
.select2-hidden-accessible {
border: 0 !important;
clip: rect(0 0 0 0) !important;
-webkit-clip-path: inset(50%) !important;
clip-path: inset(50%) !important;
height: 1px !important;
overflow: hidden !important;
padding: 0 !important;
position: absolute !important;
width: 1px !important;
white-space: nowrap !important; }
.select2-container--default .select2-selection--single {
background-color: #fff;
border: 1px solid #aaa;
border-radius: 4px; }
.select2-container--default .select2-selection--single .select2-selection__rendered {
color: #444;
line-height: 28px; }
.select2-container--default .select2-selection--single .select2-selection__clear {
cursor: pointer;
float: right;
font-weight: bold; }
.select2-container--default .select2-selection--single .select2-selection__placeholder {
color: #999; }
.select2-container--default .select2-selection--single .select2-selection__arrow {
height: 26px;
position: absolute;
top: 1px;
right: 1px;
width: 20px; }
.select2-container--default .select2-selection--single .select2-selection__arrow b {
border-color: #888 transparent transparent transparent;
border-style: solid;
border-width: 5px 4px 0 4px;
height: 0;
left: 50%;
margin-left: -4px;
margin-top: -2px;
position: absolute;
top: 50%;
width: 0; }
.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear {
float: left; }
.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow {
left: 1px;
right: auto; }
.select2-container--default.select2-container--disabled .select2-selection--single {
background-color: #eee;
cursor: default; }
.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear {
display: none; }
.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b {
border-color: transparent transparent #888 transparent;
border-width: 0 4px 5px 4px; }
.select2-container--default .select2-selection--multiple {
background-color: white;
border: 1px solid #aaa;
border-radius: 4px;
cursor: text; }
.select2-container--default .select2-selection--multiple .select2-selection__rendered {
box-sizing: border-box;
list-style: none;
margin: 0;
padding: 0 5px;
width: 100%; }
.select2-container--default .select2-selection--multiple .select2-selection__rendered li {
list-style: none; }
.select2-container--default .select2-selection--multiple .select2-selection__clear {
cursor: pointer;
float: right;
font-weight: bold;
margin-top: 5px;
margin-right: 10px;
padding: 1px; }
.select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: #e4e4e4;
border: 1px solid #aaa;
border-radius: 4px;
cursor: default;
float: left;
margin-right: 5px;
margin-top: 5px;
padding: 0 5px; }
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
color: #999;
cursor: pointer;
display: inline-block;
font-weight: bold;
margin-right: 2px; }
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
color: #333; }
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline {
float: right; }
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
margin-left: 5px;
margin-right: auto; }
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
margin-left: 2px;
margin-right: auto; }
.select2-container--default.select2-container--focus .select2-selection--multiple {
border: solid black 1px;
outline: 0; }
.select2-container--default.select2-container--disabled .select2-selection--multiple {
background-color: #eee;
cursor: default; }
.select2-container--default.select2-container--disabled .select2-selection__choice__remove {
display: none; }
.select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple {
border-top-left-radius: 0;
border-top-right-radius: 0; }
.select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0; }
.select2-container--default .select2-search--dropdown .select2-search__field {
border: 1px solid #aaa; }
.select2-container--default .select2-search--inline .select2-search__field {
background: transparent;
border: none;
outline: 0;
box-shadow: none;
-webkit-appearance: textfield; }
.select2-container--default .select2-results > .select2-results__options {
max-height: 200px;
overflow-y: auto; }
.select2-container--default .select2-results__option[role=group] {
padding: 0; }
.select2-container--default .select2-results__option[aria-disabled=true] {
color: #999; }
.select2-container--default .select2-results__option[aria-selected=true] {
background-color: #ddd; }
.select2-container--default .select2-results__option .select2-results__option {
padding-left: 1em; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__group {
padding-left: 0; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__option {
margin-left: -1em;
padding-left: 2em; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -2em;
padding-left: 3em; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -3em;
padding-left: 4em; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -4em;
padding-left: 5em; }
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -5em;
padding-left: 6em; }
.select2-container--default .select2-results__option--highlighted[aria-selected] {
background-color: #5897fb;
color: white; }
.select2-container--default .select2-results__group {
cursor: default;
display: block;
padding: 6px; }
.select2-container--classic .select2-selection--single {
background-color: #f7f7f7;
border: 1px solid #aaa;
border-radius: 4px;
outline: 0;
background-image: -webkit-linear-gradient(top, white 50%, #eeeeee 100%);
background-image: -o-linear-gradient(top, white 50%, #eeeeee 100%);
background-image: linear-gradient(to bottom, white 50%, #eeeeee 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
.select2-container--classic .select2-selection--single:focus {
border: 1px solid #5897fb; }
.select2-container--classic .select2-selection--single .select2-selection__rendered {
color: #444;
line-height: 28px; }
.select2-container--classic .select2-selection--single .select2-selection__clear {
cursor: pointer;
float: right;
font-weight: bold;
margin-right: 10px; }
.select2-container--classic .select2-selection--single .select2-selection__placeholder {
color: #999; }
.select2-container--classic .select2-selection--single .select2-selection__arrow {
background-color: #ddd;
border: none;
border-left: 1px solid #aaa;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
height: 26px;
position: absolute;
top: 1px;
right: 1px;
width: 20px;
background-image: -webkit-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
background-image: -o-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
background-image: linear-gradient(to bottom, #eeeeee 50%, #cccccc 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0); }
.select2-container--classic .select2-selection--single .select2-selection__arrow b {
border-color: #888 transparent transparent transparent;
border-style: solid;
border-width: 5px 4px 0 4px;
height: 0;
left: 50%;
margin-left: -4px;
margin-top: -2px;
position: absolute;
top: 50%;
width: 0; }
.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear {
float: left; }
.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow {
border: none;
border-right: 1px solid #aaa;
border-radius: 0;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
left: 1px;
right: auto; }
.select2-container--classic.select2-container--open .select2-selection--single {
border: 1px solid #5897fb; }
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow {
background: transparent;
border: none; }
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b {
border-color: transparent transparent #888 transparent;
border-width: 0 4px 5px 4px; }
.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single {
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0;
background-image: -webkit-linear-gradient(top, white 0%, #eeeeee 50%);
background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%);
background-image: linear-gradient(to bottom, white 0%, #eeeeee 50%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single {
border-bottom: none;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
background-image: -webkit-linear-gradient(top, #eeeeee 50%, white 100%);
background-image: -o-linear-gradient(top, #eeeeee 50%, white 100%);
background-image: linear-gradient(to bottom, #eeeeee 50%, white 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0); }
.select2-container--classic .select2-selection--multiple {
background-color: white;
border: 1px solid #aaa;
border-radius: 4px;
cursor: text;
outline: 0; }
.select2-container--classic .select2-selection--multiple:focus {
border: 1px solid #5897fb; }
.select2-container--classic .select2-selection--multiple .select2-selection__rendered {
list-style: none;
margin: 0;
padding: 0 5px; }
.select2-container--classic .select2-selection--multiple .select2-selection__clear {
display: none; }
.select2-container--classic .select2-selection--multiple .select2-selection__choice {
background-color: #e4e4e4;
border: 1px solid #aaa;
border-radius: 4px;
cursor: default;
float: left;
margin-right: 5px;
margin-top: 5px;
padding: 0 5px; }
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove {
color: #888;
cursor: pointer;
display: inline-block;
font-weight: bold;
margin-right: 2px; }
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover {
color: #555; }
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
float: right;
margin-left: 5px;
margin-right: auto; }
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
margin-left: 2px;
margin-right: auto; }
.select2-container--classic.select2-container--open .select2-selection--multiple {
border: 1px solid #5897fb; }
.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple {
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0; }
.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple {
border-bottom: none;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0; }
.select2-container--classic .select2-search--dropdown .select2-search__field {
border: 1px solid #aaa;
outline: 0; }
.select2-container--classic .select2-search--inline .select2-search__field {
outline: 0;
box-shadow: none; }
.select2-container--classic .select2-dropdown {
background-color: white;
border: 1px solid transparent; }
.select2-container--classic .select2-dropdown--above {
border-bottom: none; }
.select2-container--classic .select2-dropdown--below {
border-top: none; }
.select2-container--classic .select2-results > .select2-results__options {
max-height: 200px;
overflow-y: auto; }
.select2-container--classic .select2-results__option[role=group] {
padding: 0; }
.select2-container--classic .select2-results__option[aria-disabled=true] {
color: grey; }
.select2-container--classic .select2-results__option--highlighted[aria-selected] {
background-color: #3875d7;
color: white; }
.select2-container--classic .select2-results__group {
cursor: default;
display: block;
padding: 6px; }
.select2-container--classic.select2-container--open .select2-dropdown {
border-color: #5897fb; }

File diff suppressed because one or more lines are too long

@ -0,0 +1,621 @@
/* 选择器(过滤器界面)*/
/* SELECTOR (FILTER INTERFACE) */
.selector {
display: flex;
flex: 1;
gap: 0 10px;
}
.selector select {
height: 17.2em;
flex: 1 0 auto;
overflow: scroll;
width: 100%;
}
.selector-available, .selector-chosen {
display: flex;
flex-direction: column;
flex: 1 1;
}
.selector-available-title, .selector-chosen-title {
border: 1px solid var(--border-color);
border-radius: 4px 4px 0 0;
}
.selector .helptext {
font-size: 0.6875rem;
}
.selector-chosen .list-footer-display {
border: 1px solid var(--border-color);
border-top: none;
border-radius: 0 0 4px 4px;
margin: 0 0 10px;
padding: 8px;
text-align: center;
background: var(--primary);
color: var(--header-link-color);
cursor: pointer;
}
.selector-chosen .list-footer-display__clear {
color: var(--breadcrumbs-fg);
}
.selector-chosen-title {
background: var(--secondary);
color: var(--header-link-color);
padding: 8px;
}
.aligned .selector-chosen-title label {
color: var(--header-link-color);
width: 100%;
}
.selector-available-title {
background: var(--darkened-bg);
color: var(--body-quiet-color);
padding: 8px;
}
.aligned .selector-available-title label {
width: 100%;
}
.selector .selector-filter {
border: 1px solid var(--border-color);
border-width: 0 1px;
padding: 8px;
color: var(--body-quiet-color);
font-size: 0.625rem;
margin: 0;
text-align: left;
display: flex;
gap: 8px;
}
.selector .selector-filter label,
.inline-group .aligned .selector .selector-filter label {
float: left;
margin: 7px 0 0;
width: 18px;
height: 18px;
padding: 0;
overflow: hidden;
line-height: 1;
min-width: auto;
}
.selector-filter input {
flex-grow: 1;
}
.selector ul.selector-chooser {
align-self: center;
width: 30px;
background-color: var(--selected-bg);
border-radius: 10px;
margin: 0;
padding: 0;
transform: translateY(-17px);
}
.selector-chooser li {
margin: 0;
padding: 3px;
list-style-type: none;
}
.selector select {
padding: 0 10px;
margin: 0 0 10px;
border-radius: 0 0 4px 4px;
}
.selector .selector-chosen--with-filtered select {
margin: 0;
border-radius: 0;
height: 14em;
}
.selector .selector-chosen:not(.selector-chosen--with-filtered) .list-footer-display {
display: none;
}
.selector-add, .selector-remove {
width: 24px;
height: 24px;
display: block;
text-indent: -3000px;
overflow: hidden;
cursor: default;
opacity: 0.55;
border: none;
}
:enabled.selector-add, :enabled.selector-remove {
opacity: 1;
}
:enabled.selector-add:hover, :enabled.selector-remove:hover {
cursor: pointer;
}
.selector-add {
background: url(../img/selector-icons.svg) 0 -144px no-repeat;
background-size: 24px auto;
}
:enabled.selector-add:focus, :enabled.selector-add:hover {
background-position: 0 -168px;
}
.selector-remove {
background: url(../img/selector-icons.svg) 0 -96px no-repeat;
background-size: 24px auto;
}
:enabled.selector-remove:focus, :enabled.selector-remove:hover {
background-position: 0 -120px;
}
.selector-chooseall, .selector-clearall {
display: inline-block;
height: 16px;
text-align: left;
margin: 0 auto;
overflow: hidden;
font-weight: bold;
line-height: 16px;
color: var(--body-quiet-color);
text-decoration: none;
opacity: 0.55;
border: none;
}
:enabled.selector-chooseall:focus, :enabled.selector-clearall:focus,
:enabled.selector-chooseall:hover, :enabled.selector-clearall:hover {
color: var(--link-fg);
}
:enabled.selector-chooseall, :enabled.selector-clearall {
opacity: 1;
}
:enabled.selector-chooseall:hover, :enabled.selector-clearall:hover {
cursor: pointer;
}
.selector-chooseall {
padding: 0 18px 0 0;
background: url(../img/selector-icons.svg) right -160px no-repeat;
cursor: default;
}
:enabled.selector-chooseall:focus, :enabled.selector-chooseall:hover {
background-position: 100% -176px;
}
.selector-clearall {
padding: 0 0 0 18px;
background: url(../img/selector-icons.svg) 0 -128px no-repeat;
cursor: default;
}
:enabled.selector-clearall:focus, :enabled.selector-clearall:hover {
background-position: 0 -144px;
}
/* 堆叠选择器 */
/* STACKED SELECTORS */
.stacked {
float: left;
width: 490px;
display: block;
}
.stacked select {
width: 480px;
height: 10.1em;
}
.stacked .selector-available, .stacked .selector-chosen {
width: 480px;
}
.stacked .selector-available {
margin-bottom: 0;
}
.stacked .selector-available input {
width: 422px;
}
.stacked ul.selector-chooser {
display: flex;
height: 30px;
width: 64px;
margin: 0 0 10px 40%;
background-color: #eee;
border-radius: 10px;
transform: none;
}
.stacked .selector-chooser li {
float: left;
padding: 3px 3px 3px 5px;
}
.stacked .selector-chooseall, .stacked .selector-clearall {
display: none;
}
.stacked .selector-add {
background: url(../img/selector-icons.svg) 0 -48px no-repeat;
background-size: 24px auto;
cursor: default;
}
.stacked :enabled.selector-add {
background-position: 0 -48px;
cursor: pointer;
}
.stacked :enabled.selector-add:focus, .stacked :enabled.selector-add:hover {
background-position: 0 -72px;
cursor: pointer;
}
.stacked .selector-remove {
background: url(../img/selector-icons.svg) 0 0 no-repeat;
background-size: 24px auto;
cursor: default;
}
.stacked :enabled.selector-remove {
background-position: 0 0px;
cursor: pointer;
}
.stacked :enabled.selector-remove:focus, .stacked :enabled.selector-remove:hover {
background-position: 0 -24px;
cursor: pointer;
}
.selector .help-icon {
background: url(../img/icon-unknown.svg) 0 0 no-repeat;
display: inline-block;
vertical-align: middle;
margin: -2px 0 0 2px;
width: 13px;
height: 13px;
}
.selector .selector-chosen .help-icon {
background: url(../img/icon-unknown-alt.svg) 0 0 no-repeat;
}
.selector .search-label-icon {
background: url(../img/search.svg) 0 0 no-repeat;
display: inline-block;
height: 1.125rem;
width: 1.125rem;
}
/* 日期和时间 */
/* DATE AND TIME */
p.datetime {
line-height: 20px;
margin: 0;
padding: 0;
color: var(--body-quiet-color);
font-weight: bold;
}
.datetime span {
white-space: nowrap;
font-weight: normal;
font-size: 0.6875rem;
color: var(--body-quiet-color);
}
.datetime input, .form-row .datetime input.vDateField, .form-row .datetime input.vTimeField {
margin-left: 5px;
margin-bottom: 4px;
}
table p.datetime {
font-size: 0.6875rem;
margin-left: 0;
padding-left: 0;
}
.datetimeshortcuts .clock-icon, .datetimeshortcuts .date-icon {
position: relative;
display: inline-block;
vertical-align: middle;
height: 24px;
width: 24px;
overflow: hidden;
}
.datetimeshortcuts .clock-icon {
background: url(../img/icon-clock.svg) 0 0 no-repeat;
background-size: 24px auto;
}
.datetimeshortcuts a:focus .clock-icon,
.datetimeshortcuts a:hover .clock-icon {
background-position: 0 -24px;
}
.datetimeshortcuts .date-icon {
background: url(../img/icon-calendar.svg) 0 0 no-repeat;
background-size: 24px auto;
top: -1px;
}
.datetimeshortcuts a:focus .date-icon,
.datetimeshortcuts a:hover .date-icon {
background-position: 0 -24px;
}
.timezonewarning {
font-size: 0.6875rem;
color: var(--body-quiet-color);
}
/* URL */
p.url {
line-height: 20px;
margin: 0;
padding: 0;
color: var(--body-quiet-color);
font-size: 0.6875rem;
font-weight: bold;
}
.url a {
font-weight: normal;
}
/* 文件上传 */
/* FILE UPLOADS */
p.file-upload {
line-height: 20px;
margin: 0;
padding: 0;
color: var(--body-quiet-color);
font-size: 0.6875rem;
font-weight: bold;
}
.file-upload a {
font-weight: normal;
}
.file-upload .deletelink {
margin-left: 5px;
}
span.clearable-file-input label {
color: var(--body-fg);
font-size: 0.6875rem;
display: inline;
float: none;
}
/* 日历和时钟 */
/* CALENDARS & CLOCKS */
.calendarbox, .clockbox {
margin: 5px auto;
font-size: 0.75rem;
width: 19em;
text-align: center;
background: var(--body-bg);
color: var(--body-fg);
border: 1px solid var(--hairline-color);
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
overflow: hidden;
position: relative;
}
.clockbox {
width: auto;
}
.calendar {
margin: 0;
padding: 0;
}
.calendar table {
margin: 0;
padding: 0;
border-collapse: collapse;
background: white;
width: 100%;
}
.calendar caption, .calendarbox h2 {
margin: 0;
text-align: center;
border-top: none;
font-weight: 700;
font-size: 0.75rem;
color: #333;
background: var(--accent);
}
.calendar th {
padding: 8px 5px;
background: var(--darkened-bg);
border-bottom: 1px solid var(--border-color);
font-weight: 400;
font-size: 0.75rem;
text-align: center;
color: var(--body-quiet-color);
}
.calendar td {
font-weight: 400;
font-size: 0.75rem;
text-align: center;
padding: 0;
border-top: 1px solid var(--hairline-color);
border-bottom: none;
}
.calendar td.selected a {
background: var(--secondary);
color: var(--button-fg);
}
.calendar td.nonday {
background: var(--darkened-bg);
}
.calendar td.today a {
font-weight: 700;
}
.calendar td a, .timelist a {
display: block;
font-weight: 400;
padding: 6px;
text-decoration: none;
color: var(--body-quiet-color);
}
.calendar td a:focus, .timelist a:focus,
.calendar td a:hover, .timelist a:hover {
background: var(--primary);
color: white;
}
.calendar td a:active, .timelist a:active {
background: var(--header-bg);
color: white;
}
.calendarnav {
font-size: 0.625rem;
text-align: center;
color: #ccc;
margin: 0;
padding: 1px 3px;
}
.calendarnav a:link, #calendarnav a:visited,
#calendarnav a:focus, #calendarnav a:hover {
color: var(--body-quiet-color);
}
.calendar-shortcuts {
background: var(--body-bg);
color: var(--body-quiet-color);
font-size: 0.6875rem;
line-height: 0.6875rem;
border-top: 1px solid var(--hairline-color);
padding: 8px 0;
}
.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next {
display: block;
position: absolute;
top: 8px;
width: 15px;
height: 15px;
text-indent: -9999px;
padding: 0;
}
.calendarnav-previous {
left: 10px;
background: url(../img/calendar-icons.svg) 0 0 no-repeat;
}
.calendarnav-next {
right: 10px;
background: url(../img/calendar-icons.svg) 0 -15px no-repeat;
}
.calendar-cancel {
margin: 0;
padding: 4px 0;
font-size: 0.75rem;
background: var(--close-button-bg);
border-top: 1px solid var(--border-color);
color: var(--button-fg);
}
.calendar-cancel:focus, .calendar-cancel:hover {
background: var(--close-button-hover-bg);
}
.calendar-cancel a {
color: var(--button-fg);
display: block;
}
ul.timelist, .timelist li {
list-style-type: none;
margin: 0;
padding: 0;
}
.timelist a {
padding: 2px;
}
/* 行内编辑 */
/* EDIT INLINE */
.inline-deletelink {
float: right;
text-indent: -9999px;
background: url(../img/inline-delete.svg) 0 0 no-repeat;
width: 1.5rem;
height: 1.5rem;
border: 0px none;
margin-bottom: .25rem;
}
.inline-deletelink:focus, .inline-deletelink:hover {
cursor: pointer;
}
/* 相关小部件包装器 */
/* RELATED WIDGET WRAPPER */
.related-widget-wrapper {
display: flex;
gap: 0 10px;
flex-grow: 1;
flex-wrap: wrap;
margin-bottom: 5px;
}
.related-widget-wrapper-link {
opacity: .6;
filter: grayscale(1);
}
.related-widget-wrapper-link:link {
opacity: 1;
filter: grayscale(0);
}
/* GIS地图 */
/* GIS MAPS */
.dj_map {
width: 600px;
height: 400px;
}

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2014 Code Charm Ltd
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,7 @@
All icons are taken from Font Awesome (https://fontawesome.com/) project.
The Font Awesome font is licensed under the SIL OFL 1.1:
- https://scripts.sil.org/OFL
SVG icons source: https://github.com/encharm/Font-Awesome-SVG-PNG
Font-Awesome-SVG-PNG is licensed under the MIT license (see file license
in current folder).

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="15"
height="30"
viewBox="0 0 1792 3584"
version="1.1"
id="svg5"
sodipodi:docname="calendar-icons.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview5"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="13.3"
inkscape:cx="15.526316"
inkscape:cy="20.977444"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg5" />
<defs
id="defs2">
<g
id="previous">
<path
d="m 1037,1395 102,-102 q 19,-19 19,-45 0,-26 -19,-45 L 832,896 1139,589 q 19,-19 19,-45 0,-26 -19,-45 L 1037,397 q -19,-19 -45,-19 -26,0 -45,19 L 493,851 q -19,19 -19,45 0,26 19,45 l 454,454 q 19,19 45,19 26,0 45,-19 z m 627,-499 q 0,209 -103,385.5 Q 1458,1458 1281.5,1561 1105,1664 896,1664 687,1664 510.5,1561 334,1458 231,1281.5 128,1105 128,896 128,687 231,510.5 334,334 510.5,231 687,128 896,128 1105,128 1281.5,231 1458,334 1561,510.5 1664,687 1664,896 Z"
id="path1" />
</g>
<g
id="next">
<path
d="m 845,1395 454,-454 q 19,-19 19,-45 0,-26 -19,-45 L 845,397 q -19,-19 -45,-19 -26,0 -45,19 L 653,499 q -19,19 -19,45 0,26 19,45 l 307,307 -307,307 q -19,19 -19,45 0,26 19,45 l 102,102 q 19,19 45,19 26,0 45,-19 z m 819,-499 q 0,209 -103,385.5 Q 1458,1458 1281.5,1561 1105,1664 896,1664 687,1664 510.5,1561 334,1458 231,1281.5 128,1105 128,896 128,687 231,510.5 334,334 510.5,231 687,128 896,128 1105,128 1281.5,231 1458,334 1561,510.5 1664,687 1664,896 Z"
id="path2" />
</g>
</defs>
<use
xlink:href="#next"
x="0"
y="5376"
fill="#000000"
id="use5"
transform="translate(0,-3584)" />
<use
xlink:href="#previous"
x="0"
y="0"
fill="#333333"
id="use2"
style="fill:#000000;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

@ -0,0 +1 @@
<svg width="24" height="22" viewBox="0 0 847 779" xmlns="http://www.w3.org/2000/svg"><g><path fill="#EBECE6" d="M120 1h607c66 0 120 54 120 120v536c0 66-54 120-120 120h-607c-66 0-120-54-120-120v-536c0-66 54-120 120-120z"/><path fill="#9E9E93" d="M120 1h607c66 0 120 54 120 120v536c0 66-54 120-120 120h-607c-66 0-120-54-120-120v-536c0-66 54-120 120-120zm607 25h-607c-26 0-50 11-67 28-17 18-28 41-28 67v536c0 27 11 50 28 68 17 17 41 27 67 27h607c26 0 49-10 67-27 17-18 28-41 28-68v-536c0-26-11-49-28-67-18-17-41-28-67-28z"/><path stroke="#A9A8A4" stroke-width="20" d="M706 295l-68 281"/><path stroke="#E47474" stroke-width="20" d="M316 648l390-353M141 435l175 213"/><path stroke="#C9C9C9" stroke-width="20" d="M319 151l-178 284M706 295l-387-144"/><g fill="#040405"><path d="M319 111c22 0 40 18 40 40s-18 40-40 40-40-18-40-40 18-40 40-40zM141 395c22 0 40 18 40 40s-18 40-40 40c-23 0-41-18-41-40s18-40 41-40zM316 608c22 0 40 18 40 40 0 23-18 41-40 41s-40-18-40-41c0-22 18-40 40-40zM706 254c22 0 40 18 40 41 0 22-18 40-40 40s-40-18-40-40c0-23 18-41 40-41zM638 536c22 0 40 18 40 40s-18 40-40 40-40-18-40-40 18-40 40-40z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1 @@
<svg width="24" height="22" viewBox="0 0 847 779" xmlns="http://www.w3.org/2000/svg"><g><path fill="#F1C02A" d="M120 1h607c66 0 120 54 120 120v536c0 66-54 120-120 120h-607c-66 0-120-54-120-120v-536c0-66 54-120 120-120z"/><path fill="#9E9E93" d="M120 1h607c66 0 120 54 120 120v536c0 66-54 120-120 120h-607c-66 0-120-54-120-120v-536c0-66 54-120 120-120zm607 25h-607c-26 0-50 11-67 28-17 18-28 41-28 67v536c0 27 11 50 28 68 17 17 41 27 67 27h607c26 0 49-10 67-27 17-18 28-41 28-68v-536c0-26-11-49-28-67-18-17-41-28-67-28z"/><path stroke="#A9A8A4" stroke-width="20" d="M706 295l-68 281"/><path stroke="#E47474" stroke-width="20" d="M316 648l390-353M141 435l175 213"/><path stroke="#C9A741" stroke-width="20" d="M319 151l-178 284M706 295l-387-144"/><g fill="#040405"><path d="M319 111c22 0 40 18 40 40s-18 40-40 40-40-18-40-40 18-40 40-40zM141 395c22 0 40 18 40 40s-18 40-40 40c-23 0-41-18-41-40s18-40 41-40zM316 608c22 0 40 18 40 40 0 23-18 41-40 41s-40-18-40-41c0-22 18-40 40-40zM706 254c22 0 40 18 40 41 0 22-18 40-40 40s-40-18-40-40c0-23 18-41 40-41zM638 536c22 0 40 18 40 40s-18 40-40 40-40-18-40-40 18-40 40-40z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,3 @@
<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#5fa225" d="M1600 796v192q0 40-28 68t-68 28h-416v416q0 40-28 68t-68 28h-192q-40 0-68-28t-28-68v-416h-416q-40 0-68-28t-28-68v-192q0-40 28-68t68-28h416v-416q0-40 28-68t68-28h192q40 0 68 28t28 68v416h416q40 0 68 28t28 68z"/>
</svg>

After

Width:  |  Height:  |  Size: 331 B

@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#efb80b" d="M1024 1375v-190q0-14-9.5-23.5t-22.5-9.5h-192q-13 0-22.5 9.5t-9.5 23.5v190q0 14 9.5 23.5t22.5 9.5h192q13 0 22.5-9.5t9.5-23.5zm-2-374l18-459q0-12-10-19-13-11-24-11h-220q-11 0-24 11-10 7-10 21l17 457q0 10 10 16.5t24 6.5h185q14 0 23.5-6.5t10.5-16.5zm-14-934l768 1408q35 63-2 126-17 29-46.5 46t-63.5 17h-1536q-34 0-63.5-17t-46.5-46q-37-63-2-126l768-1408q17-31 47-49t65-18 65 18 47 49z"/>
</svg>

After

Width:  |  Height:  |  Size: 504 B

@ -0,0 +1,9 @@
<svg width="16" height="32" viewBox="0 0 1792 3584" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<g id="icon">
<path d="M192 1664h288v-288h-288v288zm352 0h320v-288h-320v288zm-352-352h288v-320h-288v320zm352 0h320v-320h-320v320zm-352-384h288v-288h-288v288zm736 736h320v-288h-320v288zm-384-736h320v-288h-320v288zm768 736h288v-288h-288v288zm-384-352h320v-320h-320v320zm-352-864v-288q0-13-9.5-22.5t-22.5-9.5h-64q-13 0-22.5 9.5t-9.5 22.5v288q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5-9.5t9.5-22.5zm736 864h288v-320h-288v320zm-384-384h320v-288h-320v288zm384 0h288v-288h-288v288zm32-480v-288q0-13-9.5-22.5t-22.5-9.5h-64q-13 0-22.5 9.5t-9.5 22.5v288q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5-9.5t9.5-22.5zm384-64v1280q0 52-38 90t-90 38h-1408q-52 0-90-38t-38-90v-1280q0-52 38-90t90-38h128v-96q0-66 47-113t113-47h64q66 0 113 47t47 113v96h384v-96q0-66 47-113t113-47h64q66 0 113 47t47 113v96h128q52 0 90 38t38 90z"/>
</g>
</defs>
<use xlink:href="#icon" x="0" y="0" fill="#447e9b" />
<use xlink:href="#icon" x="0" y="1792" fill="#003366" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,3 @@
<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#b48c08" d="M491 1536l91-91-235-235-91 91v107h128v128h107zm523-928q0-22-22-22-10 0-17 7l-542 542q-7 7-7 17 0 22 22 22 10 0 17-7l542-542q7-7 7-17zm-54-192l416 416-832 832h-416v-416zm683 96q0 53-37 90l-166 166-416-416 166-165q36-38 90-38 53 0 91 38l235 234q37 39 37 91z"/>
</svg>

After

Width:  |  Height:  |  Size: 380 B

@ -0,0 +1,9 @@
<svg width="16" height="32" viewBox="0 0 1792 3584" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<g id="icon">
<path d="M1024 544v448q0 14-9 23t-23 9h-320q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h224v-352q0-14 9-23t23-9h64q14 0 23 9t9 23zm416 352q0-148-73-273t-198-198-273-73-273 73-198 198-73 273 73 273 198 198 273 73 273-73 198-198 73-273zm224 0q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</g>
</defs>
<use xlink:href="#icon" x="0" y="0" fill="#447e9b" />
<use xlink:href="#icon" x="0" y="1792" fill="#003366" />
</svg>

After

Width:  |  Height:  |  Size: 677 B

@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#dd4646" d="M1490 1322q0 40-28 68l-136 136q-28 28-68 28t-68-28l-294-294-294 294q-28 28-68 28t-68-28l-136-136q-28-28-28-68t28-68l294-294-294-294q-28-28-28-68t28-68l136-136q28-28 68-28t68 28l294 294 294-294q28-28 68-28t68 28l136 136q28 28 28 68t-28 68l-294 294 294 294q28 28 28 68z"/>
</svg>

After

Width:  |  Height:  |  Size: 392 B

@ -0,0 +1,3 @@
<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#2b70bf" d="m555 1335 78-141q-87-63-136-159t-49-203q0-121 61-225-229 117-381 353 167 258 427 375zm389-759q0-20-14-34t-34-14q-125 0-214.5 89.5T592 832q0 20 14 34t34 14 34-14 14-34q0-86 61-147t147-61q20 0 34-14t14-34zm363-191q0 7-1 9-105 188-315 566t-316 567l-49 89q-10 16-28 16-12 0-134-70-16-10-16-28 0-12 44-87-143-65-263.5-173T20 1029Q0 998 0 960t20-69q153-235 380-371t496-136q89 0 180 17l54-97q10-16 28-16 5 0 18 6t31 15.5 33 18.5 31.5 18.5T1291 358q16 10 16 27zm37 447q0 139-79 253.5T1056 1250l280-502q8 45 8 84zm448 128q0 35-20 69-39 64-109 145-150 172-347.5 267T896 1536l74-132q212-18 392.5-137T1664 960q-115-179-282-294l63-112q95 64 182.5 153T1772 891q20 34 20 69z"/>
</svg>

After

Width:  |  Height:  |  Size: 784 B

@ -0,0 +1,3 @@
<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#dd4646" d="M1277 1122q0-26-19-45l-181-181 181-181q19-19 19-45 0-27-19-46l-90-90q-19-19-46-19-26 0-45 19l-181 181-181-181q-19-19-45-19-27 0-46 19l-90 90q-19 19-19 46 0 26 19 45l181 181-181 181q-19 19-19 45 0 27 19 46l90 90q19 19 46 19 26 0 45-19l181-181 181 181q19 19 45 19 27 0 46-19l90-90q19-19 19-46zm387-226q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 560 B

@ -0,0 +1,3 @@
<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#ffffff" d="M1024 1376v-192q0-14-9-23t-23-9h-192q-14 0-23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23-9t9-23zm256-672q0-88-55.5-163t-138.5-116-170-41q-243 0-371 213-15 24 8 42l132 100q7 6 19 6 16 0 25-12 53-68 86-92 34-24 86-24 48 0 85.5 26t37.5 59q0 38-20 61t-68 45q-63 28-115.5 86.5t-52.5 125.5v36q0 14 9 23t23 9h192q14 0 23-9t9-23q0-19 21.5-49.5t54.5-49.5q32-18 49-28.5t46-35 44.5-48 28-60.5 12.5-81zm384 192q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 655 B

@ -0,0 +1,3 @@
<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#666666" d="M1024 1376v-192q0-14-9-23t-23-9h-192q-14 0-23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23-9t9-23zm256-672q0-88-55.5-163t-138.5-116-170-41q-243 0-371 213-15 24 8 42l132 100q7 6 19 6 16 0 25-12 53-68 86-92 34-24 86-24 48 0 85.5 26t37.5 59q0 38-20 61t-68 45q-63 28-115.5 86.5t-52.5 125.5v36q0 14 9 23t23 9h192q14 0 23-9t9-23q0-19 21.5-49.5t54.5-49.5q32-18 49-28.5t46-35 44.5-48 28-60.5 12.5-81zm384 192q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 655 B

@ -0,0 +1,3 @@
<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#2b70bf" d="M1664 960q-152-236-381-353 61 104 61 225 0 185-131.5 316.5t-316.5 131.5-316.5-131.5-131.5-316.5q0-121 61-225-229 117-381 353 133 205 333.5 326.5t434.5 121.5 434.5-121.5 333.5-326.5zm-720-384q0-20-14-34t-34-14q-125 0-214.5 89.5t-89.5 214.5q0 20 14 34t34 14 34-14 14-34q0-86 61-147t147-61q20 0 34-14t14-34zm848 384q0 34-20 69-140 230-376.5 368.5t-499.5 138.5-499.5-139-376.5-368q-20-35-20-69t20-69q140-229 376.5-368t499.5-139 499.5 139 376.5 368q20 35 20 69z"/>
</svg>

After

Width:  |  Height:  |  Size: 581 B

@ -0,0 +1,3 @@
<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#70bf2b" d="M1412 734q0-28-18-46l-91-90q-19-19-45-19t-45 19l-408 407-226-226q-19-19-45-19t-45 19l-91 90q-18 18-18 46 0 27 18 45l362 362q19 19 45 19 27 0 46-19l543-543q18-18 18-45zm252 162q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 436 B

@ -0,0 +1,3 @@
<svg viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#999999" d="M1277 1122q0-26-19-45l-181-181 181-181q19-19 19-45 0-27-19-46l-90-90q-19-19-46-19-26 0-45 19l-181 181-181-181q-19-19-45-19-27 0-46 19l-90 90q-19 19-19 46 0 26 19 45l181 181-181 181q-19 19-19 45 0 27 19 46l90 90q19 19 46 19 26 0 45-19l181-181 181 181q19 19 45 19 27 0 46-19l90-90q19-19 19-46zm387-226q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 537 B

@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#555555" d="M1216 832q0-185-131.5-316.5t-316.5-131.5-316.5 131.5-131.5 316.5 131.5 316.5 316.5 131.5 316.5-131.5 131.5-316.5zm512 832q0 52-38 90t-90 38q-54 0-90-38l-343-342q-179 124-399 124-143 0-273.5-55.5t-225-150-150-225-55.5-273.5 55.5-273.5 150-225 225-150 273.5-55.5 273.5 55.5 225 150 150 225 55.5 273.5q0 220-124 399l343 343q37 37 37 90z"/>
</svg>

After

Width:  |  Height:  |  Size: 458 B

@ -0,0 +1,34 @@
<svg width="16" height="192" viewBox="0 0 1792 21504" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<g id="up">
<path d="M1412 895q0-27-18-45l-362-362-91-91q-18-18-45-18t-45 18l-91 91-362 362q-18 18-18 45t18 45l91 91q18 18 45 18t45-18l189-189v502q0 26 19 45t45 19h128q26 0 45-19t19-45v-502l189 189q19 19 45 19t45-19l91-91q18-18 18-45zm252 1q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</g>
<g id="down">
<path d="M1412 897q0-27-18-45l-91-91q-18-18-45-18t-45 18l-189 189v-502q0-26-19-45t-45-19h-128q-26 0-45 19t-19 45v502l-189-189q-19-19-45-19t-45 19l-91 91q-18 18-18 45t18 45l362 362 91 91q18 18 45 18t45-18l91-91 362-362q18-18 18-45zm252-1q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</g>
<g id="left">
<path d="M1408 960v-128q0-26-19-45t-45-19h-502l189-189q19-19 19-45t-19-45l-91-91q-18-18-45-18t-45 18l-362 362-91 91q-18 18-18 45t18 45l91 91 362 362q18 18 45 18t45-18l91-91q18-18 18-45t-18-45l-189-189h502q26 0 45-19t19-45zm256-64q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</g>
<g id="right">
<path d="M1413 896q0-27-18-45l-91-91-362-362q-18-18-45-18t-45 18l-91 91q-18 18-18 45t18 45l189 189h-502q-26 0-45 19t-19 45v128q0 26 19 45t45 19h502l-189 189q-19 19-19 45t19 45l91 91q18 18 45 18t45-18l362-362 91-91q18-18 18-45zm251 0q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</g>
<g id="clearall">
<path transform="translate(336, 336) scale(0.75)" d="M1037 1395l102-102q19-19 19-45t-19-45l-307-307 307-307q19-19 19-45t-19-45l-102-102q-19-19-45-19t-45 19l-454 454q-19 19-19 45t19 45l454 454q19 19 45 19t45-19zm627-499q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</g>
<g id="chooseall">
<path transform="translate(336, 336) scale(0.75)" d="M845 1395l454-454q19-19 19-45t-19-45l-454-454q-19-19-45-19t-45 19l-102 102q-19 19-19 45t19 45l307 307-307 307q-19 19-19 45t19 45l102 102q19 19 45 19t45-19zm819-499q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
</g>
</defs>
<use xlink:href="#up" x="0" y="0" fill="#666666" />
<use xlink:href="#up" x="0" y="1792" fill="#447e9b" />
<use xlink:href="#down" x="0" y="3584" fill="#666666" />
<use xlink:href="#down" x="0" y="5376" fill="#447e9b" />
<use xlink:href="#left" x="0" y="7168" fill="#666666" />
<use xlink:href="#left" x="0" y="8960" fill="#447e9b" />
<use xlink:href="#right" x="0" y="10752" fill="#666666" />
<use xlink:href="#right" x="0" y="12544" fill="#447e9b" />
<use xlink:href="#clearall" x="0" y="14336" fill="#666666" />
<use xlink:href="#clearall" x="0" y="16128" fill="#447e9b" />
<use xlink:href="#chooseall" x="0" y="17920" fill="#666666" />
<use xlink:href="#chooseall" x="0" y="19712" fill="#447e9b" />
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

@ -0,0 +1,19 @@
<svg width="14" height="84" viewBox="0 0 1792 10752" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<g id="sort">
<path d="M1408 1088q0 26-19 45l-448 448q-19 19-45 19t-45-19l-448-448q-19-19-19-45t19-45 45-19h896q26 0 45 19t19 45zm0-384q0 26-19 45t-45 19h-896q-26 0-45-19t-19-45 19-45l448-448q19-19 45-19t45 19l448 448q19 19 19 45z"/>
</g>
<g id="ascending">
<path d="M1408 1216q0 26-19 45t-45 19h-896q-26 0-45-19t-19-45 19-45l448-448q19-19 45-19t45 19l448 448q19 19 19 45z"/>
</g>
<g id="descending">
<path d="M1408 704q0 26-19 45l-448 448q-19 19-45 19t-45-19l-448-448q-19-19-19-45t19-45 45-19h896q26 0 45 19t19 45z"/>
</g>
</defs>
<use xlink:href="#sort" x="0" y="0" fill="#999999" />
<use xlink:href="#sort" x="0" y="1792" fill="#447e9b" />
<use xlink:href="#ascending" x="0" y="3584" fill="#999999" />
<use xlink:href="#ascending" x="0" y="5376" fill="#447e9b" />
<use xlink:href="#descending" x="0" y="7168" fill="#999999" />
<use xlink:href="#descending" x="0" y="8960" fill="#447e9b" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,3 @@
<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#ffffff" d="M1600 736v192q0 40-28 68t-68 28h-416v416q0 40-28 68t-68 28h-192q-40 0-68-28t-28-68v-416h-416q-40 0-68-28t-28-68v-192q0-40 28-68t68-28h416v-416q0-40 28-68t68-28h192q40 0 68 28t28 68v416h416q40 0 68 28t28 68z"/>
</svg>

After

Width:  |  Height:  |  Size: 331 B

@ -0,0 +1,3 @@
<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path fill="#ffffff" d="M1363 877l-742 742q-19 19-45 19t-45-19l-166-166q-19-19-19-45t19-45l531-531-531-531q-19-19-19-45t19-45l166-166q19-19 45-19t45 19l742 742q19 19 19 45t-19 45z"/>
</svg>

After

Width:  |  Height:  |  Size: 280 B

@ -0,0 +1,190 @@
'use strict';
{
const SelectBox = {
cache: {}, // 选择框缓存,用于存储选项数据
/**
* 初始化选择框缓存
* @param {string} id - 选择框元素的ID
*/
init: function(id) {
const box = document.getElementById(id);
SelectBox.cache[id] = [];
const cache = SelectBox.cache[id];
// 将选择框中的所有选项添加到缓存中
for (const node of box.options) {
cache.push({value: node.value, text: node.text, displayed: 1});
}
},
/**
* 从缓存重新显示HTML选择框
* @param {string} id - 选择框元素的ID
*/
redisplay: function(id) {
// 从缓存重新填充HTML选择框
const box = document.getElementById(id);
const scroll_value_from_top = box.scrollTop;
box.innerHTML = '';
// 只显示标记为displayed的选项
for (const node of SelectBox.cache[id]) {
if (node.displayed) {
const new_option = new Option(node.text, node.value, false, false);
// 鼠标悬停时显示提示工具
new_option.title = node.text;
box.appendChild(new_option);
}
}
box.scrollTop = scroll_value_from_top;
},
/**
* 根据文本过滤选择框中的选项
* @param {string} id - 选择框元素的ID
* @param {string} text - 过滤文本
*/
filter: function(id, text) {
// 重新显示HTML选择框仅显示包含文本中所有单词的选项AND搜索
const tokens = text.toLowerCase().split(/\s+/);
for (const node of SelectBox.cache[id]) {
node.displayed = 1;
const node_text = node.text.toLowerCase();
// 检查每个词是否都包含在选项文本中
for (const token of tokens) {
if (!node_text.includes(token)) {
node.displayed = 0;
break; // 一旦第一个词未找到就结束
}
}
}
SelectBox.redisplay(id);
},
/**
* 获取隐藏节点数量
* @param {string} id - 选择框元素的ID
* @returns {number} 隐藏节点的数量
*/
get_hidden_node_count(id) {
const cache = SelectBox.cache[id] || [];
return cache.filter(node => node.displayed === 0).length;
},
/**
* 从缓存中删除指定值的选项
* @param {string} id - 选择框元素的ID
* @param {string} value - 要删除的选项值
*/
delete_from_cache: function(id, value) {
let delete_index = null;
const cache = SelectBox.cache[id];
// 查找要删除的选项索引
for (const [i, node] of cache.entries()) {
if (node.value === value) {
delete_index = i;
break;
}
}
// 从缓存中删除选项
cache.splice(delete_index, 1);
},
/**
* 向缓存中添加选项
* @param {string} id - 选择框元素的ID
* @param {HTMLOptionElement} option - 要添加的选项元素
*/
add_to_cache: function(id, option) {
SelectBox.cache[id].push({value: option.value, text: option.text, displayed: 1});
},
/**
* 检查缓存中是否包含指定值的选项
* @param {string} id - 选择框元素的ID
* @param {string} value - 要检查的选项值
* @returns {boolean} 如果包含则返回true否则返回false
*/
cache_contains: function(id, value) {
// 检查项目是否包含在缓存中
for (const node of SelectBox.cache[id]) {
if (node.value === value) {
return true;
}
}
return false;
},
/**
* 将选中的选项从一个选择框移动到另一个选择框
* @param {string} from - 源选择框ID
* @param {string} to - 目标选择框ID
*/
move: function(from, to) {
const from_box = document.getElementById(from);
// 遍历源选择框中的所有选项
for (const option of from_box.options) {
const option_value = option.value;
// 如果选项被选中且在缓存中存在,则移动它
if (option.selected && SelectBox.cache_contains(from, option_value)) {
SelectBox.add_to_cache(to, {value: option_value, text: option.text, displayed: 1});
SelectBox.delete_from_cache(from, option_value);
}
}
// 重新显示两个选择框
SelectBox.redisplay(from);
SelectBox.redisplay(to);
},
/**
* 将所有选项从一个选择框移动到另一个选择框
* @param {string} from - 源选择框ID
* @param {string} to - 目标选择框ID
*/
move_all: function(from, to) {
const from_box = document.getElementById(from);
// 遍历源选择框中的所有选项
for (const option of from_box.options) {
const option_value = option.value;
// 如果选项在缓存中存在,则移动它
if (SelectBox.cache_contains(from, option_value)) {
SelectBox.add_to_cache(to, {value: option_value, text: option.text, displayed: 1});
SelectBox.delete_from_cache(from, option_value);
}
}
// 重新显示两个选择框
SelectBox.redisplay(from);
SelectBox.redisplay(to);
},
/**
* 对选择框中的选项按文本进行排序
* @param {string} id - 选择框元素的ID
*/
sort: function(id) {
SelectBox.cache[id].sort(function(a, b) {
a = a.text.toLowerCase();
b = b.text.toLowerCase();
if (a > b) {
return 1;
}
if (a < b) {
return -1;
}
return 0;
} );
},
/**
* 选中选择框中的所有选项
* @param {string} id - 选择框元素的ID
*/
select_all: function(id) {
const box = document.getElementById(id);
// 遍历所有选项并选中它们
for (const option of box.options) {
option.selected = true;
}
}
};
window.SelectBox = SelectBox;
}

@ -0,0 +1,380 @@
/*global SelectBox, gettext, ngettext, interpolate, quickElement, SelectFilter*/
/*
SelectFilter2 - 将多选框转换为过滤器界面
需要 core.js SelectBox.js
*/
'use strict';
{
window.SelectFilter = {
/**
* 初始化选择过滤器
* @param {string} field_id - 字段ID
* @param {string} field_name - 字段名称
* @param {boolean} is_stacked - 是否堆叠显示
*/
init: function(field_id, field_name, is_stacked) {
if (field_id.match(/__prefix__/)) {
// 不要在空表单上初始化
return;
}
const from_box = document.getElementById(field_id);
from_box.id += '_from'; // 更改ID
from_box.className = 'filtered';
from_box.setAttribute('aria-labelledby', field_id + '_from_title');
// 处理from_box的兄弟元素<p>
for (const p of from_box.parentNode.getElementsByTagName('p')) {
if (p.classList.contains("info")) {
// 移除<p class="info">,因为它会妨碍布局
from_box.parentNode.removeChild(p);
} else if (p.classList.contains("help")) {
// 将帮助文本移到顶部,这样它就不会显示在选择框下方或添加按钮右侧
from_box.parentNode.insertBefore(p, from_box.parentNode.firstChild);
}
}
// <div class="selector"> 或 <div class="selector stacked">
const selector_div = quickElement('div', from_box.parentNode);
// 确保选择器div在开头这样添加链接会显示在控件的右侧
from_box.parentNode.prepend(selector_div);
selector_div.className = is_stacked ? 'selector stacked' : 'selector';
// <div class="selector-available">
const selector_available = quickElement('div', selector_div);
selector_available.className = 'selector-available';
const selector_available_title = quickElement('div', selector_available);
selector_available_title.id = field_id + '_from_title';
selector_available_title.className = 'selector-available-title';
quickElement('label', selector_available_title, interpolate(gettext('Available %s') + ' ', [field_name]), 'for', field_id + '_from');
quickElement(
'p',
selector_available_title,
interpolate(gettext('Choose %s by selecting them and then select the "Choose" arrow button.'), [field_name]),
'class', 'helptext'
);
// 创建过滤器输入框
const filter_p = quickElement('p', selector_available, '', 'id', field_id + '_filter');
filter_p.className = 'selector-filter';
const search_filter_label = quickElement('label', filter_p, '', 'for', field_id + '_input');
quickElement(
'span', search_filter_label, '',
'class', 'help-tooltip search-label-icon',
'aria-label', interpolate(gettext("Type into this box to filter down the list of available %s."), [field_name])
);
filter_p.appendChild(document.createTextNode(' '));
const filter_input = quickElement('input', filter_p, '', 'type', 'text', 'placeholder', gettext("Filter"));
filter_input.id = field_id + '_input';
selector_available.appendChild(from_box);
// 创建"全选"按钮
const choose_all = quickElement(
'button',
selector_available,
interpolate(gettext('Choose all %s'), [field_name]),
'id', field_id + '_add_all',
'class', 'selector-chooseall',
'type', 'button'
);
// <ul class="selector-chooser">
const selector_chooser = quickElement('ul', selector_div);
selector_chooser.className = 'selector-chooser';
// 创建"添加选中"按钮
const add_button = quickElement(
'button',
quickElement('li', selector_chooser),
interpolate(gettext('Choose selected %s'), [field_name]),
'id', field_id + '_add',
'class', 'selector-add',
'type', 'button'
);
// 创建"移除选中"按钮
const remove_button = quickElement(
'button',
quickElement('li', selector_chooser),
interpolate(gettext('Remove selected %s'), [field_name]),
'id', field_id + '_remove',
'class', 'selector-remove',
'type', 'button'
);
// <div class="selector-chosen">
const selector_chosen = quickElement('div', selector_div, '', 'id', field_id + '_selector_chosen');
selector_chosen.className = 'selector-chosen';
const selector_chosen_title = quickElement('div', selector_chosen);
selector_chosen_title.className = 'selector-chosen-title';
selector_chosen_title.id = field_id + '_to_title';
quickElement('label', selector_chosen_title, interpolate(gettext('Chosen %s') + ' ', [field_name]), 'for', field_id + '_to');
quickElement(
'p',
selector_chosen_title,
interpolate(gettext('Remove %s by selecting them and then select the "Remove" arrow button.'), [field_name]),
'class', 'helptext'
);
// 创建选中项过滤器
const filter_selected_p = quickElement('p', selector_chosen, '', 'id', field_id + '_filter_selected');
filter_selected_p.className = 'selector-filter';
const search_filter_selected_label = quickElement('label', filter_selected_p, '', 'for', field_id + '_selected_input');
quickElement(
'span', search_filter_selected_label, '',
'class', 'help-tooltip search-label-icon',
'aria-label', interpolate(gettext("Type into this box to filter down the list of selected %s."), [field_name])
);
filter_selected_p.appendChild(document.createTextNode(' '));
const filter_selected_input = quickElement('input', filter_selected_p, '', 'type', 'text', 'placeholder', gettext("Filter"));
filter_selected_input.id = field_id + '_selected_input';
// 创建目标选择框
quickElement(
'select',
selector_chosen,
'',
'id', field_id + '_to',
'multiple', '',
'size', from_box.size,
'name', from_box.name,
'aria-labelledby', field_id + '_to_title',
'class', 'filtered'
);
// 创建警告页脚
const warning_footer = quickElement('div', selector_chosen, '', 'class', 'list-footer-display');
quickElement('span', warning_footer, '', 'id', field_id + '_list-footer-display-text');
quickElement('span', warning_footer, ' ' + gettext('(click to clear)'), 'class', 'list-footer-display__clear');
// 创建"全部移除"按钮
const clear_all = quickElement(
'button',
selector_chosen,
interpolate(gettext('Remove all %s'), [field_name]),
'id', field_id + '_remove_all',
'class', 'selector-clearall',
'type', 'button'
);
from_box.name = from_box.name + '_old';
// 设置选择框过滤器界面的JavaScript事件处理器
const move_selection = function(e, elem, move_func, from, to) {
if (!elem.hasAttribute('disabled')) {
move_func(from, to);
SelectFilter.refresh_icons(field_id);
SelectFilter.refresh_filtered_selects(field_id);
SelectFilter.refresh_filtered_warning(field_id);
}
e.preventDefault();
};
// 监听"全选"按钮点击事件
choose_all.addEventListener('click', function(e) {
move_selection(e, this, SelectBox.move_all, field_id + '_from', field_id + '_to');
});
// 监听"添加选中"按钮点击事件
add_button.addEventListener('click', function(e) {
move_selection(e, this, SelectBox.move, field_id + '_from', field_id + '_to');
});
// 监听"移除选中"按钮点击事件
remove_button.addEventListener('click', function(e) {
move_selection(e, this, SelectBox.move, field_id + '_to', field_id + '_from');
});
// 监听"全部移除"按钮点击事件
clear_all.addEventListener('click', function(e) {
move_selection(e, this, SelectBox.move_all, field_id + '_to', field_id + '_from');
});
// 监听警告页脚点击事件
warning_footer.addEventListener('click', function(e) {
filter_selected_input.value = '';
SelectBox.filter(field_id + '_to', '');
SelectFilter.refresh_filtered_warning(field_id);
SelectFilter.refresh_icons(field_id);
});
// 监听过滤输入框按键事件
filter_input.addEventListener('keypress', function(e) {
SelectFilter.filter_key_press(e, field_id, '_from', '_to');
});
filter_input.addEventListener('keyup', function(e) {
SelectFilter.filter_key_up(e, field_id, '_from');
});
filter_input.addEventListener('keydown', function(e) {
SelectFilter.filter_key_down(e, field_id, '_from', '_to');
});
// 监听选中项过滤输入框按键事件
filter_selected_input.addEventListener('keypress', function(e) {
SelectFilter.filter_key_press(e, field_id, '_to', '_from');
});
filter_selected_input.addEventListener('keyup', function(e) {
SelectFilter.filter_key_up(e, field_id, '_to', '_selected_input');
});
filter_selected_input.addEventListener('keydown', function(e) {
SelectFilter.filter_key_down(e, field_id, '_to', '_from');
});
// 监听选择框变更事件
selector_div.addEventListener('change', function(e) {
if (e.target.tagName === 'SELECT') {
SelectFilter.refresh_icons(field_id);
}
});
// 监听双击事件
selector_div.addEventListener('dblclick', function(e) {
if (e.target.tagName === 'OPTION') {
if (e.target.closest('select').id === field_id + '_to') {
SelectBox.move(field_id + '_to', field_id + '_from');
} else {
SelectBox.move(field_id + '_from', field_id + '_to');
}
SelectFilter.refresh_icons(field_id);
}
});
// 监听表单提交事件
from_box.closest('form').addEventListener('submit', function() {
SelectBox.filter(field_id + '_to', '');
SelectBox.select_all(field_id + '_to');
});
// 初始化选择框
SelectBox.init(field_id + '_from');
SelectBox.init(field_id + '_to');
// 将选中的from_box选项移动到to_box
SelectBox.move(field_id + '_from', field_id + '_to');
// 初始刷新图标
SelectFilter.refresh_icons(field_id);
},
/**
* 检查是否有选项被选中
* @param {HTMLSelectElement} field - 选择框元素
* @returns {boolean} 如果有选项被选中返回true否则返回false
*/
any_selected: function(field) {
// 临时添加required属性并检查有效性
field.required = true;
const any_selected = field.checkValidity();
field.required = false;
return any_selected;
},
/**
* 刷新过滤警告信息
* @param {string} field_id - 字段ID
*/
refresh_filtered_warning: function(field_id) {
const count = SelectBox.get_hidden_node_count(field_id + '_to');
const selector = document.getElementById(field_id + '_selector_chosen');
const warning = document.getElementById(field_id + '_list-footer-display-text');
selector.className = selector.className.replace('selector-chosen--with-filtered', '');
warning.textContent = interpolate(ngettext(
'%s selected option not visible',
'%s selected options not visible',
count
), [count]);
if(count > 0) {
selector.className += ' selector-chosen--with-filtered';
}
},
/**
* 刷新过滤选择框
* @param {string} field_id - 字段ID
*/
refresh_filtered_selects: function(field_id) {
SelectBox.filter(field_id + '_from', document.getElementById(field_id + "_input").value);
SelectBox.filter(field_id + '_to', document.getElementById(field_id + "_selected_input").value);
},
/**
* 刷新图标状态
* @param {string} field_id - 字段ID
*/
refresh_icons: function(field_id) {
const from = document.getElementById(field_id + '_from');
const to = document.getElementById(field_id + '_to');
// 如果没有选项被选中,则禁用按钮
document.getElementById(field_id + '_add').disabled = !SelectFilter.any_selected(from);
document.getElementById(field_id + '_remove').disabled = !SelectFilter.any_selected(to);
// 如果对应的选择框为空,则禁用按钮
document.getElementById(field_id + '_add_all').disabled = !from.querySelector('option');
document.getElementById(field_id + '_remove_all').disabled = !to.querySelector('option');
},
/**
* 过滤器按键按下事件处理
* @param {KeyboardEvent} event - 键盘事件
* @param {string} field_id - 字段ID
* @param {string} source - 源选择框后缀
* @param {string} target - 目标选择框后缀
*/
filter_key_press: function(event, field_id, source, target) {
const source_box = document.getElementById(field_id + source);
// 如果用户按下回车键,不要提交表单
if ((event.which && event.which === 13) || (event.keyCode && event.keyCode === 13)) {
source_box.selectedIndex = 0;
SelectBox.move(field_id + source, field_id + target);
source_box.selectedIndex = 0;
event.preventDefault();
}
},
/**
* 过滤器按键释放事件处理
* @param {KeyboardEvent} event - 键盘事件
* @param {string} field_id - 字段ID
* @param {string} source - 源选择框后缀
* @param {string} filter_input - 过滤输入框后缀
*/
filter_key_up: function(event, field_id, source, filter_input) {
const input = filter_input || '_input';
const source_box = document.getElementById(field_id + source);
const temp = source_box.selectedIndex;
SelectBox.filter(field_id + source, document.getElementById(field_id + input).value);
source_box.selectedIndex = temp;
SelectFilter.refresh_filtered_warning(field_id);
SelectFilter.refresh_icons(field_id);
},
/**
* 过滤器按键按下事件处理
* @param {KeyboardEvent} event - 键盘事件
* @param {string} field_id - 字段ID
* @param {string} source - 源选择框后缀
* @param {string} target - 目标选择框后缀
*/
filter_key_down: function(event, field_id, source, target) {
const source_box = document.getElementById(field_id + source);
// 右键(39)或左键(37)
const direction = source === '_from' ? 39 : 37;
// 右箭头 -- 移动到另一边
if ((event.which && event.which === direction) || (event.keyCode && event.keyCode === direction)) {
const old_index = source_box.selectedIndex;
SelectBox.move(field_id + source, field_id + target);
SelectFilter.refresh_filtered_selects(field_id);
SelectFilter.refresh_filtered_warning(field_id);
source_box.selectedIndex = (old_index === source_box.length) ? source_box.length - 1 : old_index;
return;
}
// 下箭头 -- 循环选择
if ((event.which && event.which === 40) || (event.keyCode && event.keyCode === 40)) {
source_box.selectedIndex = (source_box.length === source_box.selectedIndex + 1) ? 0 : source_box.selectedIndex + 1;
}
// 上箭头 -- 循环选择
if ((event.which && event.which === 38) || (event.keyCode && event.keyCode === 38)) {
source_box.selectedIndex = (source_box.selectedIndex === 0) ? source_box.length - 1 : source_box.selectedIndex - 1;
}
}
};
// 页面加载完成后初始化所有选择过滤器
window.addEventListener('load', function(e) {
document.querySelectorAll('select.selectfilter, select.selectfilterstacked').forEach(function(el) {
const data = el.dataset;
SelectFilter.init(el.id, data.fieldName, parseInt(data.isStacked, 10));
});
});
}

@ -0,0 +1,260 @@
/*global gettext, interpolate, ngettext, Actions*/
'use strict';
{
/**
* 显示指定选择器的元素
* @param {string} selector - CSS选择器
*/
function show(selector) {
document.querySelectorAll(selector).forEach(function(el) {
el.classList.remove('hidden');
});
}
/**
* 隐藏指定选择器的元素
* @param {string} selector - CSS选择器
*/
function hide(selector) {
document.querySelectorAll(selector).forEach(function(el) {
el.classList.add('hidden');
});
}
/**
* 显示操作问题提示
* @param {Object} options - 配置选项
*/
function showQuestion(options) {
hide(options.acrossClears);
show(options.acrossQuestions);
hide(options.allContainer);
}
/**
* 显示清除跨页选择链接
* @param {Object} options - 配置选项
*/
function showClear(options) {
show(options.acrossClears);
hide(options.acrossQuestions);
document.querySelector(options.actionContainer).classList.remove(options.selectedClass);
show(options.allContainer);
hide(options.counterContainer);
}
/**
* 重置操作状态
* @param {Object} options - 配置选项
*/
function reset(options) {
hide(options.acrossClears);
hide(options.acrossQuestions);
hide(options.allContainer);
show(options.counterContainer);
}
/**
* 清除跨页选择状态
* @param {Object} options - 配置选项
*/
function clearAcross(options) {
reset(options);
const acrossInputs = document.querySelectorAll(options.acrossInput);
acrossInputs.forEach(function(acrossInput) {
acrossInput.value = 0;
});
document.querySelector(options.actionContainer).classList.remove(options.selectedClass);
}
/**
* 选中/取消选中复选框
* @param {NodeList} actionCheckboxes - 操作复选框列表
* @param {Object} options - 配置选项
* @param {boolean} checked - 是否选中
*/
function checker(actionCheckboxes, options, checked) {
if (checked) {
showQuestion(options);
} else {
reset(options);
}
actionCheckboxes.forEach(function(el) {
el.checked = checked;
el.closest('tr').classList.toggle(options.selectedClass, checked);
});
}
/**
* 更新选中计数器
* @param {NodeList} actionCheckboxes - 操作复选框列表
* @param {Object} options - 配置选项
*/
function updateCounter(actionCheckboxes, options) {
// 计算选中的复选框数量
const sel = Array.from(actionCheckboxes).filter(function(el) {
return el.checked;
}).length;
const counter = document.querySelector(options.counterContainer);
// data-actions-icnt在生成的HTML中定义包含查询集中的对象总数
const actions_icnt = Number(counter.dataset.actionsIcnt);
// 更新计数器文本
counter.textContent = interpolate(
ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), {
sel: sel,
cnt: actions_icnt
}, true);
const allToggle = document.getElementById(options.allToggleId);
// 如果所有复选框都被选中,则选中全选复选框
allToggle.checked = sel === actionCheckboxes.length;
if (allToggle.checked) {
showQuestion(options);
} else {
clearAcross(options);
}
}
// 默认配置选项
const defaults = {
actionContainer: "div.actions", // 操作容器
counterContainer: "span.action-counter", // 计数器容器
allContainer: "div.actions span.all", // 全选容器
acrossInput: "div.actions input.select-across", // 跨页选择输入框
acrossQuestions: "div.actions span.question", // 跨页选择问题
acrossClears: "div.actions span.clear", // 跨页选择清除
allToggleId: "action-toggle", // 全选切换ID
selectedClass: "selected" // 选中项的CSS类
};
/**
* 初始化操作功能
* @param {NodeList} actionCheckboxes - 操作复选框列表
* @param {Object} options - 配置选项
*/
window.Actions = function(actionCheckboxes, options) {
options = Object.assign({}, defaults, options);
let list_editable_changed = false; // 可编辑列表是否已更改
let lastChecked = null; // 最后选中的复选框
let shiftPressed = false; // Shift键是否按下
// 监听键盘事件记录Shift键状态
document.addEventListener('keydown', (event) => {
shiftPressed = event.shiftKey;
});
document.addEventListener('keyup', (event) => {
shiftPressed = event.shiftKey;
});
// 监听全选复选框点击事件
document.getElementById(options.allToggleId).addEventListener('click', function(event) {
checker(actionCheckboxes, options, this.checked);
updateCounter(actionCheckboxes, options);
});
// 监听跨页选择问题链接点击事件
document.querySelectorAll(options.acrossQuestions + " a").forEach(function(el) {
el.addEventListener('click', function(event) {
event.preventDefault();
const acrossInputs = document.querySelectorAll(options.acrossInput);
acrossInputs.forEach(function(acrossInput) {
acrossInput.value = 1;
});
showClear(options);
});
});
// 监听跨页选择清除链接点击事件
document.querySelectorAll(options.acrossClears + " a").forEach(function(el) {
el.addEventListener('click', function(event) {
event.preventDefault();
document.getElementById(options.allToggleId).checked = false;
clearAcross(options);
checker(actionCheckboxes, options, false);
updateCounter(actionCheckboxes, options);
});
});
/**
* 获取受Shift键影响的复选框
* @param {HTMLInputElement} target - 目标复选框
* @param {boolean} withModifier - 是否按下修饰键
* @returns {Array} 受影响的复选框数组
*/
function affectedCheckboxes(target, withModifier) {
const multiSelect = (lastChecked && withModifier && lastChecked !== target);
if (!multiSelect) {
return [target];
}
const checkboxes = Array.from(actionCheckboxes);
const targetIndex = checkboxes.findIndex(el => el === target);
const lastCheckedIndex = checkboxes.findIndex(el => el === lastChecked);
const startIndex = Math.min(targetIndex, lastCheckedIndex);
const endIndex = Math.max(targetIndex, lastCheckedIndex);
const filtered = checkboxes.filter((el, index) => (startIndex <= index) && (index <= endIndex));
return filtered;
};
// 监听结果列表中的变更事件
Array.from(document.getElementById('result_list').tBodies).forEach(function(el) {
el.addEventListener('change', function(event) {
const target = event.target;
if (target.classList.contains('action-select')) {
const checkboxes = affectedCheckboxes(target, shiftPressed);
checker(checkboxes, options, target.checked);
updateCounter(actionCheckboxes, options);
lastChecked = target;
} else {
list_editable_changed = true;
}
});
});
// 监听索引按钮点击事件
document.querySelector('#changelist-form button[name=index]').addEventListener('click', function(event) {
if (list_editable_changed) {
// 如果有未保存的更改,提示用户确认
const confirmed = confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."));
if (!confirmed) {
event.preventDefault();
}
}
});
const el = document.querySelector('#changelist-form input[name=_save]');
// 如果存在保存按钮(当有可编辑字段时存在)
if (el) {
el.addEventListener('click', function(event) {
if (document.querySelector('[name=action]').value) {
// 根据是否有未保存的更改显示不同的提示信息
const text = list_editable_changed
? gettext("You have selected an action, but you havent saved your changes to individual fields yet. Please click OK to save. Youll need to re-run the action.")
: gettext("You have selected an action, and you havent made any changes on individual fields. Youre probably looking for the Go button rather than the Save button.");
if (!confirm(text)) {
event.preventDefault();
}
}
});
}
// 页面显示时同步计数器,例如通过后退按钮导航到页面时
window.addEventListener('pageshow', (event) => updateCounter(actionCheckboxes, options));
};
// 页面加载完成后调用指定函数。如果页面已经加载完成,则立即调用该函数
// http://youmightnotneedjquery.com/#ready
function ready(fn) {
if (document.readyState !== 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
// 页面加载完成后初始化操作功能
ready(function() {
const actionsEls = document.querySelectorAll('tr input.action-select');
if (actionsEls.length > 0) {
Actions(actionsEls);
}
});
}

@ -0,0 +1,437 @@
/*global Calendar, findPosX, findPosY, get_format, gettext, gettext_noop, interpolate, ngettext, quickElement*/
// 在以下元素后插入快捷按钮:
// <input type="text" class="vDateField">
// <input type="text" class="vTimeField">
'use strict';
{
const DateTimeShortcuts = {
calendars: [], // 日历数组
calendarInputs: [], // 日历输入框数组
clockInputs: [], // 时钟输入框数组
clockHours: { // 时钟快捷时间选项
default_: [
[gettext_noop('Now'), -1], // 现在
[gettext_noop('Midnight'), 0], // 午夜
[gettext_noop('6 a.m.'), 6], // 早上6点
[gettext_noop('Noon'), 12], // 中午
[gettext_noop('6 p.m.'), 18] // 晚上6点
]
},
dismissClockFunc: [], // 关闭时钟函数数组
dismissCalendarFunc: [], // 关闭日历函数数组
calendarDivName1: 'calendarbox', // 日历<div>的切换名称
calendarDivName2: 'calendarin', // 包含日历的<div>名称
calendarLinkName: 'calendarlink', // 用于切换的日历链接名称
clockDivName: 'clockbox', // 时钟<div>的切换名称
clockLinkName: 'clocklink', // 用于切换的时钟链接名称
shortCutsClass: 'datetimeshortcuts', // 时钟和日历快捷方式的类名
timezoneWarningClass: 'timezonewarning', // 时区不匹配警告的类名
timezoneOffset: 0, // 时区偏移量
/**
* 初始化函数
*/
init: function() {
// 获取服务器时区偏移量
const serverOffset = document.body.dataset.adminUtcOffset;
if (serverOffset) {
// 计算本地时区偏移量
const localOffset = new Date().getTimezoneOffset() * -60;
// 计算时区偏移差值
DateTimeShortcuts.timezoneOffset = localOffset - serverOffset;
}
// 遍历所有input元素
for (const inp of document.getElementsByTagName('input')) {
// 为时间输入框添加时钟控件
if (inp.type === 'text' && inp.classList.contains('vTimeField')) {
DateTimeShortcuts.addClock(inp);
DateTimeShortcuts.addTimezoneWarning(inp);
}
// 为日期输入框添加日历控件
else if (inp.type === 'text' && inp.classList.contains('vDateField')) {
DateTimeShortcuts.addCalendar(inp);
DateTimeShortcuts.addTimezoneWarning(inp);
}
}
},
// 返回当前时间,同时考虑服务器时区
now: function() {
const serverOffset = document.body.dataset.adminUtcOffset;
if (serverOffset) {
const localNow = new Date();
const localOffset = localNow.getTimezoneOffset() * -60;
localNow.setTime(localNow.getTime() + 1000 * (serverOffset - localOffset));
return localNow;
} else {
return new Date();
}
},
// 当浏览器和后端时区不匹配时添加警告
addTimezoneWarning: function(inp) {
const warningClass = DateTimeShortcuts.timezoneWarningClass;
let timezoneOffset = DateTimeShortcuts.timezoneOffset / 3600;
// 如果时区匹配,则不显示警告
if (!timezoneOffset) {
return;
}
// 检查是否已存在警告
if (inp.parentNode.querySelectorAll('.' + warningClass).length) {
return;
}
let message;
if (timezoneOffset > 0) {
// 用户时区比服务器时区快
message = ngettext(
'Note: You are %s hour ahead of server time.',
'Note: You are %s hours ahead of server time.',
timezoneOffset
);
}
else {
// 用户时区比服务器时区慢
timezoneOffset *= -1;
message = ngettext(
'Note: You are %s hour behind server time.',
'Note: You are %s hours behind server time.',
timezoneOffset
);
}
// 插入时区偏移量到消息中
message = interpolate(message, [timezoneOffset]);
// 创建警告元素
const warning = document.createElement('div');
warning.classList.add('help', warningClass);
warning.textContent = message;
inp.parentNode.appendChild(warning);
},
// 为指定字段添加时钟控件
addClock: function(inp) {
const num = DateTimeShortcuts.clockInputs.length;
DateTimeShortcuts.clockInputs[num] = inp;
DateTimeShortcuts.dismissClockFunc[num] = function() { DateTimeShortcuts.dismissClock(num); return true; };
// 快捷链接(时钟图标和"现在"链接)
const shortcuts_span = document.createElement('span');
shortcuts_span.className = DateTimeShortcuts.shortCutsClass;
inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling);
// 创建"现在"链接
const now_link = document.createElement('a');
now_link.href = "#";
now_link.textContent = gettext('Now');
now_link.addEventListener('click', function(e) {
e.preventDefault();
DateTimeShortcuts.handleClockQuicklink(num, -1);
});
// 创建时钟链接
const clock_link = document.createElement('a');
clock_link.href = '#';
clock_link.id = DateTimeShortcuts.clockLinkName + num;
clock_link.addEventListener('click', function(e) {
e.preventDefault();
// 避免触发文档点击处理程序关闭时钟
e.stopPropagation();
DateTimeShortcuts.openClock(num);
});
// 创建时钟图标
quickElement(
'span', clock_link, '',
'class', 'clock-icon',
'title', gettext('Choose a Time')
);
shortcuts_span.appendChild(document.createTextNode('\u00A0'));
shortcuts_span.appendChild(now_link);
shortcuts_span.appendChild(document.createTextNode('\u00A0|\u00A0'));
shortcuts_span.appendChild(clock_link);
// 创建时钟链接div
//
// 标记结构如下:
// <div id="clockbox1" class="clockbox module">
// <h2>选择时间</h2>
// <ul class="timelist">
// <li><a href="#">现在</a></li>
// <li><a href="#">午夜</a></li>
// <li><a href="#">早上6点</a></li>
// <li><a href="#">中午</a></li>
// <li><a href="#">晚上6点</a></li>
// </ul>
// <p class="calendar-cancel"><a href="#">取消</a></p>
// </div>
const clock_box = document.createElement('div');
clock_box.style.display = 'none';
clock_box.style.position = 'absolute';
clock_box.className = 'clockbox module';
clock_box.id = DateTimeShortcuts.clockDivName + num;
document.body.appendChild(clock_box);
clock_box.addEventListener('click', function(e) { e.stopPropagation(); });
quickElement('h2', clock_box, gettext('Choose a time'));
const time_list = quickElement('ul', clock_box);
time_list.className = 'timelist';
// 可以通过JavaScript覆盖选择列表例如:
// DateTimeShortcuts.clockHours.name = [['3 a.m.', 3]];
// 其中name是<input>元素的name属性
const name = typeof DateTimeShortcuts.clockHours[inp.name] === 'undefined' ? 'default_' : inp.name;
DateTimeShortcuts.clockHours[name].forEach(function(element) {
const time_link = quickElement('a', quickElement('li', time_list), gettext(element[0]), 'href', '#');
time_link.addEventListener('click', function(e) {
e.preventDefault();
DateTimeShortcuts.handleClockQuicklink(num, element[1]);
});
});
// 创建取消按钮
const cancel_p = quickElement('p', clock_box);
cancel_p.className = 'calendar-cancel';
const cancel_link = quickElement('a', cancel_p, gettext('Cancel'), 'href', '#');
cancel_link.addEventListener('click', function(e) {
e.preventDefault();
DateTimeShortcuts.dismissClock(num);
});
// 监听键盘事件ESC键关闭弹窗
document.addEventListener('keyup', function(event) {
if (event.which === 27) {
// ESC键关闭弹窗
DateTimeShortcuts.dismissClock(num);
event.preventDefault();
}
});
},
// 打开时钟
openClock: function(num) {
const clock_box = document.getElementById(DateTimeShortcuts.clockDivName + num);
const clock_link = document.getElementById(DateTimeShortcuts.clockLinkName + num);
// 重新计算时钟框位置
// 判断是左到右还是右到左布局
if (window.getComputedStyle(document.body).direction !== 'rtl') {
clock_box.style.left = findPosX(clock_link) + 17 + 'px';
}
else {
// 由于样式宽度是em单位难以计算像素值这里使用估算值
clock_box.style.left = findPosX(clock_link) - 110 + 'px';
}
clock_box.style.top = Math.max(0, findPosY(clock_link) - 30) + 'px';
// 显示时钟框
clock_box.style.display = 'block';
document.addEventListener('click', DateTimeShortcuts.dismissClockFunc[num]);
},
// 关闭时钟
dismissClock: function(num) {
document.getElementById(DateTimeShortcuts.clockDivName + num).style.display = 'none';
document.removeEventListener('click', DateTimeShortcuts.dismissClockFunc[num]);
},
// 处理时钟快捷链接
handleClockQuicklink: function(num, val) {
let d;
if (val === -1) {
d = DateTimeShortcuts.now(); // 当前时间
}
else {
d = new Date(1970, 1, 1, val, 0, 0, 0); // 指定时间
}
DateTimeShortcuts.clockInputs[num].value = d.strftime(get_format('TIME_INPUT_FORMATS')[0]);
DateTimeShortcuts.clockInputs[num].focus();
DateTimeShortcuts.dismissClock(num);
},
// 为指定字段添加日历控件
addCalendar: function(inp) {
const num = DateTimeShortcuts.calendars.length;
DateTimeShortcuts.calendarInputs[num] = inp;
DateTimeShortcuts.dismissCalendarFunc[num] = function() { DateTimeShortcuts.dismissCalendar(num); return true; };
// 快捷链接(日历图标和"今天"链接)
const shortcuts_span = document.createElement('span');
shortcuts_span.className = DateTimeShortcuts.shortCutsClass;
inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling);
// 创建"今天"链接
const today_link = document.createElement('a');
today_link.href = '#';
today_link.appendChild(document.createTextNode(gettext('Today')));
today_link.addEventListener('click', function(e) {
e.preventDefault();
DateTimeShortcuts.handleCalendarQuickLink(num, 0);
});
// 创建日历链接
const cal_link = document.createElement('a');
cal_link.href = '#';
cal_link.id = DateTimeShortcuts.calendarLinkName + num;
cal_link.addEventListener('click', function(e) {
e.preventDefault();
// 避免触发文档点击处理程序关闭日历
e.stopPropagation();
DateTimeShortcuts.openCalendar(num);
});
// 创建日期图标
quickElement(
'span', cal_link, '',
'class', 'date-icon',
'title', gettext('Choose a Date')
);
shortcuts_span.appendChild(document.createTextNode('\u00A0'));
shortcuts_span.appendChild(today_link);
shortcuts_span.appendChild(document.createTextNode('\u00A0|\u00A0'));
shortcuts_span.appendChild(cal_link);
// 创建日历框div
//
// 标记结构如下:
//
// <div id="calendarbox3" class="calendarbox module">
// <h2>
// <a href="#" class="link-previous">&lsaquo;</a>
// <a href="#" class="link-next">&rsaquo;</a> February 2003
// </h2>
// <div class="calendar" id="calendarin3">
// <!-- (cal) -->
// </div>
// <div class="calendar-shortcuts">
// <a href="#">昨天</a> | <a href="#">今天</a> | <a href="#">明天</a>
// </div>
// <p class="calendar-cancel"><a href="#">取消</a></p>
// </div>
const cal_box = document.createElement('div');
cal_box.style.display = 'none';
cal_box.style.position = 'absolute';
cal_box.className = 'calendarbox module';
cal_box.id = DateTimeShortcuts.calendarDivName1 + num;
document.body.appendChild(cal_box);
cal_box.addEventListener('click', function(e) { e.stopPropagation(); });
// 上一页-下一页链接
const cal_nav = quickElement('div', cal_box);
const cal_nav_prev = quickElement('a', cal_nav, '<', 'href', '#');
cal_nav_prev.className = 'calendarnav-previous';
cal_nav_prev.addEventListener('click', function(e) {
e.preventDefault();
DateTimeShortcuts.drawPrev(num);
});
const cal_nav_next = quickElement('a', cal_nav, '>', 'href', '#');
cal_nav_next.className = 'calendarnav-next';
cal_nav_next.addEventListener('click', function(e) {
e.preventDefault();
DateTimeShortcuts.drawNext(num);
});
// 主体框
const cal_main = quickElement('div', cal_box, '', 'id', DateTimeShortcuts.calendarDivName2 + num);
cal_main.className = 'calendar';
DateTimeShortcuts.calendars[num] = new Calendar(DateTimeShortcuts.calendarDivName2 + num, DateTimeShortcuts.handleCalendarCallback(num));
DateTimeShortcuts.calendars[num].drawCurrent();
// 日历快捷方式
const shortcuts = quickElement('div', cal_box);
shortcuts.className = 'calendar-shortcuts';
let day_link = quickElement('a', shortcuts, gettext('Yesterday'), 'href', '#');
day_link.addEventListener('click', function(e) {
e.preventDefault();
DateTimeShortcuts.handleCalendarQuickLink(num, -1);
});
shortcuts.appendChild(document.createTextNode('\u00A0|\u00A0'));
day_link = quickElement('a', shortcuts, gettext('Today'), 'href', '#');
day_link.addEventListener('click', function(e) {
e.preventDefault();
DateTimeShortcuts.handleCalendarQuickLink(num, 0);
});
shortcuts.appendChild(document.createTextNode('\u00A0|\u00A0'));
day_link = quickElement('a', shortcuts, gettext('Tomorrow'), 'href', '#');
day_link.addEventListener('click', function(e) {
e.preventDefault();
DateTimeShortcuts.handleCalendarQuickLink(num, +1);
});
// 取消栏
const cancel_p = quickElement('p', cal_box);
cancel_p.className = 'calendar-cancel';
const cancel_link = quickElement('a', cancel_p, gettext('Cancel'), 'href', '#');
cancel_link.addEventListener('click', function(e) {
e.preventDefault();
DateTimeShortcuts.dismissCalendar(num);
});
// 监听键盘事件ESC键关闭弹窗
document.addEventListener('keyup', function(event) {
if (event.which === 27) {
// ESC键关闭弹窗
DateTimeShortcuts.dismissCalendar(num);
event.preventDefault();
}
});
},
// 打开日历
openCalendar: function(num) {
const cal_box = document.getElementById(DateTimeShortcuts.calendarDivName1 + num);
const cal_link = document.getElementById(DateTimeShortcuts.calendarLinkName + num);
const inp = DateTimeShortcuts.calendarInputs[num];
// 确定输入框中的当前值是否有有效日期
// 如果有,则使用该日期的年份和月份绘制日历
if (inp.value) {
const format = get_format('DATE_INPUT_FORMATS')[0];
const selected = inp.value.strptime(format);
const year = selected.getUTCFullYear();
const month = selected.getUTCMonth() + 1;
const re = /\d{4}/;
if (re.test(year.toString()) && month >= 1 && month <= 12) {
DateTimeShortcuts.calendars[num].drawDate(month, year, selected);
}
}
// 重新计算日历框位置
// 判断是左到右还是右到左布局
if (window.getComputedStyle(document.body).direction !== 'rtl') {
cal_box.style.left = findPosX(cal_link) + 17 + 'px';
}
else {
// 由于样式宽度是em单位难以计算像素值这里使用估算值
cal_box.style.left = findPosX(cal_link) - 180 + 'px';
}
cal_box.style.top = Math.max(0, findPosY(cal_link) - 75) + 'px';
cal_box.style.display = 'block';
document.addEventListener('click', DateTimeShortcuts.dismissCalendarFunc[num]);
},
// 关闭日历
dismissCalendar: function(num) {
document.getElementById(DateTimeShortcuts.calendarDivName1 + num).style.display = 'none';
document.removeEventListener('click', DateTimeShortcuts.dismissCalendarFunc[num]);
},
// 绘制上个月
drawPrev: function(num) {
DateTimeShortcuts.calendars[num].drawPreviousMonth();
},
// 绘制下个月
drawNext: function(num) {
DateTimeShortcuts.calendars[num].drawNextMonth();
},
// 处理日历回调函数
handleCalendarCallback: function(num) {
const format = get_format('DATE_INPUT_FORMATS')[0];
return function(y, m, d) {
DateTimeShortcuts.calendarInputs[num].value = new Date(y, m - 1, d).strftime(format);
DateTimeShortcuts.calendarInputs[num].focus();
document.getElementById(DateTimeShortcuts.calendarDivName1 + num).style.display = 'none';
};
},
// 处理日历快捷链接
handleCalendarQuickLink: function(num, offset) {
const d = DateTimeShortcuts.now();
d.setDate(d.getDate() + offset);
DateTimeShortcuts.calendarInputs[num].value = d.strftime(get_format('DATE_INPUT_FORMATS')[0]);
DateTimeShortcuts.calendarInputs[num].focus();
DateTimeShortcuts.dismissCalendar(num);
}
};
window.addEventListener('load', DateTimeShortcuts.init);
window.DateTimeShortcuts = DateTimeShortcuts;
}

@ -0,0 +1,339 @@
/*global SelectBox, interpolate*/
// 处理关联对象功能raw_id_fields的查找链接和"添加另一个"链接
'use strict';
{
const $ = django.jQuery;
let popupIndex = 0;
const relatedWindows = []; // 关联窗口数组
/**
* 关闭所有子弹窗
*/
function dismissChildPopups() {
relatedWindows.forEach(function(win) {
if(!win.closed) {
win.dismissChildPopups();
win.close();
}
});
}
/**
* 设置弹窗索引
*/
function setPopupIndex() {
if(document.getElementsByName("_popup").length > 0) {
const index = window.name.lastIndexOf("__") + 2;
popupIndex = parseInt(window.name.substring(index));
} else {
popupIndex = 0;
}
}
/**
* 添加弹窗索引
* @param {string} name - 窗口名称
* @returns {string} 添加索引后的名称
*/
function addPopupIndex(name) {
return name + "__" + (popupIndex + 1);
}
/**
* 移除弹窗索引
* @param {string} name - 窗口名称
* @returns {string} 移除索引后的名称
*/
function removePopupIndex(name) {
return name.replace(new RegExp("__" + (popupIndex + 1) + "$"), '');
}
/**
* 显示管理弹窗
* @param {HTMLElement} triggeringLink - 触发链接元素
* @param {RegExp} name_regexp - 名称正则表达式
* @param {boolean} add_popup - 是否添加弹窗参数
* @returns {boolean} 总是返回false
*/
function showAdminPopup(triggeringLink, name_regexp, add_popup) {
const name = addPopupIndex(triggeringLink.id.replace(name_regexp, '')); // 生成窗口名称
const href = new URL(triggeringLink.href); // 创建URL对象
if (add_popup) {
href.searchParams.set('_popup', 1); // 添加弹窗参数
}
// 打开新窗口
const win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
relatedWindows.push(win); // 将窗口添加到关联窗口数组
win.focus(); // 聚焦到新窗口
return false;
}
/**
* 显示关联对象查找弹窗
* @param {HTMLElement} triggeringLink - 触发链接元素
* @returns {boolean} 显示弹窗的结果
*/
function showRelatedObjectLookupPopup(triggeringLink) {
return showAdminPopup(triggeringLink, /^lookup_/, true);
}
/**
* 关闭关联查找弹窗
* @param {Window} win - 弹窗对象
* @param {string} chosenId - 选中的ID
*/
function dismissRelatedLookupPopup(win, chosenId) {
const name = removePopupIndex(win.name); // 移除索引后的名称
const elem = document.getElementById(name); // 获取对应元素
// 处理多对多字段
if (elem.classList.contains('vManyToManyRawIdAdminField') && elem.value) {
elem.value += ',' + chosenId;
} else {
elem.value = chosenId; // 设置选中的ID
}
$(elem).trigger('change'); // 触发change事件
const index = relatedWindows.indexOf(win); // 查找窗口在数组中的索引
if (index > -1) {
relatedWindows.splice(index, 1); // 从数组中移除
}
win.close(); // 关闭窗口
}
/**
* 显示关联对象弹窗
* @param {HTMLElement} triggeringLink - 触发链接元素
* @returns {boolean} 显示弹窗的结果
*/
function showRelatedObjectPopup(triggeringLink) {
return showAdminPopup(triggeringLink, /^(change|add|delete)_/, false);
}
/**
* 更新关联对象链接
* @param {HTMLElement} triggeringLink - 触发链接元素
*/
function updateRelatedObjectLinks(triggeringLink) {
const $this = $(triggeringLink);
// 获取相关的查看、修改、删除链接
const siblings = $this.nextAll('.view-related, .change-related, .delete-related');
if (!siblings.length) {
return;
}
const value = $this.val(); // 获取当前值
if (value) {
// 有值时更新链接
siblings.each(function() {
const elm = $(this);
elm.attr('href', elm.attr('data-href-template').replace('__fk__', value));
elm.removeAttr('aria-disabled');
});
} else {
// 无值时禁用链接
siblings.removeAttr('href');
siblings.attr('aria-disabled', true);
}
}
/**
* 更新关联选择框选项
* @param {HTMLElement} currentSelect - 当前选择框
* @param {Window} win - 窗口对象
* @param {string} objId - 对象ID
* @param {string} newRepr - 新的表示
* @param {string} newId - 新的ID
* @param {Array} skipIds - 跳过的ID数组
*/
function updateRelatedSelectsOptions(currentSelect, win, objId, newRepr, newId, skipIds = []) {
// 在从当前选择框旁边选项中创建/编辑模型后更新页面中其余选择框的ForeignKey PK
const path = win.location.pathname;
// 从弹窗URL中提取模型 '.../<model>/add/' 或 '.../<model>/<id>/change/' 取决于操作类型
const modelName = path.split('/')[path.split('/').length - (objId ? 4 : 3)];
// 选择具有特定模型引用和"available-source"上下文的元素
const selectsRelated = document.querySelectorAll(`[data-model-ref="${modelName}"] [data-context="available-source"]`);
selectsRelated.forEach(function(select) {
// 跳过当前选择框和指定ID的选择框
if (currentSelect === select || skipIds && skipIds.includes(select.id)) {
return;
}
let option = select.querySelector(`option[value="${objId}"]`);
if (!option) {
// 如果选项不存在,则创建新选项
option = new Option(newRepr, newId);
select.options.add(option);
// 更新关联字段的SelectBox缓存
if (window.SelectBox !== undefined && !SelectBox.cache[currentSelect.id]) {
SelectBox.add_to_cache(select.id, option);
SelectBox.redisplay(select.id);
}
return;
}
// 更新选项的文本和值
option.textContent = newRepr;
option.value = newId;
});
}
/**
* 关闭添加关联对象弹窗
* @param {Window} win - 弹窗对象
* @param {string} newId - 新的ID
* @param {string} newRepr - 新的表示
*/
function dismissAddRelatedObjectPopup(win, newId, newRepr) {
const name = removePopupIndex(win.name); // 移除索引后的名称
const elem = document.getElementById(name); // 获取对应元素
if (elem) {
const elemName = elem.nodeName.toUpperCase(); // 获取元素名称
if (elemName === 'SELECT') {
// 如果是选择框,添加新选项
elem.options[elem.options.length] = new Option(newRepr, newId, true, true);
updateRelatedSelectsOptions(elem, win, null, newRepr, newId);
} else if (elemName === 'INPUT') {
// 如果是输入框,处理多对多字段
if (elem.classList.contains('vManyToManyRawIdAdminField') && elem.value) {
elem.value += ',' + newId;
} else {
elem.value = newId;
}
}
// 触发change事件以更新相关链接
$(elem).trigger('change');
} else {
const toId = name + "_to";
const toElem = document.getElementById(toId);
const o = new Option(newRepr, newId);
SelectBox.add_to_cache(toId, o);
SelectBox.redisplay(toId);
// 如果目标元素是选择框,则更新相关选择框选项
if (toElem && toElem.nodeName.toUpperCase() === 'SELECT') {
const skipIds = [name + "_from"];
updateRelatedSelectsOptions(toElem, win, null, newRepr, newId, skipIds);
}
}
const index = relatedWindows.indexOf(win); // 查找窗口在数组中的索引
if (index > -1) {
relatedWindows.splice(index, 1); // 从数组中移除
}
win.close(); // 关闭窗口
}
/**
* 关闭修改关联对象弹窗
* @param {Window} win - 弹窗对象
* @param {string} objId - 对象ID
* @param {string} newRepr - 新的表示
* @param {string} newId - 新的ID
*/
function dismissChangeRelatedObjectPopup(win, objId, newRepr, newId) {
const id = removePopupIndex(win.name.replace(/^edit_/, '')); // 移除索引后的ID
const selectsSelector = interpolate('#%s, #%s_from, #%s_to', [id, id, id]); // 选择器
const selects = $(selectsSelector); // 获取选择框元素
// 更新选项的文本和值
selects.find('option').each(function() {
if (this.value === objId) {
this.textContent = newRepr;
this.value = newId;
}
}).trigger('change');
updateRelatedSelectsOptions(selects[0], win, objId, newRepr, newId);
// 更新Select2显示
selects.next().find('.select2-selection__rendered').each(function() {
// 元素可能有清除按钮作为子元素
// 使用lastChild只修改显示的值
this.lastChild.textContent = newRepr;
this.title = newRepr;
});
const index = relatedWindows.indexOf(win); // 查找窗口在数组中的索引
if (index > -1) {
relatedWindows.splice(index, 1); // 从数组中移除
}
win.close(); // 关闭窗口
}
/**
* 关闭删除关联对象弹窗
* @param {Window} win - 弹窗对象
* @param {string} objId - 对象ID
*/
function dismissDeleteRelatedObjectPopup(win, objId) {
const id = removePopupIndex(win.name.replace(/^delete_/, '')); // 移除索引后的ID
const selectsSelector = interpolate('#%s, #%s_from, #%s_to', [id, id, id]); // 选择器
const selects = $(selectsSelector); // 获取选择框元素
// 移除对应的选项
selects.find('option').each(function() {
if (this.value === objId) {
$(this).remove();
}
}).trigger('change');
const index = relatedWindows.indexOf(win); // 查找窗口在数组中的索引
if (index > -1) {
relatedWindows.splice(index, 1); // 从数组中移除
}
win.close(); // 关闭窗口
}
// 将函数暴露到全局作用域
window.showRelatedObjectLookupPopup = showRelatedObjectLookupPopup;
window.dismissRelatedLookupPopup = dismissRelatedLookupPopup;
window.showRelatedObjectPopup = showRelatedObjectPopup;
window.updateRelatedObjectLinks = updateRelatedObjectLinks;
window.dismissAddRelatedObjectPopup = dismissAddRelatedObjectPopup;
window.dismissChangeRelatedObjectPopup = dismissChangeRelatedObjectPopup;
window.dismissDeleteRelatedObjectPopup = dismissDeleteRelatedObjectPopup;
window.dismissChildPopups = dismissChildPopups;
window.relatedWindows = relatedWindows;
// 为向后兼容保留
window.showAddAnotherPopup = showRelatedObjectPopup;
window.dismissAddAnotherPopup = dismissAddRelatedObjectPopup;
// 页面卸载时关闭所有子弹窗
window.addEventListener('unload', function(evt) {
window.dismissChildPopups();
});
// 文档加载完成后初始化
$(document).ready(function() {
setPopupIndex(); // 设置弹窗索引
// 处理数据弹窗开启器点击事件
$("a[data-popup-opener]").on('click', function(event) {
event.preventDefault();
opener.dismissRelatedLookupPopup(window, $(this).data("popup-opener"));
});
// 处理相关控件包装链接点击事件
$('body').on('click', '.related-widget-wrapper-link[data-popup="yes"]', function(e) {
e.preventDefault();
if (this.href) {
const event = $.Event('django:show-related', {href: this.href});
$(this).trigger(event);
if (!event.isDefaultPrevented()) {
showRelatedObjectPopup(this);
}
}
});
// 处理相关控件包装选择框变更事件
$('body').on('change', '.related-widget-wrapper select', function(e) {
const event = $.Event('django:update-related');
$(this).trigger(event);
if (!event.isDefaultPrevented()) {
updateRelatedObjectLinks(this);
}
});
// 触发选择框变更事件
$('.related-widget-wrapper select').trigger('change');
// 处理相关查找点击事件
$('body').on('click', '.related-lookup', function(e) {
e.preventDefault();
const event = $.Event('django:lookup-related');
$(this).trigger(event);
if (!event.isDefaultPrevented()) {
showRelatedObjectLookupPopup(this);
}
});
});
}

@ -0,0 +1,44 @@
'use strict';
{
const $ = django.jQuery;
/**
* Django管理后台Select2插件
* 为选择框元素启用Select2功能并配置AJAX数据源
* @returns {jQuery} jQuery对象
*/
$.fn.djangoAdminSelect2 = function() {
// 遍历所有选中的元素
$.each(this, function(i, element) {
// 对每个元素应用select2插件
$(element).select2({
ajax: {
// 配置AJAX数据参数
data: (params) => {
return {
term: params.term, // 搜索关键词
page: params.page, // 分页页码
app_label: element.dataset.appLabel, // 应用标签
model_name: element.dataset.modelName, // 模型名称
field_name: element.dataset.fieldName // 字段名称
};
}
}
});
});
return this;
};
// 文档加载完成后初始化
$(function() {
// 初始化除模板表单外的所有自动完成控件
// 模板表单用于添加新表单集时使用
$('.admin-autocomplete').not('[name*=__prefix__]').djangoAdminSelect2();
});
// 监听表单集添加事件
document.addEventListener('formset:added', (event) => {
// 为新添加的表单中的自动完成控件初始化Select2
$(event.target).find('.admin-autocomplete').djangoAdminSelect2();
});
}

@ -0,0 +1,312 @@
/*global gettext, pgettext, get_format, quickElement, removeChildren*/
/*
calendar.js - 日历功能由Adrian Holovaty开发
依赖core.js中的工具函数如removeChildren或quickElement
*/
'use strict';
{
// CalendarNamespace -- 提供HTML日历相关辅助函数的集合
const CalendarNamespace = {
// 一年中的月份名称
monthsOfYear: [
gettext('January'),
gettext('February'),
gettext('March'),
gettext('April'),
gettext('May'),
gettext('June'),
gettext('July'),
gettext('August'),
gettext('September'),
gettext('October'),
gettext('November'),
gettext('December')
],
// 月份名称缩写
monthsOfYearAbbrev: [
pgettext('abbrev. month January', 'Jan'),
pgettext('abbrev. month February', 'Feb'),
pgettext('abbrev. month March', 'Mar'),
pgettext('abbrev. month April', 'Apr'),
pgettext('abbrev. month May', 'May'),
pgettext('abbrev. month June', 'Jun'),
pgettext('abbrev. month July', 'Jul'),
pgettext('abbrev. month August', 'Aug'),
pgettext('abbrev. month September', 'Sep'),
pgettext('abbrev. month October', 'Oct'),
pgettext('abbrev. month November', 'Nov'),
pgettext('abbrev. month December', 'Dec')
],
// 一周中的天名称
daysOfWeek: [
gettext('Sunday'),
gettext('Monday'),
gettext('Tuesday'),
gettext('Wednesday'),
gettext('Thursday'),
gettext('Friday'),
gettext('Saturday')
],
// 天名称缩写
daysOfWeekAbbrev: [
pgettext('abbrev. day Sunday', 'Sun'),
pgettext('abbrev. day Monday', 'Mon'),
pgettext('abbrev. day Tuesday', 'Tue'),
pgettext('abbrev. day Wednesday', 'Wed'),
pgettext('abbrev. day Thursday', 'Thur'),
pgettext('abbrev. day Friday', 'Fri'),
pgettext('abbrev. day Saturday', 'Sat')
],
// 天名称首字母
daysOfWeekInitial: [
pgettext('one letter Sunday', 'S'),
pgettext('one letter Monday', 'M'),
pgettext('one letter Tuesday', 'T'),
pgettext('one letter Wednesday', 'W'),
pgettext('one letter Thursday', 'T'),
pgettext('one letter Friday', 'F'),
pgettext('one letter Saturday', 'S')
],
// 一周的第一天根据FIRST_DAY_OF_WEEK格式设置
firstDayOfWeek: parseInt(get_format('FIRST_DAY_OF_WEEK')),
/**
* 判断是否为闰年
* @param {number} year - 年份
* @returns {boolean} 如果是闰年返回true否则返回false
*/
isLeapYear: function(year) {
return (((year % 4) === 0) && ((year % 100) !== 0 ) || ((year % 400) === 0));
},
/**
* 获取指定月份的天数
* @param {number} month - 月份 (1-12)
* @param {number} year - 年份
* @returns {number} 该月的天数
*/
getDaysInMonth: function(month, year) {
let days;
// 大月(31天)
if (month === 1 || month === 3 || month === 5 || month === 7 || month === 8 || month === 10 || month === 12) {
days = 31;
}
// 小月(30天)
else if (month === 4 || month === 6 || month === 9 || month === 11) {
days = 30;
}
// 2月需要判断是否为闰年
else if (month === 2 && CalendarNamespace.isLeapYear(year)) {
days = 29;
}
else {
days = 28;
}
return days;
},
/**
* 绘制日历
* @param {number} month - 月份 (1-12)
* @param {number} year - 年份 (1-9999)
* @param {string} div_id - 用于显示日历的div元素ID
* @param {Function} callback - 点击日期时调用的回调函数
* @param {Date} selected - 选中的日期
*/
draw: function(month, year, div_id, callback, selected) { // month = 1-12, year = 1-9999
const today = new Date();
const todayDay = today.getDate();
const todayMonth = today.getMonth() + 1;
const todayYear = today.getFullYear();
let todayClass = '';
// 在这里使用UTC函数是因为日期字段不包含时间
// 使用UTC函数变体可以防止本地时间偏移改变日期特别是日期字段。
// 例如:
//
// ```
// var x = new Date('2013-10-02');
// var day = x.getDate();
// ```
//
// 在美国太平洋时区上面的day变量将是1而不是2
let isSelectedMonth = false;
if (typeof selected !== 'undefined') {
isSelectedMonth = (selected.getUTCFullYear() === year && (selected.getUTCMonth() + 1) === month);
}
month = parseInt(month);
year = parseInt(year);
const calDiv = document.getElementById(div_id);
removeChildren(calDiv); // 清空容器元素
const calTable = document.createElement('table');
// 创建表格标题,显示月份和年份
quickElement('caption', calTable, CalendarNamespace.monthsOfYear[month - 1] + ' ' + year);
const tableBody = quickElement('tbody', calTable);
// 绘制星期标题行
let tableRow = quickElement('tr', tableBody);
for (let i = 0; i < 7; i++) {
// 根据一周第一天的设置显示星期首字母
quickElement('th', tableRow, CalendarNamespace.daysOfWeekInitial[(i + CalendarNamespace.firstDayOfWeek) % 7]);
}
// 计算月份第一天之前的空白单元格数量
const startingPos = new Date(year, month - 1, 1 - CalendarNamespace.firstDayOfWeek).getDay();
const days = CalendarNamespace.getDaysInMonth(month, year);
let nonDayCell;
// 在月份第一天之前绘制空白单元格
tableRow = quickElement('tr', tableBody);
for (let i = 0; i < startingPos; i++) {
nonDayCell = quickElement('td', tableRow, ' ');
nonDayCell.className = "nonday";
}
/**
* 创建月份点击事件处理函数
* @param {number} y - 年份
* @param {number} m - 月份
* @returns {Function} 点击事件处理函数
*/
function calendarMonth(y, m) {
function onClick(e) {
e.preventDefault();
callback(y, m, this.textContent);
}
return onClick;
}
// 绘制月份中的每一天
let currentDay = 1;
for (let i = startingPos; currentDay <= days; i++) {
// 每7天开始新的一行星期日
if (i % 7 === 0 && currentDay !== 1) {
tableRow = quickElement('tr', tableBody);
}
// 如果是今天添加today类
if ((currentDay === todayDay) && (month === todayMonth) && (year === todayYear)) {
todayClass = 'today';
} else {
todayClass = '';
}
// 使用UTC函数解释见上文
// 如果是选中的日期添加selected类
if (isSelectedMonth && currentDay === selected.getUTCDate()) {
if (todayClass !== '') {
todayClass += " ";
}
todayClass += "selected";
}
// 创建日期单元格和链接
const cell = quickElement('td', tableRow, '', 'class', todayClass);
const link = quickElement('a', cell, currentDay, 'href', '#');
link.addEventListener('click', calendarMonth(year, month));
currentDay++;
}
// 在月份结束后绘制空白单元格(可选,但使代码有效)
while (tableRow.childNodes.length < 7) {
nonDayCell = quickElement('td', tableRow, ' ');
nonDayCell.className = "nonday";
}
calDiv.appendChild(calTable);
}
};
// Calendar -- 日历实例
/**
* 日历构造函数
* @param {string} div_id - 用于显示日历的元素ID
* @param {Function} callback - 点击日期时调用的回调函数
* @param {Date} selected - 选中的日期
*/
function Calendar(div_id, callback, selected) {
// div_id (string) 是用于显示日历的元素ID
// callback (string) 是一个JavaScript函数名称当点击日历中的日期时会调用该函数
// 该函数将接收参数 (year, month, day)
this.div_id = div_id;
this.callback = callback;
this.today = new Date();
this.currentMonth = this.today.getMonth() + 1;
this.currentYear = this.today.getFullYear();
if (typeof selected !== 'undefined') {
this.selected = selected;
}
}
Calendar.prototype = {
/**
* 绘制当前月份的日历
*/
drawCurrent: function() {
CalendarNamespace.draw(this.currentMonth, this.currentYear, this.div_id, this.callback, this.selected);
},
/**
* 绘制指定日期的日历
* @param {number} month - 月份
* @param {number} year - 年份
* @param {Date} selected - 选中的日期
*/
drawDate: function(month, year, selected) {
this.currentMonth = month;
this.currentYear = year;
if(selected) {
this.selected = selected;
}
this.drawCurrent();
},
/**
* 绘制上一个月的日历
*/
drawPreviousMonth: function() {
if (this.currentMonth === 1) {
this.currentMonth = 12;
this.currentYear--;
}
else {
this.currentMonth--;
}
this.drawCurrent();
},
/**
* 绘制下一个月的日历
*/
drawNextMonth: function() {
if (this.currentMonth === 12) {
this.currentMonth = 1;
this.currentYear++;
}
else {
this.currentMonth++;
}
this.drawCurrent();
},
/**
* 绘制上一年的日历
*/
drawPreviousYear: function() {
this.currentYear--;
this.drawCurrent();
},
/**
* 绘制下一年的日历
*/
drawNextYear: function() {
this.currentYear++;
this.drawCurrent();
}
};
window.Calendar = Calendar;
window.CalendarNamespace = CalendarNamespace;
}

@ -0,0 +1,36 @@
'use strict';
{
// 页面加载完成后调用指定函数。如果页面已经加载完成,则立即调用该函数
// http://youmightnotneedjquery.com/#ready
function ready(fn) {
// 检查文档是否加载完成
if (document.readyState !== 'loading') {
fn();
} else {
// 如果文档未加载完成则在DOMContentLoaded事件中调用
document.addEventListener('DOMContentLoaded', fn);
}
}
ready(function() {
// 处理点击事件的函数
function handleClick(event) {
// 阻止默认行为
event.preventDefault();
// 获取URL参数
const params = new URLSearchParams(window.location.search);
// 如果URL中有_popup参数则关闭窗口(适用于弹窗情况)
if (params.has('_popup')) {
window.close(); // 关闭弹窗
} else {
// 否则返回上一页
window.history.back(); // 返回上一页
}
}
// 为所有具有cancel-link类的元素添加点击事件监听器
document.querySelectorAll('.cancel-link').forEach(function(el) {
el.addEventListener('click', handleClick);
});
});
}

@ -0,0 +1,21 @@
'use strict';
{
// 定义表单输入元素标签数组
const inputTags = ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'];
// 从页面中获取模型名称
const modelName = document.getElementById('django-admin-form-add-constants').dataset.modelName;
// 如果模型名称存在
if (modelName) {
// 根据模型名称获取表单元素
const form = document.getElementById(modelName + '_form');
// 遍历表单中的所有元素
for (const element of form.elements) {
// HTMLElement.offsetParent 在元素未渲染时返回 null
// 查找第一个可用的输入元素并设置焦点
if (inputTags.includes(element.tagName) && !element.disabled && element.offsetParent) {
element.focus(); // 设置焦点到第一个可用的输入元素
break; // 找到后退出循环
}
}
}
}

@ -0,0 +1,291 @@
// 核心JavaScript辅助函数
'use strict';
/**
* 快速创建元素函数
* @param {string} tagType - 标签类型
* @param {HTMLElement} parentReference - 父元素引用
* @param {string} [textInChildNode] - 子节点中的文本可选
* @param {...string} [attribute] - 属性和属性值可选成对出现
* @returns {HTMLElement} 创建的元素
*
* 使用示例:
* quickElement('div', parentElement, 'Hello World', 'class', 'my-class', 'id', 'my-id')
*/
function quickElement() {
// 创建指定类型的元素
const obj = document.createElement(arguments[0]);
// 如果提供了文本内容,则添加文本节点
if (arguments[2]) {
const textNode = document.createTextNode(arguments[2]);
obj.appendChild(textNode);
}
// 处理属性参数从第4个参数开始每两个参数为一组属性名和属性值
const len = arguments.length;
for (let i = 3; i < len; i += 2) {
obj.setAttribute(arguments[i], arguments[i + 1]);
}
// 将创建的元素添加到父元素中
arguments[1].appendChild(obj);
return obj;
}
/**
* 移除元素的所有子节点
* @param {HTMLElement} a - 需要清空子节点的元素
*/
function removeChildren(a) {
// 循环移除所有子节点
while (a.hasChildNodes()) {
a.removeChild(a.lastChild);
}
}
// ----------------------------------------------------------------------------
// 查找位置函数 by PPK
// 参见 https://www.quirksmode.org/js/findpos.html
// ----------------------------------------------------------------------------
/**
* 查找元素相对于文档的水平位置(X坐标)
* @param {HTMLElement} obj - 需要查找位置的元素
* @returns {number} 元素的X坐标
*/
function findPosX(obj) {
let curleft = 0;
// 如果元素有offsetParent属性则通过遍历offsetParent来计算位置
if (obj.offsetParent) {
while (obj.offsetParent) {
curleft += obj.offsetLeft - obj.scrollLeft;
obj = obj.offsetParent;
}
} else if (obj.x) {
// 如果元素有x属性则直接使用
curleft += obj.x;
}
return curleft;
}
/**
* 查找元素相对于文档的垂直位置(Y坐标)
* @param {HTMLElement} obj - 需要查找位置的元素
* @returns {number} 元素的Y坐标
*/
function findPosY(obj) {
let curtop = 0;
// 如果元素有offsetParent属性则通过遍历offsetParent来计算位置
if (obj.offsetParent) {
while (obj.offsetParent) {
curtop += obj.offsetTop - obj.scrollTop;
obj = obj.offsetParent;
}
} else if (obj.y) {
// 如果元素有y属性则直接使用
curtop += obj.y;
}
return curtop;
}
//-----------------------------------------------------------------------------
// Date对象扩展
// ----------------------------------------------------------------------------
{
/**
* 获取12小时制的小时数
* @returns {number} 12小时制的小时数(1-12)
*/
Date.prototype.getTwelveHours = function() {
return this.getHours() % 12 || 12;
};
/**
* 获取两位数的月份
* @returns {string} 两位数的月份(01-12)
*/
Date.prototype.getTwoDigitMonth = function() {
return (this.getMonth() < 9) ? '0' + (this.getMonth() + 1) : (this.getMonth() + 1);
};
/**
* 获取两位数的日期
* @returns {string} 两位数的日期(01-31)
*/
Date.prototype.getTwoDigitDate = function() {
return (this.getDate() < 10) ? '0' + this.getDate() : this.getDate();
};
/**
* 获取两位数的12小时制小时
* @returns {string} 两位数的12小时制小时(01-12)
*/
Date.prototype.getTwoDigitTwelveHour = function() {
return (this.getTwelveHours() < 10) ? '0' + this.getTwelveHours() : this.getTwelveHours();
};
/**
* 获取两位数的24小时制小时
* @returns {string} 两位数的24小时制小时(00-23)
*/
Date.prototype.getTwoDigitHour = function() {
return (this.getHours() < 10) ? '0' + this.getHours() : this.getHours();
};
/**
* 获取两位数的分钟
* @returns {string} 两位数的分钟(00-59)
*/
Date.prototype.getTwoDigitMinute = function() {
return (this.getMinutes() < 10) ? '0' + this.getMinutes() : this.getMinutes();
};
/**
* 获取两位数的秒
* @returns {string} 两位数的秒(00-59)
*/
Date.prototype.getTwoDigitSecond = function() {
return (this.getSeconds() < 10) ? '0' + this.getSeconds() : this.getSeconds();
};
/**
* 获取缩写的星期名称
* @returns {string} 缩写的星期名称
*/
Date.prototype.getAbbrevDayName = function() {
return typeof window.CalendarNamespace === "undefined"
? '0' + this.getDay()
: window.CalendarNamespace.daysOfWeekAbbrev[this.getDay()];
};
/**
* 获取完整的星期名称
* @returns {string} 完整的星期名称
*/
Date.prototype.getFullDayName = function() {
return typeof window.CalendarNamespace === "undefined"
? '0' + this.getDay()
: window.CalendarNamespace.daysOfWeek[this.getDay()];
};
/**
* 获取缩写的月份名称
* @returns {string} 缩写的月份名称
*/
Date.prototype.getAbbrevMonthName = function() {
return typeof window.CalendarNamespace === "undefined"
? this.getTwoDigitMonth()
: window.CalendarNamespace.monthsOfYearAbbrev[this.getMonth()];
};
/**
* 获取完整的月份名称
* @returns {string} 完整的月份名称
*/
Date.prototype.getFullMonthName = function() {
return typeof window.CalendarNamespace === "undefined"
? this.getTwoDigitMonth()
: window.CalendarNamespace.monthsOfYear[this.getMonth()];
};
/**
* 格式化日期时间字符串
* @param {string} format - 格式化字符串
* @returns {string} 格式化后的日期时间字符串
*
* 支持的格式化符号:
* %a - 缩写星期名称
* %A - 完整星期名称
* %b - 缩写月份名称
* %B - 完整月份名称
* %c - 日期和时间表示法
* %d - 两位数日期
* %H - 两位数24小时制小时
* %I - 两位数12小时制小时
* %m - 两位数月份
* %M - 两位数分钟
* %p - AM或PM
* %S - 两位数秒
* %w - 星期几(0-6, 0表示星期日)
* %x - 日期表示法
* %X - 时间表示法
* %y - 两位数年份
* %Y - 四位数年份
* %% - 百分号
*/
Date.prototype.strftime = function(format) {
// 定义格式化字段映射
const fields = {
a: this.getAbbrevDayName(),
A: this.getFullDayName(),
b: this.getAbbrevMonthName(),
B: this.getFullMonthName(),
c: this.toString(),
d: this.getTwoDigitDate(),
H: this.getTwoDigitHour(),
I: this.getTwoDigitTwelveHour(),
m: this.getTwoDigitMonth(),
M: this.getTwoDigitMinute(),
p: (this.getHours() >= 12) ? 'PM' : 'AM',
S: this.getTwoDigitSecond(),
w: '0' + this.getDay(),
x: this.toLocaleDateString(),
X: this.toLocaleTimeString(),
y: ('' + this.getFullYear()).substr(2, 4),
Y: '' + this.getFullYear(),
'%': '%'
};
let result = '', i = 0;
// 遍历格式字符串并替换相应的字段
while (i < format.length) {
if (format.charAt(i) === '%') {
result += fields[format.charAt(i + 1)];
++i;
}
else {
result += format.charAt(i);
}
++i;
}
return result;
};
// ----------------------------------------------------------------------------
// String对象扩展
// ----------------------------------------------------------------------------
/**
* 解析日期时间字符串
* @param {string} format - 格式化字符串
* @returns {Date} 解析后的Date对象
*/
String.prototype.strptime = function(format) {
// 按分隔符分割格式字符串和日期字符串
const split_format = format.split(/[.\-/]/);
const date = this.split(/[.\-/]/);
let i = 0;
let day, month, year;
// 根据格式解析日期各部分
while (i < split_format.length) {
switch (split_format[i]) {
case "%d":
day = date[i];
break;
case "%m":
month = date[i] - 1;
break;
case "%Y":
year = date[i];
break;
case "%y":
// 根据Open Group规范[00, 68]范围内的%y值属于当前世纪[69, 99]范围内的属于上一个世纪
if (parseInt(date[i], 10) >= 69) {
year = date[i];
} else {
year = (new Date(Date.UTC(date[i], 0))).getUTCFullYear() + 100;
}
break;
}
++i;
}
// 从UTC创建Date对象因为解析的值应该是UTC时间而不是本地时间
// 日历也使用UTC函数来提取日期
return new Date(Date.UTC(year, month, day));
};
}

@ -0,0 +1,38 @@
/**
* 持久化变更列表过滤器状态折叠/展开
*/
'use strict';
{
// 初始化过滤器状态
// 从sessionStorage中获取之前保存的过滤器状态
let filters = JSON.parse(sessionStorage.getItem('django.admin.filtersState'));
// 如果没有保存的过滤器状态,则初始化为空对象
if (!filters) {
filters = {};
}
// 遍历所有保存的过滤器状态,恢复对应的过滤器显示状态
Object.entries(filters).forEach(([key, value]) => {
// 根据过滤器标题查找对应的detail元素
const detailElement = document.querySelector(`[data-filter-title='${CSS.escape(key)}']`);
// 检查过滤器是否存在,因为它可能来自其他视图
if (detailElement) {
// 根据保存的状态设置open属性控制过滤器的展开或折叠
value ? detailElement.setAttribute('open', '') : detailElement.removeAttribute('open');
}
});
// 当点击过滤器时保存过滤器状态
const details = document.querySelectorAll('details');
details.forEach(detail => {
// 为每个details元素添加toggle事件监听器
detail.addEventListener('toggle', event => {
// 将当前过滤器的展开状态保存到filters对象中
filters[`${event.target.dataset.filterTitle}`] = detail.open;
// 将更新后的过滤器状态保存到sessionStorage中
sessionStorage.setItem('django.admin.filtersState', JSON.stringify(filters));
});
});
}

@ -0,0 +1,426 @@
/*global DateTimeShortcuts, SelectFilter*/
/**
* Django管理后台内联表单
*
* 基于 jQuery Formset 1.1
* @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com)
* @requires jQuery 1.2.6 或更高版本
*
* 版权所有 (c) 2009, Stanislaus Madueke
* 保留所有权利
*
* 包含了Zain Memon的GSoC项目2009的代码
* 并由Jannis Leidel, Travis Swicegood和Julien Phalip为Django进行了修改
*
* 基于新BSD许可证授权
* 参见: https://opensource.org/licenses/bsd-license.php
*/
'use strict';
{
const $ = django.jQuery;
/**
* 表单集插件
* @param {Object} opts - 配置选项
* @returns {jQuery} jQuery对象
*/
$.fn.formset = function(opts) {
// 合并默认选项和用户选项
const options = $.extend({}, $.fn.formset.defaults, opts);
const $this = $(this);
const $parent = $this.parent();
/**
* 更新元素索引
* @param {HTMLElement} el - 元素
* @param {string} prefix - 前缀
* @param {number} ndx - 新索引
*/
const updateElementIndex = function(el, prefix, ndx) {
const id_regex = new RegExp("(" + prefix + "-(\\d+|__prefix__))");
const replacement = prefix + "-" + ndx;
if ($(el).prop("for")) {
$(el).prop("for", $(el).prop("for").replace(id_regex, replacement));
}
if (el.id) {
el.id = el.id.replace(id_regex, replacement);
}
if (el.name) {
el.name = el.name.replace(id_regex, replacement);
}
};
// 获取总表单数、最大表单数和最小表单数的输入框
const totalForms = $("#id_" + options.prefix + "-TOTAL_FORMS").prop("autocomplete", "off");
let nextIndex = parseInt(totalForms.val(), 10);
const maxForms = $("#id_" + options.prefix + "-MAX_NUM_FORMS").prop("autocomplete", "off");
const minForms = $("#id_" + options.prefix + "-MIN_NUM_FORMS").prop("autocomplete", "off");
let addButton;
/**
* 添加内联添加按钮
* "添加另一个MyModel"按钮位于内联表单下方
*/
const addInlineAddButton = function() {
if (addButton === null) {
if ($this.prop("tagName") === "TR") {
// 如果表单以表格行形式排列,在新行中插入"添加"按钮:
const numCols = $this.eq(-1).children().length;
$parent.append('<tr class="' + options.addCssClass + '"><td colspan="' + numCols + '"><a role="button" class="addlink" href="#">' + options.addText + "</a></tr>");
addButton = $parent.find("tr:last a");
} else {
// 否则,将其插入到最后一个表单之后:
$this.filter(":last").after('<div class="' + options.addCssClass + '"><a role="button" class="addlink" href="#">' + options.addText + "</a></div>");
addButton = $this.filter(":last").next().find("a");
}
}
// 为添加按钮绑定点击事件处理器
addButton.on('click', addInlineClickHandler);
};
/**
* 内联添加按钮点击事件处理器
* @param {Event} e - 点击事件
*/
const addInlineClickHandler = function(e) {
e.preventDefault();
const template = $("#" + options.prefix + "-empty");
const row = template.clone(true);
// 移除空行CSS类添加表单CSS类设置ID
row.removeClass(options.emptyCssClass)
.addClass(options.formCssClass)
.attr("id", options.prefix + "-" + nextIndex);
addInlineDeleteButton(row);
// 更新新表单中所有元素的索引
row.find("*").each(function() {
updateElementIndex(this, options.prefix, totalForms.val());
});
// 在完全编辑后插入新表单
row.insertBefore($(template));
// 更新总表单数
$(totalForms).val(parseInt(totalForms.val(), 10) + 1);
nextIndex += 1;
// 如果达到限制数量,隐藏添加按钮
if ((maxForms.val() !== '') && (maxForms.val() - totalForms.val()) <= 0) {
addButton.parent().hide();
}
// 如果表单数超过最小数量,显示删除按钮
toggleDeleteButtonVisibility(row.closest('.inline-group'));
// 如果提供了添加后的回调函数,则调用它
if (options.added) {
options.added(row);
}
// 触发formset:added自定义事件
row.get(0).dispatchEvent(new CustomEvent("formset:added", {
bubbles: true,
detail: {
formsetName: options.prefix
}
}));
};
/**
* 为每个未保存的内联表单添加"X"按钮
* (保存后会被"删除"复选框替换)
* @param {jQuery} row - 表单行
*/
const addInlineDeleteButton = function(row) {
if (row.is("tr")) {
// 如果表单以表格行形式排列,将删除按钮插入到最后一个单元格:
row.children(":last").append('<div><a role="button" class="' + options.deleteCssClass + '" href="#">' + options.deleteText + "</a></div>");
} else if (row.is("ul") || row.is("ol")) {
// 如果以有序/无序列表形式排列,在最后一个列表项后插入<li>:
row.append('<li><a role="button" class="' + options.deleteCssClass + '" href="#">' + options.deleteText + "</a></li>");
} else {
// 否则,将删除按钮作为表单容器的最后一个子元素插入:
row.children(":first").append('<span><a role="button" class="' + options.deleteCssClass + '" href="#">' + options.deleteText + "</a></span>");
}
// 为每一行添加删除事件处理器
row.find("a." + options.deleteCssClass).on('click', inlineDeleteHandler.bind(this));
};
/**
* 内联删除处理器
* @param {Event} e1 - 点击事件
*/
const inlineDeleteHandler = function(e1) {
e1.preventDefault();
const deleteButton = $(e1.target);
const row = deleteButton.closest('.' + options.formCssClass);
const inlineGroup = row.closest('.inline-group');
// 删除包含此按钮的父表单,以及包含非字段错误的相关行:
const prevRow = row.prev();
if (prevRow.length && prevRow.hasClass('row-form-errors')) {
prevRow.remove();
}
row.remove();
nextIndex -= 1;
// 如果提供了删除后的回调函数,则调用它
if (options.removed) {
options.removed(row);
}
// 触发formset:removed自定义事件
document.dispatchEvent(new CustomEvent("formset:removed", {
detail: {
formsetName: options.prefix
}
}));
// 更新TOTAL_FORMS表单计数
const forms = $("." + options.formCssClass);
$("#id_" + options.prefix + "-TOTAL_FORMS").val(forms.length);
// 如果未达到最大数量,重新显示添加按钮
if ((maxForms.val() === '') || (maxForms.val() - forms.length) > 0) {
addButton.parent().show();
}
// 如果达到最小数量,隐藏删除按钮
toggleDeleteButtonVisibility(inlineGroup);
// 同时更新所有剩余表单控件的名称和ID使其保持顺序:
let i, formCount;
const updateElementCallback = function() {
updateElementIndex(this, options.prefix, i);
};
for (i = 0, formCount = forms.length; i < formCount; i++) {
updateElementIndex($(forms).get(i), options.prefix, i);
$(forms.get(i)).find("*").each(updateElementCallback);
}
};
/**
* 切换删除按钮可见性
* @param {jQuery} inlineGroup - 内联组
*/
const toggleDeleteButtonVisibility = function(inlineGroup) {
if ((minForms.val() !== '') && (minForms.val() - totalForms.val()) >= 0) {
inlineGroup.find('.inline-deletelink').hide();
} else {
inlineGroup.find('.inline-deletelink').show();
}
};
// 为每个表单添加CSS类
$this.each(function(i) {
$(this).not("." + options.emptyCssClass).addClass(options.formCssClass);
});
// 为所有未保存的内联表单创建删除按钮:
$this.filter('.' + options.formCssClass + ':not(.has_original):not(.' + options.emptyCssClass + ')').each(function() {
addInlineDeleteButton($(this));
});
toggleDeleteButtonVisibility($this);
// 创建添加按钮,初始隐藏
addButton = options.addButton;
addInlineAddButton();
// 如果允许添加更多项,则显示添加按钮
// 注意 max_num = None 会转换为空字符串
const showAddButton = maxForms.val() === '' || (maxForms.val() - totalForms.val()) > 0;
if ($this.length && showAddButton) {
addButton.parent().show();
} else {
addButton.parent().hide();
}
return this;
};
/* 设置插件默认值 */
$.fn.formset.defaults = {
prefix: "form", // Django表单集的表单前缀
addText: "add another", // 添加链接的文本
deleteText: "remove", // 删除链接的文本
addCssClass: "add-row", // 应用于添加链接的CSS类
deleteCssClass: "delete-row", // 应用于删除链接的CSS类
emptyCssClass: "empty-row", // 应用于空行的CSS类
formCssClass: "dynamic-form", // 应用于表单集中的每个表单的CSS类
added: null, // 每次添加新表单时调用的函数
removed: null, // 每次删除表单时调用的函数
addButton: null // 要使用的现有添加按钮
};
// 表格内联 ---------------------------------------------------------
/**
* 表格形式的内联表单集
* @param {string} selector - 选择器
* @param {Object} options - 配置选项
* @returns {jQuery} jQuery对象
*/
$.fn.tabularFormset = function(selector, options) {
const $rows = $(this);
/**
* 重新初始化日期时间快捷方式
*/
const reinitDateTimeShortCuts = function() {
// 强制重新初始化日历和时钟控件
if (typeof DateTimeShortcuts !== "undefined") {
$(".datetimeshortcuts").remove();
DateTimeShortcuts.init();
}
};
/**
* 更新选择过滤器
*/
const updateSelectFilter = function() {
// 如果新表单中有SelectFilter控件
// 为其创建新的SelectFilter实例
if (typeof SelectFilter !== 'undefined') {
$('.selectfilter').each(function(index, value) {
SelectFilter.init(value.id, this.dataset.fieldName, false);
});
$('.selectfilterstacked').each(function(index, value) {
SelectFilter.init(value.id, this.dataset.fieldName, true);
});
}
};
/**
* 初始化预填充字段
* @param {jQuery} row - 表单行
*/
const initPrepopulatedFields = function(row) {
row.find('.prepopulated_field').each(function() {
const field = $(this),
input = field.find('input, select, textarea'),
dependency_list = input.data('dependency_list') || [],
dependencies = [];
$.each(dependency_list, function(i, field_name) {
dependencies.push('#' + row.find('.field-' + field_name).find('input, select, textarea').attr('id'));
});
if (dependencies.length) {
input.prepopulate(dependencies, input.attr('maxlength'));
}
});
};
// 初始化表单集
$rows.formset({
prefix: options.prefix,
addText: options.addText,
formCssClass: "dynamic-" + options.prefix,
deleteCssClass: "inline-deletelink",
deleteText: options.deleteText,
emptyCssClass: "empty-form",
added: function(row) {
initPrepopulatedFields(row);
reinitDateTimeShortCuts();
updateSelectFilter();
},
addButton: options.addButton
});
return $rows;
};
// 堆叠内联 ---------------------------------------------------------
/**
* 堆叠形式的内联表单集
* @param {string} selector - 选择器
* @param {Object} options - 配置选项
* @returns {jQuery} jQuery对象
*/
$.fn.stackedFormset = function(selector, options) {
const $rows = $(this);
/**
* 更新内联标签
* @param {jQuery} row - 表单行
*/
const updateInlineLabel = function(row) {
$(selector).find(".inline_label").each(function(i) {
const count = i + 1;
$(this).html($(this).html().replace(/(#\d+)/g, "#" + count));
});
};
/**
* 重新初始化日期时间快捷方式
*/
const reinitDateTimeShortCuts = function() {
// 强制重新初始化日历和时钟控件
if (typeof DateTimeShortcuts !== "undefined") {
$(".datetimeshortcuts").remove();
DateTimeShortcuts.init();
}
};
/**
* 更新选择过滤器
*/
const updateSelectFilter = function() {
// 如果添加了SelectFilter控件创建新实例
if (typeof SelectFilter !== "undefined") {
$(".selectfilter").each(function(index, value) {
SelectFilter.init(value.id, this.dataset.fieldName, false);
});
$(".selectfilterstacked").each(function(index, value) {
SelectFilter.init(value.id, this.dataset.fieldName, true);
});
}
};
/**
* 初始化预填充字段
* @param {jQuery} row - 表单行
*/
const initPrepopulatedFields = function(row) {
row.find('.prepopulated_field').each(function() {
const field = $(this),
input = field.find('input, select, textarea'),
dependency_list = input.data('dependency_list') || [],
dependencies = [];
$.each(dependency_list, function(i, field_name) {
// 字段集中的依赖项
let field_element = row.find('.form-row .field-' + field_name);
// 没有字段集的依赖项
if (!field_element.length) {
field_element = row.find('.form-row.field-' + field_name);
}
dependencies.push('#' + field_element.find('input, select, textarea').attr('id'));
});
if (dependencies.length) {
input.prepopulate(dependencies, input.attr('maxlength'));
}
});
};
// 初始化表单集
$rows.formset({
prefix: options.prefix,
addText: options.addText,
formCssClass: "dynamic-" + options.prefix,
deleteCssClass: "inline-deletelink",
deleteText: options.deleteText,
emptyCssClass: "empty-form",
removed: updateInlineLabel,
added: function(row) {
initPrepopulatedFields(row);
reinitDateTimeShortCuts();
updateSelectFilter();
updateInlineLabel(row);
},
addButton: options.addButton
});
return $rows;
};
// 文档加载完成后初始化
$(document).ready(function() {
$(".js-inline-admin-formset").each(function() {
const data = $(this).data(),
inlineOptions = data.inlineFormset;
let selector;
// 根据内联类型初始化相应的表单集
switch(data.inlineType) {
case "stacked":
selector = inlineOptions.name + "-group .inline-related";
$(selector).stackedFormset(selector, inlineOptions.options);
break;
case "tabular":
selector = inlineOptions.name + "-group .tabular.inline-related tbody:first > tr.form-row";
$(selector).tabularFormset(selector, inlineOptions.options);
break;
}
});
});
}

@ -0,0 +1,8 @@
/*global jQuery:false*/
'use strict';
/* jQuery使noConflict'true'
* 这确保了引入的jQuery不会污染全局命名空间
* (即保留了window.$和window.jQuery的预先存在值)
*/
// 将jQuery设置为django对象的属性使用noConflict(true)解决命名冲突
window.django = {jQuery: jQuery.noConflict(true)};

@ -0,0 +1,106 @@
'use strict';
{
// 获取导航侧边栏切换按钮和相关元素
const toggleNavSidebar = document.getElementById('toggle-nav-sidebar');
if (toggleNavSidebar !== null) {
const navSidebar = document.getElementById('nav-sidebar');
const main = document.getElementById('main');
// 从localStorage获取导航侧边栏的开关状态
let navSidebarIsOpen = localStorage.getItem('django.admin.navSidebarIsOpen');
// 如果没有保存的状态,默认设为打开
if (navSidebarIsOpen === null) {
navSidebarIsOpen = 'true';
}
// 根据状态设置主内容区域的样式和侧边栏的展开属性
main.classList.toggle('shifted', navSidebarIsOpen === 'true');
navSidebar.setAttribute('aria-expanded', navSidebarIsOpen);
// 为切换按钮添加点击事件监听器
toggleNavSidebar.addEventListener('click', function() {
// 切换导航侧边栏的开关状态
if (navSidebarIsOpen === 'true') {
navSidebarIsOpen = 'false';
} else {
navSidebarIsOpen = 'true';
}
// 保存状态到localStorage
localStorage.setItem('django.admin.navSidebarIsOpen', navSidebarIsOpen);
// 切换主内容区域的样式
main.classList.toggle('shifted');
// 设置侧边栏的展开属性
navSidebar.setAttribute('aria-expanded', navSidebarIsOpen);
});
}
/**
* 初始化侧边栏快速过滤功能
* 允许用户通过输入关键字快速过滤导航菜单项
*/
function initSidebarQuickFilter() {
const options = []; // 存储导航菜单项的数组
const navSidebar = document.getElementById('nav-sidebar');
// 如果没有导航侧边栏,则直接返回
if (!navSidebar) {
return;
}
// 遍历所有导航链接将它们添加到options数组中
navSidebar.querySelectorAll('th[scope=row] a').forEach((container) => {
options.push({title: container.innerHTML, node: container});
});
/**
* 检查过滤值并更新显示
* @param {Event} event - 事件对象
*/
function checkValue(event) {
let filterValue = event.target.value;
// 如果有过滤值,则转换为小写以便进行不区分大小写的匹配
if (filterValue) {
filterValue = filterValue.toLowerCase();
}
// 如果按下Escape键则清空过滤器
if (event.key === 'Escape') {
filterValue = '';
event.target.value = ''; // 清空输入框
}
let matches = false;
// 遍历所有选项,根据过滤值显示或隐藏
for (const o of options) {
let displayValue = '';
if (filterValue) {
// 如果选项标题不包含过滤值,则隐藏该项
if (o.title.toLowerCase().indexOf(filterValue) === -1) {
displayValue = 'none';
} else {
matches = true; // 标记找到了匹配项
}
}
// 显示或隐藏父级<tr>元素
o.node.parentNode.parentNode.style.display = displayValue;
}
// 根据是否有匹配项设置输入框的样式
if (!filterValue || matches) {
event.target.classList.remove('no-results');
} else {
event.target.classList.add('no-results');
}
// 将过滤值保存到sessionStorage中
sessionStorage.setItem('django.admin.navSidebarFilterValue', filterValue);
}
// 获取导航过滤输入框并添加事件监听器
const nav = document.getElementById('nav-filter');
nav.addEventListener('change', checkValue, false); // 变更事件
nav.addEventListener('input', checkValue, false); // 输入事件
nav.addEventListener('keyup', checkValue, false); // 键盘弹起事件
// 从sessionStorage获取之前保存的过滤值并应用
const storedValue = sessionStorage.getItem('django.admin.navSidebarFilterValue');
if (storedValue) {
nav.value = storedValue;
checkValue({target: nav, key: ''});
}
}
window.initSidebarQuickFilter = initSidebarQuickFilter;
initSidebarQuickFilter();
}

@ -0,0 +1,21 @@
'use strict';
{
// 从页面中获取弹窗响应的初始化数据
const initData = JSON.parse(document.getElementById('django-admin-popup-response-constants').dataset.popupResponse);
// 根据不同的操作类型执行相应的处理函数
switch(initData.action) {
case 'change':
// 处理修改对象的弹窗响应
opener.dismissChangeRelatedObjectPopup(window, initData.value, initData.obj, initData.new_value);
break;
case 'delete':
// 处理删除对象的弹窗响应
opener.dismissDeleteRelatedObjectPopup(window, initData.value);
break;
default:
// 默认处理添加对象的弹窗响应
opener.dismissAddRelatedObjectPopup(window, initData.value, initData.obj);
break;
}
}

@ -0,0 +1,58 @@
/*global URLify*/
'use strict';
{
const $ = django.jQuery;
/**
* 预填充插件
* 根据依赖字段的值填充选定字段生成URL友好的字符串
* @param {Array} dependencies - 依赖字段ID数组
* @param {number} maxLength - URLify字符串的最大长度
* @param {boolean} allowUnicode - URLify字符串是否支持Unicode
* @returns {jQuery} jQuery对象
*/
$.fn.prepopulate = function(dependencies, maxLength, allowUnicode) {
/*
依赖 urlify.js
使用依赖字段的值填充选定字段生成URL友好的并截短的字符串
dependencies - 依赖字段ID数组
maxLength - URLify处理后字符串的最大长度
allowUnicode - URLify处理后的字符串是否支持Unicode
*/
return this.each(function() {
const prepopulatedField = $(this);
/**
* 填充字段值
*/
const populate = function() {
// 如果用户已更改字段值,则不进行预填充
if (prepopulatedField.data('_changed')) {
return;
}
const values = [];
// 收集所有依赖字段的值
$.each(dependencies, function(i, field) {
field = $(field);
if (field.val().length > 0) {
values.push(field.val());
}
});
// 使用URLify函数处理连接后的值并设置到预填充字段中
prepopulatedField.val(URLify(values.join(' '), maxLength, allowUnicode));
};
// 初始化字段变更标记为false
prepopulatedField.data('_changed', false);
// 监听字段变更事件,标记字段已被用户更改
prepopulatedField.on('change', function() {
prepopulatedField.data('_changed', true);
});
// 如果预填充字段为空,则监听依赖字段的事件并进行填充
if (!prepopulatedField.val()) {
$(dependencies.join(',')).on('keyup change focus', populate);
}
});
};
}

@ -0,0 +1,21 @@
'use strict';
{
const $ = django.jQuery;
// 从页面常量中获取预填充字段的配置信息
const fields = $('#django-admin-prepopulated-fields-constants').data('prepopulatedFields');
// 遍历所有预填充字段并进行初始化
$.each(fields, function(index, field) {
// 为所有空表单中的预填充字段添加CSS类
$(
'.empty-form .form-row .field-' + field.name +
', .empty-form.form-row .field-' + field.name +
', .empty-form .form-row.field-' + field.name
).addClass('prepopulated_field');
// 为字段元素设置依赖列表数据并调用prepopulate插件进行初始化
$(field.id).data('dependency_list', field.dependency_list).prepopulate(
field.dependency_ids, field.maxLength, field.allowUnicode
);
});
}

@ -0,0 +1,68 @@
'use strict';
{
/**
* 设置主题模式
* @param {string} mode - 主题模式可选值: "light"(浅色), "dark"(深色), "auto"(自动)
*/
function setTheme(mode) {
// 验证主题模式参数是否有效
if (mode !== "light" && mode !== "dark" && mode !== "auto") {
console.error(`Got invalid theme mode: ${mode}. Resetting to auto.`);
mode = "auto";
}
// 设置文档根元素的主题属性
document.documentElement.dataset.theme = mode;
// 将主题选择保存到本地存储
localStorage.setItem("theme", mode);
}
/**
* 循环切换主题模式
*/
function cycleTheme() {
// 获取当前主题设置,默认为"auto"
const currentTheme = localStorage.getItem("theme") || "auto";
// 检查用户是否偏好深色主题
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
if (prefersDark) {
// 深色偏好下的主题切换顺序: 自动(深色) -> 浅色 -> 深色
if (currentTheme === "auto") {
setTheme("light");
} else if (currentTheme === "light") {
setTheme("dark");
} else {
setTheme("auto");
}
} else {
// 浅色偏好下的主题切换顺序: 自动(浅色) -> 深色 -> 浅色
if (currentTheme === "auto") {
setTheme("dark");
} else if (currentTheme === "dark") {
setTheme("light");
} else {
setTheme("auto");
}
}
}
/**
* 初始化主题设置
*/
function initTheme() {
// 如果本地存储中有主题设置则使用,否则默认使用自动模式
const currentTheme = localStorage.getItem("theme");
currentTheme ? setTheme(currentTheme) : setTheme("auto");
}
// 页面加载完成后为所有主题切换按钮添加点击事件监听器
window.addEventListener('load', function(_) {
const buttons = document.getElementsByClassName("theme-toggle");
Array.from(buttons).forEach((btn) => {
btn.addEventListener("click", cycleTheme);
});
});
// 初始化主题
initTheme();
}

@ -0,0 +1,39 @@
"use strict";
// 为不支持:has选择器的浏览器提供的后备JS方案
// 用于admin/css/unusable_password_fields.css
// 一旦所有支持的浏览器都支持:has选择器可以删除此文件
try {
// 如果浏览器不支持:has选择器这将引发错误
document.querySelector("form:has(input)");
} catch (error) {
console.log("默认使用JavaScript管理可用密码表单: " + error);
// 不支持:has选择器时的JS替代方案
document.querySelectorAll('input[name="usable_password"]').forEach(option => {
// 为每个选项添加变更事件监听器
option.addEventListener('change', function() {
// 判断是否为可用密码
const usablePassword = (this.value === "true" ? this.checked : !this.checked);
// 获取提交按钮和警告信息元素
const submit1 = document.querySelector('input[type="submit"].set-password');
const submit2 = document.querySelector('input[type="submit"].unset-password');
const messages = document.querySelector('#id_unusable_warning');
// 根据密码可用性显示或隐藏密码输入字段
document.getElementById('id_password1').closest('.form-row').hidden = !usablePassword;
document.getElementById('id_password2').closest('.form-row').hidden = !usablePassword;
// 根据密码可用性显示或隐藏警告信息
if (messages) {
messages.hidden = usablePassword;
}
// 根据密码可用性显示或隐藏相应的提交按钮
if (submit1 && submit2) {
submit1.hidden = !usablePassword;
submit2.hidden = usablePassword;
}
});
// 触发初始变更事件
option.dispatchEvent(new Event('change'));
});
}

@ -0,0 +1,219 @@
/*global XRegExp*/
'use strict';
{
// 拉丁字符映射表
const LATIN_MAP = {
'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'Æ': 'AE',
'Ç': 'C', 'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E', 'Ì': 'I', 'Í': 'I',
'Î': 'I', 'Ï': 'I', 'Ð': 'D', 'Ñ': 'N', 'Ò': 'O', 'Ó': 'O', 'Ô': 'O',
'Õ': 'O', 'Ö': 'O', 'Ő': 'O', 'Ø': 'O', 'Ù': 'U', 'Ú': 'U', 'Û': 'U',
'Ü': 'U', 'Ű': 'U', 'Ý': 'Y', 'Þ': 'TH', 'Ÿ': 'Y', 'ß': 'ss', 'à': 'a',
'á': 'a', 'â': 'a', 'ã': 'a', 'ä': 'a', 'å': 'a', 'æ': 'ae', 'ç': 'c',
'è': 'e', 'é': 'e', 'ê': 'e', 'ë': 'e', 'ì': 'i', 'í': 'i', 'î': 'i',
'ï': 'i', 'ð': 'd', 'ñ': 'n', 'ò': 'o', 'ó': 'o', 'ô': 'o', 'õ': 'o',
'ö': 'o', 'ő': 'o', 'ø': 'o', 'ù': 'u', 'ú': 'u', 'û': 'u', 'ü': 'u',
'ű': 'u', 'ý': 'y', 'þ': 'th', 'ÿ': 'y'
};
// 拉丁符号映射表
const LATIN_SYMBOLS_MAP = {
'©': '(c)'
};
// 希腊字符映射表
const GREEK_MAP = {
'α': 'a', 'β': 'b', 'γ': 'g', 'δ': 'd', 'ε': 'e', 'ζ': 'z', 'η': 'h',
'θ': '8', 'ι': 'i', 'κ': 'k', 'λ': 'l', 'μ': 'm', 'ν': 'n', 'ξ': '3',
'ο': 'o', 'π': 'p', 'ρ': 'r', 'σ': 's', 'τ': 't', 'υ': 'y', 'φ': 'f',
'χ': 'x', 'ψ': 'ps', 'ω': 'w', 'ά': 'a', 'έ': 'e', 'ί': 'i', 'ό': 'o',
'ύ': 'y', 'ή': 'h', 'ώ': 'w', 'ς': 's', 'ϊ': 'i', 'ΰ': 'y', 'ϋ': 'y',
'ΐ': 'i', 'Α': 'A', 'Β': 'B', 'Γ': 'G', 'Δ': 'D', 'Ε': 'E', 'Ζ': 'Z',
'Η': 'H', 'Θ': '8', 'Ι': 'I', 'Κ': 'K', 'Λ': 'L', 'Μ': 'M', 'Ν': 'N',
'Ξ': '3', 'Ο': 'O', 'Π': 'P', 'Ρ': 'R', 'Σ': 'S', 'Τ': 'T', 'Υ': 'Y',
'Φ': 'F', 'Χ': 'X', 'Ψ': 'PS', 'Ω': 'W', 'Ά': 'A', 'Έ': 'E', 'Ί': 'I',
'Ό': 'O', 'Ύ': 'Y', 'Ή': 'H', 'Ώ': 'W', 'Ϊ': 'I', 'Ϋ': 'Y'
};
// 土耳其字符映射表
const TURKISH_MAP = {
'ş': 's', 'Ş': 'S', 'ı': 'i', 'İ': 'I', 'ç': 'c', 'Ç': 'C', 'ü': 'u',
'Ü': 'U', 'ö': 'o', 'Ö': 'O', 'ğ': 'g', 'Ğ': 'G'
};
// 罗马尼亚字符映射表
const ROMANIAN_MAP = {
'ă': 'a', 'î': 'i', 'ș': 's', 'ț': 't', 'â': 'a',
'Ă': 'A', 'Î': 'I', 'Ș': 'S', 'Ț': 'T', 'Â': 'A'
};
// 俄语字符映射表
const RUSSIAN_MAP = {
'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd', 'е': 'e', 'ё': 'yo',
'ж': 'zh', 'з': 'z', 'и': 'i', 'й': 'j', 'к': 'k', 'л': 'l', 'м': 'm',
'н': 'n', 'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't', 'у': 'u',
'ф': 'f', 'х': 'h', 'ц': 'c', 'ч': 'ch', 'ш': 'sh', 'щ': 'sh', 'ъ': '',
'ы': 'y', 'ь': '', 'э': 'e', 'ю': 'yu', 'я': 'ya',
'А': 'A', 'Б': 'B', 'В': 'V', 'Г': 'G', 'Д': 'D', 'Е': 'E', 'Ё': 'Yo',
'Ж': 'Zh', 'З': 'Z', 'И': 'I', 'Й': 'J', 'К': 'K', 'Л': 'L', 'М': 'M',
'Н': 'N', 'О': 'O', 'П': 'P', 'Р': 'R', 'С': 'S', 'Т': 'T', 'У': 'U',
'Ф': 'F', 'Х': 'H', 'Ц': 'C', 'Ч': 'Ch', 'Ш': 'Sh', 'Щ': 'Sh', 'Ъ': '',
'Ы': 'Y', 'Ь': '', 'Э': 'E', 'Ю': 'Yu', 'Я': 'Ya'
};
// 乌克兰字符映射表
const UKRAINIAN_MAP = {
'Є': 'Ye', 'І': 'I', 'Ї': 'Yi', 'Ґ': 'G', 'є': 'ye', 'і': 'i',
'ї': 'yi', 'ґ': 'g'
};
// 捷克字符映射表
const CZECH_MAP = {
'č': 'c', 'ď': 'd', 'ě': 'e', 'ň': 'n', 'ř': 'r', 'š': 's', 'ť': 't',
'ů': 'u', 'ž': 'z', 'Č': 'C', 'Ď': 'D', 'Ě': 'E', 'Ň': 'N', 'Ř': 'R',
'Š': 'S', 'Ť': 'T', 'Ů': 'U', 'Ž': 'Z'
};
// 斯洛伐克字符映射表
const SLOVAK_MAP = {
'á': 'a', 'ä': 'a', 'č': 'c', 'ď': 'd', 'é': 'e', 'í': 'i', 'ľ': 'l',
'ĺ': 'l', 'ň': 'n', 'ó': 'o', 'ô': 'o', 'ŕ': 'r', 'š': 's', 'ť': 't',
'ú': 'u', 'ý': 'y', 'ž': 'z',
'Á': 'a', 'Ä': 'A', 'Č': 'C', 'Ď': 'D', 'É': 'E', 'Í': 'I', 'Ľ': 'L',
'Ĺ': 'L', 'Ň': 'N', 'Ó': 'O', 'Ô': 'O', 'Ŕ': 'R', 'Š': 'S', 'Ť': 'T',
'Ú': 'U', 'Ý': 'Y', 'Ž': 'Z'
};
// 波兰字符映射表
const POLISH_MAP = {
'ą': 'a', 'ć': 'c', 'ę': 'e', 'ł': 'l', 'ń': 'n', 'ó': 'o', 'ś': 's',
'ź': 'z', 'ż': 'z',
'Ą': 'A', 'Ć': 'C', 'Ę': 'E', 'Ł': 'L', 'Ń': 'N', 'Ó': 'O', 'Ś': 'S',
'Ź': 'Z', 'Ż': 'Z'
};
// 拉脱维亚字符映射表
const LATVIAN_MAP = {
'ā': 'a', 'č': 'c', 'ē': 'e', 'ģ': 'g', 'ī': 'i', 'ķ': 'k', 'ļ': 'l',
'ņ': 'n', 'š': 's', 'ū': 'u', 'ž': 'z',
'Ā': 'A', 'Č': 'C', 'Ē': 'E', 'Ģ': 'G', 'Ī': 'I', 'Ķ': 'K', 'Ļ': 'L',
'Ņ': 'N', 'Š': 'S', 'Ū': 'U', 'Ž': 'Z'
};
// 阿拉伯字符映射表
const ARABIC_MAP = {
'أ': 'a', 'ب': 'b', 'ت': 't', 'ث': 'th', 'ج': 'g', 'ح': 'h', 'خ': 'kh', 'د': 'd',
'ذ': 'th', 'ر': 'r', 'ز': 'z', 'س': 's', 'ش': 'sh', 'ص': 's', 'ض': 'd', 'ط': 't',
'ظ': 'th', 'ع': 'aa', 'غ': 'gh', 'ف': 'f', 'ق': 'k', 'ك': 'k', 'ل': 'l', 'م': 'm',
'ن': 'n', 'ه': 'h', 'و': 'o', 'ي': 'y'
};
// 立陶宛字符映射表
const LITHUANIAN_MAP = {
'ą': 'a', 'č': 'c', 'ę': 'e', 'ė': 'e', 'į': 'i', 'š': 's', 'ų': 'u',
'ū': 'u', 'ž': 'z',
'Ą': 'A', 'Č': 'C', 'Ę': 'E', 'Ė': 'E', 'Į': 'I', 'Š': 'S', 'Ų': 'U',
'Ū': 'U', 'Ž': 'Z'
};
// 塞尔维亚字符映射表
const SERBIAN_MAP = {
'ђ': 'dj', 'ј': 'j', 'љ': 'lj', 'њ': 'nj', 'ћ': 'c', 'џ': 'dz',
'đ': 'dj', 'Ђ': 'Dj', 'Ј': 'j', 'Љ': 'Lj', 'Њ': 'Nj', 'Ћ': 'C',
'Џ': 'Dz', 'Đ': 'Dj'
};
// 阿塞拜疆字符映射表
const AZERBAIJANI_MAP = {
'ç': 'c', 'ə': 'e', 'ğ': 'g', 'ı': 'i', 'ö': 'o', 'ş': 's', 'ü': 'u',
'Ç': 'C', 'Ə': 'E', 'Ğ': 'G', 'İ': 'I', 'Ö': 'O', 'Ş': 'S', 'Ü': 'U'
};
// 格鲁吉亚字符映射表
const GEORGIAN_MAP = {
'ა': 'a', 'ბ': 'b', 'გ': 'g', 'დ': 'd', 'ე': 'e', 'ვ': 'v', 'ზ': 'z',
'თ': 't', 'ი': 'i', 'კ': 'k', 'ლ': 'l', 'მ': 'm', 'ნ': 'n', 'ო': 'o',
'პ': 'p', 'ჟ': 'j', 'რ': 'r', 'ს': 's', 'ტ': 't', 'უ': 'u', 'ფ': 'f',
'ქ': 'q', 'ღ': 'g', '': 'y', 'შ': 'sh', 'ჩ': 'ch', 'ც': 'c', 'ძ': 'dz',
'წ': 'w', 'ჭ': 'ch', 'ხ': 'x', 'ჯ': 'j', 'ჰ': 'h'
};
// 所有字符映射表的数组
const ALL_DOWNCODE_MAPS = [
LATIN_MAP,
LATIN_SYMBOLS_MAP,
GREEK_MAP,
TURKISH_MAP,
ROMANIAN_MAP,
RUSSIAN_MAP,
UKRAINIAN_MAP,
CZECH_MAP,
SLOVAK_MAP,
POLISH_MAP,
LATVIAN_MAP,
ARABIC_MAP,
LITHUANIAN_MAP,
SERBIAN_MAP,
AZERBAIJANI_MAP,
GEORGIAN_MAP
];
// 字符降码处理对象
const Downcoder = {
/**
* 初始化字符映射表
*/
'Initialize': function() {
if (Downcoder.map) { // 如果已经初始化过,则直接返回
return;
}
Downcoder.map = {};
// 将所有映射表合并到一个对象中
for (const lookup of ALL_DOWNCODE_MAPS) {
Object.assign(Downcoder.map, lookup);
}
// 创建用于匹配所有映射字符的正则表达式
Downcoder.regex = new RegExp(Object.keys(Downcoder.map).join('|'), 'g');
}
};
/**
* 将特殊字符转换为普通字符
* @param {string} slug - 需要转换的字符串
* @returns {string} 转换后的字符串
*/
function downcode(slug) {
Downcoder.Initialize();
return slug.replace(Downcoder.regex, function(m) {
return Downcoder.map[m];
});
}
/**
* 将字符串转换为URL友好的格式
* 例如将"Petty theft"转换为"petty-theft"
* @param {string} s - 输入字符串
* @param {number} num_chars - 最大字符数
* @param {boolean} allowUnicode - 是否允许Unicode字符
* @returns {string} URL友好的字符串
*/
function URLify(s, num_chars, allowUnicode) {
// 如果不允许Unicode字符则进行字符降码处理
if (!allowUnicode) {
s = downcode(s);
}
s = s.toLowerCase(); // 转换为小写
// 如果降码处理没有命中,字符将在这里被移除
if (allowUnicode) {
// 保留Unicode字母包括大小写字符、空白字符和连字符移除其他字符
s = XRegExp.replace(s, XRegExp('[^-_\\p{L}\\p{N}\\s]', 'g'), '');
} else {
s = s.replace(/[^-\w\s]/g, ''); // 移除不需要的字符
}
s = s.replace(/^\s+|\s+$/g, ''); // 去除开头和结尾的空格
s = s.replace(/[-\s]+/g, '-'); // 将空格转换为连字符
s = s.substring(0, num_chars); // 截取指定长度的字符
return s.replace(/-+$/g, ''); // 去除末尾的连字符
}
window.URLify = URLify;
}

@ -0,0 +1,20 @@
Copyright OpenJS Foundation and other contributors, https://openjsf.org/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save