From aae5d022ecbeb7ac93a432bf07b0143c4fbc5048 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Sat, 11 Apr 2026 16:39:04 +0200 Subject: [PATCH 01/15] fields: Add ColorField This will allow us to store color codes cleanly in the database. --- patchwork/fields.py | 24 ++++++++++++++++++++++++ patchwork/forms.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/patchwork/fields.py b/patchwork/fields.py index 5cdebcd55..dc5f92ba7 100644 --- a/patchwork/fields.py +++ b/patchwork/fields.py @@ -27,3 +27,27 @@ def from_db_value(self, value, *args, **kwargs): def db_type(self, connection=None): return 'char(%d)' % self.n_bytes + + +class ColorField(models.Field): + description = 'Hex color code' + + def get_internal_type(self): + return 'PositiveIntegerField' + + def to_python(self, value): + if isinstance(value, str) or value is None: + return value + return '#%06x' % value + + def from_db_value(self, value, *args, **kwargs): + return self.to_python(value) + + def get_prep_value(self, value): + return int(value.lstrip('#'), 16) + + def formfield(self, *args, **kwargs): + from patchwork import forms # noqa + + kwargs['form_class'] = forms.ColorField + return super(ColorField, self).formfield(*args, **kwargs) diff --git a/patchwork/forms.py b/patchwork/forms.py index efedfa976..1e62368a9 100644 --- a/patchwork/forms.py +++ b/patchwork/forms.py @@ -3,9 +3,11 @@ # # SPDX-License-Identifier: GPL-2.0-or-later +import re + from django.contrib.auth.models import User from django import forms -from django.forms import renderers +from django.forms import widgets, renderers from django.db.models import Q from django.db.utils import ProgrammingError from django.template.backends import django as django_template_backend @@ -253,3 +255,28 @@ def save(self, instance, commit=True): if commit: instance.save() return instance + + +class ColorInput(widgets.Input): + input_type = 'color' + + +class ColorField(forms.CharField): + widget = ColorInput + default_error_messages = { + 'invalid': 'Enter a valid colour value: e.g. "#ff0022"', + } + + def clean(self, value): + if not re.match('^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$', value): + raise forms.ValidationError(self.error_messages['invalid']) + + value = int(value.lstrip('#'), 16) + super(ColorField, self).clean(value) + + return value + + def widget_attrs(self, widget): + attrs = super().widget_attrs(widget) + attrs['maxlength'] = 7 + return attrs From cb9686f7f7d0088d5ea742859ec7619aa041512b Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Sat, 11 Apr 2026 16:42:38 +0200 Subject: [PATCH 02/15] models: Add submission labels Labels are arbitrary bits of metadata attached to a submission. They can be used to signify priority, category, or other similar information. They can also be used to filter patches and identify the ones most interesting to a given user. Labels can be associated with a project to ensure that one project can use a totally different set of labels to another and to, in the future, allow a project administrator to use their own labels. However, they can also be global, which is useful for things that would be common across multiple projects such as "RFC". --- patchwork/fixtures/default_labels.xml | 9 +++ patchwork/migrations/0049_add_patch_labels.py | 74 +++++++++++++++++++ patchwork/models.py | 40 +++++++++- .../notes/labels-6d0096c7d8505627.yaml | 11 +++ 4 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 patchwork/fixtures/default_labels.xml create mode 100644 patchwork/migrations/0049_add_patch_labels.py create mode 100644 releasenotes/notes/labels-6d0096c7d8505627.yaml diff --git a/patchwork/fixtures/default_labels.xml b/patchwork/fixtures/default_labels.xml new file mode 100644 index 000000000..1e1aa705b --- /dev/null +++ b/patchwork/fixtures/default_labels.xml @@ -0,0 +1,9 @@ + + + + + RFC + "RFC" stands for "Request for Comment", which tells maintainers that they should review your patch thoroughly and provide feedback. RFC is typically used when sending feature patches for the first time, or anytime the patch is more than just a simple bug fix. + #EDEDED + + diff --git a/patchwork/migrations/0049_add_patch_labels.py b/patchwork/migrations/0049_add_patch_labels.py new file mode 100644 index 000000000..565aab9cd --- /dev/null +++ b/patchwork/migrations/0049_add_patch_labels.py @@ -0,0 +1,74 @@ +# Generated by Django 6.0.7 on 2026-07-31 21:26 + +import django.db.models.deletion +import patchwork.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('patchwork', '0048_series_dependencies'), + ] + + operations = [ + migrations.CreateModel( + name='Label', + fields=[ + ( + 'id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ( + 'name', + models.CharField( + help_text='The label value.', max_length=25 + ), + ), + ( + 'description', + models.TextField( + blank=True, + help_text='A description of what the label is and when it should be applied.', + null=True, + ), + ), + ( + 'color', + patchwork.fields.ColorField( + help_text='The color code to use in the UI.' + ), + ), + ( + 'project', + models.ForeignKey( + blank=True, + help_text='The project this label is associated with. If unset, this label is global to the instance', + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name='labels', + related_query_name='label', + to='patchwork.project', + ), + ), + ], + options={ + 'ordering': ['name'], + 'unique_together': {('project', 'name')}, + }, + ), + migrations.AddField( + model_name='cover', + name='labels', + field=models.ManyToManyField(to='patchwork.label'), + ), + migrations.AddField( + model_name='patch', + name='labels', + field=models.ManyToManyField(to='patchwork.label'), + ), + ] diff --git a/patchwork/models.py b/patchwork/models.py index d5cb31de9..e122ca3d2 100644 --- a/patchwork/models.py +++ b/patchwork/models.py @@ -18,7 +18,7 @@ from django.utils.functional import cached_property from django.utils import timezone as tz_utils -from patchwork.fields import HashField +from patchwork.fields import HashField, ColorField from patchwork.hasher import hash_diff if settings.ENABLE_REST_API: @@ -256,6 +256,43 @@ class Meta: ordering = ['ordering'] +class Label(models.Model): + """Labels for patches. + + Labels are arbitrary bits of metadata attached to a patch. They can + be used to signify priority, category, or other similar information. + They can also be used to filter patches and identify the ones most + interesting to a given user. + """ + + project = models.ForeignKey( + Project, + related_name='labels', + related_query_name='label', + null=True, + blank=True, + on_delete=models.CASCADE, + help_text='The project this label is associated with. If unset, this ' + 'label is global to the instance', + ) + + name = models.CharField(max_length=25, help_text='The label value.') + description = models.TextField( + null=True, + blank=True, + help_text='A description of what the label is and when it should be ' + 'applied.', + ) + color = ColorField(help_text='The color code to use in the UI.') + + def __str__(self): + return self.name + + class Meta: + ordering = ['name'] + unique_together = [('project', 'name')] + + class Tag(models.Model): name = models.CharField(max_length=20) pattern = models.CharField( @@ -435,6 +472,7 @@ class SubmissionMixin(FilenameMixin, EmailMixin, models.Model): # submission metadata name = models.CharField(max_length=255) + labels = models.ManyToManyField(Label) @cached_property def list_archive_url(self): diff --git a/releasenotes/notes/labels-6d0096c7d8505627.yaml b/releasenotes/notes/labels-6d0096c7d8505627.yaml new file mode 100644 index 000000000..fdebd6b7a --- /dev/null +++ b/releasenotes/notes/labels-6d0096c7d8505627.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + Add support for patch labels. Labels are a way to organize and prioritize + submissions. You can apply labels to patches and cover letters to signify + priority, category, or any other marker you wish. + + Labels can be either global or tied to the project they are created in. + Labels can have an optional description attached, which will provide a + little insight into the purpose of the label. Labels are completely + customizable and the labels available will vary by instance. From 65dcede3fdc624441af974c9834bc3be5932ee73 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Sat, 11 Apr 2026 16:48:28 +0200 Subject: [PATCH 03/15] parser: Extract and save labels --- patchwork/parser.py | 18 ++++++++++++++++++ patchwork/tests/unit/test_parser.py | 16 ++++++++++++++++ patchwork/tests/utils.py | 15 +++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/patchwork/parser.py b/patchwork/parser.py index 703d07c14..c4d5686bb 100644 --- a/patchwork/parser.py +++ b/patchwork/parser.py @@ -17,6 +17,7 @@ from urllib.parse import urlparse, parse_qs from django.contrib.auth.models import User +from django.db.models import Q from django.db.utils import IntegrityError from django.db import transaction from django.utils import timezone as tz_utils @@ -26,6 +27,7 @@ from patchwork.models import CoverComment from patchwork.models import DelegationRule from patchwork.models import get_default_initial_patch_state +from patchwork.models import Label from patchwork.models import Patch from patchwork.models import PatchComment from patchwork.models import Person @@ -627,6 +629,17 @@ def parse_version(subject, subject_prefixes): return 1 +def parse_labels(subject_prefixes, project): + """Extract labels from subject. + + Args: + subject_prefixes: List of subject prefixes to extract tags from + """ + return Label.objects.filter( + Q(project=project) | Q(project=None), name__in=subject_prefixes + ) + + def _find_content(mail): """Extract the payload(s) from a mail. @@ -1270,6 +1283,8 @@ def parse_mail(mail, list_id=None): if Patch.objects.filter(project=project, msgid=msgid): raise DuplicateMailError(msgid=msgid) + labels = parse_labels(prefixes, project) + patch = Patch.objects.create( msgid=msgid, project=project, @@ -1283,6 +1298,9 @@ def parse_mail(mail, list_id=None): delegate=delegate, state=find_state(mail), ) + if labels: + patch.labels.set(labels) + logger.debug('Patch saved') for attempt in range(1, 11): # arbitrary retry count diff --git a/patchwork/tests/unit/test_parser.py b/patchwork/tests/unit/test_parser.py index 1411ef0bc..5e104b642 100644 --- a/patchwork/tests/unit/test_parser.py +++ b/patchwork/tests/unit/test_parser.py @@ -28,6 +28,7 @@ from patchwork.parser import find_comment_content from patchwork.parser import find_project from patchwork.parser import find_series +from patchwork.parser import parse_labels from patchwork.parser import parse_mail as _parse_mail from patchwork.parser import parse_pull_request from patchwork.parser import parse_series_marker @@ -41,6 +42,7 @@ from patchwork.tests.utils import create_cover_comment from patchwork.tests.utils import create_patch from patchwork.tests.utils import create_patch_comment +from patchwork.tests.utils import create_label from patchwork.tests.utils import create_project from patchwork.tests.utils import create_series from patchwork.tests.utils import create_series_reference @@ -1199,6 +1201,20 @@ def test_version(self): self.assertEqual(parse_version('Hello, world (v2)', []), 2) self.assertEqual(parse_version('Hello, world (V6)', []), 6) + def test_labels(self): + label = create_label(name='RFC') + + self.assertEqual(list(parse_labels(['RFC'], label.project)), [label]) + self.assertEqual(list(parse_labels(['rfcx'], label.project)), []) + + project = create_project() + + self.assertEqual(list(parse_labels(['RFC'], project)), []) + + label = create_label(name='stuff', project=None) + + self.assertEqual(list(parse_labels(['stuff'], project)), [label]) + class SubjectMatchTest(TestCase): def setUp(self): diff --git a/patchwork/tests/utils.py b/patchwork/tests/utils.py index 4f4048912..d4d5029f6 100644 --- a/patchwork/tests/utils.py +++ b/patchwork/tests/utils.py @@ -15,6 +15,7 @@ from patchwork.models import Check from patchwork.models import Cover from patchwork.models import CoverComment +from patchwork.models import Label from patchwork.models import Patch from patchwork.models import PatchComment from patchwork.models import PatchRelation @@ -319,6 +320,20 @@ def create_relation(**kwargs): return PatchRelation.objects.create(**kwargs) +def create_label(**kwargs): + """Create a 'Label' object.""" + num = Label.objects.count() + + values = { + 'name': 'label%d' % num, + 'project': create_project() if 'project' not in kwargs else None, + 'color': '#fff', + } + values.update(**kwargs) + + return Label.objects.create(**values) + + def _create_submissions(create_func, count=1, **kwargs): """Create 'count' SubmissionMixin-based objects. From e6815ec592b30714c412a15f05c6c4f674af01d2 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Sat, 11 Apr 2026 16:53:55 +0200 Subject: [PATCH 04/15] admin: Add label views There are two added: a general labels view that includes both project and non-project labels, and an inline labels view that's part of the project. --- patchwork/admin.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/patchwork/admin.py b/patchwork/admin.py index d3bdae1bb..267a3820f 100644 --- a/patchwork/admin.py +++ b/patchwork/admin.py @@ -13,6 +13,7 @@ from patchwork.models import Cover from patchwork.models import CoverComment from patchwork.models import DelegationRule +from patchwork.models import Label from patchwork.models import Patch from patchwork.models import PatchComment from patchwork.models import PatchRelation @@ -44,11 +45,18 @@ class DelegationRuleInline(admin.TabularInline): fields = ('path', 'user', 'priority') +class LabelInline(admin.TabularInline): + model = Label + fields = ('name', 'description', 'color') + extra = 0 + + @admin.register(Project) class ProjectAdmin(admin.ModelAdmin): list_display = ('name', 'linkname', 'listid', 'listemail') inlines = [ DelegationRuleInline, + LabelInline, ] @@ -91,6 +99,7 @@ class PatchAdmin(admin.ModelAdmin): 'is_pull_request', ) list_filter = ('project', 'submitter', 'state', 'archived') + readonly_fields = ('labels',) list_select_related = ('submitter', 'project', 'state') search_fields = ('name', 'submitter__name', 'submitter__email') date_hierarchy = 'date' @@ -194,3 +203,12 @@ class TagAdmin(admin.ModelAdmin): @admin.register(PatchRelation) class PatchRelationAdmin(admin.ModelAdmin): model = PatchRelation + + +@admin.register(Label) +class LabelAdmin(admin.ModelAdmin): + list_display = ('name', 'color') + + def get_queryset(self, request): + qs = super(LabelAdmin, self).get_queryset(request) + return qs.filter(project=None) From 324ca42b6cd38c2c6e1f81de9d00f69567b26037 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Sat, 11 Apr 2026 16:54:38 +0200 Subject: [PATCH 05/15] views: Populate 'project' attribute of PatchForm This is required to ensure we can filter delegates by project. If we don't do this, our ' + % (self.param, value) + ) + + FILTERS = [ SeriesFilter, SubmitterFilter, @@ -518,6 +576,7 @@ def set_status(self, *args, **kwargs): SearchFilter, ArchiveFilter, DelegateFilter, + LabelsFilter, ] @@ -547,14 +606,22 @@ def available_filters(self): def apply(self, queryset): kwargs = collections.OrderedDict() + q_filters = [] for f in self._filters: if f.applied: - kwargs.update(f.kwargs) + if f.kwargs is not None: + kwargs.update(f.kwargs) + else: + q_filters.append(f) - if not kwargs: + if not kwargs and not q_filters: return queryset - return queryset.filter(**kwargs) + queryset = queryset.filter(**kwargs) + for f in q_filters: + queryset = f.q(queryset) + + return queryset def querystring(self, remove=None): params = self.params diff --git a/patchwork/models.py b/patchwork/models.py index e122ca3d2..28bfe6805 100644 --- a/patchwork/models.py +++ b/patchwork/models.py @@ -498,6 +498,24 @@ class Meta: abstract = True +def exclude_submissions_by_labels(queryset, labels): + if len(labels) > 0: + queryset = queryset.exclude(labels__name__in=labels) + + return queryset + + +def filter_submissions_by_labels(queryset, labels): + if len(labels) > 0: + queryset = ( + queryset.filter(labels__name__in=labels) + .annotate(num_labels=models.Count('labels', distinct=True)) + .filter(num_labels__gte=len(labels)) + ) + + return queryset + + class Cover(SubmissionMixin): def get_absolute_url(self): return reverse( From 50e4e37e239fdaeab9d154d00bfcb8fd691a1fb0 Mon Sep 17 00:00:00 2001 From: Franciszek Stachura Date: Fri, 31 Jul 2026 14:57:56 +0200 Subject: [PATCH 10/15] REST: Add filtering by labels to API Patches in the API can be filtered by labels using the 'labels' query parameter. --- docs/api/schemas/latest/patchwork.yaml | 9 ++ docs/api/schemas/patchwork.j2 | 11 ++ docs/api/schemas/v1.4/patchwork.yaml | 9 ++ patchwork/api/filters.py | 20 ++++ patchwork/tests/unit/api/test_patch.py | 34 +++++++ patchwork/tests/unit/views/test_patch.py | 123 +++++++++++++++++++++++ patchwork/tests/utils.py | 8 ++ 7 files changed, 214 insertions(+) diff --git a/docs/api/schemas/latest/patchwork.yaml b/docs/api/schemas/latest/patchwork.yaml index 1b85e9521..196a25151 100644 --- a/docs/api/schemas/latest/patchwork.yaml +++ b/docs/api/schemas/latest/patchwork.yaml @@ -578,6 +578,15 @@ paths: schema: title: '' type: string + - in: query + name: labels + description: | + List of labels assigned to queried patches, separated by ','. + If a label name begins with a '-', patches with that label won't + be included. + schema: + title: '' + type: string responses: '200': description: 'List of patches' diff --git a/docs/api/schemas/patchwork.j2 b/docs/api/schemas/patchwork.j2 index cea28fc08..98e0845fe 100644 --- a/docs/api/schemas/patchwork.j2 +++ b/docs/api/schemas/patchwork.j2 @@ -596,6 +596,17 @@ paths: schema: title: '' type: string +{% endif %} +{% if version >= (1, 4) %} + - in: query + name: labels + description: | + List of labels assigned to queried patches, separated by ','. + If a label name begins with a '-', patches with that label won't + be included. + schema: + title: '' + type: string {% endif %} responses: '200': diff --git a/docs/api/schemas/v1.4/patchwork.yaml b/docs/api/schemas/v1.4/patchwork.yaml index 359e8224e..a63f18fb5 100644 --- a/docs/api/schemas/v1.4/patchwork.yaml +++ b/docs/api/schemas/v1.4/patchwork.yaml @@ -578,6 +578,15 @@ paths: schema: title: '' type: string + - in: query + name: labels + description: | + List of labels assigned to queried patches, separated by ','. + If a label name begins with a '-', patches with that label won't + be included. + schema: + title: '' + type: string responses: '200': description: 'List of patches' diff --git a/patchwork/api/filters.py b/patchwork/api/filters.py index e332c5316..9bd42254f 100644 --- a/patchwork/api/filters.py +++ b/patchwork/api/filters.py @@ -26,6 +26,9 @@ from patchwork.models import Series from patchwork.models import State +from patchwork.models import exclude_submissions_by_labels +from patchwork.models import filter_submissions_by_labels + # custom backend @@ -175,6 +178,21 @@ def msgid_filter(queryset, name, value): return queryset.filter(**{name: '<' + value + '>'}) +def labels_filter(queryset, _, value): + label_names = value.split(',') + + labels_pos, labels_neg = [], [] + for label in label_names: + if not label.startswith('-'): + labels_pos.append(label) + else: + labels_neg.append(label[1:]) + + queryset = exclude_submissions_by_labels(queryset, labels_neg) + queryset = filter_submissions_by_labels(queryset, labels_pos) + return queryset + + class CoverFilterSet(TimestampMixin, BaseFilterSet): project = ProjectFilter(queryset=Project.objects.all(), distinct=False) # NOTE(stephenfin): We disable the select-based HTML widgets for these @@ -206,6 +224,7 @@ class PatchFilterSet(TimestampMixin, BaseFilterSet): state = StateFilter(queryset=State.objects.all(), distinct=False) hash = CharFilter(lookup_expr='iexact') msgid = CharFilter(method=msgid_filter) + labels = CharFilter(method=labels_filter) class Meta: model = Patch @@ -225,6 +244,7 @@ class Meta: ) versioned_fields = { '1.2': ('hash', 'msgid'), + '1.4': ('labels'), } diff --git a/patchwork/tests/unit/api/test_patch.py b/patchwork/tests/unit/api/test_patch.py index 19f2af8fa..1facf3118 100644 --- a/patchwork/tests/unit/api/test_patch.py +++ b/patchwork/tests/unit/api/test_patch.py @@ -228,6 +228,40 @@ def test_list_filter_msgid(self): resp = self.client.get(self.api_url(), {'msgid': 'fishfish@fish.fish'}) self.assertEqual(0, len(resp.data)) + def test_list_filter_labels(self): + """Filter patches by labels.""" + label1 = create_label() + label2 = create_label() + label3 = create_label() + + person_obj = create_person(email='test@example.com') + project_obj = create_project(linkname='myproject') + state_obj = create_state(name='Under Review') + patch_kwargs = { + 'state': state_obj, + 'project': project_obj, + 'submitter': person_obj, + } + patch_no_labels = create_patch(**patch_kwargs) + patch1 = create_patch(**patch_kwargs, labels=[label1]) + patch2 = create_patch(**patch_kwargs, labels=[label1, label2]) + + resp = self.client.get(self.api_url(), {'labels': label1.name}) + self.assertEqual([patch1.id, patch2.id], [x['id'] for x in resp.data]) + + resp = self.client.get(self.api_url(), {'labels': label3.name}) + self.assertEqual(0, len(resp.data)) + + resp = self.client.get(self.api_url(), {'labels': '-' + label2.name}) + self.assertEqual( + [patch_no_labels.id, patch1.id], [x['id'] for x in resp.data] + ) + + resp = self.client.get( + self.api_url(), {'labels': label1.name + ',' + label2.name} + ) + self.assertEqual([patch2.id], [x['id'] for x in resp.data]) + @utils.store_samples('patch-list-1-0') def test_list_version_1_0(self): """List patches using API v1.0.""" diff --git a/patchwork/tests/unit/views/test_patch.py b/patchwork/tests/unit/views/test_patch.py index 133bae366..983cf85b6 100644 --- a/patchwork/tests/unit/views/test_patch.py +++ b/patchwork/tests/unit/views/test_patch.py @@ -23,6 +23,7 @@ from patchwork.tests.utils import create_patches from patchwork.tests.utils import create_person from patchwork.tests.utils import create_project +from patchwork.tests.utils import create_label from patchwork.tests.utils import create_state from patchwork.tests.utils import create_user from patchwork.tests.utils import read_patch @@ -203,6 +204,128 @@ def test_utf8_handling(self): self.assertEqual(response.status_code, 200) +class PatchListLabelFilteringTest(TestCase): + def setUp(self): + self.project = create_project() + self.label1 = create_label(project=self.project) + self.label2 = create_label(project=self.project) + self.global_label = create_label(project=None) + + person = create_person(name='test', email='test@example.com') + + self.patches = [ + create_patch(submitter=person, project=self.project, labels=[]), + create_patch( + submitter=person, project=self.project, labels=[self.label1] + ), + create_patch( + submitter=person, + project=self.project, + labels=[self.label1, self.label2], + ), + create_patch( + submitter=person, + project=self.project, + labels=[self.label1, self.global_label], + ), + ] + + def _extract_patch_ids(self, response): + id_re = re.compile(r' Date: Sun, 12 Apr 2026 16:38:31 +0200 Subject: [PATCH 11/15] htdocs: Update selectize to 0.15.2 a741c957 upgraded selectize to 0.13.5. However, it looks like a non-standalone version was uploaded by accident. On page load, the following error can be seen in debug console: "Selectize: Dependency MicroPlugin is missing". Because of that, the submitter autocomplete no longer works. Version 0.13.5 was not released on Github, so I have decided to upgrade the library to the latest available version. Add updated files to .gitattributes to skip them in patches. --- .gitattributes | 2 ++ htdocs/README.rst | 2 +- htdocs/css/selectize.bootstrap3.css | 26 +++++++++++++++++----- htdocs/js/selectize.min.js | 34 +++++++++++++++++++++++++++-- 4 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..e36065822 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +htdocs/js/*.min.js binary +htdocs/css/selectize.bootstrap3.css binary diff --git a/htdocs/README.rst b/htdocs/README.rst index 9a5211233..7f7eff94e 100644 --- a/htdocs/README.rst +++ b/htdocs/README.rst @@ -140,4 +140,4 @@ js :Website: https://selectize.github.io/selectize.js/ :GitHub: https://github.com/selectize/selectize.js - :Version: 0.13.5 + :Version: 0.15.2 diff --git a/htdocs/css/selectize.bootstrap3.css b/htdocs/css/selectize.bootstrap3.css index 3165727e7..ca53d550b 100644 --- a/htdocs/css/selectize.bootstrap3.css +++ b/htdocs/css/selectize.bootstrap3.css @@ -1,6 +1,20 @@ -.selectize-control.plugin-drag_drop.multi>.selectize-input.dragging{overflow:visible}.selectize-control.plugin-drag_drop.multi>.selectize-input>div.ui-sortable-placeholder{visibility:visible !important;background:#f2f2f2 !important;background:rgba(0,0,0,.06) !important;border:0 none !important;box-shadow:inset 0 0 12px 4px #fff}.selectize-control.plugin-drag_drop .ui-sortable-placeholder::after{content:"!";visibility:hidden}.selectize-control.plugin-drag_drop .ui-sortable-helper{box-shadow:0 2px 5px rgba(0,0,0,.2)}.selectize-control .dropdown-header{position:relative;padding:6px 12px;border-bottom:1px solid #d0d0d0;background:#f8f8f8;border-radius:4px 4px 0 0}.selectize-control .dropdown-header-close{position:absolute;right:12px;top:50%;color:#333;opacity:.4;margin-top:-12px;line-height:20px;font-size:20px !important}.selectize-control .dropdown-header-close:hover{color:#000}.selectize-dropdown.plugin-optgroup_columns .selectize-dropdown-content{display:flex}.selectize-dropdown.plugin-optgroup_columns .optgroup{border-right:1px solid #f2f2f2;border-top:0 none;flex-grow:1;flex-basis:0;min-width:0}.selectize-dropdown.plugin-optgroup_columns .optgroup:last-child{border-right:0 none}.selectize-dropdown.plugin-optgroup_columns .optgroup:before{display:none}.selectize-dropdown.plugin-optgroup_columns .optgroup-header{border-top:0 none}.selectize-control.plugin-remove_button .item{display:inline-flex;align-items:center;padding-right:0 !important}.selectize-control.plugin-remove_button .item .remove{color:inherit;text-decoration:none;vertical-align:middle;display:inline-block;padding:1px 5px;border-left:1px solid rgba(0,0,0,0);border-radius:0 2px 2px 0;box-sizing:border-box;margin-left:5px}.selectize-control.plugin-remove_button .item .remove:hover{background:rgba(0,0,0,.05)}.selectize-control.plugin-remove_button .item.active .remove{border-left-color:rgba(0,0,0,0)}.selectize-control.plugin-remove_button .disabled .item .remove:hover{background:none}.selectize-control.plugin-remove_button .disabled .item .remove{border-left-color:rgba(77,77,77,0)}.selectize-control.plugin-remove_button .remove-single{position:absolute;right:0;top:0;font-size:23px}.selectize-control{position:relative}.selectize-dropdown,.selectize-input,.selectize-input input{color:#333;font-family:inherit;font-size:inherit;line-height:20px;font-smoothing:inherit}.selectize-input,.selectize-control.single .selectize-input.input-active{background:#fff;cursor:text;display:inline-block}.selectize-input{border:1px solid #ccc;padding:6px 12px;display:inline-block;width:100%;position:relative;z-index:1;box-sizing:border-box;box-shadow:none;border-radius:4px}.selectize-control.multi .selectize-input.has-items{padding:calc( - 6px - 1px - 0 - ) 12px calc( - 6px - 1px - 3px - - 0 - )}.selectize-input.full{background-color:#fff}.selectize-input.disabled,.selectize-input.disabled *{cursor:default !important}.selectize-input.focus{box-shadow:inset 0 1px 2px rgba(0,0,0,.15)}.selectize-input.dropdown-active{border-radius:4px 4px 0 0}.selectize-input>*{vertical-align:baseline;display:inline-block;zoom:1}.selectize-control.multi .selectize-input>div{cursor:pointer;margin:0 3px 3px 0;padding:1px 5px;background:#efefef;color:#333;border:0 solid rgba(0,0,0,0)}.selectize-control.multi .selectize-input>div.active{background:#337ab7;color:#fff;border:0 solid rgba(0,0,0,0)}.selectize-control.multi .selectize-input.disabled>div,.selectize-control.multi .selectize-input.disabled>div.active{color:gray;background:#fff;border:0 solid rgba(77,77,77,0)}.selectize-input>input{display:inline-block !important;padding:0 !important;min-height:0 !important;max-height:none !important;max-width:100% !important;margin:0 !important;text-indent:0 !important;border:0 none !important;background:none !important;line-height:inherit !important;user-select:auto !important;box-shadow:none !important}.selectize-input>input::-ms-clear{display:none}.selectize-input>input:focus{outline:none !important}.selectize-input>input[placeholder]{box-sizing:initial}.selectize-input.has-items>input{margin:0 0px !important}.selectize-input::after{content:" ";display:block;clear:left}.selectize-input.dropdown-active::before{content:" ";display:block;position:absolute;background:#fff;height:1px;bottom:0;left:0;right:0}.selectize-dropdown{position:absolute;top:100%;left:0;width:100%;z-index:10;border:1px solid #d0d0d0;background:#fff;margin:-1px 0 0 0;border-top:0 none;box-sizing:border-box;box-shadow:0 1px 3px rgba(0,0,0,.1);border-radius:0 0 4px 4px}.selectize-dropdown [data-selectable]{cursor:pointer;overflow:hidden}.selectize-dropdown [data-selectable] .highlight{background:rgba(255,237,40,.4);border-radius:1px}.selectize-dropdown .option,.selectize-dropdown .optgroup-header,.selectize-dropdown .no-results,.selectize-dropdown .create{padding:3px 12px}.selectize-dropdown .option,.selectize-dropdown [data-disabled],.selectize-dropdown [data-disabled] [data-selectable].option{cursor:inherit;opacity:.5}.selectize-dropdown [data-selectable].option{opacity:1;cursor:pointer}.selectize-dropdown .optgroup:first-child .optgroup-header{border-top:0 none}.selectize-dropdown .optgroup-header{color:#777;background:#fff;cursor:default}.selectize-dropdown .active{background-color:#f5f5f5;color:#262626}.selectize-dropdown .active.create{color:#262626}.selectize-dropdown .create{color:rgba(51,51,51,.5)}.selectize-dropdown-content{overflow-y:auto;overflow-x:hidden;max-height:200px;overflow-scrolling:touch}.selectize-dropdown-emptyoptionlabel{text-align:center}.selectize-dropdown .spinner{display:inline-block;width:30px;height:30px;margin:3px 12px}.selectize-dropdown .spinner:after{content:" ";display:block;width:24px;height:24px;margin:3px;border-radius:50%;border:5px solid #d0d0d0;border-color:#d0d0d0 rgba(0,0,0,0) #d0d0d0 rgba(0,0,0,0);animation:lds-dual-ring 1.2s linear infinite}@keyframes lds-dual-ring{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.selectize-control.single .selectize-input,.selectize-control.single .selectize-input input{cursor:pointer}.selectize-control.single .selectize-input.input-active,.selectize-control.single .selectize-input.input-active input{cursor:text}.selectize-control.single .selectize-input:after{content:" ";display:block;position:absolute;top:50%;right:17px;margin-top:-3px;width:0;height:0;border-style:solid;border-width:5px 5px 0 5px;border-color:#333 rgba(0,0,0,0) rgba(0,0,0,0) rgba(0,0,0,0)}.selectize-control.single .selectize-input.dropdown-active:after{margin-top:-4px;border-width:0 5px 5px 5px;border-color:rgba(0,0,0,0) rgba(0,0,0,0) #333 rgba(0,0,0,0)}.selectize-control.rtl{text-align:right}.selectize-control.rtl.single .selectize-input:after{left:17px;right:auto}.selectize-control.rtl .selectize-input>input{margin:0 4px 0 -2px !important}.selectize-control .selectize-input.disabled{opacity:.5;background-color:#fff}.selectize-dropdown,.selectize-dropdown.form-control{height:auto;padding:0;margin:2px 0 0 0;z-index:1000;background:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;box-shadow:0 6px 12px rgba(0,0,0,.175)}.selectize-dropdown .optgroup-header{font-size:12px;line-height:1.428571429}.selectize-dropdown .optgroup:first-child:before{display:none}.selectize-dropdown .optgroup:before{content:" ";display:block;height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5;margin-left:-12px;margin-right:-12px}.selectize-dropdown-content{padding:5px 0}.selectize-dropdown-emptyoptionlabel{text-align:center}.selectize-input{min-height:34px}.selectize-input.dropdown-active{border-radius:4px}.selectize-input.dropdown-active::before{display:none}.selectize-input.focus{border-color:#66afe9;outline:0;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.has-error .selectize-input{border-color:#a94442;box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .selectize-input:focus{border-color:#843534;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.selectize-control.multi .selectize-input.has-items{padding-left:7px;padding-right:7px}.selectize-control.multi .selectize-input>div{border-radius:3px}.form-control.selectize-control{padding:0;height:auto;border:none;background:none;box-shadow:none;border-radius:0} \ No newline at end of file +/** + * Selectize (v0.15.2) + * https://selectize.dev + * + * Copyright (c) 2013-2015 Brian Reavis & contributors + * Copyright (c) 2020-2022 Selectize Team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * @author Brian Reavis + * @author Ris Adams + */ +.selectize-control.plugin-drag_drop.multi>.selectize-input.dragging{overflow:visible}.selectize-control.plugin-drag_drop.multi>.selectize-input>div.ui-sortable-placeholder{visibility:visible !important;background:#f2f2f2 !important;background:rgba(0,0,0,0.06) !important;border:0 none !important;box-shadow:inset 0 0 12px 4px #fff}.selectize-control.plugin-drag_drop .ui-sortable-placeholder::after{content:"!";visibility:hidden}.selectize-control.plugin-drag_drop .ui-sortable-helper{box-shadow:0 2px 5px rgba(0,0,0,0.2)}.selectize-dropdown.plugin-dropdown_header .selectize-dropdown-header{position:relative;padding:6px 12px;border-bottom:1px solid #d0d0d0;background:#f8f8f8;border-radius:4px 4px 0 0}.selectize-dropdown.plugin-dropdown_header .selectize-dropdown-header-close{position:absolute;right:12px;top:50%;color:#333;opacity:.4;margin-top:-12px;line-height:20px;font-size:20px !important;text-decoration:none}.selectize-dropdown.plugin-dropdown_header .selectize-dropdown-header-close:hover{color:black}.selectize-dropdown.plugin-optgroup_columns .selectize-dropdown-content{display:flex}.selectize-dropdown.plugin-optgroup_columns .optgroup{border-right:1px solid #f2f2f2;border-top:0 none;flex-grow:1;flex-basis:0;min-width:0}.selectize-dropdown.plugin-optgroup_columns .optgroup:last-child{border-right:0 none}.selectize-dropdown.plugin-optgroup_columns .optgroup:before{display:none}.selectize-dropdown.plugin-optgroup_columns .optgroup-header{border-top:0 none}.selectize-control.plugin-remove_button .item{display:inline-flex;align-items:center;padding-right:0 !important}.selectize-control.plugin-remove_button .item .remove{color:inherit;text-decoration:none;vertical-align:middle;display:inline-block;padding:1px 5px;border-left:1px solid rgba(0,0,0,0);border-radius:0 2px 2px 0;box-sizing:border-box;margin-left:5px}.selectize-control.plugin-remove_button .item .remove:hover{background:rgba(0,0,0,0.05)}.selectize-control.plugin-remove_button .item.active .remove{border-left-color:rgba(0,0,0,0)}.selectize-control.plugin-remove_button .disabled .item .remove:hover{background:0}.selectize-control.plugin-remove_button .disabled .item .remove{border-left-color:rgba(77,77,77,0)}.selectize-control.plugin-clear_button .clear{text-decoration:none;display:flex;position:absolute;height:100%;width:25px;top:0;right:calc(12px - 5px);color:#000;opacity:.4;font-weight:bold;border:0;cursor:pointer;z-index:1;font-size:21px;justify-content:center;align-items:center}.selectize-control.plugin-clear_button .clear:hover{opacity:1}.selectize-control.plugin-clear_button.single .clear{right:calc(12px - 5px + 1.5rem)}.selectize-dropdown.plugin-auto_position.selectize-position-top{border-top:1px solid #d0d0d0;border-bottom:0 none;border-radius:3px 3px 0 0;box-shadow:0 -6px 12px rgba(0,0,0,0.18)}.selectize-control.plugin-auto_position .selectize-input.selectize-position-top.dropdown-active{border-radius:0 0 3px 3px;border-top:0 none}.selectize-control.plugin-auto_position .selectize-input.selectize-position-top.dropdown-active::before{top:0;bottom:unset}.selectize-control{position:relative}.selectize-dropdown,.selectize-input,.selectize-input input{color:#333;font-family:inherit;font-size:inherit;line-height:20px;font-smoothing:inherit}.selectize-input,.selectize-control.single .selectize-input.input-active{background:#fff;cursor:text;display:inline-block}.selectize-input{border:1px solid #ccc;padding:6px 12px;display:inline-block;width:100%;position:relative;z-index:1;box-sizing:border-box;box-shadow:none;border-radius:4px}.selectize-control.multi .selectize-input.has-items{padding:calc(6px - 1px - 0px) 12px calc(6px - 1px - 3px - 0px)}.selectize-input.full{background-color:#fff}.selectize-input.disabled,.selectize-input.disabled *{cursor:default !important}.selectize-input.focus{box-shadow:inset 0 1px 2px rgba(0,0,0,0.15)}.selectize-input.dropdown-active{border-radius:4px 4px 0 0}.selectize-input>*{vertical-align:baseline;display:inline-block;zoom:1}.selectize-control.multi .selectize-input>div{cursor:pointer;margin:0 3px 3px 0;padding:1px 5px;background:#efefef;color:#333;border:0 solid rgba(0,0,0,0)}.selectize-control.multi .selectize-input>div.active{background:#337ab7;color:#fff;border:0 solid rgba(0,0,0,0)}.selectize-control.multi .selectize-input.disabled>div,.selectize-control.multi .selectize-input.disabled>div.active{color:gray;background:white;border:0 solid rgba(77,77,77,0)}.selectize-input>input{display:inline-block !important;padding:0 !important;min-height:0 !important;max-height:none !important;max-width:100% !important;margin:0 !important;text-indent:0 !important;border:0 none !important;background:none !important;line-height:inherit !important;user-select:auto !important;box-shadow:none !important}.selectize-input>input::-ms-clear{display:none}.selectize-input>input:focus{outline:none !important}.selectize-input>input[placeholder]{box-sizing:initial}.selectize-input.has-items>input{margin:0 0 !important}.selectize-input::after{content:" ";display:block;clear:left}.selectize-input.dropdown-active::before{content:" ";display:block;position:absolute;background:#d6d6d6;height:1px;bottom:0;left:0;right:0}.selectize-dropdown{position:absolute;top:100%;left:0;width:100%;z-index:10;border:1px solid #d0d0d0;background:#fff;margin:-1px 0 0 0;border-top:0 none;box-sizing:border-box;box-shadow:0 1px 3px rgba(0,0,0,0.1);border-radius:0 0 4px 4px}.selectize-dropdown [data-selectable]{cursor:pointer;overflow:hidden}.selectize-dropdown [data-selectable] .highlight{background:rgba(255,237,40,0.4);border-radius:1px}.selectize-dropdown .option,.selectize-dropdown .optgroup-header,.selectize-dropdown .no-results,.selectize-dropdown .create{padding:3px 12px}.selectize-dropdown .option,.selectize-dropdown [data-disabled],.selectize-dropdown [data-disabled] [data-selectable].option{cursor:inherit;opacity:.5}.selectize-dropdown [data-selectable].option{opacity:1;cursor:pointer}.selectize-dropdown .optgroup:first-child .optgroup-header{border-top:0 none}.selectize-dropdown .optgroup-header{color:#777;background:#fff;cursor:default}.selectize-dropdown .active{background-color:#337ab7;color:#fff}.selectize-dropdown .active.create{color:#262626}.selectize-dropdown .selected{background-color:#337ab7;color:#fff}.selectize-dropdown .create{color:rgba(51,51,51,0.5)}.selectize-dropdown .active:not(.selected){background:#f5f5f5;color:#262626}.selectize-dropdown-content{overflow-y:auto;overflow-x:hidden;max-height:200px;overflow-scrolling:touch}.selectize-dropdown-emptyoptionlabel{text-align:center}.selectize-dropdown .spinner{display:inline-block;width:30px;height:30px;margin:3px 12px}.selectize-dropdown .spinner:after{content:" ";display:block;width:24px;height:24px;margin:3px;border-radius:50%;border:5px solid #d0d0d0;border-color:#d0d0d0 transparent #d0d0d0 transparent;animation:lds-dual-ring 1.2s linear infinite}@keyframes lds-dual-ring{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.selectize-control.single .selectize-input,.selectize-control.single .selectize-input input{cursor:pointer}.selectize-control.single .selectize-input.input-active,.selectize-control.single .selectize-input.input-active input:not(:read-only){cursor:text}.selectize-control.single .selectize-input:after{content:" ";display:block;position:absolute;top:50%;right:17px;margin-top:-3px;width:0;height:0;border-style:solid;border-width:5px 5px 0 5px;border-color:#333 transparent transparent transparent}.selectize-control.single .selectize-input.dropdown-active:after{margin-top:-4px;border-width:0 5px 5px 5px;border-color:transparent transparent #333 transparent}.selectize-control.rtl{text-align:right}.selectize-control.rtl.single .selectize-input:after{left:17px;right:auto}.selectize-control.rtl .selectize-input>input{margin:0 4px 0 -2px !important}.selectize-control .selectize-input.disabled{opacity:.5;background-color:#fff}.selectize-dropdown,.selectize-dropdown.form-control{height:auto;padding:0;margin:2px 0 0 0;z-index:1000;background:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);border-radius:4px;box-shadow:0 6px 12px rgba(0,0,0,0.175)}.selectize-dropdown .optgroup-header{font-size:12px;line-height:1.428571429}.selectize-dropdown .optgroup:first-child:before{display:none}.selectize-dropdown .optgroup:before{content:" ";display:block;height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5;margin-left:-12px;margin-right:-12px}.selectize-dropdown-content{padding:5px 0}.selectize-dropdown-emptyoptionlabel{text-align:center}.selectize-input{min-height:34px}.selectize-input.dropdown-active{border-radius:4px}.selectize-input.dropdown-active::before{display:none}.selectize-input.focus{border-color:#66afe9;outline:0;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.has-error .selectize-input{border-color:#a94442;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .selectize-input:focus{border-color:#843534;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483}.selectize-control.multi .selectize-input.has-items{padding-left:7px;padding-right:7px}.selectize-control.multi .selectize-input>div{border-radius:3px}.form-control.selectize-control{padding:0;height:auto;border:0;background:0;box-shadow:none;border-radius:0}.selectize-dropdown.plugin-auto_position.selectize-position-top{border-top:1px solid #d0d0d0;border-bottom:0 none;border-radius:3px 3px 0 0;box-shadow:0 -6px 12px rgba(0,0,0,0.18)}.selectize-control.plugin-auto_position .selectize-input.selectize-position-top.dropdown-active{border-radius:0 0 3px 3px;border-top:0 none}.selectize-control.plugin-auto_position .selectize-input.selectize-position-top.dropdown-active::before{top:0;bottom:unset}.selectize-control.plugin-clear_button .clear{text-decoration:none;display:flex;position:absolute;height:100%;width:25px;top:0;right:calc(12px - 5px);color:#000;opacity:.4;font-weight:bold;border:0;cursor:pointer;z-index:1;font-size:21px;justify-content:center;align-items:center}.selectize-control.plugin-clear_button .clear:hover{opacity:1}.selectize-control.plugin-clear_button.single .clear{right:calc(12px - 5px + 1.5rem)}.selectize-control.plugin-drag_drop.multi>.selectize-input.dragging{overflow:visible}.selectize-control.plugin-drag_drop.multi>.selectize-input>div.ui-sortable-placeholder{visibility:visible !important;background:#f2f2f2 !important;background:rgba(0,0,0,0.06) !important;border:0 none !important;box-shadow:inset 0 0 12px 4px #fff}.selectize-control.plugin-drag_drop .ui-sortable-placeholder::after{content:"!";visibility:hidden}.selectize-control.plugin-drag_drop .ui-sortable-helper{box-shadow:0 2px 5px rgba(0,0,0,0.2)}.selectize-dropdown.plugin-dropdown_header .selectize-dropdown-header{position:relative;padding:6px 12px;border-bottom:1px solid #d0d0d0;background:#f8f8f8;border-radius:4px 4px 0 0}.selectize-dropdown.plugin-dropdown_header .selectize-dropdown-header-close{position:absolute;right:12px;top:50%;color:#333;opacity:.4;margin-top:-12px;line-height:20px;font-size:20px !important;text-decoration:none}.selectize-dropdown.plugin-dropdown_header .selectize-dropdown-header-close:hover{color:black}.selectize-dropdown.plugin-optgroup_columns .selectize-dropdown-content{display:flex}.selectize-dropdown.plugin-optgroup_columns .optgroup{border-right:1px solid #f2f2f2;border-top:0 none;flex-grow:1;flex-basis:0;min-width:0}.selectize-dropdown.plugin-optgroup_columns .optgroup:last-child{border-right:0 none}.selectize-dropdown.plugin-optgroup_columns .optgroup:before{display:none}.selectize-dropdown.plugin-optgroup_columns .optgroup-header{border-top:0 none}.selectize-control.plugin-remove_button .item{display:inline-flex;align-items:center;padding-right:0 !important}.selectize-control.plugin-remove_button .item .remove{color:inherit;text-decoration:none;vertical-align:middle;display:inline-block;padding:1px 5px;border-left:1px solid rgba(0,0,0,0);border-radius:0 2px 2px 0;box-sizing:border-box;margin-left:5px}.selectize-control.plugin-remove_button .item .remove:hover{background:rgba(0,0,0,0.05)}.selectize-control.plugin-remove_button .item.active .remove{border-left-color:rgba(0,0,0,0)}.selectize-control.plugin-remove_button .disabled .item .remove:hover{background:0}.selectize-control.plugin-remove_button .disabled .item .remove{border-left-color:rgba(77,77,77,0)} \ No newline at end of file diff --git a/htdocs/js/selectize.min.js b/htdocs/js/selectize.min.js index 0a5384be6..4fb14c096 100644 --- a/htdocs/js/selectize.min.js +++ b/htdocs/js/selectize.min.js @@ -1,2 +1,32 @@ -/*! selectize.js - v0.13.5 | https://github.com/selectize/selectize.js | Apache License (v2) */ -var highlight=function($element,pattern){var regex,highlight;if("string"!=typeof pattern||pattern.length)return regex="string"==typeof pattern?new RegExp(pattern,"i"):pattern,highlight=function(node){var skip=0;if(3===node.nodeType){var spannode,match,pos=node.data.search(regex);0<=pos&&0/g,">").replace(/"/g,""")},escape_replace=function(str){return(str+"").replace(/\$/g,"$$$$")},hook={before:function(self,method,fn){var original=self[method];self[method]=function(){return fn.apply(self,arguments),original.apply(self,arguments)}},after:function(self,method,fn){var original=self[method];self[method]=function(){var result=original.apply(self,arguments);return fn.apply(self,arguments),result}}},once=function(fn){var called=!1;return function(){called||(called=!0,fn.apply(this,arguments))}},debounce=function(fn,delay){var timeout;return function(){var self=this,args=arguments;window.clearTimeout(timeout),timeout=window.setTimeout(function(){fn.apply(self,args)},delay)}},debounce_events=function(self,types,fn){var type,trigger=self.trigger,event_args={};for(type in self.trigger=function(){var type=arguments[0];if(-1===types.indexOf(type))return trigger.apply(self,arguments);event_args[type]=arguments},fn.apply(self,[]),self.trigger=trigger,event_args)event_args.hasOwnProperty(type)&&trigger.apply(self,event_args[type])},watchChildEvent=function($parent,event,selector,fn){$parent.on(event,selector,function(e){for(var child=e.target;child&&child.parentNode!==$parent[0];)child=child.parentNode;return e.currentTarget=child,fn.apply(this,[e])})},getSelection=function(input){var sel,selLen,result={};return void 0===input?console.warn("WARN getSelection cannot locate input control"):"selectionStart"in input?(result.start=input.selectionStart,result.length=input.selectionEnd-result.start):document.selection&&(input.focus(),sel=document.selection.createRange(),selLen=document.selection.createRange().text.length,sel.moveStart("character",-input.value.length),result.start=sel.text.length-selLen,result.length=selLen),result},transferStyles=function($from,$to,properties){var i,n,styles={};if(properties)for(i=0,n=properties.length;i").css({position:"absolute",width:"auto",padding:0,whiteSpace:"pre"}),$("
").css({position:"absolute",width:0,height:0,overflow:"hidden"}).append(Selectize.$testInput).appendTo("body")),Selectize.$testInput.text(str),transferStyles($parent,Selectize.$testInput,["letterSpacing","fontSize","fontFamily","fontWeight","textTransform"]),Selectize.$testInput.width()):0},autoGrow=function($input){function update(e,options){var printable,keyCode,selection;options=options||{},(e=e||window.event||{}).metaKey||e.altKey||!options.force&&!1===$input.data("grow")||(options=$input.val(),e.type&&"keydown"===e.type.toLowerCase()&&(printable=48<=(keyCode=e.keyCode)&&keyCode<=57||65<=keyCode&&keyCode<=90||96<=keyCode&&keyCode<=111||186<=keyCode&&keyCode<=222||32===keyCode,keyCode===KEY_DELETE||keyCode===KEY_BACKSPACE?(selection=getSelection($input[0])).length?options=options.substring(0,selection.start)+options.substring(selection.start+selection.length):keyCode===KEY_BACKSPACE&&selection.start?options=options.substring(0,selection.start-1)+options.substring(selection.start+1):keyCode===KEY_DELETE&&void 0!==selection.start&&(options=options.substring(0,selection.start)+options.substring(selection.start+1)):printable&&(keyCode=e.shiftKey,selection=String.fromCharCode(e.keyCode),options+=selection=keyCode?selection.toUpperCase():selection.toLowerCase())),e=(printable=$input.attr("placeholder"))?measureString(printable,$input)+4:0,(keyCode=Math.max(measureString(options,$input),e)+4)!==currentWidth&&(currentWidth=keyCode,$input.width(keyCode),$input.triggerHandler("resize")))}var currentWidth=null;$input.on("keydown keyup update blur",update),update()},domToString=function(d){var tmp=document.createElement("div");return tmp.appendChild(d.cloneNode(!0)),tmp.innerHTML},logError=function(message,options){options=options||{};console.error("Selectize: "+message),options.explanation&&(console.group&&console.group(),console.error(options.explanation),console.group&&console.groupEnd())},Selectize=function($input,settings){var i,n,input=$input[0],computedStyle=(input.selectize=this,window.getComputedStyle&&window.getComputedStyle(input,null));if(computedStyle=(computedStyle?computedStyle.getPropertyValue("direction"):input.currentStyle&&input.currentStyle.direction)||$input.parents("[dir]:first").attr("dir")||"",$.extend(this,{order:0,settings:settings,$input:$input,tabIndex:$input.attr("tabindex")||"",tagType:"select"===input.tagName.toLowerCase()?TAG_SELECT:TAG_INPUT,rtl:/rtl/i.test(computedStyle),eventNS:".selectize"+ ++Selectize.count,highlightedValue:null,isBlurring:!1,isOpen:!1,isDisabled:!1,isRequired:$input.is("[required]"),isInvalid:!1,isLocked:!1,isFocused:!1,isInputHidden:!1,isSetup:!1,isShiftDown:!1,isCmdDown:!1,isCtrlDown:!1,ignoreFocus:!1,ignoreBlur:!1,ignoreHover:!1,hasOptions:!1,currentResults:null,lastValue:"",lastValidValue:"",caretPos:0,loading:0,loadedSearches:{},$activeOption:null,$activeItems:[],optgroups:{},options:{},userOptions:{},items:[],renderCache:{},onSearchChange:null===settings.loadThrottle?this.onSearchChange:debounce(this.onSearchChange,settings.loadThrottle)}),this.sifter=new Sifter(this.options,{diacritics:settings.diacritics}),this.settings.options){for(i=0,n=this.settings.options.length;i").addClass(settings.wrapperClass).addClass(classes).addClass(inputMode),$control=$("
").addClass(settings.inputClass).addClass("items").appendTo($wrapper),$control_input=$('').appendTo($control).attr("tabindex",$input.is(":disabled")?"-1":self.tabIndex),$dropdown_parent=$(settings.dropdownParent||$wrapper),inputMode=$("
").addClass(settings.dropdownClass).addClass(inputMode).hide().appendTo($dropdown_parent),$dropdown_parent=$("
").addClass(settings.dropdownContentClass).attr("tabindex","-1").appendTo(inputMode);(inputId=$input.attr("id"))&&($control_input.attr("id",inputId+"-selectized"),$("label[for='"+inputId+"']").attr("for",inputId+"-selectized")),self.settings.copyClassesToDropdown&&inputMode.addClass(classes),$wrapper.css({width:$input[0].style.width}),self.plugins.names.length&&(inputId="plugin-"+self.plugins.names.join(" plugin-"),$wrapper.addClass(inputId),inputMode.addClass(inputId)),(null===settings.maxItems||1[data-selectable]",function(e){e.stopImmediatePropagation()}),inputMode.on("mouseenter","[data-selectable]",function(){return self.onOptionHover.apply(self,arguments)}),inputMode.on("mousedown click","[data-selectable]",function(){return self.onOptionSelect.apply(self,arguments)}),watchChildEvent($control,"mousedown","*:not(input)",function(){return self.onItemSelect.apply(self,arguments)}),autoGrow($control_input),$control.on({mousedown:function(){return self.onMouseDown.apply(self,arguments)},click:function(){return self.onClick.apply(self,arguments)}}),$control_input.on({mousedown:function(e){e.stopPropagation()},keydown:function(){return self.onKeyDown.apply(self,arguments)},keypress:function(){return self.onKeyPress.apply(self,arguments)},input:function(){return self.onInput.apply(self,arguments)},resize:function(){self.positionDropdown.apply(self,[])},blur:function(){return self.onBlur.apply(self,arguments)},focus:function(){return self.ignoreBlur=!1,self.onFocus.apply(self,arguments)},paste:function(){return self.onPaste.apply(self,arguments)}}),$document.on("keydown"+eventNS,function(e){self.isCmdDown=e[IS_MAC?"metaKey":"ctrlKey"],self.isCtrlDown=e[IS_MAC?"altKey":"ctrlKey"],self.isShiftDown=e.shiftKey}),$document.on("keyup"+eventNS,function(e){e.keyCode===KEY_CTRL&&(self.isCtrlDown=!1),e.keyCode===KEY_SHIFT&&(self.isShiftDown=!1),e.keyCode===KEY_CMD&&(self.isCmdDown=!1)}),$document.on("mousedown"+eventNS,function(e){if(self.isFocused){if(e.target===self.$dropdown[0]||e.target.parentNode===self.$dropdown[0])return!1;self.$control.has(e.target).length||e.target===self.$control[0]||self.blur(e.target)}}),$window.on(["scroll"+eventNS,"resize"+eventNS].join(" "),function(){self.isOpen&&self.positionDropdown.apply(self,arguments)}),$window.on("mousemove"+eventNS,function(){self.ignoreHover=!1}),this.revertSettings={$children:$input.children().detach(),tabindex:$input.attr("tabindex")},$input.attr("tabindex",-1).hide().after(self.$wrapper),Array.isArray(settings.items)&&(self.lastValidValue=settings.items,self.setValue(settings.items),delete settings.items),SUPPORTS_VALIDITY_API&&$input.on("invalid"+eventNS,function(e){e.preventDefault(),self.isInvalid=!0,self.refreshState()}),self.updateOriginalInput(),self.refreshItems(),self.refreshState(),self.updatePlaceholder(),self.isSetup=!0,$input.is(":disabled")&&self.disable(),self.on("change",this.onChange),$input.data("selectize",self),$input.addClass("selectized"),self.trigger("initialize"),!0===settings.preload&&self.onSearchChange("")},setupTemplates:function(){var field_label=this.settings.labelField,field_value=this.settings.valueField,field_optgroup=this.settings.optgroupLabelField;this.settings.render=$.extend({},{optgroup:function(data){return'
'+data.html+"
"},optgroup_header:function(data,escape){return'
'+escape(data[field_optgroup])+"
"},option:function(data,escape){return'
'+escape(data[field_label])+"
"},item:function(data,escape){return'
'+escape(data[field_label])+"
"},option_create:function(data,escape){return'
Add '+escape(data.input)+"
"}},this.settings.render)},setupCallbacks:function(){var key,fn,callbacks={initialize:"onInitialize",change:"onChange",item_add:"onItemAdd",item_remove:"onItemRemove",clear:"onClear",option_add:"onOptionAdd",option_remove:"onOptionRemove",option_clear:"onOptionClear",optgroup_add:"onOptionGroupAdd",optgroup_remove:"onOptionGroupRemove",optgroup_clear:"onOptionGroupClear",dropdown_open:"onDropdownOpen",dropdown_close:"onDropdownClose",type:"onType",load:"onLoad",focus:"onFocus",blur:"onBlur",dropdown_item_activate:"onDropdownItemActivate",dropdown_item_deactivate:"onDropdownItemDeactivate"};for(key in callbacks)callbacks.hasOwnProperty(key)&&(fn=this.settings[callbacks[key]])&&this.on(key,fn)},onClick:function(e){this.isFocused&&this.isOpen||(this.focus(),e.preventDefault())},onMouseDown:function(e){var self=this,defaultPrevented=e.isDefaultPrevented();$(e.target);if(self.isFocused){if(e.target!==self.$control_input[0])return"single"===self.settings.mode?self.isOpen?self.close():self.open():defaultPrevented||self.setActiveItem(null),!1}else defaultPrevented||window.setTimeout(function(){self.focus()},0)},onChange:function(){""!==this.getValue()&&(this.lastValidValue=this.getValue()),this.$input.trigger("input"),this.$input.trigger("change")},onPaste:function(e){var self=this;self.isFull()||self.isInputHidden||self.isLocked?e.preventDefault():self.settings.splitOn&&setTimeout(function(){var pastedText=self.$control_input.val();if(pastedText.match(self.settings.splitOn))for(var splitInput=pastedText.trim().split(self.settings.splitOn),i=0,n=splitInput.length;i=this.settings.maxItems},updateOriginalInput:function(opts){var i,n,options,label;if(opts=opts||{},this.tagType===TAG_SELECT){for(options=[],i=0,n=this.items.length;i'+escape_html(label)+"");options.length||this.$input.attr("multiple")||options.push(''),this.$input.html(options.join(""))}else this.$input.val(this.getValue()),this.$input.attr("value",this.$input.val());this.isSetup&&!opts.silent&&this.trigger("change",this.$input.val())},updatePlaceholder:function(){var $input;this.settings.placeholder&&($input=this.$control_input,this.items.length?$input.removeAttr("placeholder"):$input.attr("placeholder",this.settings.placeholder),$input.triggerHandler("update",{force:!0}))},open:function(){this.isLocked||this.isOpen||"multi"===this.settings.mode&&this.isFull()||(this.focus(),this.isOpen=!0,this.refreshState(),this.$dropdown.css({visibility:"hidden",display:"block"}),this.positionDropdown(),this.$dropdown.css({visibility:"visible"}),this.trigger("dropdown_open",this.$dropdown))},close:function(){var trigger=this.isOpen;"single"===this.settings.mode&&this.items.length&&(this.hideInput(),this.isBlurring&&this.$control_input.blur()),this.isOpen=!1,this.$dropdown.hide(),this.setActiveOption(null),this.refreshState(),trigger&&this.trigger("dropdown_close",this.$dropdown)},positionDropdown:function(){var $control=this.$control,offset="body"===this.settings.dropdownParent?$control.offset():$control.position();offset.top+=$control.outerHeight(!0),this.$dropdown.css({width:$control[0].getBoundingClientRect().width,top:offset.top,left:offset.left})},clear:function(silent){this.items.length&&(this.$control.children(":not(input)").remove(),this.items=[],this.lastQuery=null,this.setCaret(0),this.setActiveItem(null),this.updatePlaceholder(),this.updateOriginalInput({silent:silent}),this.refreshState(),this.showInput(),this.trigger("clear"))},insertAtCaret:function($el){var caret=Math.min(this.caretPos,this.items.length),$el=$el[0],target=this.buffer||this.$control[0];0===caret?target.insertBefore($el,target.firstChild):target.insertBefore($el,target.childNodes[caret]),this.setCaret(caret+1)},deleteSelection:function(e){var i,n,values,option_select,$tail,direction=e&&e.keyCode===KEY_BACKSPACE?-1:1,selection=getSelection(this.$control_input[0]);if(this.$activeOption&&!this.settings.hideSelected&&(option_select=("string"==typeof this.settings.deselectBehavior&&"top"===this.settings.deselectBehavior?this.getFirstOption():this.getAdjacentOption(this.$activeOption,-1)).attr("data-value")),values=[],this.$activeItems.length){for($tail=this.$control.children(".active:"+(0'+settings.emptyOptionLabel+""+input_html)),("select"===tag_name?init_select:function($input,settings_element){var i,n,values,option,data_raw=$input.attr(attr_data);if(data_raw)for(settings_element.options=JSON.parse(data_raw),i=0,n=settings_element.options.length;iwindow.innerHeight?POSITION.top:POSITION.bottom,styles={width:$control.outerWidth(),left:offset.left};dropdownHeight===POSITION.top?(Object.assign(styles,{bottom:offset.top,top:"unset",margin:"0 0 5px 0"}),this.$dropdown.addClass("selectize-position-top")):(Object.assign(styles,{top:offset.top,bottom:"unset",margin:"5px 0 0 0"}),this.$dropdown.removeClass("selectize-position-top")),this.$dropdown.css(styles)}}),Selectize.define("auto_select_on_type",function(options){var originalBlur,self=this;self.onBlur=(originalBlur=self.onBlur,function(e){var $matchedItem=self.getFirstItemMatchedByTextContent(self.lastValue,!0);return void 0!==$matchedItem.attr("data-value")&&self.getValue()!==$matchedItem.attr("data-value")&&self.setValue($matchedItem.attr("data-value")),originalBlur.apply(this,arguments)})}),Selectize.define("autofill_disable",function(options){var original,self=this;self.setup=(original=self.setup,function(){original.apply(self,arguments),self.$control_input.attr({autocomplete:"new-password",autofill:"no"})})}),Selectize.define("drag_drop",function(options){if(!$.fn.sortable)throw new Error('The "drag_drop" plugin requires jQuery UI "sortable".');var self,original;"multi"===this.settings.mode&&((self=this).lock=(original=self.lock,function(){var sortable=self.$control.data("sortable");return sortable&&sortable.disable(),original.apply(self,arguments)}),self.unlock=function(){var original=self.unlock;return function(){var sortable=self.$control.data("sortable");return sortable&&sortable.enable(),original.apply(self,arguments)}}(),self.setup=function(){var original=self.setup;return function(){original.apply(this,arguments);var $control=self.$control.sortable({items:"[data-value]",forcePlaceholderSize:!0,disabled:self.isLocked,start:function(e,ui){ui.placeholder.css("width",ui.helper.css("width")),$control.addClass("dragging")},stop:function(){$control.removeClass("dragging");var active=self.$activeItems?self.$activeItems.slice():null,values=[];$control.children("[data-value]").each(function(){values.push($(this).attr("data-value"))}),self.isFocused=!1,self.setValue(values),self.isFocused=!0,self.setActiveItem(active),self.positionDropdown()}})}}())}),Selectize.define("dropdown_header",function(options){var original,self=this;options=$.extend({title:"Untitled",headerClass:"selectize-dropdown-header",titleRowClass:"selectize-dropdown-header-title",labelClass:"selectize-dropdown-header-label",closeClass:"selectize-dropdown-header-close",html:function(data){return'
'+data.title+'×
'}},options),self.setup=(original=self.setup,function(){original.apply(self,arguments),self.$dropdown_header=$(options.html(options)),self.$dropdown.prepend(self.$dropdown_header)})}),Selectize.define("optgroup_columns",function(options){function equalizeSizes(){var i,height_max,width,width_parent,$optgroups=$("[data-group]",self.$dropdown_content),n=$optgroups.length;if(n&&self.$dropdown_content.width()){if(options.equalizeHeight){for(i=height_max=0;i
',div=div.firstChild,doc.body.appendChild(div),width=getScrollbarWidth.width=div.offsetWidth-div.clientWidth,doc.body.removeChild(div)),width});(options.equalizeHeight||options.equalizeWidth)&&(hook.after(this,"positionDropdown",equalizeSizes),hook.after(this,"refreshOptions",equalizeSizes))}),Selectize.define("remove_button",function(options){options=$.extend({label:"×",title:"Remove",className:"remove",append:!0},options),("single"===this.settings.mode?function(thisRef,options){options.className="remove-single";var original,self=thisRef,html=''+options.label+"";thisRef.setup=(original=self.setup,function(){var id,render_item;options.append&&(id=$(self.$input.context).attr("id"),$("#"+id),render_item=self.settings.render.item,self.settings.render.item=function(data){return html_container=render_item.apply(thisRef,arguments),html_element=html,$("").append(html_container).append(html_element);var html_container,html_element}),original.apply(thisRef,arguments),thisRef.$control.on("click","."+options.className,function(e){e.preventDefault(),self.isLocked||self.clear()})})}:function(thisRef,options){var original,self=thisRef,html=''+options.label+"";thisRef.setup=(original=self.setup,function(){var render_item;options.append&&(render_item=self.settings.render.item,self.settings.render.item=function(data){return html_container=render_item.apply(thisRef,arguments),html_element=html,pos=html_container.search(/(<\/[^>]+>\s*)$/),html_container.substring(0,pos)+html_element+html_container.substring(pos);var html_container,html_element,pos}),original.apply(thisRef,arguments),thisRef.$control.on("click","."+options.className,function(e){if(e.preventDefault(),!self.isLocked)return e=$(e.currentTarget).parent(),self.setActiveItem(e),self.deleteSelection()&&self.setCaret(self.items.length),!1})})})(this,options)}),Selectize.define("restore_on_backspace",function(options){var original,self=this;options.text=options.text||function(option){return option[this.settings.labelField]},this.onKeyDown=(original=self.onKeyDown,function(e){var index;return e.keyCode===KEY_BACKSPACE&&""===this.$control_input.val()&&!this.$activeItems.length&&0<=(index=this.caretPos-1)&&index"+($items.length-limit)+""))}}}(),this.onFocus=function(){const original=self.onFocus;return function(e){if(original.apply(this,e),e){const $control=this.$control,$items=$control.find(".item");$items.show(),$control.find("span").remove()}}}()}); \ No newline at end of file +/** + * Selectize (v0.15.2) + * https://selectize.dev + * + * Copyright (c) 2013-2015 Brian Reavis & contributors + * Copyright (c) 2020-2022 Selectize Team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * @author Brian Reavis + * @author Ris Adams + */ +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define(['jquery'], factory); + } else if (typeof module === 'object' && typeof module.exports === 'object') { + module.exports = factory(require('jquery')); + } else { + root.Selectize = factory(root.jQuery); + } +}(this, function ($) { + 'use strict'; +var highlight=function(t,e){var r,a;if("string"!=typeof e||e.length)return r="string"==typeof e?new RegExp(e,"i"):e,a=function(t){var e=0;if(3===t.nodeType){var n,i,o=t.data.search(r);0<=o&&0/g,">").replace(/"/g,""")},escape_replace=function(t){return(t+"").replace(/\$/g,"$$$$")},hook={before:function(t,e,n){var i=t[e];t[e]=function(){return n.apply(t,arguments),i.apply(t,arguments)}},after:function(e,t,n){var i=e[t];e[t]=function(){var t=i.apply(e,arguments);return n.apply(e,arguments),t}}},once=function(t){var e=!1;return function(){e||(e=!0,t.apply(this,arguments))}},debounce=function(n,i){var o;return function(){var t=this,e=arguments;window.clearTimeout(o),o=window.setTimeout(function(){n.apply(t,e)},i)}},debounce_events=function(e,n,t){var i,o=e.trigger,s={};for(i in e.trigger=function(){var t=arguments[0];if(-1===n.indexOf(t))return o.apply(e,arguments);s[t]=arguments},t.apply(e,[]),e.trigger=o,s)s.hasOwnProperty(i)&&o.apply(e,s[i])},watchChildEvent=function(n,t,e,i){n.on(t,e,function(t){for(var e=t.target;e&&e.parentNode!==n[0];)e=e.parentNode;return t.currentTarget=e,i.apply(this,[t])})},getInputSelection=function(t){var e,n,i={};return void 0===t?console.warn("WARN getInputSelection cannot locate input control"):"selectionStart"in t?(i.start=t.selectionStart,i.length=t.selectionEnd-i.start):document.selection&&(t.focus(),e=document.selection.createRange(),n=document.selection.createRange().text.length,e.moveStart("character",-t.value.length),i.start=e.text.length-n,i.length=n),i},transferStyles=function(t,e,n){var i,o,s={};if(n)for(i=0,o=n.length;i").css({position:"absolute",width:"auto",padding:0,whiteSpace:"pre"}),$("
").css({position:"absolute",width:0,height:0,overflow:"hidden"}).append(Selectize.$testInput).appendTo("body")),Selectize.$testInput.text(t),transferStyles(e,Selectize.$testInput,["letterSpacing","fontSize","fontFamily","fontWeight","textTransform"]),Selectize.$testInput.width()):0},autoGrow=function(s){function t(t,e){var n,i,o;e=e||{},(t=t||window.event||{}).metaKey||t.altKey||!e.force&&!1===s.data("grow")||(e=s.val(),t.type&&"keydown"===t.type.toLowerCase()&&(n=48<=(i=t.keyCode)&&i<=57||65<=i&&i<=90||96<=i&&i<=111||186<=i&&i<=222||32===i,i===KEY_DELETE||i===KEY_BACKSPACE?(o=getInputSelection(s[0])).length?e=e.substring(0,o.start)+e.substring(o.start+o.length):i===KEY_BACKSPACE&&o.start?e=e.substring(0,o.start-1)+e.substring(o.start+1):i===KEY_DELETE&&void 0!==o.start&&(e=e.substring(0,o.start)+e.substring(o.start+1)):n&&(i=t.shiftKey,o=String.fromCharCode(t.keyCode),e+=o=i?o.toUpperCase():o.toLowerCase())),t=(n=s.attr("placeholder"))?measureString(n,s)+4:0,(i=Math.max(measureString(e,s),t)+4)===r)||(r=i,s.width(i),s.triggerHandler("resize"))}var r=null;s.on("keydown keyup update blur",t),t()},domToString=function(t){var e=document.createElement("div");return e.appendChild(t.cloneNode(!0)),e.innerHTML},logError=function(t,e){e=e||{};console.error("Selectize: "+t),e.explanation&&(console.group&&console.group(),console.error(e.explanation),console.group)&&console.groupEnd()},isJSON=function(t){try{JSON.parse(str)}catch(t){return!1}return!0},Selectize=function(t,e){var n,i,o=this,s=t[0],r=(s.selectize=o,window.getComputedStyle&&window.getComputedStyle(s,null));if(r=(r?r.getPropertyValue("direction"):s.currentStyle&&s.currentStyle.direction)||t.parents("[dir]:first").attr("dir")||"",$.extend(o,{order:0,settings:e,$input:t,tabIndex:t.attr("tabindex")||"",tagType:"select"===s.tagName.toLowerCase()?TAG_SELECT:TAG_INPUT,rtl:/rtl/i.test(r),eventNS:".selectize"+ ++Selectize.count,highlightedValue:null,isBlurring:!1,isOpen:!1,isDisabled:!1,isRequired:t.is("[required]"),isInvalid:!1,isLocked:!1,isFocused:!1,isInputHidden:!1,isSetup:!1,isShiftDown:!1,isCmdDown:!1,isCtrlDown:!1,ignoreFocus:!1,ignoreBlur:!1,ignoreHover:!1,hasOptions:!1,currentResults:null,lastValue:"",lastValidValue:"",lastOpenTarget:!1,caretPos:0,loading:0,loadedSearches:{},isDropdownClosing:!1,$activeOption:null,$activeItems:[],optgroups:{},options:{},userOptions:{},items:[],renderCache:{},onSearchChange:null===e.loadThrottle?o.onSearchChange:debounce(o.onSearchChange,e.loadThrottle)}),o.sifter=new Sifter(this.options,{diacritics:e.diacritics}),o.settings.options){for(n=0,i=o.settings.options.length;n").addClass(t.wrapperClass).addClass(a+" selectize-control").addClass(r),p=$("
").addClass(t.inputClass+" selectize-input items").appendTo(l),c=$('').appendTo(p).attr("tabindex",s.is(":disabled")?"-1":e.tabIndex),u=$(t.dropdownParent||l),r=$("
").addClass(t.dropdownClass).addClass(r+" selectize-dropdown").hide().appendTo(u),u=$("
").addClass(t.dropdownContentClass+" selectize-dropdown-content").attr("tabindex","-1").appendTo(r),d=((d=s.attr("id"))&&(c.attr("id",d+"-selectized"),$("label[for='"+d+"']").attr("for",d+"-selectized")),e.settings.copyClassesToDropdown&&r.addClass(a),l.css({width:s[0].style.width}),e.plugins.names.length&&(d="plugin-"+e.plugins.names.join(" plugin-"),l.addClass(d),r.addClass(d)),(null===t.maxItems||1[data-selectable]",function(t){t.stopImmediatePropagation()}),r.on("mouseenter","[data-selectable]",function(){return e.onOptionHover.apply(e,arguments)}),r.on("mouseup click","[data-selectable]",function(){return e.onOptionSelect.apply(e,arguments)}),watchChildEvent(p,"mouseup","*:not(input)",function(){return e.onItemSelect.apply(e,arguments)}),autoGrow(c),p.on({mousedown:function(){return e.onMouseDown.apply(e,arguments)},click:function(){return e.onClick.apply(e,arguments)}}),c.on({mousedown:function(t){""===e.$control_input.val()&&!e.settings.openOnFocus||t.stopPropagation()},keydown:function(){return e.onKeyDown.apply(e,arguments)},keypress:function(){return e.onKeyPress.apply(e,arguments)},input:function(){return e.onInput.apply(e,arguments)},resize:function(){e.positionDropdown.apply(e,[])},focus:function(){return e.ignoreBlur=!1,e.onFocus.apply(e,arguments)},paste:function(){return e.onPaste.apply(e,arguments)}}),o.on("keydown"+n,function(t){e.isCmdDown=t[IS_MAC?"metaKey":"ctrlKey"],e.isCtrlDown=t[IS_MAC?"altKey":"ctrlKey"],e.isShiftDown=t.shiftKey}),o.on("keyup"+n,function(t){t.keyCode===KEY_CTRL&&(e.isCtrlDown=!1),t.keyCode===KEY_SHIFT&&(e.isShiftDown=!1),t.keyCode===KEY_CMD&&(e.isCmdDown=!1)}),o.on("mousedown"+n,function(t){if(e.isFocused){if(t.target===e.$dropdown[0]||t.target.parentNode===e.$dropdown[0])return!1;e.$dropdown.has(t.target).length||t.target===e.$control[0]||e.blur(t.target)}}),i.on(["scroll"+n,"resize"+n].join(" "),function(){e.isOpen&&e.positionDropdown.apply(e,arguments)}),i.on("mousemove"+n,function(){e.ignoreHover=e.settings.ignoreHover}),$("
")),a=s.children().detach();s.replaceWith(d),d.replaceWith(s),this.revertSettings={$children:a,tabindex:s.attr("tabindex")},s.attr("tabindex",-1).hide().after(e.$wrapper),Array.isArray(t.items)&&(e.lastValidValue=t.items,e.setValue(t.items),delete t.items),SUPPORTS_VALIDITY_API&&s.on("invalid"+n,function(t){t.preventDefault(),e.isInvalid=!0,e.refreshState()}),e.updateOriginalInput(),e.refreshItems(),e.refreshState(),e.updatePlaceholder(),e.isSetup=!0,s.is(":disabled")&&e.disable(),e.on("change",this.onChange),s.data("selectize",e),s.addClass("selectized"),e.trigger("initialize"),!0===t.preload&&e.onSearchChange("")},setupTemplates:function(){var t=this,i=t.settings.labelField,o=t.settings.valueField,n=t.settings.optgroupLabelField;t.settings.render=$.extend({},{optgroup:function(t){return'
'+t.html+"
"},optgroup_header:function(t,e){return'
'+e(t[n])+"
"},option:function(t,e){var n=t.classes?" "+t.classes:"";return n+=""===t[o]?" selectize-dropdown-emptyoptionlabel":"","'+e(t[i])+"
"},item:function(t,e){return'
'+e(t[i])+"
"},option_create:function(t,e){return'
Add '+e(t.input)+"
"}},t.settings.render)},setupCallbacks:function(){var t,e,n={initialize:"onInitialize",change:"onChange",item_add:"onItemAdd",item_remove:"onItemRemove",clear:"onClear",option_add:"onOptionAdd",option_remove:"onOptionRemove",option_clear:"onOptionClear",optgroup_add:"onOptionGroupAdd",optgroup_remove:"onOptionGroupRemove",optgroup_clear:"onOptionGroupClear",dropdown_open:"onDropdownOpen",dropdown_close:"onDropdownClose",type:"onType",load:"onLoad",focus:"onFocus",blur:"onBlur",dropdown_item_activate:"onDropdownItemActivate",dropdown_item_deactivate:"onDropdownItemDeactivate"};for(t in n)n.hasOwnProperty(t)&&(e=this.settings[n[t]])&&this.on(t,e)},onClick:function(t){this.isDropdownClosing||this.isFocused&&this.isOpen||(this.focus(),t.preventDefault())},onMouseDown:function(t){var e=this,n=t.isDefaultPrevented();$(t.target);if(e.isFocused||n||window.setTimeout(function(){e.focus()},0),t.target!==e.$control_input[0]||""===e.$control_input.val())return"single"===e.settings.mode?e.isOpen?e.close():e.open():(n||e.setActiveItem(null),e.settings.openOnFocus||(e.isOpen&&t.target===e.lastOpenTarget?(e.close(),e.lastOpenTarget=!1):(e.isOpen||(e.refreshOptions(),e.open()),e.lastOpenTarget=t.target))),!1},onChange:function(){""!==this.getValue()&&(this.lastValidValue=this.getValue()),this.$input.trigger("input"),this.$input.trigger("change")},onPaste:function(t){var o=this;o.isFull()||o.isInputHidden||o.isLocked?t.preventDefault():o.settings.splitOn&&setTimeout(function(){var t=o.$control_input.val();if(t.match(o.settings.splitOn))for(var e=t.trim().split(o.settings.splitOn),n=0,i=e.length;n=this.settings.maxItems},updateOriginalInput:function(t){var e,n,i,o,s,r,a=this;t=t||{},a.tagType===TAG_SELECT?(o=a.$input.find("option"),e=[],n=[],i=[],r=[],o.get().forEach(function(t){e.push(t.value)}),a.items.forEach(function(t){s=a.options[t][a.settings.labelField]||"",r.push(t),-1==e.indexOf(t)&&n.push('")}),i=e.filter(function(t){return r.indexOf(t)<0}).map(function(t){return'option[value="'+t+'"]'}),e.length-i.length+n.length!==0||a.$input.attr("multiple")||n.push(''),a.$input.find(i.join(", ")).remove(),a.$input.append(n.join(""))):(a.$input.val(a.getValue()),a.$input.attr("value",a.$input.val())),a.isSetup&&!t.silent&&a.trigger("change",a.$input.val())},updatePlaceholder:function(){var t;this.settings.placeholder&&(t=this.$control_input,this.items.length?t.removeAttr("placeholder"):t.attr("placeholder",this.settings.placeholder),t.triggerHandler("update",{force:!0}))},open:function(){var t=this;t.isLocked||t.isOpen||"multi"===t.settings.mode&&t.isFull()||(t.focus(),t.isOpen=!0,t.refreshState(),t.$dropdown.css({visibility:"hidden",display:"block"}),t.setupDropdownHeight(),t.positionDropdown(),t.$dropdown.css({visibility:"visible"}),t.trigger("dropdown_open",t.$dropdown))},close:function(){var t=this,e=t.isOpen;"single"===t.settings.mode&&t.items.length&&(t.hideInput(),t.isBlurring)&&t.$control_input[0].blur(),t.isOpen=!1,t.$dropdown.hide(),t.setActiveOption(null),t.refreshState(),e&&t.trigger("dropdown_close",t.$dropdown)},positionDropdown:function(){var t=this.$control,e="body"===this.settings.dropdownParent?t.offset():t.position(),t=(e.top+=t.outerHeight(!0),t[0].getBoundingClientRect().width);this.settings.minWidth&&this.settings.minWidth>t&&(t=this.settings.minWidth),this.$dropdown.css({width:t,top:e.top,left:e.left})},setupDropdownHeight:function(){if("object"==typeof this.settings.dropdownSize&&"auto"!==this.settings.dropdownSize.sizeType){var t=this.settings.dropdownSize.sizeValue;if("numberItems"===this.settings.dropdownSize.sizeType){for(var e=this.$dropdown_content.find("*").not(".optgroup, .highlight").not(this.settings.ignoreOnDropwdownHeight),n=0,i=0,o=0,s=0,r=0;r'+i+""+l)),{placeholder:n,options:[],optgroups:[],items:[]});if("select"===e)u(t,i);else{var o,s,r,a,l=t,p=i,n=l.attr(m);if(n)for(p.options=JSON.parse(n),o=0,s=p.options.length;owindow.innerHeight?o.top:o.bottom,i={width:t.outerWidth(),left:e.left};n===o.top?(n={bottom:e.top,top:"unset"},"body"===this.settings.dropdownParent&&(n.top=e.top-this.$dropdown.outerHeight(!0)-t.outerHeight(!0),n.bottom="unset"),Object.assign(i,n),this.$dropdown.addClass("selectize-position-top"),this.$control.addClass("selectize-position-top")):(Object.assign(i,{top:e.top,bottom:"unset"}),this.$dropdown.removeClass("selectize-position-top"),this.$control.removeClass("selectize-position-top")),this.$dropdown.css(i)}}),Selectize.define("auto_select_on_type",function(t){var n,i=this;i.onBlur=(n=i.onBlur,function(t){var e=i.getFirstItemMatchedByTextContent(i.lastValue,!0);return void 0!==e.attr("data-value")&&i.getValue()!==e.attr("data-value")&&i.setValue(e.attr("data-value")),n.apply(this,arguments)})}),Selectize.define("autofill_disable",function(t){var e,n=this;n.setup=(e=n.setup,function(){e.apply(n,arguments),n.$control_input.attr({autocomplete:"new-password",autofill:"no"})})}),Selectize.define("clear_button",function(e){var t,n=this;e=$.extend({title:"Clear",className:"clear",label:"×",html:function(t){return' '+t.label+""}},e),n.setup=(t=n.setup,function(){t.apply(n,arguments),n.$button_clear=$(e.html(e)),"single"===n.settings.mode&&n.$wrapper.addClass("single"),n.$wrapper.append(n.$button_clear),""!==n.getValue()&&0!==n.getValue().length||n.$wrapper.find("."+e.className).css("display","none"),n.on("change",function(){""===n.getValue()||0===n.getValue().length?n.$wrapper.find("."+e.className).css("display","none"):n.$wrapper.find("."+e.className).css("display","")}),n.$wrapper.on("click","."+e.className,function(t){t.preventDefault(),t.stopImmediatePropagation(),t.stopPropagation(),n.isLocked||(n.clear(),n.$wrapper.find("."+e.className).css("display","none"))})})}),Selectize.define("drag_drop",function(t){if(!$.fn.sortable)throw new Error('The "drag_drop" plugin requires jQuery UI "sortable".');var i,e,n,o;"multi"===this.settings.mode&&((i=this).lock=(e=i.lock,function(){var t=i.$control.data("sortable");return t&&t.disable(),e.apply(i,arguments)}),i.unlock=(n=i.unlock,function(){var t=i.$control.data("sortable");return t&&t.enable(),n.apply(i,arguments)}),i.setup=(o=i.setup,function(){o.apply(this,arguments);var n=i.$control.sortable({items:"[data-value]",forcePlaceholderSize:!0,disabled:i.isLocked,start:function(t,e){e.placeholder.css("width",e.helper.css("width")),n.addClass("dragging")},stop:function(){n.removeClass("dragging");var t=i.$activeItems?i.$activeItems.slice():null,e=[];n.children("[data-value]").each(function(){e.push($(this).attr("data-value"))}),i.isFocused=!1,i.setValue(e),i.isFocused=!0,i.setActiveItem(t),i.positionDropdown()}})}))}),Selectize.define("dropdown_header",function(t){var e,n=this;t=$.extend({title:"Untitled",headerClass:"selectize-dropdown-header",titleRowClass:"selectize-dropdown-header-title",labelClass:"selectize-dropdown-header-label",closeClass:"selectize-dropdown-header-close",html:function(t){return'
'+t.title+'×
'}},t),n.setup=(e=n.setup,function(){e.apply(n,arguments),n.$dropdown_header=$(t.html(t)),n.$dropdown.prepend(n.$dropdown_header),n.$dropdown_header.find("."+t.closeClass).on("click",function(){n.close()})})}),Selectize.define("optgroup_columns",function(r){function t(){var t,e,n,i,o=$("[data-group]",a.$dropdown_content),s=o.length;if(s&&a.$dropdown_content.width()){if(r.equalizeHeight){for(t=e=0;t
',t=t.firstChild,n.body.appendChild(t),e=l.width=t.offsetWidth-t.clientWidth,n.body.removeChild(t)),e});(r.equalizeHeight||r.equalizeWidth)&&(hook.after(this,"positionDropdown",t),hook.after(this,"refreshOptions",t))}),Selectize.define("remove_button",function(t){var s,e,n,i,r;"single"!==this.settings.mode&&(t=$.extend({label:"×",title:"Remove",className:"remove",append:!0},t),i=s=this,r=''+e.label+"",s.setup=(n=i.setup,function(){var o;e.append&&(o=i.settings.render.item,i.settings.render.item=function(t){return e=o.apply(s,arguments),n=r,i=e.search(/(<\/[^>]+>\s*)$/),e.substring(0,i)+n+e.substring(i);var e,n,i}),n.apply(s,arguments),s.$control.on("click","."+e.className,function(t){if(t.preventDefault(),!i.isLocked)return t=$(t.currentTarget).parent(),i.setActiveItem(t),i.deleteSelection()&&i.setCaret(i.items.length),!1})}))}),Selectize.define("restore_on_backspace",function(n){var i,t=this;n.text=n.text||function(t){return t[this.settings.labelField]},this.onKeyDown=(i=t.onKeyDown,function(t){var e;if(!(t.keyCode===KEY_BACKSPACE&&""===this.$control_input.val()&&!this.$activeItems.length&&0<=(e=this.caretPos-1)&&e"+(e.length-n)+""))}}}(),this.onFocus=function(){const e=t.onFocus;return function(t){e.apply(this,t),t&&((t=this.$control).find(".item").show(),t.find("span").remove())}}()}); + return Selectize; +})); From b3125aeadf7b0ae90548ed0a3d8e58852ddbedf7 Mon Sep 17 00:00:00 2001 From: Franciszek Stachura Date: Sat, 11 Apr 2026 22:11:22 +0200 Subject: [PATCH 12/15] filters: Add autocomplete to labels filter --- .../templates/patchwork/partials/filters.html | 44 +++++++++++++++++++ patchwork/views/__init__.py | 7 ++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/patchwork/templates/patchwork/partials/filters.html b/patchwork/templates/patchwork/partials/filters.html index 1175af147..8bad9d4e2 100644 --- a/patchwork/templates/patchwork/partials/filters.html +++ b/patchwork/templates/patchwork/partials/filters.html @@ -35,6 +35,26 @@ })(); }); +// Submit form on shift+enter or ctrl+enter +Selectize.define('mod_enter_key_submit', function (options) { + var self = this; + + this.onKeyDown = (function (e) { + var original = self.onKeyDown; + + return function (e) { + var wasOpened = this.isOpen; + + if (e.keyCode === 13 && (e.shiftKey || e.ctrlKey)) { + self.trigger('submit'); + return; + } + + original.apply(this, arguments); + }; + })(); +}); + $(document).ready(function() { $('#submitter_input').selectize({ plugins: ['enter_key_submit'], @@ -76,6 +96,30 @@ }); } }); + + $('#labels_input').selectize({ + plugins: ['mod_enter_key_submit'], + maxItems: null, + persist: false, + hideSelected: true, + // Search for options that begin with a minus sign + respect_word_boundaries: false, + delimiter: ' ', + options: [ +{% for label in project_labels %} + { + value: "{{ label.name }}", + text: "{{ label.name }}", + }, +{% endfor %} +{% for label in project_labels %} + { + value: "-{{ label.name }}", + text: "-{{ label.name }}", + }, +{% endfor %} + ], + }); }); diff --git a/patchwork/views/__init__.py b/patchwork/views/__init__.py index 2685d2a59..c3f40b921 100644 --- a/patchwork/views/__init__.py +++ b/patchwork/views/__init__.py @@ -7,7 +7,7 @@ from django.contrib import messages from django.shortcuts import get_object_or_404 -from django.db.models import Prefetch +from django.db.models import Prefetch, Q from patchwork.filters import Filters from patchwork.forms import CreateBundleForm @@ -189,10 +189,15 @@ def generic_list( if not filter_settings: filter_settings = [] + project_labels = Label.objects.filter( + Q(project=project) | Q(project__isnull=True) + ).all() + filters = Filters(request) context = { 'project': project, 'projects': Project.objects.all(), + 'project_labels': project_labels, 'filters': filters, } From f8e2d09d28dbc02202ca72138a974be822feb798 Mon Sep 17 00:00:00 2001 From: Franciszek Stachura Date: Sat, 11 Apr 2026 22:11:22 +0200 Subject: [PATCH 13/15] forms: Selectize label input in update forms Add autocomplete to labels field of update form in the patch details view (only visible for users with project admin rights). --- htdocs/css/style.css | 8 ++++++++ patchwork/forms.py | 6 ++++++ .../templates/patchwork/partials/patch-forms.html | 13 +++++++++++++ 3 files changed, 27 insertions(+) diff --git a/htdocs/css/style.css b/htdocs/css/style.css index 268a8c372..db3a066cf 100644 --- a/htdocs/css/style.css +++ b/htdocs/css/style.css @@ -471,6 +471,14 @@ select[class^=change-property-], .archive-patch-select, .add-bundle { margin: 0px 4px 0px 4px; } +.labels-field { + padding: 4px; + margin-right: 8px; + box-sizing: border-box; + border-radius: 4px; + min-width: 200px; +} + .patch-form-submit { font-weight: bold; padding: 4px; diff --git a/patchwork/forms.py b/patchwork/forms.py index 19d19d643..394812835 100644 --- a/patchwork/forms.py +++ b/patchwork/forms.py @@ -150,6 +150,9 @@ def __init__(self, instance=None, project=None, *args, **kwargs): Q(project=project) | Q(project=None) ), required=False, + widget=forms.SelectMultiple( + attrs={'class': 'labels-field', 'placeholder': 'Labels'} + ), ) class Meta: @@ -234,6 +237,9 @@ def __init__(self, project, *args, **kwargs): queryset=Label.objects.filter( Q(project=project) | Q(project=None) ), + widget=forms.SelectMultiple( + attrs={'class': 'labels-field', 'placeholder': 'Labels to add'} + ), required=False, ) self.fields['state'] = OptionalModelChoiceField( diff --git a/patchwork/templates/patchwork/partials/patch-forms.html b/patchwork/templates/patchwork/partials/patch-forms.html index 2dee82302..1722a9316 100644 --- a/patchwork/templates/patchwork/partials/patch-forms.html +++ b/patchwork/templates/patchwork/partials/patch-forms.html @@ -1,3 +1,16 @@ +{% load static %} + + +
{% if patch_form %}
From 7f5107e1a261cd250e66c09c5af8eb75fcb6e670 Mon Sep 17 00:00:00 2001 From: Franciszek Stachura Date: Fri, 3 Apr 2026 19:48:18 +0200 Subject: [PATCH 14/15] management: Add relabel command The command re-parses subject headers of patches from given projects and adds label information to the database. The main usecase is to refresh label information on existing patches after adding a new label to the project. --- docs/deployment/management.rst | 27 +++++++++ patchwork/management/commands/relabel.py | 75 ++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 patchwork/management/commands/relabel.py diff --git a/docs/deployment/management.rst b/docs/deployment/management.rst index dcee9ff51..dcbbf2194 100644 --- a/docs/deployment/management.rst +++ b/docs/deployment/management.rst @@ -192,3 +192,30 @@ patches for these new tags. .. option:: patch_id a patch ID number. If not supplied, all patches will be updated. + +.. _command-relabel: + +relabel +~~~~~~~ + +.. program:: manage.py relabel + +Relabel patches based on the subject line. + +.. code-block:: shell + + ./manage.py relabel [PROJECT [PROJECT...]] + +Patchwork extracts labels from square brackets at the beginning of the subject +line, eg. ``[label1, label2] Patch title``. + +Only labels created in the admin interface will be recognized. +The script prioritizes project-specific labels if both project-specific and +general labels with the same name exist. + +The script will not remove labels manually added to patches, but will re-add +labels manually removed from patches. + +.. option:: PROJECT + + list of IDs of projects to update. Relabels all projects if none specified. diff --git a/patchwork/management/commands/relabel.py b/patchwork/management/commands/relabel.py new file mode 100644 index 000000000..954f2cd3c --- /dev/null +++ b/patchwork/management/commands/relabel.py @@ -0,0 +1,75 @@ +# Patchwork - automated patch tracking system +# Copyright (C) 2026, The Linux Foundation +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from django.core.management.base import BaseCommand + +from email.parser import HeaderParser + +from patchwork.models import Label, Patch +from patchwork.parser import clean_subject + + +class Command(BaseCommand): + help = ( + 'Update labels for existing patches based on the subject line. ' + 'NOTE: Labels have to be created in the admin interface first.' + ) + + def add_arguments(self, parser): + parser.add_argument( + 'projects', + metavar='PROJECT', + nargs='*', + help='list of listIDs of projects to be relabeld', + ) + + def handle(self, *args, **options): + query = Patch.objects.all().only('id', 'headers', 'project_id') + labels = { + (label.name, label.project_id): label + for label in Label.objects.all() + } + labels_to_add = [] + + if options['projects']: + query = query.filter(project__listid__in=options['projects']) + + count = query.count() + + for i, patch in enumerate(query.iterator(chunk_size=1000)): + parser = HeaderParser() + headers = parser.parsestr(patch.headers) + subject = headers['Subject'] + if subject is None: + continue + + _, prefixes = clean_subject(subject) + + for prefix in set(prefixes): + label = labels.get((prefix, patch.project_id)) + + if label is None: + label = labels.get((prefix, None)) + + if label is not None: + labels_to_add.append( + Patch.labels.through( + patch_id=patch.id, label_id=label.id + ) + ) + + if (i % 100) == 0: + self.stdout.write('%06d/%06d\r' % (i, count), ending='') + self.stdout.flush() + + if len(labels_to_add) >= 1000: + Patch.labels.through.objects.bulk_create(labels_to_add) + labels_to_add = [] + + if len(labels_to_add) > 0: + Patch.labels.through.objects.bulk_create(labels_to_add) + labels_to_add = [] + + self.stdout.write('\ndone') From cc81c1599bb751b3f727eabc40fc336b0e3a5202 Mon Sep 17 00:00:00 2001 From: Franciszek Stachura Date: Sun, 12 Apr 2026 21:15:08 +0200 Subject: [PATCH 15/15] docs: Add information on labels --- docs/usage/overview.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/usage/overview.rst b/docs/usage/overview.rst index 539de9ca3..b0bd0271c 100644 --- a/docs/usage/overview.rst +++ b/docs/usage/overview.rst @@ -162,6 +162,29 @@ or a cover letter or the web URL to a patch or a series: __ https://www.kernel.org/doc/html/latest/process/submitting-patches.html +Labels +~~~~~~ + +Labels are a way to organize and prioritize submissions. You can apply labels +to patches and cover letters to signify priority, category, or any other marker +you wish. + +Labels can be either global or tied to the project they are created in. Labels +can have an optional description attached, which will provide a little insight +into the purpose of the label. Labels are completely customizable and the +labels available will vary by instance. + +Labels are parsed out of the square brackets of the subject line. +For example, subject line ``[RFC, main] Add tests`` may cause the patch to be +labeled with ``RFC`` and ``main``, if such labels were created for the project +or instance. Labels can be created in the admin panel. + +.. note:: + + In order to refresh the list of labels for existing patches, for example + after updating Patchwork or adding a new label in the admin panel, + use the :ref:`command-relabel` management command. + Checks ~~~~~~