diff --git a/.github/workflows/production-suite.yml b/.github/workflows/production-suite.yml index 750ddae43e..799dff134b 100644 --- a/.github/workflows/production-suite.yml +++ b/.github/workflows/production-suite.yml @@ -20,6 +20,7 @@ on: - web/webpack.config.ts - zerver/worker/queue_processors.py - zerver/lib/push_notifications.py + - zerver/lib/storage.py - zerver/decorator.py - zproject/** workflow_dispatch: diff --git a/.github/workflows/zulip-ci.yml b/.github/workflows/zulip-ci.yml index 9f3eea647a..c6f215b8a1 100644 --- a/.github/workflows/zulip-ci.yml +++ b/.github/workflows/zulip-ci.yml @@ -217,7 +217,7 @@ jobs: - name: Test locked requirements if: ${{ matrix.os == 'jammy' }} run: | - . /srv/zulip-py3-venv/bin/activate && \ + source tools/ci/activate-venv ./tools/test-locked-requirements - name: Upload coverage reports diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index abedc8589e..92fe3d59c6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -134,9 +134,14 @@ repository](https://github.com/zulip/zulip/issues?q=is%3Aopen+is%3Aissue+label%3 alone. You can look through issues tagged with the "help wanted" label, which is used -to indicate the issues that are ready for contributions. Some repositories also -use the "good first issue" label to tag issues that are especially approachable -for new contributors. +to indicate the issues that are open for contributions. You'll be able to claim +unassigned issues, which you can find using the `no:assignee` filter in GitHub. +You can also pick up issues that are assigned but are no longer being worked on. + +Some repositories use the "good first issue" label to tag issues that are +especially approachable for new contributors. + +Here are some handy links for issues to look through: - [Server and web app](https://github.com/zulip/zulip/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) - Mobile apps: no "help wanted" label, but see the @@ -155,21 +160,29 @@ changes to tests). We recommend the following process for finding an issue to work on: -1. Read the description of an issue tagged with the "help wanted" label and make - sure you understand it. -2. If it seems promising, poke around the product +1. Find an issue tagged with the "help wanted" label that is either unassigned, + or looks to be abandoned. +1. Read the description of the issue and make sure you understand it. +1. If it seems promising, poke around the product (on [chat.zulip.org](https://chat.zulip.org) or in the development environment) until you know how the piece being described fits into the bigger picture. If after some exploration the description seems confusing or ambiguous, post a question on the GitHub issue, as others may benefit from the clarification as well. -3. When you find an issue you like, try to get started working on it. See if you +1. When you find an issue you like, try to get started working on it. See if you can find the part of the code you'll need to modify (`git grep` is your friend!) and get some idea of how you'll approach the problem. -4. If you feel lost, that's OK! Go through these steps again with another issue. +1. If you feel lost, that's OK! Go through these steps again with another issue. There's plenty to work on, and the exploration you do will help you learn more about the project. +An assigned issue can be considered abandoned if: + +- There is no recent contributor activity. +- There are no open PRs, or an open PR needs work in order to be ready for + review. For example, a PR may need to be updated to address reviewer feedback + or to pass tests. + Note that you are _not_ claiming an issue while you are iterating through steps 1-4. _Before you claim an issue_, you should be confident that you will be able to tackle it effectively. @@ -204,9 +217,10 @@ are set up with a GitHub workflow bot called requests in order to create a better workflow for Zulip contributors. To claim an issue in these repositories, simply post a comment that says -`@zulipbot claim` to the issue thread. If the issue is tagged with a [help -wanted](https://github.com/zulip/zulip/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) -label, Zulipbot will immediately assign the issue to you. +`@zulipbot claim` to the issue thread. If the issue is [tagged with a help +wanted label and is not assigned to someone +else](https://github.com/zulip/zulip/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22+no%3Aassignee), +Zulipbot will immediately assign the issue to you. Note that new contributors can only claim one issue until their first pull request is merged. This is to encourage folks to finish ongoing work before starting @@ -270,7 +284,7 @@ labels. ### Common questions -- **What if somebody is already working on the issue I want do claim?** There +- **What if somebody is already working on the issue I want to claim?** There are lots of issue to work on! If somebody else is actively working on the issue, you can find a different one, or help with reviewing their work. diff --git a/api_docs/changelog.md b/api_docs/changelog.md index 6b5288807a..7c72e426f5 100644 --- a/api_docs/changelog.md +++ b/api_docs/changelog.md @@ -20,6 +20,81 @@ format used by the Zulip server that they are interacting with. ## Changes in Zulip 10.0 +**Feature level 319** + +* [Markdown message + formatting](/api/message-formatting#links-to-channels-topics-and-messages): Added + new `message-link` format for special direct links to messages. + +**Feature level 318** + +* [`POST /register`](/api/register-queue): Updated + `realm_incoming_webhook_bots` with a new `config_options` key, + defining which options should be offered when creating URLs for this + integration. + +**Feature level 317** + +* [`POST /user_groups/create`](/api/create-user-group): + Added `group_id` to the success response of the user group creation + endpoint, enabling clients to easily access the unique identifier + of the newly created user group. + +**Feature level 316** + +* `PATCH /realm`, [`GET /events`](/api/get-events), + [`POST /register`](/api/register-queue): + Added `can_move_messages_between_topics_group` realm setting which is a + [group-setting value](/api/group-setting-values) describing the set of users + with permission to move messages from one topic to another within a channel + in the organization. +* `PATCH /realm`, [`GET /events`](/api/get-events): Removed + `edit_topic_policy` property, as the permission to move messages between + topics in the organization is now controlled by + `can_move_messages_between_topics_group` setting. + +**Feature level 315** + +* [POST /register](/api/register-queue), [`GET + /streams/{stream_id}`](/api/get-stream-by-id), [`GET + /events`](/api/get-events), [GET + /users/me/subscriptions](/api/get-subscriptions): The `is_archived` + property has been added to channel and subscription objects. + +* [`GET /streams`](/api/get-streams): The new parameter + `exclude_archived` controls whether archived channels should be + returned. + +* [`POST /register`](/api/register-queue): The new `archived_channels` + [client + capability](/api/register-queue#parameter-client_capabilities) + allows the client to specify whether it supports archived channels + being present in the response. + +**Feature level 314** + +* `PATCH /realm`, [`POST /register`](/api/register-queue), + [`GET /events`](/api/get-events): Anonymous groups are now accepted + by `create_multiuse_invite_group` realm setting, which is a now a + [group-setting value](/api/group-setting-values) instead of an + integer ID of the group. +* `PATCH /realm`, [`POST /register`](/api/register-queue), + [`GET /events`](/api/get-events): Anonymous groups are now accepted + by `can_access_all_users_group` realm setting, which is a now a + [group-setting value](/api/group-setting-values) instead of an + integer ID of the group. + +**Feature level 313** + +* [`PATCH /users/{user_id}`](/api/update-user): Added `new_email` field to + allow updating the email address of the target user. The requester must be + an organization administrator and have the `can_change_user_emails` special + permission. +* [`PATCH /users/{email}`](/api/update-user-by-email): Added new endpoint, + which is a copy of [`PATCH /users/{user_id}`](/api/update-user), but the user + is specified by their email address, following the same rules as [`GET + /users/{email}`](/api/get-user-by-email). + **Feature level 312** * [`GET /events`](/api/get-events): Added `realm_export_consent` event diff --git a/api_docs/include/rest-endpoints.md b/api_docs/include/rest-endpoints.md index 69618ce4cd..eb9f006102 100644 --- a/api_docs/include/rest-endpoints.md +++ b/api_docs/include/rest-endpoints.md @@ -66,6 +66,7 @@ * [Get all users](/api/get-users) * [Create a user](/api/create-user) * [Update a user](/api/update-user) +* [Update a user by email](/api/update-user-by-email) * [Deactivate a user](/api/deactivate-user) * [Deactivate own user](/api/deactivate-own-user) * [Reactivate a user](/api/reactivate-user) diff --git a/api_docs/message-formatting.md b/api_docs/message-formatting.md index 2f0cbad79e..bb5290435e 100644 --- a/api_docs/message-formatting.md +++ b/api_docs/message-formatting.md @@ -23,6 +23,42 @@ for syntax highlighting. This field is used in the mentions][help-global-time] to supported Markdown message formatting features. +## Links to channels, topics, and messages + +Zulip's markup supports special readable Markdown syntax for [linking +to channels, topics, and messages](/help/link-to-a-message-or-conversation). + +Sample HTML formats are as follows: +``` html + + + #announce + + + + + #announce > Zulip updates + + + + + #announce > Zulip updates @ 💬 + +``` + +The older stream/topic elements include a `data-stream-id`, which +historically was used in order to display the current channel name if +the channel had been renamed. That field is **deprecated**, because +displaying an updated value for the most common forms of this syntax +requires parsing the URL to get the topic to use anyway. + +**Changes**: In Zulip 10.0 (feature level 319), added Markdown syntax +for linking to a specific message in a conversation. Declared the +`data-stream-id` field to be deprecated as detailed above. + ## Image previews When a Zulip message is sent linking to an uploaded image, Zulip will diff --git a/corporate/lib/stripe.py b/corporate/lib/stripe.py index a6d585a406..671102a1b2 100644 --- a/corporate/lib/stripe.py +++ b/corporate/lib/stripe.py @@ -54,9 +54,10 @@ from zerver.lib.send_email import ( from zerver.lib.timestamp import datetime_to_timestamp, timestamp_to_datetime from zerver.lib.url_encoding import append_url_query_string from zerver.lib.utils import assert_is_not_none -from zerver.models import Realm, RealmAuditLog, UserProfile +from zerver.models import Realm, RealmAuditLog, Stream, UserProfile from zerver.models.realm_audit_logs import AuditLogEventType from zerver.models.realms import get_org_type_display_name, get_realm +from zerver.models.streams import get_stream from zerver.models.users import get_system_bot from zilencer.lib.remote_counts import MissingDataError from zilencer.models import ( @@ -1093,7 +1094,7 @@ class BillingSession(ABC): metadata=stripe_customer_data.metadata, ) event_time = timestamp_to_datetime(stripe_customer.created) - with transaction.atomic(): + with transaction.atomic(durable=True): self.write_to_audit_log(BillingSessionEventType.STRIPE_CUSTOMER_CREATED, event_time) customer = self.update_or_create_customer(stripe_customer.id) return customer @@ -1792,7 +1793,7 @@ class BillingSession(ABC): # TODO: The correctness of this relies on user creation, deactivation, etc being # in a transaction.atomic() with the relevant RealmAuditLog entries - with transaction.atomic(): + with transaction.atomic(durable=True): # We get the current license count here in case the number of billable # licenses has changed since the upgrade process began. current_licenses_count = self.get_billable_licenses_for_customer( @@ -2197,7 +2198,7 @@ class BillingSession(ABC): # event_time should roughly be timezone_now(). Not designed to handle # event_times in the past or future - @transaction.atomic + @transaction.atomic(savepoint=False) def make_end_of_cycle_updates_if_needed( self, plan: CustomerPlan, event_time: datetime ) -> tuple[CustomerPlan | None, LicenseLedger | None]: @@ -2911,7 +2912,7 @@ class BillingSession(ABC): if status is not None: if status == CustomerPlan.ACTIVE: assert plan.status < CustomerPlan.LIVE_STATUS_THRESHOLD - with transaction.atomic(): + with transaction.atomic(durable=True): # Switch to a different plan was cancelled. We end the next plan # and set the current one as active. if plan.status == CustomerPlan.SWITCH_PLAN_TIER_AT_PLAN_END: @@ -3411,7 +3412,7 @@ class BillingSession(ABC): raise BillingError("Form validation error", message=message) request_context = self.get_sponsorship_request_session_specific_context() - with transaction.atomic(): + with transaction.atomic(durable=True): # Ensures customer is created first before updating sponsorship status. self.update_customer_sponsorship_status(True) sponsorship_request = ZulipSponsorshipRequest( @@ -3843,6 +3844,31 @@ class BillingSession(ABC): return last_ledger + def send_support_admin_realm_internal_message( + self, channel_name: str, topic: str, message: str + ) -> None: + from zerver.actions.message_send import ( + internal_send_private_message, + internal_send_stream_message, + ) + + admin_realm = get_realm(settings.SYSTEM_BOT_REALM) + sender = get_system_bot(settings.NOTIFICATION_BOT, admin_realm.id) + try: + channel = get_stream(channel_name, admin_realm) + internal_send_stream_message( + sender, + channel, + topic, + message, + ) + except Stream.DoesNotExist: # nocoverage + direct_message = ( + f":red_circle: Channel named '{channel_name}' doesn't exist.\n\n{topic}:\n{message}" + ) + for user in admin_realm.get_human_admin_users(): + internal_send_private_message(sender, user, direct_message) + class RealmBillingSession(BillingSession): def __init__( @@ -4006,6 +4032,7 @@ class RealmBillingSession(BillingSession): return customer @override + @transaction.atomic(savepoint=False) def do_change_plan_type( self, *, tier: int | None, is_sponsored: bool = False, background_update: bool = False ) -> None: @@ -4211,6 +4238,14 @@ class RealmBillingSession(BillingSession): # anything weird. pass + def send_realm_created_internal_admin_message(self) -> None: + channel = "signups" + topic = "new organizations" + support_url = self.support_url() + organization_type = get_org_type_display_name(self.realm.org_type) + message = f"[{self.realm.name}]({support_url}) ([{self.realm.display_subdomain}]({self.realm.url})). Organization type: {organization_type}" + self.send_support_admin_realm_internal_message(channel, topic, message) + class RemoteRealmBillingSession(BillingSession): def __init__( @@ -4404,7 +4439,7 @@ class RemoteRealmBillingSession(BillingSession): return customer @override - @transaction.atomic + @transaction.atomic(savepoint=False) def do_change_plan_type( self, *, tier: int | None, is_sponsored: bool = False, background_update: bool = False ) -> None: # nocoverage @@ -4505,7 +4540,7 @@ class RemoteRealmBillingSession(BillingSession): def process_downgrade( self, plan: CustomerPlan, background_update: bool = False ) -> None: # nocoverage - with transaction.atomic(): + with transaction.atomic(savepoint=False): old_plan_type = self.remote_realm.plan_type new_plan_type = RemoteRealm.PLAN_TYPE_SELF_MANAGED self.remote_realm.plan_type = new_plan_type @@ -4841,7 +4876,7 @@ class RemoteServerBillingSession(BillingSession): return customer @override - @transaction.atomic + @transaction.atomic(savepoint=False) def do_change_plan_type( self, *, tier: int | None, is_sponsored: bool = False, background_update: bool = False ) -> None: @@ -4932,7 +4967,7 @@ class RemoteServerBillingSession(BillingSession): def process_downgrade( self, plan: CustomerPlan, background_update: bool = False ) -> None: # nocoverage - with transaction.atomic(): + with transaction.atomic(savepoint=False): old_plan_type = self.remote_server.plan_type new_plan_type = RemoteZulipServer.PLAN_TYPE_SELF_MANAGED self.remote_server.plan_type = new_plan_type @@ -5231,7 +5266,7 @@ def ensure_customer_does_not_have_active_plan(customer: Customer) -> None: raise UpgradeWithExistingPlanError -@transaction.atomic +@transaction.atomic(durable=True) def do_reactivate_remote_server(remote_server: RemoteZulipServer) -> None: """ Utility function for reactivating deactivated registrations. @@ -5253,7 +5288,7 @@ def do_reactivate_remote_server(remote_server: RemoteZulipServer) -> None: ) -@transaction.atomic +@transaction.atomic(durable=True) def do_deactivate_remote_server( remote_server: RemoteZulipServer, billing_session: RemoteServerBillingSession ) -> None: diff --git a/corporate/lib/support.py b/corporate/lib/support.py index 23cc5933a8..c013bc8887 100644 --- a/corporate/lib/support.py +++ b/corporate/lib/support.py @@ -1,11 +1,8 @@ from dataclasses import dataclass from datetime import datetime, timedelta from typing import Optional, TypedDict, Union -from urllib.parse import urlencode, urljoin, urlunsplit -from django.conf import settings from django.db.models import Sum -from django.urls import reverse from django.utils.timezone import now as timezone_now from corporate.lib.stripe import ( @@ -30,7 +27,7 @@ from corporate.models import ( ) from zerver.models import Realm from zerver.models.realm_audit_logs import AuditLogEventType -from zerver.models.realms import get_org_type_display_name, get_realm +from zerver.models.realms import get_org_type_display_name from zilencer.lib.remote_counts import MissingDataError from zilencer.models import ( RemoteCustomerUserCount, @@ -130,15 +127,6 @@ def get_stripe_customer_url(stripe_id: str) -> str: return f"https://dashboard.stripe.com/customers/{stripe_id}" # nocoverage -def get_realm_support_url(realm: Realm) -> str: - support_realm_url = get_realm(settings.STAFF_SUBDOMAIN).url - support_url = urljoin( - support_realm_url, - urlunsplit(("", "", reverse("support"), urlencode({"q": realm.string_id}), "")), - ) - return support_url - - def get_realm_user_data(realm: Realm) -> UserData: non_guests = get_non_guest_user_count(realm) guests = get_guest_user_count(realm) diff --git a/corporate/views/remote_activity.py b/corporate/views/remote_activity.py index f7e244ebda..f43cd27828 100644 --- a/corporate/views/remote_activity.py +++ b/corporate/views/remote_activity.py @@ -29,20 +29,20 @@ def get_remote_server_activity(request: HttpRequest) -> HttpResponse: query = SQL( """ - with mobile_push_forwarded_count as ( + with remote_server_push_forwarded_count as ( select server_id, - sum(coalesce(value, 0)) as push_forwarded_count + sum(coalesce(value, 0)) as server_push_forwarded_count from zilencer_remoteinstallationcount where property = 'mobile_pushes_forwarded::day' and end_time >= current_timestamp(0) - interval '7 days' group by server_id ), - remote_push_devices as ( + remote_server_push_devices as ( select server_id, - count(distinct(user_id, user_uuid)) as push_user_count + count(distinct(user_id, user_uuid)) as server_push_user_count from zilencer_remotepushdevicetoken group by server_id ), @@ -68,6 +68,23 @@ def get_remote_server_activity(request: HttpRequest) -> HttpResponse: is_system_bot_realm = False and realm_deactivated = False group by server_id, id, name, org_type + ), + remote_realm_push_devices as ( + select + remote_realm_id, + count(distinct(user_id, user_uuid)) as realm_push_user_count + from zilencer_remotepushdevicetoken + group by remote_realm_id + ), + remote_realm_push_forwarded_count as ( + select + remote_realm_id, + sum(coalesce(value, 0)) as realm_push_forwarded_count + from zilencer_remoterealmcount + where + property = 'mobile_pushes_forwarded::day' + and end_time >= current_timestamp(0) - interval '7 days' + group by remote_realm_id ) select rserver.id, @@ -80,16 +97,20 @@ def get_remote_server_activity(request: HttpRequest) -> HttpResponse: rserver.contact_email, rserver.last_version, rserver.last_audit_log_update, - push_user_count, - push_forwarded_count, + server_push_user_count, + realm_push_user_count, + server_push_forwarded_count, + realm_push_forwarded_count, realm_type from zilencer_remotezulipserver rserver - left join mobile_push_forwarded_count on mobile_push_forwarded_count.server_id = rserver.id - left join remote_push_devices on remote_push_devices.server_id = rserver.id + left join remote_server_push_forwarded_count on remote_server_push_forwarded_count.server_id = rserver.id + left join remote_server_push_devices on remote_server_push_devices.server_id = rserver.id left join remote_realms on remote_realms.server_id = rserver.id left join remote_server_audit_log on remote_server_audit_log.server_id = rserver.id + left join remote_realm_push_devices on remote_realm_push_devices.remote_realm_id = realm_id + left join remote_realm_push_forwarded_count on remote_realm_push_forwarded_count.remote_realm_id = realm_id where not deactivated - order by push_user_count DESC NULLS LAST + order by server_push_user_count DESC NULLS LAST """ ) @@ -102,8 +123,8 @@ def get_remote_server_activity(request: HttpRequest) -> HttpResponse: "Server contact email", "Server Zulip version", "Server last audit log update (UTC)", - "Server mobile users", - "Server mobile pushes", + "Mobile users", + "Mobile pushes", "Realm organization type", "Plan name", "Plan status", @@ -120,6 +141,10 @@ def get_remote_server_activity(request: HttpRequest) -> HttpResponse: REALM_CREATED = 2 SERVER_HOST = 3 REALM_HOST = 4 + SERVER_PUSH_USER_COUNT = 9 + REALM_PUSH_USER_COUNT = 10 + SERVER_PUSH_FORWARDED = 11 + REALM_PUSH_FORWARDED = 12 # Column constants: DATE_CREATED = 2 @@ -153,12 +178,16 @@ def get_remote_server_activity(request: HttpRequest) -> HttpResponse: ids_string = f"{server_id}" row.insert(SERVER_AND_REALM_IDS, ids_string) - # Set date created data - # For remote realm row, remove server created value; - # for remote server row, remove realm created value + # Remove extra mobile user/push data and set created date + # For remote realm row, remove server push data and created date; + # for remote server row, remove realm push data and created date. if realm_id is not None: + row.pop(SERVER_PUSH_FORWARDED) + row.pop(SERVER_PUSH_USER_COUNT) row.pop(SERVER_CREATED) else: + row.pop(REALM_PUSH_FORWARDED) + row.pop(REALM_PUSH_USER_COUNT) row.pop(REALM_CREATED) # Get server_host for support link diff --git a/corporate/views/support.py b/corporate/views/support.py index bcac54854a..2b2338bafe 100644 --- a/corporate/views/support.py +++ b/corporate/views/support.py @@ -98,7 +98,7 @@ class DemoRequestForm(forms.Form): @zulip_login_required @typed_endpoint_without_parameters def support_request(request: HttpRequest) -> HttpResponse: - from corporate.lib.support import get_realm_support_url + from corporate.lib.stripe import build_support_url user = request.user assert user.is_authenticated @@ -119,7 +119,7 @@ def support_request(request: HttpRequest) -> HttpResponse: "realm_string_id": user.realm.string_id, "request_subject": form.cleaned_data["request_subject"], "request_message": form.cleaned_data["request_message"], - "support_url": get_realm_support_url(user.realm), + "support_url": build_support_url("support", user.realm.string_id), "user_role": user.get_role_name(), } # Sent to the server's support team, so this email is not user-facing. diff --git a/docs/contributing/continuing-unfinished-work.md b/docs/contributing/continuing-unfinished-work.md index b6d820c0b7..4fc291d3ae 100644 --- a/docs/contributing/continuing-unfinished-work.md +++ b/docs/contributing/continuing-unfinished-work.md @@ -1,9 +1,9 @@ # Continuing unfinished work -Sometimes, work on an issue is started, but not brought to completion. This may -happen for a variety of reasons — the contributor working on the project gets -busy, maintainers cannot prioritize reviewing the work, a contributor doesn't -have the skills required to complete the project, there is +Sometimes, work is started on an issue or PR, but not brought to completion. +This may happen for a variety of reasons — the contributor working on the +project gets busy, maintainers cannot prioritize reviewing the work, a +contributor doesn't have the skills required to complete the project, there is an unexpected technical challenge or blocker, etc. Completing work that someone else has started is a great way to contribute! Here @@ -12,18 +12,21 @@ are the steps required: 1. [Find work to be completed.](#find-work-to-be-completed) 1. [Review existing work and feedback.](#review-existing-work-and-feedback) 1. [Decide how to use prior work.](#decide-how-to-use-prior-work) +1. [Credit prior work in your commit history.](#credit-prior-work-in-your-commit-history) 1. [Present your pull request.](#present-your-pull-request) ## Find work to be completed In Zulip's server and web app [repository](https://github.com/zulip/zulip), pull requests that have significant work towards something valuable are often tagged -with a [completion candidate label][completion-candidate] label. You can review -this label for issues that you find interesting and have the skills to complete. +with a [completion candidate][completion-candidate] label. You can review +this label for unfinished work that you find interesting and have the skills to +complete. -In general, it's common to see one or more pull request linked to an issue +Note that it's common to see one or more pull requests linked to an issue you're interested in. The guidelines below apply regardless of whether you -intentionally set out to find work to complete. +intentionally set out to find work to complete or simply find yourself +building on someone else's work. ## Review existing work and feedback @@ -45,6 +48,7 @@ If prior work looks like a good start: 1. Carefully address any open feedback from reviewers. 1. Make any other changes you think are needed, including completing any parts of the work that had not been finished. +1. Make sure the work of others is [properly credited](#credit-prior-work-in-your-commit-history). 1. [Self-review](../contributing/code-reviewing.md), test, and revise the work, including potentially [splitting out](../contributing/commit-discipline.md) preparatory commits to make it easier to read. You should be proud of the @@ -57,6 +61,61 @@ Otherwise, you can: 1. Go through reviewer feedback on prior work. Would any of it apply to the changes you're proposing? Be sure to address it if so. +## Credit prior work in your commit history + +When you use or build upon someone else's unmerged work, it is both +professionally and ethically necessary to [properly +credit][coauthor-git-guide] their contributions in the commit history +of work that you submit. Git, used properly, does a good job of +preserving the original authorship of commits. + +However, it's normal to find yourself making changes to commits +originally authored by other contributors, whether resolving merge +conflicts when doing `git rebase` or fixing bugs to create an +atomically correct commit compliant with Zulip's [commit +guidelines](../contributing/commit-discipline.md). + +When you do that, it's your responsibility to ensure the resulting +commit series correctly credits the work of everyone who materially +contributed to it. The most direct way to credit the work of someone +beyond the commit's author maintained in the Git metadata is +`Co-authored-by:` line after a blank line at the end of your commit +message: + + Co-authored-by: Greg Price + +Be careful to type it precisely, because software parses these commit +message records in generating statistics. You should add such a line +in two scenarios: + +- If your own work was squashed into a commit originally authored by + another contributor, add such a line crediting yourself. +- If you used another contributor's work in generating your own + commit, add such a line crediting the other contributor(s). + +Sometimes, you make a mistake when rebasing and accidentally squash +commits in a way that messes up Git's authorship records. Often, +undoing the rebase change via `git reflog` is the best way to correct +such mistakes, but there are two other Git commands that can be used +to correct Git's primary authorship information after the fact: + +- `git commit --amend --reset-author` will replace the Git commit + metadata (date, author, etc.) of the currently checked out commit + with yourself. This is useful to correct a commit that incorrectly + shows someone else as the author of your work. +- `git commit --amend -C ` will replace the commit metadata + (date, author, etc.) on a commit with that of the provided commit + ID. This is useful if you accidentally made someone else's commit + show yourself as the author, or lost a useful commit message via + accidental squashing. (You can usually find the right commit ID to + use with `git reflog` or from GitHub). + +As an aside, maintainers who modify commits before merging them are +credited via Git's "Committer" records (visible with `git show +--pretty=fuller`, for example). As a result, they may not bother with +adding a separate `Co-authored-by` record on commits that they revise +as part of merging a pull request. + ## Present your pull request In addition to the usual [guidance](../contributing/reviewable-prs.md) for @@ -71,12 +130,9 @@ putting together your pull request, there are a few key points to keep in mind. - Test the work carefully, even if others have tested it before. There may be problems that the reviewers missed, or that were introduced by rebasing across other changes. -- **Give credit where credit is due.** In the commit message for any commits - that use somebody else's work, [credit][coauthor-git-guide] co-authors by - adding a `Co-authored-by:` line after a blank line at the end of your commit - message: - - Co-authored-by: Greg Price +- **Give credit where credit is due.** Reviewers should be able to examine your + commit history and see that you have [properly credited](#credit-prior-work-in-your-commit-history) + the work of others. - **Explain the relationship between your PR and prior work** in the description for your pull request. diff --git a/docs/overview/changelog.md b/docs/overview/changelog.md index 7d4d8ed404..07367a50e9 100644 --- a/docs/overview/changelog.md +++ b/docs/overview/changelog.md @@ -21,6 +21,10 @@ _Unreleased_ synchronizing role, and otherwise functions like the old one, except Zulip custom profile fields are referred to with the prefix `custom__`. See the updated comment documentation in `/etc/zulip/settings.py` for details. +- PostgreSQL 12 is no longer supported; if you are currently using it, + you will need to [upgrade + PostgreSQL](../production/upgrade.md#upgrading-postgresql) before + upgrading Zulip. ## Zulip Server 9.x series diff --git a/docs/production/deployment.md b/docs/production/deployment.md index 35ebf37993..0b37fee39a 100644 --- a/docs/production/deployment.md +++ b/docs/production/deployment.md @@ -68,7 +68,7 @@ as well as those mentioned in the [install](install.md#installer-options) documentation: - `--postgresql-version`: Sets the version of PostgreSQL that will be - installed. We currently support PostgreSQL 12, 13, 14, 15, and 16, with 16 + installed. We currently support PostgreSQL 13, 14, 15, and 16, with 16 being the default. - `--postgresql-database-name=exampledbname`: With this option, you diff --git a/docs/production/install-existing-server.md b/docs/production/install-existing-server.md index 11a9a9f871..4566f41728 100644 --- a/docs/production/install-existing-server.md +++ b/docs/production/install-existing-server.md @@ -75,7 +75,7 @@ $ sudo service puppet stop ### PostgreSQL -Zulip expects to install PostgreSQL 12, and find that listening on +Zulip expects to install PostgreSQL 16, and find that listening on port 5432; any other version of PostgreSQL that is detected at install time will cause the install to abort. If you already have PostgreSQL installed, you can pass `--postgresql-version=` to the installer to diff --git a/docs/production/postgresql-support-table.md b/docs/production/postgresql-support-table.md index 9e34c4be16..8623e99e55 100644 --- a/docs/production/postgresql-support-table.md +++ b/docs/production/postgresql-support-table.md @@ -7,3 +7,4 @@ | 7.x | 12, 13, 14, 15 | | 8.x | 12, 13, 14, 15, 16 | | 9.x | 12, 13, 14, 15, 16 | +| 10.x (unreleased) | 13, 14, 15, 16 | diff --git a/help-beta/src/styles/main.css b/help-beta/src/styles/main.css index 4e5f94be81..fafbda062e 100644 --- a/help-beta/src/styles/main.css +++ b/help-beta/src/styles/main.css @@ -3,3 +3,37 @@ pointer-events: none; cursor: default; } + +.sl-markdown-content { + img { + vertical-align: top; + box-shadow: 0 0 4px hsl(0deg 0% 0% / 5%); + border: 1px solid hsl(0deg 0% 87%); + border-radius: 4px; + margin-top: 0; + + &.emoji-small { + display: inline-block; + width: 1.25em; + box-shadow: none; + border: none; + vertical-align: text-top; + } + + &.emoji-big { + display: inline-block; + width: 1.5em; + box-shadow: none; + border: none; + vertical-align: text-top; + } + + &.help-center-icon { + display: inline-block; + width: 1.25em; + box-shadow: none; + border: none; + vertical-align: text-top; + } + } +} diff --git a/help/archive-a-channel.md b/help/archive-a-channel.md index 75d0743beb..66094ff4d2 100644 --- a/help/archive-a-channel.md +++ b/help/archive-a-channel.md @@ -2,19 +2,29 @@ {!admin-only.md!} -Archiving a channel will immediately unsubscribe all users from the channel, -remove the channel from search and other typeaheads, and remove the channel's -messages from **Combined feed**. +You can archive channels you no longer plan to use. Archiving a channel: -Archiving a channel does not delete a channel's messages. Users will still be -able to find any given message by searching for it. However, links to -messages and topics in the channel may or may not continue to work. +- Removes it from the left sidebar for all users. +- Prevents new messages from being sent to the channel. +- Prevents messages in the channel from being edited, deleted, or moved. -In most cases, we recommend [renaming channels](/help/rename-a-channel) rather -than archiving them. +Archiving a channel does not remove subscribers, or change who can access it. +Messages in archived channels still appear in [search +results](/help/search-for-messages), the [combined feed](/help/combined-feed), +and [recent conversations](/help/recent-conversations). + +To hide the content in a channel, you can make it +[private](/help/change-the-privacy-of-a-channel) and [unsubscribe +users](/help/manage-user-channel-subscriptions) from it prior to archiving. ## Archive a channel +!!! warn "" + + Channels can be [unarchived](#unarchiving-archived-channels) only by + [contacting support](/help/contact-support) for organizations hosted + on Zulip Cloud, or by your self-hosted server's administrator. + {start_tabs} {tab|desktop-web} @@ -23,8 +33,8 @@ than archiving them. 1. Select a channel. -1. Click the **trash** icon near the top right - corner of the channel settings panel. +1. Click the **archive** () icon + in the upper right corner of the channel settings panel. 1. Approve by clicking **Confirm**. @@ -32,22 +42,19 @@ than archiving them. You can also hover over a channel in the left sidebar, click on the **ellipsis** (), and - select **Channel settings** to access the **trash** - icon. + select **Channel settings** to access settings for the channel. {end_tabs} -!!! warn "" - - Archiving a channel is currently irreversible via the UI. - ## Unarchiving archived channels +Zulip Cloud organizations that need to unarchive a channel can [contact Zulip +support](/help/contact-support). + If you are self-hosting, you can unarchive an archived channel using the `unarchive_channel` [management command][management-command]. This will restore it as a private channel with shared history, and subscribe all organization -owners to it. If you are using Zulip Cloud, you can [contact us](/help/contact-support) -for help. +owners to it. [management-command]: https://zulip.readthedocs.io/en/latest/production/management-commands.html#other-useful-manage-py-commands @@ -59,4 +66,3 @@ https://zulip.readthedocs.io/en/latest/production/management-commands.html#other * [Delete a topic](/help/delete-a-topic) * [Message retention policy](/help/message-retention-policy) * [Channel permissions](/help/channel-permissions) -* [Zulip Cloud or self-hosting?](/help/zulip-cloud-or-self-hosting) diff --git a/help/configure-emoticon-translations.md b/help/configure-emoticon-translations.md index a5f0054a77..cb4fa01fc1 100644 --- a/help/configure-emoticon-translations.md +++ b/help/configure-emoticon-translations.md @@ -1,19 +1,7 @@ # Configure emoticon translations You can configure whether emoticons like `:)` or `:(` will be automatically -translated into emoji equivalents like -smile -or -slight_frown -by Zulip. +translated into emoji equivalents like 🙂 or 🙁 by Zulip. ## Configure emoticon translations diff --git a/help/configure-home-view.md b/help/configure-home-view.md index a0b9cc35b8..6f958508de 100644 --- a/help/configure-home-view.md +++ b/help/configure-home-view.md @@ -13,8 +13,8 @@ on how to use these views. You can configure which view is set as your home view, and whether the Esc key navigates to the home view. Also, you can -always reach the home view by using the Ctrl + [ -shortcut. +always reach the home view by using the +Ctrl + [ shortcut. ## Change home view @@ -44,7 +44,8 @@ organization settings: 1. To see your changes in action, open a new Zulip tab, or use a keyboard shortcut twice to exit the settings and navigate to your home view - (Ctrl + [ or Esc if enabled). + (Ctrl + [ or Esc + if enabled). !!! tip "" @@ -62,8 +63,8 @@ designed to enhance the user experience in the app. By default, the Esc key shortcut will ultimately navigate to your home view. You can disable this key binding if you would prefer. This will not disable other Esc key shortcuts used in Zulip, -and will not affect the behavior of the Ctrl + [ -shortcut. +and will not affect the behavior of the +Ctrl + [ shortcut. ### Toggle whether Esc navigates to the home view diff --git a/help/create-user-groups.md b/help/create-user-groups.md index 11969156ae..6e32aa8420 100644 --- a/help/create-user-groups.md +++ b/help/create-user-groups.md @@ -2,6 +2,13 @@ {!user-groups-intro.md!} +Many organizations find it helpful to create groups for: + +- Each team, e.g., “mobile”, “design”, or “IT”. +- Leadership roles, e.g., “managers”, “engineering-leads”. + +{!user-groups-applications.md!} + ## How to create a user group {!how-to-create-a-user-group.md!} diff --git a/help/emoji-and-emoticons.md b/help/emoji-and-emoticons.md index d963e738e4..ea9d95bf53 100644 --- a/help/emoji-and-emoticons.md +++ b/help/emoji-and-emoticons.md @@ -47,13 +47,7 @@ ### Use an emoticon You can configure Zulip to convert emoticons into emoji, so that, e.g., `:)` -will be displayed as -smile -. +will be displayed as 🙂 . {start_tabs} diff --git a/help/emoji-reactions.md b/help/emoji-reactions.md index d1851343dc..13a94a24b4 100644 --- a/help/emoji-reactions.md +++ b/help/emoji-reactions.md @@ -32,9 +32,7 @@ message. !!! keyboard_tip "" - You can react to the selected message with :thumbs_up: by using the + shortcut. + You can react to the selected message with 👍 by using the + shortcut. {tab|mobile} diff --git a/help/include/how-to-start-a-new-topic.md b/help/include/how-to-start-a-new-topic.md index 76eb8aa79f..b75f923c90 100644 --- a/help/include/how-to-start-a-new-topic.md +++ b/help/include/how-to-start-a-new-topic.md @@ -1,30 +1,36 @@ {start_tabs} -{tab|desktop-web} +{tab|via-left-sidebar} -1. Click the **Start new conversation** button at the bottom of the app, or - use the C keyboard shortcut. +1. Click the **plus** () button next + to the name of the channel where you'd like to start a conversation. -1. _(optional)_ You can change the destination channel for your message using - the dropdown in the top left of the compose box. You can start typing to - filter channels. - -1. Enter a topic name. Auto-complete will provide suggestions for previously - used topics. +1. Enter a topic name. Think about finishing the sentence: “Hey, can we chat about… ?” {!compose-and-send-message.md!} -!!! tip "" +!!! keyboard_tip "" - You can click on the - **Clear topic** () icon - near the upper right of the compose box to erase the topic name. + You can also use the C keyboard shortcut to start a new topic in + the channel you're viewing. -!!! warn "" +{tab|via-compose-box} - In Zulip, you can compose a message to a different place than the one you - are viewing. In this situation, the message feed will fade to indicate - what's going on. +1. Click the **Start new conversation** button at the bottom of the app. + +1. _(optional)_ You can change the destination channel for your message using + the dropdown in the top left of the compose box. Start typing to filter + channels. + +1. Enter a topic name. Think about finishing the sentence: “Hey, can we chat + about… ?” + +{!compose-and-send-message.md!} + +!!! keyboard_tip "" + + You can also use the C keyboard shortcut to start a new topic in + the channel you're viewing. {tab|mobile} diff --git a/help/include/links-examples.md b/help/include/links-examples.md index d21c56e327..25b97d0ec5 100644 --- a/help/include/links-examples.md +++ b/help/include/links-examples.md @@ -4,7 +4,8 @@ Named link: [Zulip homepage](zulip.com) A URL (links automatically): zulip.com Channel link: #**channel name** -Channel and topic link: #**channel name>topic name** +Topic link: #**channel name>topic name** +Message link: #**channel name>topic name@123** Custom linkifier: For example, #2468 can automatically link to an issue in your tracker. ``` diff --git a/help/include/links-intro.md b/help/include/links-intro.md index d6398c90c8..2bb497b5d0 100644 --- a/help/include/links-intro.md +++ b/help/include/links-intro.md @@ -2,7 +2,6 @@ In Zulip, you can insert a named link using Markdown formatting. In addition, Zu automatically creates links for you when you enter: - A URL -- An appropriately formatted channel name, or a channel name followed by a topic - (see also [Link to a message or - conversation](/help/link-to-a-message-or-conversation)) +- A reference to a channel, topic, or specific message (see also [Link to a + message or conversation](/help/link-to-a-message-or-conversation)) - Text that matches a [custom linkifier](/help/add-a-custom-linkifier) set up by your organization diff --git a/help/include/review-organization-settings-instructions.md b/help/include/review-organization-settings-instructions.md index 50b7236871..50c8fd5d46 100644 --- a/help/include/review-organization-settings-instructions.md +++ b/help/include/review-organization-settings-instructions.md @@ -1,5 +1,7 @@ -Review the settings for your organization to set everything up how you -want it to be. +Review the settings for your organization to set everything up how you want it +to be. + +{!user-groups-intro.md!} {start_tabs} diff --git a/help/include/send-dm.md b/help/include/send-dm.md index f82799bbdb..c9a0146881 100644 --- a/help/include/send-dm.md +++ b/help/include/send-dm.md @@ -2,8 +2,9 @@ {tab|desktop-web} -1. Click the **New direct message** button at the bottom of the app, or the - **Start new conversation** button if you are in a direct message view. +1. Click the **plus** () button next + to **DIRECT MESSAGES** in the left sidebar, or the **New direct message** + button at the bottom of the app. 1. Start typing the name of the person or [group](/help/user-groups) you want to message, and select their name from the list of suggestions. You can continue diff --git a/help/include/sidebar_index.md b/help/include/sidebar_index.md index 44a4c30225..dbc93afd40 100644 --- a/help/include/sidebar_index.md +++ b/help/include/sidebar_index.md @@ -23,11 +23,11 @@ ## Setting up your organization * [Migrating from other chat tools](/help/migrating-from-other-chat-tools) * [Create your organization profile](/help/create-your-organization-profile) +* [Create user groups](/help/create-user-groups) * [Customize organization settings](/help/customize-organization-settings) * [Create channels](/help/create-channels) * [Customize settings for new users](/help/customize-settings-for-new-users) * [Invite users to join](/help/invite-users-to-join) -* [Create user groups](/help/create-user-groups) * [Set up integrations](/help/set-up-integrations) ## Account basics @@ -168,7 +168,6 @@ # Zulip administration ## Organization basics -* [Review your organization's settings](/help/review-your-organization-settings) * [Organization type](/help/organization-type) * [Communities directory](/help/communities-directory) * [Import from Mattermost](/help/import-from-mattermost) diff --git a/help/include/user-groups-applications.md b/help/include/user-groups-applications.md new file mode 100644 index 0000000000..4ba1f71ea8 --- /dev/null +++ b/help/include/user-groups-applications.md @@ -0,0 +1,9 @@ +Groups provide an easy way to refer to multiple users at once. You can: + +- [Mention](/help/mention-a-user-or-group) a group of users, + [notifying](/help/dm-mention-alert-notifications) everyone in the group as if + they were personally mentioned. +- Compose a [direct message](/help/direct-messages) to a user group. This + automatically puts all the users in the group into the addressee field. +- Subscribe a user group to a channel. This individually subscribes all the users + in the group. diff --git a/help/include/user-groups-intro.md b/help/include/user-groups-intro.md index d9bd4dfd72..0618ab30ff 100644 --- a/help/include/user-groups-intro.md +++ b/help/include/user-groups-intro.md @@ -1,14 +1,4 @@ -User groups make it easier to manage any organization with multiple teams or job -functions. You can: - -- Assign permissions to user groups. -- Add a user group to another user group. -- Subscribe a user group to a channel. This individually subscribes all the users - in the group. -- [Mention](/help/mention-a-user-or-group) a group of users, - [notifying](/help/dm-mention-alert-notifications) everyone in the group as if - they were personally mentioned. -- Compose a [direct message](/help/direct-messages) to a user group. This - automatically puts all the users in the group into the addressee field. - -You may want to create a user group for each team in your organization. +User groups offer a flexible way to manage permissions in your organization. +Most permissions in Zulip can be granted to any combination of +[roles](/help/roles-and-permissions), [groups](/help/user-groups), and +individual [users](/help/manage-a-user). diff --git a/help/include/when-to-start-a-new-topic.md b/help/include/when-to-start-a-new-topic.md index 531e36553a..8cda46ed5e 100644 --- a/help/include/when-to-start-a-new-topic.md +++ b/help/include/when-to-start-a-new-topic.md @@ -8,6 +8,7 @@ Topic names should be brief but specific, for example: * **Not so good topic names:** "question", "hi", "help", "this topic is about a question I have about topics" -!!! tip "" - Don't overthink naming your topic. The first 2-3 words that come to mind - are probably fine! + +Don't stress about making it perfect! The first 2-3 words that +come to mind are probably fine, and you can always [change it +later](/help/rename-a-topic). diff --git a/help/introduction-to-topics.md b/help/introduction-to-topics.md index 600ffee4bd..01cb6955bd 100644 --- a/help/introduction-to-topics.md +++ b/help/introduction-to-topics.md @@ -8,6 +8,8 @@ ## How to start a new topic +Zulip lets you start a new conversation in any channel, no matter where you are. + {!how-to-start-a-new-topic.md!} ## Further reading diff --git a/help/keyboard-shortcuts.md b/help/keyboard-shortcuts.md index fd72a98282..c6c025e7db 100644 --- a/help/keyboard-shortcuts.md +++ b/help/keyboard-shortcuts.md @@ -32,9 +32,9 @@ in the Zulip app to add more to your repertoire as needed. Ctrl + V, and press Ctrl + Z to remove formatting. -* **Cancel compose and save draft**: Esc or Ctrl + - [ — Close the compose box and save the unsent message as a - draft. +* **Cancel compose and save draft**: Esc or + Ctrl + [ — Close the compose box + and save the unsent message as a draft. * **View drafts**: D — Use the arrow keys and Enter to restore a draft. Press D again to close. @@ -54,8 +54,8 @@ in the Zulip app to add more to your repertoire as needed. * **Toggle keyboard shortcuts view**: ? -* **Go to your home view**: Ctrl + [ (or - Esc, [if enabled][disable-escape]) +* **Go to your home view**: Ctrl + [ + (or Esc, [if enabled][disable-escape]) until you are in your [home view](/help/configure-home-view). [disable-escape]: /help/configure-home-view#configure-whether-esc-navigates-to-the-home-view @@ -65,7 +65,7 @@ in the Zulip app to add more to your repertoire as needed. * **Filter channels**: Q -* **Search people**: W +* **Filter users**: W ## Scrolling @@ -158,8 +158,9 @@ in the Zulip app to add more to your repertoire as needed. * **Toggle preview mode**: Alt + P -* **Cancel compose and save draft**: Esc or Ctrl + - [ — Close the compose box and save the unsent message as a draft. +* **Cancel compose and save draft**: Esc or + Ctrl + [ — Close the compose box + and save the unsent message as a draft. ## Message actions @@ -185,9 +186,7 @@ in the Zulip app to add more to your repertoire as needed. * **Star message**: Ctrl + S -* **React with :thumbs_up:**: + +* **React with 👍**: + * **Toggle first emoji reaction**: = diff --git a/help/link-to-a-message-or-conversation.md b/help/link-to-a-message-or-conversation.md index 86dafe762c..6957508681 100644 --- a/help/link-to-a-message-or-conversation.md +++ b/help/link-to-a-message-or-conversation.md @@ -50,13 +50,21 @@ This copies to your clipboard a permanent link to the message, displayed in its thread (i.e. topic view for messages in a channel). Viewing a topic via a message link will never mark messages as read. -These links will still work even when the message is -[moved to another topic](/help/move-content-to-another-topic) -or [channel](/help/move-content-to-another-channel) or -if its [topic is resolved](/help/resolve-a-topic). +These links will still work even when the message is [moved to another +topic](/help/move-content-to-another-topic) or +[channel](/help/move-content-to-another-channel), or if its [topic is +resolved](/help/resolve-a-topic). Zulip uses the same permanent link syntax when +[quoting a message](/help/quote-and-reply). -Zulip uses the same permanent link syntax when [quoting a -message](/help/quote-and-reply). +When you paste a message link into the compose box, it gets automatically +formatted to be easy to read: + +``` +#**channel name>topic name@message ID** +``` + +When you send your message, the link will appear as **#channel name>topic +name@💬**. {start_tabs} @@ -71,6 +79,12 @@ message](/help/quote-and-reply). If using Zulip in a browser, you can also click on the timestamp of a message, and copy the URL from your browser's address bar. +!!! tip "" + + When you paste a message link into Zulip, it is automatically + formatted for you. You can paste as plain text if you prefer with + Ctrl + Shift + V. + {end_tabs} ### Get a link to a specific topic diff --git a/help/manage-user-groups.md b/help/manage-user-groups.md index d977cca0cc..71ff469703 100644 --- a/help/manage-user-groups.md +++ b/help/manage-user-groups.md @@ -2,6 +2,8 @@ {!user-groups-intro.md!} +{!user-groups-applications.md!} + ## Create a user group !!! tip "" @@ -34,10 +36,14 @@ !!! warn "" - Guests can never manage user groups, add anyone else to a group, or remove + Guests can never administer user groups, add anyone else to a group, or remove anyone else from a group, even if they belong to a group that has permissions to do so. +!!! tip "" + + Users who can add members to a group can always join the group. + {start_tabs} {tab|desktop-web} @@ -48,7 +54,7 @@ 1. Select the **General** tab on the right. -1. Under **Group permissions**, configure **Who can manage this group**, **Who +1. Under **Group permissions**, configure **Who can administer this group**, **Who can mention this group**, **Who can add members to this group**, **Who can join this group**, and **Who can leave this group**. @@ -58,6 +64,10 @@ ## Add groups and users to a group +You can add users to a group, or add a group to any other group. Nesting groups +makes them easier to maintain. For example, moving a user from one team group to +another can automatically update what department group they belong to. + {start_tabs} {tab|desktop-web} @@ -131,16 +141,17 @@ so. {end_tabs} -## Configure who can manage user groups +## Configure who can administer all user groups {!admin-only.md!} -You can configure who can manage groups in your organization. Guests can never -manage user groups, even if they belong to a group that has permissions to do -so. +You can configure who can administer all user groups in your +organization. Guests can never administer user groups, even if they +belong to a group that has permissions to do so. -In addition, you can [give users permission](#configure-group-permissions) to -manage a specific group. +In addition, you can [give users +permission](#configure-group-permissions) to administer a specific +group. {start_tabs} @@ -148,7 +159,7 @@ manage a specific group. {settings_tab|organization-permissions} -1. Under **Other permissions**, configure **Who can manage user groups**. +1. Under **Other permissions**, configure **Who can administer all user groups**. {!save-changes.md!} diff --git a/help/moving-to-zulip.md b/help/moving-to-zulip.md index 9d6786079e..93d3eec27a 100644 --- a/help/moving-to-zulip.md +++ b/help/moving-to-zulip.md @@ -116,6 +116,9 @@ you will need to upgrade your plan. 1. [Create your organization profile](/help/create-your-organization-profile), which is displayed on your organization's registration and login pages. +1. [Create user groups](/help/create-user-groups), which offer a flexible way to + manage permissions. + 1. Review [organization permissions](/help/roles-and-permissions), such as who can invite users, create channels, etc. diff --git a/help/review-your-organization-settings.md b/help/review-your-organization-settings.md deleted file mode 100644 index 22bbdfeded..0000000000 --- a/help/review-your-organization-settings.md +++ /dev/null @@ -1,19 +0,0 @@ -# Review your organization settings - -It's good to periodically review your organization's settings. Zulip is in -active development and is constantly adding new features, and as your -organization grows, features that may not have been applicable at a small -scale may be applicable now. - -Note that most organization settings are visible to all users, though -generally only organization administrators can interact with them. - -### Review your organization settings - -{start_tabs} - -{relative|gear|organization-settings} - -1. Click on each tab on the left. - -{end_tabs} diff --git a/help/roles-and-permissions.md b/help/roles-and-permissions.md index 0fe3b2d0fb..7692461cca 100644 --- a/help/roles-and-permissions.md +++ b/help/roles-and-permissions.md @@ -1,12 +1,12 @@ # Roles and permissions -## User roles - User roles make it convenient to configure different permissions for different users in your organization. You can decide what role a user will have when you [send them an invitation](/help/invite-new-users), and later [change a user's role](/help/roles-and-permissions#change-a-users-role) if needed. +{!user-groups-intro.md!} + !!! tip "" Learn about [channel permissions](/help/channel-permissions), including @@ -65,6 +65,7 @@ role](/help/roles-and-permissions#change-a-users-role) if needed. ## Related articles * [Change a user's role](/help/change-a-users-role) +* [User groups](/help/user-groups) * [Channel permissions](/help/channel-permissions) * [Inviting new users](/help/invite-new-users) * [Zulip Cloud billing](/help/zulip-cloud-billing) diff --git a/help/search-for-messages.md b/help/search-for-messages.md index 167e7a4e51..0c3a619d4e 100644 --- a/help/search-for-messages.md +++ b/help/search-for-messages.md @@ -41,9 +41,7 @@ Some details to keep in mind: - Zulip search ignores very common words like `a`, `the`, and about 100 others. - [Emoji](/help/emoji-and-emoticons) in messages (but not [emoji reactions](/help/emoji-reactions)) are included in searches, so if you search - for `octopus`, your results will include messages with the `:octopus:` emoji ( - octopus). + for `thumbs_up`, your results will include messages with the `:thumbs_up:` emoji (👍). ## Search filters @@ -184,6 +182,6 @@ A summary of the search filters above is available in the Zulip app. ## Related articles * [Configure multi-language search](/help/configure-multi-language-search) -* [Search people](/help/user-list#search-people) +* [Filter users](/help/user-list#filter-users) * [Link to a message or conversation](/help/link-to-a-message-or-conversation#link-to-zulip-from-anywhere) diff --git a/help/user-groups.md b/help/user-groups.md index 1d523d5e37..326a4ce574 100644 --- a/help/user-groups.md +++ b/help/user-groups.md @@ -2,6 +2,8 @@ {!user-groups-intro.md!} +{!user-groups-applications.md!} + ## Browse and join user groups {start_tabs} diff --git a/help/user-list.md b/help/user-list.md index 1294c90008..a3c3fd0a42 100644 --- a/help/user-list.md +++ b/help/user-list.md @@ -1,33 +1,32 @@ # User list In the Zulip web and desktop app, the right sidebar shows a list of users in -your organization. It shows -[non-deactivated](/help/deactivate-or-reactivate-a-user) users, and does not -include [bots](/help/bots-overview). +your organization. The user list has up to three section: -In organizations with up to 600 users, everyone is shown. In larger organizations, -only users who have been active in the last two weeks are listed. All users are -included when you search for people. +- **In this conversation**: Recent participants in the conversation you're viewing. +- **In this channel** or **Others in this channel**: Subscribers to the channel you're viewing. +- **Others**: Everyone else. -When you view a channel or a topic within a channel, subscribers to that channel -are shown separately from other members of your organization. To avoid -distraction, you can hide the user list any time. +In organizations with up to 600 users, everyone is shown. In larger +organizations, only users who have been active in the last two weeks are shown, +but everyone is included when you [search](#filter-users). +[Deactivated users](/help/deactivate-or-reactivate-a-user) and +[bots](/help/bots-overview) are not listed. -## Search people +To avoid distraction, you can [hide](#show-or-hide-the-user-list) the user list +any time. + +## Filter users {start_tabs} {tab|desktop-web} -1. If the user list in the right sidebar is hidden, click the **user list** ( ) icon in the upper right to -show it. +1. If the user list is hidden, click the **user list** ( ) icon in the upper right to show it. -1. Click the **search** () icon at the top of the right sidebar to open the - search box. - -1. Type the name of the user you are looking for. +1. Type the name of the user you are looking for in the **Filter users** box at + the top of the right sidebar. !!! keyboard_tip "" @@ -47,7 +46,7 @@ show it. !!! keyboard_tip "" - The W keyboard shortcut to search people reveals the user list if + The W keyboard shortcut to filter users reveals the user list if it is hidden. {end_tabs} diff --git a/package.json b/package.json index 8357f621b7..b9edc1988b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "packageManager": "pnpm@9.12.0+sha512.4abf725084d7bcbafbd728bfc7bee61f2f791f977fd87542b3579dcb23504d170d46337945e4c66485cd12d588a0c0e570ed9c477e7ccdd8507cf05f3f92eaca", + "packageManager": "pnpm@9.12.3+sha512.cce0f9de9c5a7c95bef944169cc5dfe8741abfb145078c0d508b868056848a87c81e626246cb60967cbd7fd29a6c062ef73ff840d96b3c86c40ac92cf4a813ee", "dependencies": { "@babel/core": "^7.5.5", "@babel/preset-env": "^7.5.5", @@ -79,6 +79,7 @@ "stackframe": "^1.3.4", "stacktrace-gps": "^3.0.4", "style-loader": "^4.0.0", + "stylelint-high-performance-animation": "^1.10.0", "text-field-edit": "^4.0.0", "textarea-caret": "^3.1.0", "tippy.js": "^6.3.7", @@ -108,7 +109,7 @@ "@types/lodash": "^4.14.172", "@types/micromodal": "^0.3.3", "@types/minimalistic-assert": "^1.0.1", - "@types/node": "^20.11.20", + "@types/node": "^22.9.0", "@types/plotly.js": "^2.12.20", "@types/sortablejs": "^1.15.1", "@types/spectrum": "^1.8.4", @@ -151,7 +152,7 @@ "swagger-parser": "^10.0.0", "ts-node": "^10.0.0", "typescript": "^5.0.2", - "vnu-jar": "^23.4.11", + "vnu-jar": "^24.10.17", "webpack-dev-server": "^5.0.2", "xvfb": "^0.4.0", "yaml": "^2.0.0-8", @@ -163,7 +164,6 @@ "source-map@^0.6": "npm:source-map-js@^1.2.1" }, "patchedDependencies": { - "puppeteer-core": "patches/puppeteer-core.patch", "source-sans@3.46.0": "patches/source-sans@3.46.0.patch", "tippy.js@6.3.7": "patches/tippy.js@6.3.7.patch" } diff --git a/patches/puppeteer-core.patch b/patches/puppeteer-core.patch deleted file mode 100644 index 87a2a14634..0000000000 --- a/patches/puppeteer-core.patch +++ /dev/null @@ -1,56 +0,0 @@ -diff --git a/lib/cjs/puppeteer/common/QueryHandler.js b/lib/cjs/puppeteer/common/QueryHandler.js -index db0ff2ee001ac452b6de5804ec8810e566992299..13437d905fe4fca94ad69e4e2fd95ba6b2cce89a 100644 ---- a/lib/cjs/puppeteer/common/QueryHandler.js -+++ b/lib/cjs/puppeteer/common/QueryHandler.js -@@ -163,8 +163,7 @@ class QueryHandler { - return await frame.isolatedRealm().adoptHandle(elementOrFrame); - })(), false); - const { visible = false, hidden = false, timeout, signal } = options; -- const polling = options.polling ?? -- (visible || hidden ? "raf" /* PollingOptions.RAF */ : "mutation" /* PollingOptions.MUTATION */); -+ const polling = visible || hidden ? "raf" /* PollingOptions.RAF */ : options.polling; - try { - const env_4 = { stack: [], error: void 0, hasError: false }; - try { -diff --git a/lib/es5-iife/puppeteer-core-browser.js b/lib/es5-iife/puppeteer-core-browser.js -index 3262ea9f5fed2d0e3d7a0ece16ad31c7e5bc4cf6..f73deab795a92d80c913adafb6c3eff7eda5e5e0 100644 ---- a/lib/es5-iife/puppeteer-core-browser.js -+++ b/lib/es5-iife/puppeteer-core-browser.js -@@ -4594,7 +4594,7 @@ var Puppeteer = function (exports, _PuppeteerURL, _LazyArg, _ARIAQueryHandler, _ - timeout, - signal - } = options; -- const polling = options.polling ?? (visible || hidden ? "raf" /* PollingOptions.RAF */ : "mutation" /* PollingOptions.MUTATION */); -+ const polling = visible || hidden ? "raf" /* PollingOptions.RAF */ : options.polling; - try { - const env_4 = { - stack: [], -diff --git a/lib/esm/puppeteer/common/QueryHandler.js b/lib/esm/puppeteer/common/QueryHandler.js -index b07ddf54fb1830c8ddc42dc6d0452b288696caa4..cb0203c7c1f98f03ea6658b33c8da3710ab71b29 100644 ---- a/lib/esm/puppeteer/common/QueryHandler.js -+++ b/lib/esm/puppeteer/common/QueryHandler.js -@@ -160,8 +160,7 @@ export class QueryHandler { - return await frame.isolatedRealm().adoptHandle(elementOrFrame); - })(), false); - const { visible = false, hidden = false, timeout, signal } = options; -- const polling = options.polling ?? -- (visible || hidden ? "raf" /* PollingOptions.RAF */ : "mutation" /* PollingOptions.MUTATION */); -+ const polling = visible || hidden ? "raf" /* PollingOptions.RAF */ : options.polling; - try { - const env_4 = { stack: [], error: void 0, hasError: false }; - try { -diff --git a/src/common/QueryHandler.ts b/src/common/QueryHandler.ts -index f377771f22a193164760e703c2f3af123588ab16..db10e1cf94ada080c710f33d4dda6f4a116400fa 100644 ---- a/src/common/QueryHandler.ts -+++ b/src/common/QueryHandler.ts -@@ -162,9 +162,7 @@ export class QueryHandler { - })(); - - const {visible = false, hidden = false, timeout, signal} = options; -- const polling = -- options.polling ?? -- (visible || hidden ? PollingOptions.RAF : PollingOptions.MUTATION); -+ const polling = visible || hidden ? PollingOptions.RAF : options.polling; - - try { - signal?.throwIfAborted(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45ebb456bc..55c1df8655 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,6 @@ overrides: source-map@^0.6: npm:source-map-js@^1.2.1 patchedDependencies: - puppeteer-core: - hash: arlsztxuq6xlcowulqo36wnkoa - path: patches/puppeteer-core.patch source-sans@3.46.0: hash: 4n7ij66tzyhzaqnxsenbilrxr4 path: patches/source-sans@3.46.0.patch @@ -24,28 +21,28 @@ importers: dependencies: '@babel/core': specifier: ^7.5.5 - version: 7.25.8 + version: 7.26.0 '@babel/preset-env': specifier: ^7.5.5 - version: 7.25.8(@babel/core@7.25.8) + version: 7.26.0(@babel/core@7.26.0) '@babel/preset-typescript': specifier: ^7.15.0 - version: 7.25.7(@babel/core@7.25.8) + version: 7.26.0(@babel/core@7.26.0) '@babel/register': specifier: ^7.6.2 - version: 7.25.7(@babel/core@7.25.8) + version: 7.25.9(@babel/core@7.26.0) '@fontsource-variable/open-sans': specifier: ^5.0.9 version: 5.1.0 '@formatjs/intl': specifier: ^2.0.0 - version: 2.10.8(typescript@5.6.3) + version: 2.10.14(typescript@5.6.3) '@gfx/zopfli': specifier: ^1.0.15 version: 1.0.15 '@giphy/js-components': specifier: ^5.13.0 - version: 5.13.0(@babel/core@7.25.8) + version: 5.13.0(@babel/core@7.26.0) '@giphy/js-fetch-api': specifier: ^5.6.0 version: 5.6.0 @@ -54,22 +51,22 @@ importers: version: 5.1.1(koa@2.15.3) '@sentry/browser': specifier: ^8.33.1 - version: 8.34.0 + version: 8.37.1 '@sentry/core': specifier: ^8.33.1 - version: 8.34.0 + version: 8.37.1 '@uppy/core': specifier: ^4.2.0 - version: 4.2.2 + version: 4.2.3 '@uppy/drag-drop': specifier: ^4.0.2 - version: 4.0.3(@uppy/core@4.2.2) + version: 4.0.4(@uppy/core@4.2.3) '@uppy/progress-bar': specifier: ^4.0.0 - version: 4.0.0(@uppy/core@4.2.2) + version: 4.0.1(@uppy/core@4.2.3) '@uppy/tus': specifier: ^4.1.0 - version: 4.1.2(@uppy/core@4.2.2) + version: 4.1.3(@uppy/core@4.2.3) '@zxcvbn-ts/core': specifier: ^3.0.1 version: 3.0.4 @@ -84,10 +81,10 @@ importers: version: 5.0.2 babel-loader: specifier: ^9.1.0 - version: 9.2.1(@babel/core@7.25.8)(webpack@5.95.0) + version: 9.2.1(@babel/core@7.26.0)(webpack@5.96.1) babel-plugin-formatjs: specifier: ^10.2.6 - version: 10.5.18 + version: 10.5.24 blueimp-md5: specifier: ^2.10.0 version: 2.19.0 @@ -102,16 +99,16 @@ importers: version: 2.9.3 compression-webpack-plugin: specifier: ^11.1.0 - version: 11.1.0(webpack@5.95.0) + version: 11.1.0(webpack@5.96.1) core-js: specifier: ^3.37.0 - version: 3.38.1 + version: 3.39.0 css-loader: specifier: ^7.1.1 - version: 7.1.2(webpack@5.95.0) + version: 7.1.2(webpack@5.96.1) css-minimizer-webpack-plugin: specifier: ^7.0.0 - version: 7.0.0(clean-css@5.3.3)(webpack@5.95.0) + version: 7.0.0(clean-css@5.3.3)(webpack@5.96.1) date-fns: specifier: ^4.1.0 version: 4.1.0 @@ -132,7 +129,7 @@ importers: version: 2.1.4 expose-loader: specifier: ^5.0.0 - version: 5.0.0(webpack@5.95.0) + version: 5.0.0(webpack@5.96.1) flatpickr: specifier: ^4.5.7 version: 4.6.13 @@ -153,10 +150,10 @@ importers: version: 1.7.3(handlebars@4.7.8) html-webpack-plugin: specifier: ^5.3.2 - version: 5.6.0(webpack@5.95.0) + version: 5.6.3(webpack@5.96.1) intl-messageformat: specifier: ^10.3.0 - version: 10.7.0 + version: 10.7.6 is-url: specifier: ^1.2.4 version: 1.2.4 @@ -186,7 +183,7 @@ importers: version: 0.4.10 mini-css-extract-plugin: specifier: ^2.2.2 - version: 2.9.1(webpack@5.95.0) + version: 2.9.2(webpack@5.96.1) minimalistic-assert: specifier: ^1.0.1 version: 1.0.1 @@ -195,7 +192,7 @@ importers: version: 9.4.3 plotly.js: specifier: ^2.0.0 - version: 2.35.2(mapbox-gl@1.13.3)(webpack@5.95.0) + version: 2.35.2(mapbox-gl@1.13.3)(webpack@5.96.1) postcss: specifier: ^8.0.3 version: 8.4.47 @@ -207,13 +204,13 @@ importers: version: 16.1.0(postcss@8.4.47) postcss-loader: specifier: ^8.0.0 - version: 8.1.1(postcss@8.4.47)(typescript@5.6.3)(webpack@5.95.0) + version: 8.1.1(postcss@8.4.47)(typescript@5.6.3)(webpack@5.96.1) postcss-prefixwrap: specifier: ^1.24.0 version: 1.52.0(postcss@8.4.47) postcss-preset-env: specifier: ^10.0.2 - version: 10.0.7(postcss@8.4.47) + version: 10.0.9(postcss@8.4.47) postcss-simple-vars: specifier: ^7.0.0 version: 7.0.1(postcss@8.4.47) @@ -252,7 +249,10 @@ importers: version: 3.1.2 style-loader: specifier: ^4.0.0 - version: 4.0.0(webpack@5.95.0) + version: 4.0.0(webpack@5.96.1) + stylelint-high-performance-animation: + specifier: ^1.10.0 + version: 1.10.0(stylelint@16.10.0(typescript@5.6.3)) text-field-edit: specifier: ^4.0.0 version: 4.1.1 @@ -267,7 +267,7 @@ importers: version: 7.2.0 url-loader: specifier: ^4.1.1 - version: 4.1.1(webpack@5.95.0) + version: 4.1.1(webpack@5.96.1) url-template: specifier: ^2.0.8 version: 2.0.8 @@ -276,13 +276,13 @@ importers: version: 8.0.1 webpack: specifier: ^5.61.0 - version: 5.95.0(webpack-cli@5.1.4) + version: 5.96.1(webpack-cli@5.1.4) webpack-bundle-tracker: specifier: ^3.0.1 version: 3.1.1 webpack-cli: specifier: ^5.0.1 - version: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.95.0) + version: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.96.1) winchan: specifier: ^0.2.1 version: 0.2.2 @@ -292,13 +292,13 @@ importers: devDependencies: '@babel/eslint-parser': specifier: ^7.11.3 - version: 7.25.8(@babel/core@7.25.8)(eslint@8.57.1) + version: 7.25.9(@babel/core@7.26.0)(eslint@8.57.1) '@babel/plugin-transform-modules-commonjs': specifier: ^7.19.6 - version: 7.25.7(@babel/core@7.25.8) + version: 7.25.9(@babel/core@7.26.0) '@formatjs/cli': specifier: ^6.0.0 - version: 6.2.15 + version: 6.3.8 '@types/autosize': specifier: ^4.0.1 version: 4.0.3 @@ -313,7 +313,7 @@ importers: version: 1.2.32 '@types/jquery': specifier: ^3.3.31 - version: 3.5.31 + version: 3.5.32 '@types/jquery.validation': specifier: ^1.16.7 version: 1.17.0 @@ -328,7 +328,7 @@ importers: version: 2.15.0 '@types/lodash': specifier: ^4.14.172 - version: 4.17.10 + version: 4.17.13 '@types/micromodal': specifier: ^0.3.3 version: 0.3.5 @@ -336,11 +336,11 @@ importers: specifier: ^1.0.1 version: 1.0.3 '@types/node': - specifier: ^20.11.20 - version: 20.16.11 + specifier: ^22.9.0 + version: 22.9.0 '@types/plotly.js': specifier: ^2.12.20 - version: 2.33.4 + version: 2.33.5 '@types/sortablejs': specifier: ^1.15.1 version: 1.15.8 @@ -358,10 +358,10 @@ importers: version: 5.0.5 '@typescript-eslint/eslint-plugin': specifier: ^8.2.0 - version: 8.9.0(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) + version: 8.13.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) '@typescript-eslint/parser': specifier: ^8.2.0 - version: 8.9.0(eslint@8.57.1)(typescript@5.6.3) + version: 8.13.0(eslint@8.57.1)(typescript@5.6.3) babel-plugin-istanbul: specifier: ^7.0.0 version: 7.0.0 @@ -406,13 +406,13 @@ importers: version: 9.1.0(eslint@8.57.1) eslint-import-resolver-webpack: specifier: ^0.13.4 - version: 0.13.9(eslint-plugin-import@2.31.0)(webpack@5.95.0) + version: 0.13.9(eslint-plugin-import@2.31.0)(webpack@5.96.1) eslint-plugin-formatjs: specifier: ^5.0.0 - version: 5.1.0(eslint@8.57.1) + version: 5.2.2(eslint@8.57.1)(typescript@5.6.3) eslint-plugin-import: specifier: ^2.22.0 - version: 2.31.0(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-webpack@0.13.9)(eslint@8.57.1) + version: 2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-webpack@0.13.9)(eslint@8.57.1) eslint-plugin-no-jquery: specifier: ^3.0.2 version: 3.0.2(eslint@8.57.1) @@ -442,7 +442,7 @@ importers: version: 3.3.3 puppeteer: specifier: ^23.5.0 - version: 23.5.3(typescript@5.6.3) + version: 23.7.0(typescript@5.6.3) source-map: specifier: npm:source-map-js@^1.2.1 version: source-map-js@1.2.1 @@ -460,16 +460,16 @@ importers: version: 10.0.3(openapi-types@12.1.3) ts-node: specifier: ^10.0.0 - version: 10.9.2(@types/node@20.16.11)(typescript@5.6.3) + version: 10.9.2(@types/node@22.9.0)(typescript@5.6.3) typescript: specifier: ^5.0.2 version: 5.6.3 vnu-jar: - specifier: ^23.4.11 - version: 23.4.11 + specifier: ^24.10.17 + version: 24.10.17 webpack-dev-server: specifier: ^5.0.2 - version: 5.1.0(webpack-cli@5.1.4)(webpack@5.95.0) + version: 5.1.0(webpack-cli@5.1.4)(webpack@5.96.1) xvfb: specifier: ^0.4.0 version: 0.4.0 @@ -481,7 +481,7 @@ importers: version: 17.7.2 zulip-js: specifier: ^2.0.8 - version: 2.0.9(encoding@0.1.13) + version: 2.1.0(encoding@0.1.13) help-beta: dependencies: @@ -490,10 +490,10 @@ importers: version: 0.9.4(prettier@3.3.3)(typescript@5.6.3) '@astrojs/starlight': specifier: ^0.28.2 - version: 0.28.3(astro@4.16.5(@types/node@20.16.11)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)) + version: 0.28.6(astro@4.16.9(@types/node@22.9.0)(rollup@4.24.4)(terser@5.36.0)(typescript@5.6.3)) astro: specifier: ^4.10.2 - version: 4.16.5(@types/node@20.16.11)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) + version: 4.16.9(@types/node@22.9.0)(rollup@4.24.4)(terser@5.36.0)(typescript@5.6.3) sharp: specifier: ^0.33.5 version: 0.33.5 @@ -537,8 +537,8 @@ packages: '@astrojs/internal-helpers@0.4.1': resolution: {integrity: sha512-bMf9jFihO8YP940uD70SI/RDzIhUHJAolWVcO1v5PUivxGKvfLZTLTVVxEYzGYyPsA3ivdLNqMnL5VgmQySa+g==} - '@astrojs/language-server@2.15.0': - resolution: {integrity: sha512-wJHSjGApm5X8Rg1GvkevoatZBfvaFizY4kCPvuSYgs3jGCobuY3KstJGKC1yNLsRJlDweHruP+J54iKn9vEKoA==} + '@astrojs/language-server@2.15.4': + resolution: {integrity: sha512-JivzASqTPR2bao9BWsSc/woPHH7OGSGc9aMxXL4U6egVTqBycB3ZHdBJPuOCVtcGLrzdWTosAqVPz1BVoxE0+A==} hasBin: true peerDependencies: prettier: ^3.0.0 @@ -552,8 +552,8 @@ packages: '@astrojs/markdown-remark@5.3.0': resolution: {integrity: sha512-r0Ikqr0e6ozPb5bvhup1qdWnSPUvQu6tub4ZLYaKyG50BXZ0ej6FhGz3GpChKpH7kglRFPObJd/bDyf2VM9pkg==} - '@astrojs/mdx@3.1.8': - resolution: {integrity: sha512-4o/+pvgoLFG0eG96cFs4t3NzZAIAOYu57fKAprWHXJrnq/qdBV0av6BYDjoESxvxNILUYoj8sdZVWtlPWVDLog==} + '@astrojs/mdx@3.1.9': + resolution: {integrity: sha512-3jPD4Bff6lIA20RQoonnZkRtZ9T3i0HFm6fcDF7BMsKIZ+xBP2KXzQWiuGu62lrVCmU612N+SQVGl5e0fI+zWg==} engines: {node: ^18.17.1 || ^20.3.0 || >=21.0.0} peerDependencies: astro: ^4.8.0 @@ -565,8 +565,8 @@ packages: '@astrojs/sitemap@3.2.1': resolution: {integrity: sha512-uxMfO8f7pALq0ADL6Lk68UV6dNYjJ2xGUzyjjVj60JLBs5a6smtlkBYv3tQ0DzoqwS7c9n4FUx5lgv0yPo/fgA==} - '@astrojs/starlight@0.28.3': - resolution: {integrity: sha512-GXXIPKSu5d50mLVtgI4jf6pb3FPQm8n4MI6ZXuQQqqnA0xg7PJQ76WFSVyrICeqM5fKABSqcBksp/glyEJes/A==} + '@astrojs/starlight@0.28.6': + resolution: {integrity: sha512-lY+rbRMIVxDGiXhS4lBuVrU2jTUezEt4QeTxUTHxfj2tuKBwquG7Jg+alON6l+uaV+anbOkFb001MMXZF8X85w==} peerDependencies: astro: ^4.14.0 @@ -574,52 +574,52 @@ packages: resolution: {integrity: sha512-/ca/+D8MIKEC8/A9cSaPUqQNZm+Es/ZinRv0ZAzvu2ios7POQSsVD+VOj7/hypWNsNM3T7RpfgNq7H2TU1KEHA==} engines: {node: ^18.17.1 || ^20.3.0 || >=21.0.0} - '@astrojs/yaml2ts@0.2.1': - resolution: {integrity: sha512-CBaNwDQJz20E5WxzQh4thLVfhB3JEEGz72wRA+oJp6fQR37QLAqXZJU0mHC+yqMOQ6oj0GfRPJrz6hjf+zm6zA==} + '@astrojs/yaml2ts@0.2.2': + resolution: {integrity: sha512-GOfvSr5Nqy2z5XiwqTouBBpy5FyI6DEe+/g/Mk5am9SjILN1S5fOEvYK0GuWHg98yS/dobP4m8qyqw/URW35fQ==} - '@babel/code-frame@7.25.7': - resolution: {integrity: sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==} + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.25.8': - resolution: {integrity: sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==} + '@babel/compat-data@7.26.2': + resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} engines: {node: '>=6.9.0'} - '@babel/core@7.25.8': - resolution: {integrity: sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==} + '@babel/core@7.26.0': + resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} engines: {node: '>=6.9.0'} - '@babel/eslint-parser@7.25.8': - resolution: {integrity: sha512-Po3VLMN7fJtv0nsOjBDSbO1J71UhzShE9MuOSkWEV9IZQXzhZklYtzKZ8ZD/Ij3a0JBv1AG3Ny2L3jvAHQVOGg==} + '@babel/eslint-parser@7.25.9': + resolution: {integrity: sha512-5UXfgpK0j0Xr/xIdgdLEhOFxaDZ0bRPWJJchRpqOSur/3rZoPbqqki5mm0p4NE2cs28krBEiSM2MB7//afRSQQ==} engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} peerDependencies: '@babel/core': ^7.11.0 eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 - '@babel/generator@7.25.7': - resolution: {integrity: sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==} + '@babel/generator@7.26.2': + resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@7.25.7': - resolution: {integrity: sha512-4xwU8StnqnlIhhioZf1tqnVWeQ9pvH/ujS8hRfw/WOza+/a+1qv69BWNy+oY231maTCWgKWhfBU7kDpsds6zAA==} + '@babel/helper-annotate-as-pure@7.25.9': + resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} engines: {node: '>=6.9.0'} - '@babel/helper-builder-binary-assignment-operator-visitor@7.25.7': - resolution: {integrity: sha512-12xfNeKNH7jubQNm7PAkzlLwEmCs1tfuX3UjIw6vP6QXi+leKh6+LyC/+Ed4EIQermwd58wsyh070yjDHFlNGg==} + '@babel/helper-builder-binary-assignment-operator-visitor@7.25.9': + resolution: {integrity: sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.25.7': - resolution: {integrity: sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==} + '@babel/helper-compilation-targets@7.25.9': + resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.25.7': - resolution: {integrity: sha512-bD4WQhbkx80mAyj/WCm4ZHcF4rDxkoLFO6ph8/5/mQ3z4vAzltQXAmbc7GvVJx5H+lk5Mi5EmbTeox5nMGCsbw==} + '@babel/helper-create-class-features-plugin@7.25.9': + resolution: {integrity: sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-create-regexp-features-plugin@7.25.7': - resolution: {integrity: sha512-byHhumTj/X47wJ6C6eLpK7wW/WBEcnUeb7D0FNc/jFQnQVw7DOso3Zz5u9x/zLrFVkHa89ZGDbkAa1D54NdrCQ==} + '@babel/helper-create-regexp-features-plugin@7.25.9': + resolution: {integrity: sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -629,103 +629,99 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - '@babel/helper-member-expression-to-functions@7.25.7': - resolution: {integrity: sha512-O31Ssjd5K6lPbTX9AAYpSKrZmLeagt9uwschJd+Ixo6QiRyfpvgtVQp8qrDR9UNFjZ8+DO34ZkdrN+BnPXemeA==} + '@babel/helper-member-expression-to-functions@7.25.9': + resolution: {integrity: sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.25.7': - resolution: {integrity: sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==} + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.25.7': - resolution: {integrity: sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==} + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-optimise-call-expression@7.25.7': - resolution: {integrity: sha512-VAwcwuYhv/AT+Vfr28c9y6SHzTan1ryqrydSTFGjU0uDJHw3uZ+PduI8plCLkRsDnqK2DMEDmwrOQRsK/Ykjng==} + '@babel/helper-optimise-call-expression@7.25.9': + resolution: {integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==} engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.25.7': - resolution: {integrity: sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==} + '@babel/helper-plugin-utils@7.25.9': + resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} engines: {node: '>=6.9.0'} - '@babel/helper-remap-async-to-generator@7.25.7': - resolution: {integrity: sha512-kRGE89hLnPfcz6fTrlNU+uhgcwv0mBE4Gv3P9Ke9kLVJYpi4AMVVEElXvB5CabrPZW4nCM8P8UyyjrzCM0O2sw==} + '@babel/helper-remap-async-to-generator@7.25.9': + resolution: {integrity: sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-replace-supers@7.25.7': - resolution: {integrity: sha512-iy8JhqlUW9PtZkd4pHM96v6BdJ66Ba9yWSE4z0W4TvSZwLBPkyDsiIU3ENe4SmrzRBs76F7rQXTy1lYC49n6Lw==} + '@babel/helper-replace-supers@7.25.9': + resolution: {integrity: sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-simple-access@7.25.7': - resolution: {integrity: sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==} + '@babel/helper-simple-access@7.25.9': + resolution: {integrity: sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==} engines: {node: '>=6.9.0'} - '@babel/helper-skip-transparent-expression-wrappers@7.25.7': - resolution: {integrity: sha512-pPbNbchZBkPMD50K0p3JGcFMNLVUCuU/ABybm/PGNj4JiHrpmNyqqCphBk4i19xXtNV0JhldQJJtbSW5aUvbyA==} + '@babel/helper-skip-transparent-expression-wrappers@7.25.9': + resolution: {integrity: sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.25.7': - resolution: {integrity: sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==} + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.25.7': - resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==} + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.25.7': - resolution: {integrity: sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==} + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} engines: {node: '>=6.9.0'} - '@babel/helper-wrap-function@7.25.7': - resolution: {integrity: sha512-MA0roW3JF2bD1ptAaJnvcabsVlNQShUaThyJbCDD4bCp8NEgiFvpoqRI2YS22hHlc2thjO/fTg2ShLMC3jygAg==} + '@babel/helper-wrap-function@7.25.9': + resolution: {integrity: sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.25.7': - resolution: {integrity: sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==} + '@babel/helpers@7.26.0': + resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} engines: {node: '>=6.9.0'} - '@babel/highlight@7.25.7': - resolution: {integrity: sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.25.8': - resolution: {integrity: sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==} + '@babel/parser@7.26.2': + resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.7': - resolution: {integrity: sha512-UV9Lg53zyebzD1DwQoT9mzkEKa922LNUp5YkTJ6Uta0RbyXaQNUgcvSt7qIu1PpPzVb6rd10OVNTzkyBGeVmxQ==} + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9': + resolution: {integrity: sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.7': - resolution: {integrity: sha512-GDDWeVLNxRIkQTnJn2pDOM1pkCgYdSqPeT1a9vh9yIqu2uzzgw1zcqEb+IJOhy+dTBMlNdThrDIksr2o09qrrQ==} + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9': + resolution: {integrity: sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.7': - resolution: {integrity: sha512-wxyWg2RYaSUYgmd9MR0FyRGyeOMQE/Uzr1wzd/g5cf5bwi9A4v6HFdDm7y1MgDtod/fLOSTZY6jDgV0xU9d5bA==} + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9': + resolution: {integrity: sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.7': - resolution: {integrity: sha512-Xwg6tZpLxc4iQjorYsyGMyfJE7nP5MV8t/Ka58BgiA7Jw0fRqQNcANlLfdJ/yvBt9z9LD2We+BEkT7vLqZRWng==} + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9': + resolution: {integrity: sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.13.0 - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.7': - resolution: {integrity: sha512-UVATLMidXrnH+GMUIuxq55nejlj02HP7F5ETyBONzP6G87fPBogG4CH6kxrSrdIuAjdwNO9VzyaYsrZPscWUrw==} + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9': + resolution: {integrity: sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -736,26 +732,26 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-assertions@7.25.7': - resolution: {integrity: sha512-ZvZQRmME0zfJnDQnVBKYzHxXT7lYBB3Revz1GuS7oLXWMgqUPX4G+DDbT30ICClht9WKV34QVrZhSw6WdklwZQ==} + '@babel/plugin-syntax-import-assertions@7.26.0': + resolution: {integrity: sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-attributes@7.25.7': - resolution: {integrity: sha512-AqVo+dguCgmpi/3mYBdu9lkngOBlQ2w2vnNpa6gfiCxQZLzV4ZbhsXitJ2Yblkoe1VQwtHSaNmIaGll/26YWRw==} + '@babel/plugin-syntax-import-attributes@7.26.0': + resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-jsx@7.25.7': - resolution: {integrity: sha512-ruZOnKO+ajVL/MVx+PwNBPOkrnXTXoWMtte1MBpegfCArhqOe3Bj52avVj1huLLxNKYKXYaSxZ2F+woK1ekXfw==} + '@babel/plugin-syntax-jsx@7.25.9': + resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-typescript@7.25.7': - resolution: {integrity: sha512-rR+5FDjpCHqqZN2bzZm18bVYGaejGq5ZkpVCJLXor/+zlSrSoc4KWcHI0URVWjl/68Dyr1uwZUz/1njycEAv9g==} + '@babel/plugin-syntax-typescript@7.25.9': + resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -766,314 +762,320 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-transform-arrow-functions@7.25.7': - resolution: {integrity: sha512-EJN2mKxDwfOUCPxMO6MUI58RN3ganiRAG/MS/S3HfB6QFNjroAMelQo/gybyYq97WerCBAZoyrAoW8Tzdq2jWg==} + '@babel/plugin-transform-arrow-functions@7.25.9': + resolution: {integrity: sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-async-generator-functions@7.25.8': - resolution: {integrity: sha512-9ypqkozyzpG+HxlH4o4gdctalFGIjjdufzo7I2XPda0iBnZ6a+FO0rIEQcdSPXp02CkvGsII1exJhmROPQd5oA==} + '@babel/plugin-transform-async-generator-functions@7.25.9': + resolution: {integrity: sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-async-to-generator@7.25.7': - resolution: {integrity: sha512-ZUCjAavsh5CESCmi/xCpX1qcCaAglzs/7tmuvoFnJgA1dM7gQplsguljoTg+Ru8WENpX89cQyAtWoaE0I3X3Pg==} + '@babel/plugin-transform-async-to-generator@7.25.9': + resolution: {integrity: sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-block-scoped-functions@7.25.7': - resolution: {integrity: sha512-xHttvIM9fvqW+0a3tZlYcZYSBpSWzGBFIt/sYG3tcdSzBB8ZeVgz2gBP7Df+sM0N1850jrviYSSeUuc+135dmQ==} + '@babel/plugin-transform-block-scoped-functions@7.25.9': + resolution: {integrity: sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-block-scoping@7.25.7': - resolution: {integrity: sha512-ZEPJSkVZaeTFG/m2PARwLZQ+OG0vFIhPlKHK/JdIMy8DbRJ/htz6LRrTFtdzxi9EHmcwbNPAKDnadpNSIW+Aow==} + '@babel/plugin-transform-block-scoping@7.25.9': + resolution: {integrity: sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-properties@7.25.7': - resolution: {integrity: sha512-mhyfEW4gufjIqYFo9krXHJ3ElbFLIze5IDp+wQTxoPd+mwFb1NxatNAwmv8Q8Iuxv7Zc+q8EkiMQwc9IhyGf4g==} + '@babel/plugin-transform-class-properties@7.25.9': + resolution: {integrity: sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-static-block@7.25.8': - resolution: {integrity: sha512-e82gl3TCorath6YLf9xUwFehVvjvfqFhdOo4+0iVIVju+6XOi5XHkqB3P2AXnSwoeTX0HBoXq5gJFtvotJzFnQ==} + '@babel/plugin-transform-class-static-block@7.26.0': + resolution: {integrity: sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.12.0 - '@babel/plugin-transform-classes@7.25.7': - resolution: {integrity: sha512-9j9rnl+YCQY0IGoeipXvnk3niWicIB6kCsWRGLwX241qSXpbA4MKxtp/EdvFxsc4zI5vqfLxzOd0twIJ7I99zg==} + '@babel/plugin-transform-classes@7.25.9': + resolution: {integrity: sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-computed-properties@7.25.7': - resolution: {integrity: sha512-QIv+imtM+EtNxg/XBKL3hiWjgdLjMOmZ+XzQwSgmBfKbfxUjBzGgVPklUuE55eq5/uVoh8gg3dqlrwR/jw3ZeA==} + '@babel/plugin-transform-computed-properties@7.25.9': + resolution: {integrity: sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-destructuring@7.25.7': - resolution: {integrity: sha512-xKcfLTlJYUczdaM1+epcdh1UGewJqr9zATgrNHcLBcV2QmfvPPEixo/sK/syql9cEmbr7ulu5HMFG5vbbt/sEA==} + '@babel/plugin-transform-destructuring@7.25.9': + resolution: {integrity: sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-dotall-regex@7.25.7': - resolution: {integrity: sha512-kXzXMMRzAtJdDEgQBLF4oaiT6ZCU3oWHgpARnTKDAqPkDJ+bs3NrZb310YYevR5QlRo3Kn7dzzIdHbZm1VzJdQ==} + '@babel/plugin-transform-dotall-regex@7.25.9': + resolution: {integrity: sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-duplicate-keys@7.25.7': - resolution: {integrity: sha512-by+v2CjoL3aMnWDOyCIg+yxU9KXSRa9tN6MbqggH5xvymmr9p4AMjYkNlQy4brMceBnUyHZ9G8RnpvT8wP7Cfg==} + '@babel/plugin-transform-duplicate-keys@7.25.9': + resolution: {integrity: sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.7': - resolution: {integrity: sha512-HvS6JF66xSS5rNKXLqkk7L9c/jZ/cdIVIcoPVrnl8IsVpLggTjXs8OWekbLHs/VtYDDh5WXnQyeE3PPUGm22MA==} + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9': + resolution: {integrity: sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-transform-dynamic-import@7.25.8': - resolution: {integrity: sha512-gznWY+mr4ZQL/EWPcbBQUP3BXS5FwZp8RUOw06BaRn8tQLzN4XLIxXejpHN9Qo8x8jjBmAAKp6FoS51AgkSA/A==} + '@babel/plugin-transform-dynamic-import@7.25.9': + resolution: {integrity: sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-exponentiation-operator@7.25.7': - resolution: {integrity: sha512-yjqtpstPfZ0h/y40fAXRv2snciYr0OAoMXY/0ClC7tm4C/nG5NJKmIItlaYlLbIVAWNfrYuy9dq1bE0SbX0PEg==} + '@babel/plugin-transform-exponentiation-operator@7.25.9': + resolution: {integrity: sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-export-namespace-from@7.25.8': - resolution: {integrity: sha512-sPtYrduWINTQTW7FtOy99VCTWp4H23UX7vYcut7S4CIMEXU+54zKX9uCoGkLsWXteyaMXzVHgzWbLfQ1w4GZgw==} + '@babel/plugin-transform-export-namespace-from@7.25.9': + resolution: {integrity: sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-for-of@7.25.7': - resolution: {integrity: sha512-n/TaiBGJxYFWvpJDfsxSj9lEEE44BFM1EPGz4KEiTipTgkoFVVcCmzAL3qA7fdQU96dpo4gGf5HBx/KnDvqiHw==} + '@babel/plugin-transform-for-of@7.25.9': + resolution: {integrity: sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-function-name@7.25.7': - resolution: {integrity: sha512-5MCTNcjCMxQ63Tdu9rxyN6cAWurqfrDZ76qvVPrGYdBxIj+EawuuxTu/+dgJlhK5eRz3v1gLwp6XwS8XaX2NiQ==} + '@babel/plugin-transform-function-name@7.25.9': + resolution: {integrity: sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-json-strings@7.25.8': - resolution: {integrity: sha512-4OMNv7eHTmJ2YXs3tvxAfa/I43di+VcF+M4Wt66c88EAED1RoGaf1D64cL5FkRpNL+Vx9Hds84lksWvd/wMIdA==} + '@babel/plugin-transform-json-strings@7.25.9': + resolution: {integrity: sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-literals@7.25.7': - resolution: {integrity: sha512-fwzkLrSu2fESR/cm4t6vqd7ebNIopz2QHGtjoU+dswQo/P6lwAG04Q98lliE3jkz/XqnbGFLnUcE0q0CVUf92w==} + '@babel/plugin-transform-literals@7.25.9': + resolution: {integrity: sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-logical-assignment-operators@7.25.8': - resolution: {integrity: sha512-f5W0AhSbbI+yY6VakT04jmxdxz+WsID0neG7+kQZbCOjuyJNdL5Nn4WIBm4hRpKnUcO9lP0eipUhFN12JpoH8g==} + '@babel/plugin-transform-logical-assignment-operators@7.25.9': + resolution: {integrity: sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-member-expression-literals@7.25.7': - resolution: {integrity: sha512-Std3kXwpXfRV0QtQy5JJcRpkqP8/wG4XL7hSKZmGlxPlDqmpXtEPRmhF7ztnlTCtUN3eXRUJp+sBEZjaIBVYaw==} + '@babel/plugin-transform-member-expression-literals@7.25.9': + resolution: {integrity: sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-amd@7.25.7': - resolution: {integrity: sha512-CgselSGCGzjQvKzghCvDTxKHP3iooenLpJDO842ehn5D2G5fJB222ptnDwQho0WjEvg7zyoxb9P+wiYxiJX5yA==} + '@babel/plugin-transform-modules-amd@7.25.9': + resolution: {integrity: sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-commonjs@7.25.7': - resolution: {integrity: sha512-L9Gcahi0kKFYXvweO6n0wc3ZG1ChpSFdgG+eV1WYZ3/dGbJK7vvk91FgGgak8YwRgrCuihF8tE/Xg07EkL5COg==} + '@babel/plugin-transform-modules-commonjs@7.25.9': + resolution: {integrity: sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-systemjs@7.25.7': - resolution: {integrity: sha512-t9jZIvBmOXJsiuyOwhrIGs8dVcD6jDyg2icw1VL4A/g+FnWyJKwUfSSU2nwJuMV2Zqui856El9u+ElB+j9fV1g==} + '@babel/plugin-transform-modules-systemjs@7.25.9': + resolution: {integrity: sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-umd@7.25.7': - resolution: {integrity: sha512-p88Jg6QqsaPh+EB7I9GJrIqi1Zt4ZBHUQtjw3z1bzEXcLh6GfPqzZJ6G+G1HBGKUNukT58MnKG7EN7zXQBCODw==} + '@babel/plugin-transform-modules-umd@7.25.9': + resolution: {integrity: sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-named-capturing-groups-regex@7.25.7': - resolution: {integrity: sha512-BtAT9LzCISKG3Dsdw5uso4oV1+v2NlVXIIomKJgQybotJY3OwCwJmkongjHgwGKoZXd0qG5UZ12JUlDQ07W6Ow==} + '@babel/plugin-transform-named-capturing-groups-regex@7.25.9': + resolution: {integrity: sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-transform-new-target@7.25.7': - resolution: {integrity: sha512-CfCS2jDsbcZaVYxRFo2qtavW8SpdzmBXC2LOI4oO0rP+JSRDxxF3inF4GcPsLgfb5FjkhXG5/yR/lxuRs2pySA==} + '@babel/plugin-transform-new-target@7.25.9': + resolution: {integrity: sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-nullish-coalescing-operator@7.25.8': - resolution: {integrity: sha512-Z7WJJWdQc8yCWgAmjI3hyC+5PXIubH9yRKzkl9ZEG647O9szl9zvmKLzpbItlijBnVhTUf1cpyWBsZ3+2wjWPQ==} + '@babel/plugin-transform-nullish-coalescing-operator@7.25.9': + resolution: {integrity: sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-numeric-separator@7.25.8': - resolution: {integrity: sha512-rm9a5iEFPS4iMIy+/A/PiS0QN0UyjPIeVvbU5EMZFKJZHt8vQnasbpo3T3EFcxzCeYO0BHfc4RqooCZc51J86Q==} + '@babel/plugin-transform-numeric-separator@7.25.9': + resolution: {integrity: sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-object-rest-spread@7.25.8': - resolution: {integrity: sha512-LkUu0O2hnUKHKE7/zYOIjByMa4VRaV2CD/cdGz0AxU9we+VA3kDDggKEzI0Oz1IroG+6gUP6UmWEHBMWZU316g==} + '@babel/plugin-transform-object-rest-spread@7.25.9': + resolution: {integrity: sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-object-super@7.25.7': - resolution: {integrity: sha512-pWT6UXCEW3u1t2tcAGtE15ornCBvopHj9Bps9D2DsH15APgNVOTwwczGckX+WkAvBmuoYKRCFa4DK+jM8vh5AA==} + '@babel/plugin-transform-object-super@7.25.9': + resolution: {integrity: sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-catch-binding@7.25.8': - resolution: {integrity: sha512-EbQYweoMAHOn7iJ9GgZo14ghhb9tTjgOc88xFgYngifx7Z9u580cENCV159M4xDh3q/irbhSjZVpuhpC2gKBbg==} + '@babel/plugin-transform-optional-catch-binding@7.25.9': + resolution: {integrity: sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-chaining@7.25.8': - resolution: {integrity: sha512-q05Bk7gXOxpTHoQ8RSzGSh/LHVB9JEIkKnk3myAWwZHnYiTGYtbdrYkIsS8Xyh4ltKf7GNUSgzs/6P2bJtBAQg==} + '@babel/plugin-transform-optional-chaining@7.25.9': + resolution: {integrity: sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-parameters@7.25.7': - resolution: {integrity: sha512-FYiTvku63me9+1Nz7TOx4YMtW3tWXzfANZtrzHhUZrz4d47EEtMQhzFoZWESfXuAMMT5mwzD4+y1N8ONAX6lMQ==} + '@babel/plugin-transform-parameters@7.25.9': + resolution: {integrity: sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-private-methods@7.25.7': - resolution: {integrity: sha512-KY0hh2FluNxMLwOCHbxVOKfdB5sjWG4M183885FmaqWWiGMhRZq4DQRKH6mHdEucbJnyDyYiZNwNG424RymJjA==} + '@babel/plugin-transform-private-methods@7.25.9': + resolution: {integrity: sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-private-property-in-object@7.25.8': - resolution: {integrity: sha512-8Uh966svuB4V8RHHg0QJOB32QK287NBksJOByoKmHMp1TAobNniNalIkI2i5IPj5+S9NYCG4VIjbEuiSN8r+ow==} + '@babel/plugin-transform-private-property-in-object@7.25.9': + resolution: {integrity: sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-property-literals@7.25.7': - resolution: {integrity: sha512-lQEeetGKfFi0wHbt8ClQrUSUMfEeI3MMm74Z73T9/kuz990yYVtfofjf3NuA42Jy3auFOpbjDyCSiIkTs1VIYw==} + '@babel/plugin-transform-property-literals@7.25.9': + resolution: {integrity: sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx@7.25.7': - resolution: {integrity: sha512-vILAg5nwGlR9EXE8JIOX4NHXd49lrYbN8hnjffDtoULwpL9hUx/N55nqh2qd0q6FyNDfjl9V79ecKGvFbcSA0Q==} + '@babel/plugin-transform-react-jsx@7.25.9': + resolution: {integrity: sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regenerator@7.25.7': - resolution: {integrity: sha512-mgDoQCRjrY3XK95UuV60tZlFCQGXEtMg8H+IsW72ldw1ih1jZhzYXbJvghmAEpg5UVhhnCeia1CkGttUvCkiMQ==} + '@babel/plugin-transform-regenerator@7.25.9': + resolution: {integrity: sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-reserved-words@7.25.7': - resolution: {integrity: sha512-3OfyfRRqiGeOvIWSagcwUTVk2hXBsr/ww7bLn6TRTuXnexA+Udov2icFOxFX9abaj4l96ooYkcNN1qi2Zvqwng==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-shorthand-properties@7.25.7': - resolution: {integrity: sha512-uBbxNwimHi5Bv3hUccmOFlUy3ATO6WagTApenHz9KzoIdn0XeACdB12ZJ4cjhuB2WSi80Ez2FWzJnarccriJeA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-spread@7.25.7': - resolution: {integrity: sha512-Mm6aeymI0PBh44xNIv/qvo8nmbkpZze1KvR8MkEqbIREDxoiWTi18Zr2jryfRMwDfVZF9foKh060fWgni44luw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-sticky-regex@7.25.7': - resolution: {integrity: sha512-ZFAeNkpGuLnAQ/NCsXJ6xik7Id+tHuS+NT+ue/2+rn/31zcdnupCdmunOizEaP0JsUmTFSTOPoQY7PkK2pttXw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-template-literals@7.25.7': - resolution: {integrity: sha512-SI274k0nUsFFmyQupiO7+wKATAmMFf8iFgq2O+vVFXZ0SV9lNfT1NGzBEhjquFmD8I9sqHLguH+gZVN3vww2AA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-typeof-symbol@7.25.7': - resolution: {integrity: sha512-OmWmQtTHnO8RSUbL0NTdtpbZHeNTnm68Gj5pA4Y2blFNh+V4iZR68V1qL9cI37J21ZN7AaCnkfdHtLExQPf2uA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-typescript@7.25.7': - resolution: {integrity: sha512-VKlgy2vBzj8AmEzunocMun2fF06bsSWV+FvVXohtL6FGve/+L217qhHxRTVGHEDO/YR8IANcjzgJsd04J8ge5Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-unicode-escapes@7.25.7': - resolution: {integrity: sha512-BN87D7KpbdiABA+t3HbVqHzKWUDN3dymLaTnPFAMyc8lV+KN3+YzNhVRNdinaCPA4AUqx7ubXbQ9shRjYBl3SQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-unicode-property-regex@7.25.7': - resolution: {integrity: sha512-IWfR89zcEPQGB/iB408uGtSPlQd3Jpq11Im86vUgcmSTcoWAiQMCTOa2K2yNNqFJEBVICKhayctee65Ka8OB0w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-unicode-regex@7.25.7': - resolution: {integrity: sha512-8JKfg/hiuA3qXnlLx8qtv5HWRbgyFx2hMMtpDDuU2rTckpKkGu4ycK5yYHwuEa16/quXfoxHBIApEsNyMWnt0g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-unicode-sets-regex@7.25.7': - resolution: {integrity: sha512-YRW8o9vzImwmh4Q3Rffd09bH5/hvY0pxg+1H1i0f7APoUeg12G7+HhLj9ZFNIrYkgBXhIijPJ+IXypN0hLTIbw==} + '@babel/plugin-transform-regexp-modifiers@7.26.0': + resolution: {integrity: sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/preset-env@7.25.8': - resolution: {integrity: sha512-58T2yulDHMN8YMUxiLq5YmWUnlDCyY1FsHM+v12VMx+1/FlrUj5tY50iDCpofFQEM8fMYOaY9YRvym2jcjn1Dg==} + '@babel/plugin-transform-reserved-words@7.25.9': + resolution: {integrity: sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.25.9': + resolution: {integrity: sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-spread@7.25.9': + resolution: {integrity: sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-sticky-regex@7.25.9': + resolution: {integrity: sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.25.9': + resolution: {integrity: sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.25.9': + resolution: {integrity: sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.25.9': + resolution: {integrity: sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-escapes@7.25.9': + resolution: {integrity: sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-property-regex@7.25.9': + resolution: {integrity: sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.25.9': + resolution: {integrity: sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-sets-regex@7.25.9': + resolution: {integrity: sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/preset-env@7.26.0': + resolution: {integrity: sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1083,32 +1085,32 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 - '@babel/preset-typescript@7.25.7': - resolution: {integrity: sha512-rkkpaXJZOFN45Fb+Gki0c+KMIglk4+zZXOoMJuyEK8y8Kkc8Jd3BDmP7qPsz0zQMJj+UD7EprF+AqAXcILnexw==} + '@babel/preset-typescript@7.26.0': + resolution: {integrity: sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/register@7.25.7': - resolution: {integrity: sha512-qHTd2Rhn/rKhSUwdY6+n98FmwXN+N+zxSVx3zWqRe9INyvTpv+aQ5gDV2+43ACd3VtMBzPPljbb0gZb8u5ma6Q==} + '@babel/register@7.25.9': + resolution: {integrity: sha512-8D43jXtGsYmEeDvm4MWHYUpWf8iiXgWYx3fW7E7Wb7Oe6FWqJPl5K6TuFW0dOwNZzEE5rjlaSJYH9JjrUKJszA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime@7.25.7': - resolution: {integrity: sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==} + '@babel/runtime@7.26.0': + resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} engines: {node: '>=6.9.0'} - '@babel/template@7.25.7': - resolution: {integrity: sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==} + '@babel/template@7.25.9': + resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.25.7': - resolution: {integrity: sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==} + '@babel/traverse@7.25.9': + resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} engines: {node: '>=6.9.0'} - '@babel/types@7.25.8': - resolution: {integrity: sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==} + '@babel/types@7.26.0': + resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} engines: {node: '>=6.9.0'} '@choojs/findup@0.2.1': @@ -1123,39 +1125,39 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@csstools/cascade-layer-name-parser@2.0.2': - resolution: {integrity: sha512-rRWNJ8n16okpQT+8RWEbPfSl8D9WVoDZGBfHkjYnMYWcC20RiMpu/iGeKqUl1hR+SQIKg6p/QJap5rZJaHtVOg==} + '@csstools/cascade-layer-name-parser@2.0.4': + resolution: {integrity: sha512-7DFHlPuIxviKYZrOiwVU/PiHLm3lLUR23OMuEEtfEOQTOp9hzQ2JjdY6X5H18RVuUPJqSCI+qNnD5iOLMVE0bA==} engines: {node: '>=18'} peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.2 - '@csstools/css-tokenizer': ^3.0.2 + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 '@csstools/color-helpers@5.0.1': resolution: {integrity: sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==} engines: {node: '>=18'} - '@csstools/css-calc@2.0.2': - resolution: {integrity: sha512-N70YZw+R6WDP9EEd5xAT3xd+SgZFZsllXR6kclq6U8e2thlakNpWCKhuOiWfCKU8HpeWOyL+2ArSX8uDszMytA==} + '@csstools/css-calc@2.0.4': + resolution: {integrity: sha512-8/iCd8lH10gKNsq5detnbGWiFd6PXK2wB8wjE6fHNNhtqvshyMrIJgffwRcw6yl/gzGTH+N1i+KRhjqHxqYTmg==} engines: {node: '>=18'} peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.2 - '@csstools/css-tokenizer': ^3.0.2 + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 - '@csstools/css-color-parser@3.0.3': - resolution: {integrity: sha512-mnOTQ6KbQ6GHfdVHVTNXffroW0r5P5531h73bIyEzWAScGjMPQi+1XYgAydYVaZiKeXlQ4GHG9dnBWq9h7xFIQ==} + '@csstools/css-color-parser@3.0.5': + resolution: {integrity: sha512-4Wo8raj9YF3PnZ5iGrAl+BSsk2MYBOEUS/X4k1HL9mInhyCVftEG02MywdvelXlwZGUF2XTQ0qj9Jd398mhqrw==} engines: {node: '>=18'} peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.2 - '@csstools/css-tokenizer': ^3.0.2 + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 - '@csstools/css-parser-algorithms@3.0.2': - resolution: {integrity: sha512-6tC/MnlEvs5suR4Ahef4YlBccJDHZuxGsAlxXmybWjZ5jPxlzLSMlRZ9mVHSRvlD+CmtE7+hJ+UQbfXrws/rUQ==} + '@csstools/css-parser-algorithms@3.0.4': + resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==} engines: {node: '>=18'} peerDependencies: - '@csstools/css-tokenizer': ^3.0.2 + '@csstools/css-tokenizer': ^3.0.3 - '@csstools/css-tokenizer@3.0.2': - resolution: {integrity: sha512-IuTRcD53WHsXPCZ6W7ubfGqReTJ9Ra0yRRFmXYP/Re8hFYYfoIYIK4080X5luslVLWimhIeFq0hj09urVMQzTw==} + '@csstools/css-tokenizer@3.0.3': + resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==} engines: {node: '>=18'} '@csstools/media-query-list-parser@3.0.1': @@ -1165,39 +1167,39 @@ packages: '@csstools/css-parser-algorithms': ^3.0.1 '@csstools/css-tokenizer': ^3.0.1 - '@csstools/media-query-list-parser@4.0.0': - resolution: {integrity: sha512-nUfbCGeqCju55Po8ujRNQ8DjuKYth5UcsDj5HsVzSfqnaFdpOwYCUAhRJ2grfwrXhb9+KuRXHQ6JHzaI0Qhu8Q==} + '@csstools/media-query-list-parser@4.0.2': + resolution: {integrity: sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==} engines: {node: '>=18'} peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.2 - '@csstools/css-tokenizer': ^3.0.2 + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 - '@csstools/postcss-cascade-layers@5.0.0': - resolution: {integrity: sha512-h+VunB3KXaoWTWEPBcdVk8Kz1eZ/CtDD+HXgKw5JLdbsViLEQdKUtFYH73VIQigdodng8s5DCrrwNQY7pnuWBA==} + '@csstools/postcss-cascade-layers@5.0.1': + resolution: {integrity: sha512-XOfhI7GShVcKiKwmPAnWSqd2tBR0uxt+runAxttbSp/LY2U16yAVPmAf7e9q4JJ0d+xMNmpwNDLBXnmRCl3HMQ==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - '@csstools/postcss-color-function@4.0.3': - resolution: {integrity: sha512-dziWTvbyBsXze7Li+BemXyYX9yCf8udlGKB78evZismrBf7SNN6K5S0qE4sRQELKEkttugcGz0hwqyXilEhoUA==} + '@csstools/postcss-color-function@4.0.5': + resolution: {integrity: sha512-6dHr2NDsBMiZCPkGDi2qMfIbzV2kWV8Dh7SVb1FZGnN/r2TI4TSAkVF8rCG5L70yQZHMcQGB84yp8Zm+RGhoHA==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - '@csstools/postcss-color-mix-function@3.0.3': - resolution: {integrity: sha512-L7v0pQlLC3VejShxn5bjrdo3GhhHExSVGB8CgZqIcED/W/eK9pKGxubyGivNcJQYl+iznBtTU3mFPMmOrLccBQ==} + '@csstools/postcss-color-mix-function@3.0.5': + resolution: {integrity: sha512-jgq0oGbit7TxWYP8y2hWWfV64xzcAgJk54PBYZ2fDrRgEDy1l5YMCrFawnn+5JETh/E1jjXPDFhFEYhwr3vA3g==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - '@csstools/postcss-content-alt-text@2.0.2': - resolution: {integrity: sha512-GzMdDJrNPAOq4XxGac5xv5Ae2pB3JjvYWIJhJPcE6g87Q38gXG1Daaqq55QUU8DnC+iVm8lrO/JGvSC2T4YBOA==} + '@csstools/postcss-content-alt-text@2.0.4': + resolution: {integrity: sha512-YItlZUOuZJCBlRaCf8Aucc1lgN41qYGALMly0qQllrxYJhiyzlI6RxOTMUvtWk+KhS8GphMDsDhKQ7KTPfEMSw==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - '@csstools/postcss-exponential-functions@2.0.2': - resolution: {integrity: sha512-gSGeXEKse3U3lDzSXh9XE1DgdicMWolo+eyXN8nH96Vr5mWPd6jUwk6W+x8yRNwM5dDkoAE/HkYK6/WzSo9Jsw==} + '@csstools/postcss-exponential-functions@2.0.4': + resolution: {integrity: sha512-xmzFCGTkkLDs7q9vVaRGlnu8s51lRRJzHsaJ/nXmkQuyg0q7gh7rTbJ0bY5sSVet+KB7MTIxRXRUCl2tm7RODA==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -1208,20 +1210,20 @@ packages: peerDependencies: postcss: ^8.4 - '@csstools/postcss-gamut-mapping@2.0.3': - resolution: {integrity: sha512-1mbYE41F3fluEdjExw70b339NVU62O6sz43mya5O+LultfZQdmY68qRsWT+rw85Imya9aeLCDgBHaxwgXf1Z/g==} + '@csstools/postcss-gamut-mapping@2.0.5': + resolution: {integrity: sha512-VQDayRhC/Mg1fuo8/4F43La5aROgvVyqtCqdNyGvRKi6L1+zXfwQ583nImi7k/gn2GNJH82Bf9mutTuT1GtXzA==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - '@csstools/postcss-gradients-interpolation-method@5.0.3': - resolution: {integrity: sha512-TW+utpEOOn2HLlRZTEVNS8XBlG5bOcSNBanIKjPWnkmdgkFjcj1eIaEtWezpGX2hKJpkiwZeIEyP/UItWk6c3g==} + '@csstools/postcss-gradients-interpolation-method@5.0.5': + resolution: {integrity: sha512-l3ShDdAt/szbyBh3Jz27MRFt5WPAbnVCMsU7Vs7EbBxJQNgVDrcu1APBB2nPagDJOyhI6/IahuW7nb6grWVTpA==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - '@csstools/postcss-hwb-function@4.0.3': - resolution: {integrity: sha512-HBeApQzk6UlqAAWbuXSiWmF0Xtc/hfMTESSbkRUpolXshuPkUaBWXflfQuoo6exv3MvID6iTmv11GZT1ZfADDQ==} + '@csstools/postcss-hwb-function@4.0.5': + resolution: {integrity: sha512-bPn/SQyiiYjWkwK2ykc7O9LliMR50YfUGukd6jQI2okHzB7NxNt/IS45tS1Muk7Hhf3B9Lbmg1Ofq36tBmM92Q==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -1238,14 +1240,14 @@ packages: peerDependencies: postcss: ^8.4 - '@csstools/postcss-is-pseudo-class@5.0.0': - resolution: {integrity: sha512-E/CjrT03BL06WmrjupnrT0VUBTvxJdoW1hRVeXFa9qatWtvcLLw0j8hP372G4A9PpSGEMXi3/AoHzPf7DNryCQ==} + '@csstools/postcss-is-pseudo-class@5.0.1': + resolution: {integrity: sha512-JLp3POui4S1auhDR0n8wHd/zTOWmMsmK3nQd3hhL6FhWPaox5W7j1se6zXOG/aP07wV2ww0lxbKYGwbBszOtfQ==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - '@csstools/postcss-light-dark-function@2.0.5': - resolution: {integrity: sha512-mSqqxuwlBg10YyErq2YYB71KtvWDueBYE9WAnC6B7GHU+z0ECcGf+sR9zxpvePGzesuBNDB+cp15cW2CvOyszA==} + '@csstools/postcss-light-dark-function@2.0.7': + resolution: {integrity: sha512-ZZ0rwlanYKOHekyIPaU+sVm3BEHCe+Ha0/px+bmHe62n0Uc1lL34vbwrLYn6ote8PHlsqzKeTQdIejQCJ05tfw==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -1274,20 +1276,20 @@ packages: peerDependencies: postcss: ^8.4 - '@csstools/postcss-logical-viewport-units@3.0.2': - resolution: {integrity: sha512-oog7VobKvrS34oyUKslI6wCphtJxx0ldiA8RToPQ0HXPWNiXXSM7IbgwOTImJKTIUjo3eL7o5uuPxeu5MsnkvA==} + '@csstools/postcss-logical-viewport-units@3.0.3': + resolution: {integrity: sha512-OC1IlG/yoGJdi0Y+7duz/kU/beCwO+Gua01sD6GtOtLi7ByQUpcIqs7UE/xuRPay4cHgOMatWdnDdsIDjnWpPw==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - '@csstools/postcss-media-minmax@2.0.2': - resolution: {integrity: sha512-zodxyIwRNuro/SIjN+zrYeZCQJvMd1obPtsvmNxLRvk3FOM3KwuuX8GEev9if19OGlNVvJZIe9wH77c+jIbXzA==} + '@csstools/postcss-media-minmax@2.0.4': + resolution: {integrity: sha512-zgdBOCI9aKoy5GK9tb/3ve0pl7vH0HJg7rfQEWT3TZiIKh7XEWucDSTSwnwgdgtgz50UxrOfbK+C59M+u2fE2Q==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.2': - resolution: {integrity: sha512-9bEvSC8hIkdqHwehYIADcwC7/TvuJeb1hAw0STI7BMRVE57nFxHyXY+WzfLPXtmhpdFqGcKJIyQkDcenQI3Sow==} + '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.4': + resolution: {integrity: sha512-AnGjVslHMm5xw9keusQYvjVWvuS7KWK+OJagaG0+m9QnIjZsrysD2kJP/tr/UJIyYtMCtu8OkUd+Rajb4DqtIQ==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -1304,8 +1306,8 @@ packages: peerDependencies: postcss: ^8.4 - '@csstools/postcss-oklab-function@4.0.3': - resolution: {integrity: sha512-BrhnL98OSpWt5EOMk5Hm+kL0kjA8BhBc9DGG0jYgww1GhWItn+L/McQ4WgHE2cm9+jSUE2OMy/31WvSRKhWpnQ==} + '@csstools/postcss-oklab-function@4.0.5': + resolution: {integrity: sha512-19bsJQFyJNSEhpaVq0Mq1E0HDXfx8qMHa/bR1MaHr1UD4DWvM2/J6YXb9OVGS7eFl92Y3c84Yggn9uFv13vsiQ==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -1316,20 +1318,20 @@ packages: peerDependencies: postcss: ^8.4 - '@csstools/postcss-relative-color-syntax@3.0.3': - resolution: {integrity: sha512-1VYBTdGiFSOFrlczaYcUNybCU3XZRL9DDY3ooYRkvweWJZas8dQVHi6vy9SUmxnk0vfGbMbrISXLOIHw4LjKDg==} + '@csstools/postcss-relative-color-syntax@3.0.5': + resolution: {integrity: sha512-5VrE4hAwv/ZpuL1Yo0ZGGFi1QPpIikp/rzz7LnpQ31ACQVRIA5/M9qZmJbRlZVsJ4bUFSQ3dq6kHSHrCt2uM6Q==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - '@csstools/postcss-scope-pseudo-class@4.0.0': - resolution: {integrity: sha512-+ZUOBtVMDcmHZcZqsP/jcNRriEILfWQflTI3tCTA+/RheXAg57VkFGyPDAilpQSqlCpxWLWG8VUFKFtZJPwuOg==} + '@csstools/postcss-scope-pseudo-class@4.0.1': + resolution: {integrity: sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - '@csstools/postcss-stepped-value-functions@4.0.2': - resolution: {integrity: sha512-AxLKGIV0zYIAkeN02fo4o/vcG39WEZjT9dXs78ajy87dM94OFNIu5huxqBgkFGKLiXhQIKBRxAF/MtJmuIWi8w==} + '@csstools/postcss-stepped-value-functions@4.0.4': + resolution: {integrity: sha512-JjShuWZkmIOT8EfI7lYjl7V5qM29LNDdnnSo5O7v/InJJHfeiQjtxyAaZzKGXzpkghPrCAcgLfJ+IyqTdXo7IA==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -1340,8 +1342,8 @@ packages: peerDependencies: postcss: ^8.4 - '@csstools/postcss-trigonometric-functions@4.0.2': - resolution: {integrity: sha512-hQzJkTWNvHKGYa5ySpdex2K/ODX6bI3z8Pmdl3W/opRlaXMA7Xvq7Nagp31BTkr1ngzfnqTY9XNKEI2FqaO3fg==} + '@csstools/postcss-trigonometric-functions@4.0.4': + resolution: {integrity: sha512-nn+gWTZZlSnwbyUtGQCnvBXIx1TX+HVStvIm3221dWGQvp47bB5giMBbuAK4a/UJGBbfDQhGKEbYq++WWM1i1A==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -1352,11 +1354,11 @@ packages: peerDependencies: postcss: ^8.4 - '@csstools/selector-resolve-nested@2.0.0': - resolution: {integrity: sha512-oklSrRvOxNeeOW1yARd4WNCs/D09cQjunGZUgSq6vM8GpzFswN+8rBZyJA29YFZhOTQ6GFzxgLDNtVbt9wPZMA==} + '@csstools/selector-resolve-nested@3.0.0': + resolution: {integrity: sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ==} engines: {node: '>=18'} peerDependencies: - postcss-selector-parser: ^6.1.0 + postcss-selector-parser: ^7.0.0 '@csstools/selector-specificity@2.2.0': resolution: {integrity: sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==} @@ -1370,6 +1372,12 @@ packages: peerDependencies: postcss-selector-parser: ^6.1.0 + '@csstools/selector-specificity@5.0.0': + resolution: {integrity: sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==} + engines: {node: '>=18'} + peerDependencies: + postcss-selector-parser: ^7.0.0 + '@csstools/utilities@2.0.0': resolution: {integrity: sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==} engines: {node: '>=18'} @@ -1614,14 +1622,14 @@ packages: cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.4.0': - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + '@eslint-community/eslint-utils@4.4.1': + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.11.1': - resolution: {integrity: sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==} + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} '@eslint/eslintrc@2.1.4': @@ -1647,8 +1655,8 @@ packages: '@fontsource-variable/open-sans@5.1.0': resolution: {integrity: sha512-NuJdoORzM6qp+bf5j7dyo6MO8rBEEiSw8wiZv9t7sga0RIODgLLbBAMaZMaZiLwNbXcGPxhFcHcuDaQr1xj9kA==} - '@formatjs/cli@6.2.15': - resolution: {integrity: sha512-s31YblAseSVqgFvY2EoIZaaEycifR/CadvMj1WcNvFvHK+2Xn02OuSX1jiKM/Nx29hX2x8k0raFJ6PtnXZgjtQ==} + '@formatjs/cli@6.3.8': + resolution: {integrity: sha512-zXUkIdPE+L+nu5A6mgEsIuB5tX369C7FnqR1aRHj33zvj/G5fwuyJZiavZCdR/XAvDWflKza6/pMVC91fNlR/w==} engines: {node: '>= 16'} hasBin: true peerDependencies: @@ -1678,37 +1686,37 @@ packages: vue: optional: true - '@formatjs/ecma402-abstract@2.2.0': - resolution: {integrity: sha512-IpM+ev1E4QLtstniOE29W1rqH9eTdx5hQdNL8pzrflMj/gogfaoONZqL83LUeQScHAvyMbpqP5C9MzNf+fFwhQ==} + '@formatjs/ecma402-abstract@2.2.3': + resolution: {integrity: sha512-aElGmleuReGnk2wtYOzYFmNWYoiWWmf1pPPCYg0oiIQSJj0mjc4eUfzUXaSOJ4S8WzI/cLqnCTWjqz904FT2OQ==} - '@formatjs/fast-memoize@2.2.1': - resolution: {integrity: sha512-XS2RcOSyWxmUB7BUjj3mlPH0exsUzlf6QfhhijgI941WaJhVxXQ6mEWkdUFIdnKi3TuTYxRdelsgv3mjieIGIA==} + '@formatjs/fast-memoize@2.2.3': + resolution: {integrity: sha512-3jeJ+HyOfu8osl3GNSL4vVHUuWFXR03Iz9jjgI7RwjG6ysu/Ymdr0JRCPHfF5yGbTE6JCrd63EpvX1/WybYRbA==} - '@formatjs/icu-messageformat-parser@2.7.10': - resolution: {integrity: sha512-wlQfqCZ7PURkUNL2+8VTEFavPovtADU/isSKLFvDbdFmV7QPZIYqFMkhklaDYgMyLSBJa/h2MVQ2aFvoEJhxgg==} + '@formatjs/icu-messageformat-parser@2.9.3': + resolution: {integrity: sha512-9L99QsH14XjOCIp4TmbT8wxuffJxGK8uLNO1zNhLtcZaVXvv626N0s4A2qgRCKG3dfYWx9psvGlFmvyVBa6u/w==} - '@formatjs/icu-skeleton-parser@1.8.4': - resolution: {integrity: sha512-LMQ1+Wk1QSzU4zpd5aSu7+w5oeYhupRwZnMQckLPRYhSjf2/8JWQ882BauY9NyHxs5igpuQIXZDgfkaH3PoATg==} + '@formatjs/icu-skeleton-parser@1.8.7': + resolution: {integrity: sha512-fI+6SmS2g7h3srfAKSWa5dwreU5zNEfon2uFo99OToiLF6yxGE+WikvFSbsvMAYkscucvVmTYNlWlaDPp0n5HA==} - '@formatjs/intl-displaynames@6.6.10': - resolution: {integrity: sha512-tUz5qT61og1WwMM0K1/p46J69gLl1YJbty8xhtbigDA9LhbBmW2ShDg4ld+aqJTwCq4WK3Sj0VlFCKvFYeY3rQ==} + '@formatjs/intl-displaynames@6.8.4': + resolution: {integrity: sha512-HDVNBspDAOW0yTWluWTPHX2fk/9iBO4oST4R96f/IUaPGsFtjsHrpakwc+XDRPa3U5RniSEU2z34ZY0W78+E6Q==} - '@formatjs/intl-listformat@7.5.9': - resolution: {integrity: sha512-HqtGkxUh2Uz0oGVTxHAvPZ3EGxc8+ol5+Bx7S9xB97d4PEJJd9oOgHrerIghHA0gtIjsNKBFUae3P0My+F6YUA==} + '@formatjs/intl-listformat@7.7.4': + resolution: {integrity: sha512-lipFspH2MZcoeXxR6WSR/Jy9unzJ/iT0w+gbL8vgv25Ap0S9cUtcDVAce4ECEKI1bDtAvEU3b6+9Dha27gAikA==} - '@formatjs/intl-localematcher@0.5.5': - resolution: {integrity: sha512-t5tOGMgZ/i5+ALl2/offNqAQq/lfUnKLEw0mXQI4N4bqpedhrSE+fyKLpwnd22sK0dif6AV+ufQcTsKShB9J1g==} + '@formatjs/intl-localematcher@0.5.7': + resolution: {integrity: sha512-GGFtfHGQVFe/niOZp24Kal5b2i36eE2bNL0xi9Sg/yd0TR8aLjcteApZdHmismP5QQax1cMnZM9yWySUUjJteA==} - '@formatjs/intl@2.10.8': - resolution: {integrity: sha512-eY8r8RMmrRTTkLdbNBOZLFGXN3OnrEmInaNt8s4msIVfo+xuLqAqvB3W1jevj0I9QjU6ueIP7tEk+1rj6Xbv5A==} + '@formatjs/intl@2.10.14': + resolution: {integrity: sha512-4CA1EO75i/mSMHdjwfpgRj3Rsdsm6WjALeu/nlzYhBmAPxGu/Ha5GIRHAet5SO05TnpmqxmEGOsskWqFm0IeoA==} peerDependencies: typescript: ^4.7 || 5 peerDependenciesMeta: typescript: optional: true - '@formatjs/ts-transformer@3.13.16': - resolution: {integrity: sha512-ZIV7KB2EQ5w9k7yrwSsdGdoOgqlXNd2sfG317pbJPHDgIo04sxoRzZPayCiNo7VWaRyqkVYUpME94rd54FDvuw==} + '@formatjs/ts-transformer@3.13.22': + resolution: {integrity: sha512-+Zfz0wZ6wkdQE2zePiIQWIf4dVWeJGFXjkZxoCwzqxXdDrRhyAsQm91kbdFRIcVrjILA6KV0gOz8X7OBbLP4fQ==} peerDependencies: ts-jest: '>=27' peerDependenciesMeta: @@ -1737,8 +1745,8 @@ packages: '@giphy/js-types@5.1.0': resolution: {integrity: sha512-BZYCDtYNRR7cUWkbDLB4wmm3qmWMsVCQdUiBNOfmZ3yAazCgygKJoDI/5Rq4CK5MBaOc5LVdF8viC2WtoBdaPA==} - '@giphy/js-util@5.1.0': - resolution: {integrity: sha512-CvmeJi9H6tj0jscL7xpRXBhNuTgkQkXo7PQZK+UbWMR3NmJ07JibYxrw02bLmBZHxw+4F0cGeZL3zf8u5d+o5A==} + '@giphy/js-util@5.2.0': + resolution: {integrity: sha512-Qt7pGh2cqiNmXLeWAgb459wK8+BuMLtIxTfg4ZksnPHPsLthiHT9hhzs2QhqUh7Pp/HOq+Cbv2etGDfnq+xiKA==} '@hapi/bourne@3.0.0': resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==} @@ -1969,12 +1977,12 @@ packages: resolution: {integrity: sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==} engines: {node: '>=6.0.0'} - '@maplibre/maplibre-gl-style-spec@20.3.1': - resolution: {integrity: sha512-5ueL4UDitzVtceQ8J4kY+Px3WK+eZTsmGwha3MBKHKqiHvKrjWWwBCIl1K8BuJSc5OFh83uI8IFNoFvQxX2uUw==} + '@maplibre/maplibre-gl-style-spec@20.4.0': + resolution: {integrity: sha512-AzBy3095fTFPjDjmWpR2w6HVRAZJ6hQZUCwk5Plz6EyfnfuQW1odeW5i2Ai47Y6TBA2hQnC+azscjBSALpaWgw==} hasBin: true - '@mdx-js/mdx@3.0.1': - resolution: {integrity: sha512-eIQ4QTrOWyL3LWEe/bu6Taqzq2HQvHcyTMaOrI95P2/LmJE7AsfPfgJGuFLPVqBUE1BC1rik3VIhU+s9u72arA==} + '@mdx-js/mdx@3.1.0': + resolution: {integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==} '@mixmark-io/domino@2.2.0': resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==} @@ -2060,13 +2068,13 @@ packages: '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} - '@puppeteer/browsers@2.4.0': - resolution: {integrity: sha512-x8J1csfIygOwf6D6qUAZ0ASk3z63zPb7wkNeHRerCMh82qWKUrOgkuP005AJC8lDL6/evtXETGEJVcwykKT4/g==} + '@puppeteer/browsers@2.4.1': + resolution: {integrity: sha512-0kdAbmic3J09I6dT8e9vE2JOCSt13wHCW5x/ly8TSt2bDtuIWe2TgLZZDHdcziw9AVCzflMAXCrVyRIhIs44Ng==} engines: {node: '>=18'} hasBin: true - '@rollup/pluginutils@5.1.2': - resolution: {integrity: sha512-/FIdS3PyZ39bjZlwqFnWqCOVnW7o963LtKMwQOD0NhQqw22gSr2YY1afu3FxRip4ZCZNsD5jq6Aaz6QV3D/Njw==} + '@rollup/pluginutils@5.1.3': + resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 @@ -2074,132 +2082,142 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.24.0': - resolution: {integrity: sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==} + '@rollup/rollup-android-arm-eabi@4.24.4': + resolution: {integrity: sha512-jfUJrFct/hTA0XDM5p/htWKoNNTbDLY0KRwEt6pyOA6k2fmk0WVwl65PdUdJZgzGEHWx+49LilkcSaumQRyNQw==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.24.0': - resolution: {integrity: sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==} + '@rollup/rollup-android-arm64@4.24.4': + resolution: {integrity: sha512-j4nrEO6nHU1nZUuCfRKoCcvh7PIywQPUCBa2UsootTHvTHIoIu2BzueInGJhhvQO/2FTRdNYpf63xsgEqH9IhA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.24.0': - resolution: {integrity: sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==} + '@rollup/rollup-darwin-arm64@4.24.4': + resolution: {integrity: sha512-GmU/QgGtBTeraKyldC7cDVVvAJEOr3dFLKneez/n7BvX57UdhOqDsVwzU7UOnYA7AAOt+Xb26lk79PldDHgMIQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.24.0': - resolution: {integrity: sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==} + '@rollup/rollup-darwin-x64@4.24.4': + resolution: {integrity: sha512-N6oDBiZCBKlwYcsEPXGDE4g9RoxZLK6vT98M8111cW7VsVJFpNEqvJeIPfsCzbf0XEakPslh72X0gnlMi4Ddgg==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.24.0': - resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==} + '@rollup/rollup-freebsd-arm64@4.24.4': + resolution: {integrity: sha512-py5oNShCCjCyjWXCZNrRGRpjWsF0ic8f4ieBNra5buQz0O/U6mMXCpC1LvrHuhJsNPgRt36tSYMidGzZiJF6mw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.24.4': + resolution: {integrity: sha512-L7VVVW9FCnTTp4i7KrmHeDsDvjB4++KOBENYtNYAiYl96jeBThFfhP6HVxL74v4SiZEVDH/1ILscR5U9S4ms4g==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.24.4': + resolution: {integrity: sha512-10ICosOwYChROdQoQo589N5idQIisxjaFE/PAnX2i0Zr84mY0k9zul1ArH0rnJ/fpgiqfu13TFZR5A5YJLOYZA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.24.0': - resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==} + '@rollup/rollup-linux-arm-musleabihf@4.24.4': + resolution: {integrity: sha512-ySAfWs69LYC7QhRDZNKqNhz2UKN8LDfbKSMAEtoEI0jitwfAG2iZwVqGACJT+kfYvvz3/JgsLlcBP+WWoKCLcw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.24.0': - resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==} + '@rollup/rollup-linux-arm64-gnu@4.24.4': + resolution: {integrity: sha512-uHYJ0HNOI6pGEeZ/5mgm5arNVTI0nLlmrbdph+pGXpC9tFHFDQmDMOEqkmUObRfosJqpU8RliYoGz06qSdtcjg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.24.0': - resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==} + '@rollup/rollup-linux-arm64-musl@4.24.4': + resolution: {integrity: sha512-38yiWLemQf7aLHDgTg85fh3hW9stJ0Muk7+s6tIkSUOMmi4Xbv5pH/5Bofnsb6spIwD5FJiR+jg71f0CH5OzoA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': - resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==} + '@rollup/rollup-linux-powerpc64le-gnu@4.24.4': + resolution: {integrity: sha512-q73XUPnkwt9ZNF2xRS4fvneSuaHw2BXuV5rI4cw0fWYVIWIBeDZX7c7FWhFQPNTnE24172K30I+dViWRVD9TwA==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.24.0': - resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==} + '@rollup/rollup-linux-riscv64-gnu@4.24.4': + resolution: {integrity: sha512-Aie/TbmQi6UXokJqDZdmTJuZBCU3QBDA8oTKRGtd4ABi/nHgXICulfg1KI6n9/koDsiDbvHAiQO3YAUNa/7BCw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.24.0': - resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==} + '@rollup/rollup-linux-s390x-gnu@4.24.4': + resolution: {integrity: sha512-P8MPErVO/y8ohWSP9JY7lLQ8+YMHfTI4bAdtCi3pC2hTeqFJco2jYspzOzTUB8hwUWIIu1xwOrJE11nP+0JFAQ==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.24.0': - resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==} + '@rollup/rollup-linux-x64-gnu@4.24.4': + resolution: {integrity: sha512-K03TljaaoPK5FOyNMZAAEmhlyO49LaE4qCsr0lYHUKyb6QacTNF9pnfPpXnFlFD3TXuFbFbz7tJ51FujUXkXYA==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.24.0': - resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==} + '@rollup/rollup-linux-x64-musl@4.24.4': + resolution: {integrity: sha512-VJYl4xSl/wqG2D5xTYncVWW+26ICV4wubwN9Gs5NrqhJtayikwCXzPL8GDsLnaLU3WwhQ8W02IinYSFJfyo34Q==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.24.0': - resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==} + '@rollup/rollup-win32-arm64-msvc@4.24.4': + resolution: {integrity: sha512-ku2GvtPwQfCqoPFIJCqZ8o7bJcj+Y54cZSr43hHca6jLwAiCbZdBUOrqE6y29QFajNAzzpIOwsckaTFmN6/8TA==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.24.0': - resolution: {integrity: sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==} + '@rollup/rollup-win32-ia32-msvc@4.24.4': + resolution: {integrity: sha512-V3nCe+eTt/W6UYNr/wGvO1fLpHUrnlirlypZfKCT1fG6hWfqhPgQV/K/mRBXBpxc0eKLIF18pIOFVPh0mqHjlg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.24.0': - resolution: {integrity: sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==} + '@rollup/rollup-win32-x64-msvc@4.24.4': + resolution: {integrity: sha512-LTw1Dfd0mBIEqUVCxbvTE/LLo+9ZxVC9k99v1v4ahg9Aak6FpqOfNu5kRkeTAn0wphoC4JU7No1/rL+bBCEwhg==} cpu: [x64] os: [win32] '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@sentry-internal/browser-utils@8.34.0': - resolution: {integrity: sha512-4AcYOzPzD1tL5eSRQ/GpKv5enquZf4dMVUez99/Bh3va8qiJrNP55AcM7UzZ7WZLTqKygIYruJTU5Zu2SpEAPQ==} + '@sentry-internal/browser-utils@8.37.1': + resolution: {integrity: sha512-OSR/V5GCsSCG7iapWtXCT/y22uo3HlawdEgfM1NIKk1mkP15UyGQtGEzZDdih2H+SNuX1mp9jQLTjr5FFp1A5w==} engines: {node: '>=14.18'} - '@sentry-internal/feedback@8.34.0': - resolution: {integrity: sha512-aYSM2KPUs0FLPxxbJCFSwCYG70VMzlT04xepD1Y/tTlPPOja/02tSv2tyOdZbv8Uw7xslZs3/8Lhj74oYcTBxw==} + '@sentry-internal/feedback@8.37.1': + resolution: {integrity: sha512-Se25NXbSapgS2S+JssR5YZ48b3OY4UGmAuBOafgnMW91LXMxRNWRbehZuNUmjjHwuywABMxjgu+Yp5uJDATX+g==} engines: {node: '>=14.18'} - '@sentry-internal/replay-canvas@8.34.0': - resolution: {integrity: sha512-x8KhZcCDpbKHqFOykYXiamX6x0LRxv6N1OJHoH+XCrMtiDBZr4Yo30d/MaS6rjmKGMtSRij30v+Uq+YWIgxUrg==} + '@sentry-internal/replay-canvas@8.37.1': + resolution: {integrity: sha512-1JLAaPtn1VL5vblB0BMELFV0D+KUm/iMGsrl4/JpRm0Ws5ESzQl33DhXVv1IX/ZAbx9i14EjR7MG9+Hj70tieQ==} engines: {node: '>=14.18'} - '@sentry-internal/replay@8.34.0': - resolution: {integrity: sha512-EoMh9NYljNewZK1quY23YILgtNdGgrkzJ9TPsj6jXUG0LZ0Q7N7eFWd0xOEDBvFxrmI3cSXF1i4d1sBb+eyKRw==} + '@sentry-internal/replay@8.37.1': + resolution: {integrity: sha512-E/Plhisk/pXJjOdOU12sg8m/APTXTA21iEniidP6jW3/+O0tD/H/UovEqa4odNTqxPMa798xHQSQNt5loYiaLA==} engines: {node: '>=14.18'} - '@sentry/browser@8.34.0': - resolution: {integrity: sha512-3HHG2NXxzHq1lVmDy2uRjYjGNf9NsJsTPlOC70vbQdOb+S49EdH/XMPy+J3ruIoyv6Cu0LwvA6bMOM6rHZOgNQ==} + '@sentry/browser@8.37.1': + resolution: {integrity: sha512-5ym+iGiIpjIKKpMWi9S3/tXh9xneS+jqxwRTJqed3cb8i4ydfMAAP8sM3U8xMCWWABpWyIUW+fpewC0tkhE1aQ==} engines: {node: '>=14.18'} - '@sentry/core@8.34.0': - resolution: {integrity: sha512-adrXCTK/zsg5pJ67lgtZqdqHvyx6etMjQW3P82NgWdj83c8fb+zH+K79Z47pD4zQjX0ou2Ws5nwwi4wJbz4bfA==} + '@sentry/core@8.37.1': + resolution: {integrity: sha512-82csXby589iDupM3VgCHJeWZagUyEEaDnbFcoZ/Z91QX2Sjq8FcF5OsforoXjw09i0XTFqlkFAnQVpDBmMXcpQ==} engines: {node: '>=14.18'} - '@sentry/types@8.34.0': - resolution: {integrity: sha512-zLRc60CzohGCo6zNsNeQ9JF3SiEeRE4aDCP9fDDdIVCOKovS+mn1rtSip0qd0Vp2fidOu0+2yY0ALCz1A3PJSQ==} + '@sentry/types@8.37.1': + resolution: {integrity: sha512-ryMOTROLSLINKFEbHWvi7GigNrsQhsaScw2NddybJGztJQ5UhxIGESnxGxWCufBmWFDwd7+5u0jDPCVUJybp7w==} engines: {node: '>=14.18'} - '@sentry/utils@8.34.0': - resolution: {integrity: sha512-W1KoRlFUjprlh3t86DZPFxLfM6mzjRzshVfMY7vRlJFymBelJsnJ3A1lPeBZM9nCraOSiw6GtOWu6k5BAkiGIg==} + '@sentry/utils@8.37.1': + resolution: {integrity: sha512-Qtn2IfpII12K17txG/ZtTci35XYjYi4CxbQ3j7nXY7toGv/+MqPXwV5q2i9g94XaSXlE5Wy9/hoCZoZpZs/djA==} engines: {node: '>=14.18'} - '@shikijs/core@1.22.0': - resolution: {integrity: sha512-S8sMe4q71TJAW+qG93s5VaiihujRK6rqDFqBnxqvga/3LvqHEnxqBIOPkt//IdXVtHkQWKu4nOQNk0uBGicU7Q==} + '@shikijs/core@1.22.2': + resolution: {integrity: sha512-bvIQcd8BEeR1yFvOYv6HDiyta2FFVePbzeowf5pPS1avczrPK+cjmaxxh0nx5QzbON7+Sv0sQfQVciO7bN72sg==} - '@shikijs/engine-javascript@1.22.0': - resolution: {integrity: sha512-AeEtF4Gcck2dwBqCFUKYfsCq0s+eEbCEbkUuFou53NZ0sTGnJnJ/05KHQFZxpii5HMXbocV9URYVowOP2wH5kw==} + '@shikijs/engine-javascript@1.22.2': + resolution: {integrity: sha512-iOvql09ql6m+3d1vtvP8fLCVCK7BQD1pJFmHIECsujB0V32BJ0Ab6hxk1ewVSMFA58FI0pR2Had9BKZdyQrxTw==} - '@shikijs/engine-oniguruma@1.22.0': - resolution: {integrity: sha512-5iBVjhu/DYs1HB0BKsRRFipRrD7rqjxlWTj4F2Pf+nQSPqc3kcyqFFeZXnBMzDf0HdqaFVvhDRAGiYNvyLP+Mw==} + '@shikijs/engine-oniguruma@1.22.2': + resolution: {integrity: sha512-GIZPAGzQOy56mGvWMoZRPggn0dTlBf1gutV5TdceLCZlFNqWmuc7u+CzD0Gd9vQUTgLbrt0KLzz6FNprqYAxlA==} - '@shikijs/types@1.22.0': - resolution: {integrity: sha512-Fw/Nr7FGFhlQqHfxzZY8Cwtwk5E9nKDUgeLjZgt3UuhcM3yJR9xj3ZGNravZZok8XmEZMiYkSMTPlPkULB8nww==} + '@shikijs/types@1.22.2': + resolution: {integrity: sha512-NCWDa6LGZqTuzjsGfXOBWfjS/fDIbDdmVDug+7ykVe1IKT4c1gakrvlfFYp5NhAXH/lyqLM8wsAPo5wNy73Feg==} '@shikijs/vscode-textmate@9.3.0': resolution: {integrity: sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==} @@ -2302,6 +2320,9 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/eslint-scope@3.7.7': + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} + '@types/eslint@9.6.1': resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} @@ -2314,8 +2335,8 @@ packages: '@types/express-serve-static-core@4.19.6': resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} - '@types/express-serve-static-core@5.0.0': - resolution: {integrity: sha512-AbXMTZGt40T+KON9/Fdxx0B2WK5hsgxcfXJLr5bFpZ7b4JCex2WyQPTEKdXqfHiY5nKKBScZ7yCoO6Pvgxfvnw==} + '@types/express-serve-static-core@5.0.1': + resolution: {integrity: sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==} '@types/express@4.17.21': resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} @@ -2335,8 +2356,8 @@ packages: '@types/html-minifier-terser@6.1.0': resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==} - '@types/http-assert@1.5.5': - resolution: {integrity: sha512-4+tE/lwdAahgZT1g30Jkdm9PzFRde0xwxBNUyRsCitRvCQB90iuA2uJYdUnhnANRcqGXaWOGY4FEoxeElNAK2g==} + '@types/http-assert@1.5.6': + resolution: {integrity: sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==} '@types/http-errors@2.0.4': resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} @@ -2359,8 +2380,8 @@ packages: '@types/jquery.validation@1.17.0': resolution: {integrity: sha512-4cbCiPm6T4VXHsAaoYZ+HbqIIIbd6h+dyEAht9qIhjVrI7yxgkvFLCdbmYGgV6wtYg16dlRtxnfZIMhuVCB9gQ==} - '@types/jquery@3.5.31': - resolution: {integrity: sha512-rf/iB+cPJ/YZfMwr+FVuQbm7IaWC4y3FVYfVDxRGqmUCFjjPII0HWaP0vTPJGp6m4o13AXySCcMbWfrWtBFAKw==} + '@types/jquery@3.5.32': + resolution: {integrity: sha512-b9Xbf4CkMqS02YH8zACqN1xzdxc3cO735Qe5AbSUFmyOiaWAbcpqh9Wna+Uk0vgACvoQHpWDg2rGdHkYPLmCiQ==} '@types/js-cookie@3.0.6': resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} @@ -2368,8 +2389,8 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/json-stable-stringify@1.0.36': - resolution: {integrity: sha512-b7bq23s4fgBB76n34m2b3RBf6M369B0Z9uRR8aHTMd8kZISRkmDEpPD8hhpYvDFzr3bJCPES96cm3Q6qRNDbQw==} + '@types/json-stable-stringify@1.1.0': + resolution: {integrity: sha512-ESTsHWB72QQq+pjUFIbEz9uSCZppD31YrVkbt2rnUciTYEvcwN6uZIhX5JZeBHqRlFJ41x/7MewCs7E2Qux6Cg==} '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} @@ -2389,8 +2410,8 @@ packages: '@types/lodash-es@4.17.12': resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} - '@types/lodash@4.17.10': - resolution: {integrity: sha512-YpS0zzoduEhuOWjAotS6A5AVCva7X4lVlYLF0FYHAY9sdraBfnatttHItlWeZdGhuEkf+OzMNg2ZYAx8t+52uQ==} + '@types/lodash@4.17.13': + resolution: {integrity: sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==} '@types/mapbox__point-geometry@0.1.4': resolution: {integrity: sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==} @@ -2428,11 +2449,8 @@ packages: '@types/node@17.0.45': resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} - '@types/node@18.19.55': - resolution: {integrity: sha512-zzw5Vw52205Zr/nmErSEkN5FLqXPuKX/k5d1D7RKHATGqU7y6YfX9QxZraUzUrFGqH6XzOzG196BC35ltJC4Cw==} - - '@types/node@20.16.11': - resolution: {integrity: sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==} + '@types/node@22.9.0': + resolution: {integrity: sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -2443,14 +2461,14 @@ packages: '@types/pbf@3.0.5': resolution: {integrity: sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==} - '@types/picomatch@2.3.4': - resolution: {integrity: sha512-0so8lU8O5zatZS/2Fi4zrwks+vZv7e0dygrgEZXljODXBig97l4cPQD+9LabXfGJOWwoRkTVz6Q4edZvD12UOA==} + '@types/picomatch@3.0.1': + resolution: {integrity: sha512-1MRgzpzY0hOp9pW/kLRxeQhUWwil6gnrUYd3oEpeYBqp/FexhaCPv3F8LsYr47gtUU45fO2cm1dbwkSrHEo8Uw==} - '@types/plotly.js@2.33.4': - resolution: {integrity: sha512-BzAbsJTiUQyALkkYx1D31YZ9YvcU2ag3LlE/iePMo19eDPvM30cbM2EFNIcu31n39EhXj/9G7800XLA8/rfApA==} + '@types/plotly.js@2.33.5': + resolution: {integrity: sha512-TSXtrlc/4Zz7FP8HyDjmhsFFZ9JlzRk0KdHxXieDno4yZB4Jm5ET873QH+qPm5iZMaRZAEJMOrs1AGgN7r4e4g==} - '@types/qs@6.9.16': - resolution: {integrity: sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==} + '@types/qs@6.9.17': + resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==} '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} @@ -2470,8 +2488,8 @@ packages: '@types/serve-static@1.15.7': resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} - '@types/sizzle@2.3.8': - resolution: {integrity: sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==} + '@types/sizzle@2.3.9': + resolution: {integrity: sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==} '@types/sockjs@0.3.36': resolution: {integrity: sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==} @@ -2503,8 +2521,8 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@types/ws@8.5.12': - resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==} + '@types/ws@8.5.13': + resolution: {integrity: sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==} '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -2515,8 +2533,8 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript-eslint/eslint-plugin@8.9.0': - resolution: {integrity: sha512-Y1n621OCy4m7/vTXNlCbMVp87zSd7NH0L9cXD8aIpOaNlzeWxIK4+Q19A68gSmTNRZn92UjocVUWDthGxtqHFg==} + '@typescript-eslint/eslint-plugin@8.13.0': + resolution: {integrity: sha512-nQtBLiZYMUPkclSeC3id+x4uVd1SGtHuElTxL++SfP47jR0zfkZBJHc+gL4qPsgTuypz0k8Y2GheaDYn6Gy3rg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 @@ -2526,8 +2544,8 @@ packages: typescript: optional: true - '@typescript-eslint/parser@8.9.0': - resolution: {integrity: sha512-U+BLn2rqTTHnc4FL3FJjxaXptTxmf9sNftJK62XLz4+GxG3hLHm/SUNaaXP5Y4uTiuYoL5YLy4JBCJe3+t8awQ==} + '@typescript-eslint/parser@8.13.0': + resolution: {integrity: sha512-w0xp+xGg8u/nONcGw1UXAr6cjCPU1w0XVyBs6Zqaj5eLmxkKQAByTdV/uGgNN5tVvN/kKpoQlP2cL7R+ajZZIQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -2536,16 +2554,12 @@ packages: typescript: optional: true - '@typescript-eslint/scope-manager@8.5.0': - resolution: {integrity: sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg==} + '@typescript-eslint/scope-manager@8.13.0': + resolution: {integrity: sha512-XsGWww0odcUT0gJoBZ1DeulY1+jkaHUciUq4jKNv4cpInbvvrtDoyBH9rE/n2V29wQJPk8iCH1wipra9BhmiMA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/scope-manager@8.9.0': - resolution: {integrity: sha512-bZu9bUud9ym1cabmOYH9S6TnbWRzpklVmwqICeOulTCZ9ue2/pczWzQvt/cGj2r2o1RdKoZbuEMalJJSYw3pHQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/type-utils@8.9.0': - resolution: {integrity: sha512-JD+/pCqlKqAk5961vxCluK+clkppHY07IbV3vett97KOV+8C6l+CPEPwpUuiMwgbOz/qrN3Ke4zzjqbT+ls+1Q==} + '@typescript-eslint/type-utils@8.13.0': + resolution: {integrity: sha512-Rqnn6xXTR316fP4D2pohZenJnp+NwQ1mo7/JM+J1LWZENSLkJI8ID8QNtlvFeb0HnFSK94D6q0cnMX6SbE5/vA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -2553,16 +2567,12 @@ packages: typescript: optional: true - '@typescript-eslint/types@8.5.0': - resolution: {integrity: sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw==} + '@typescript-eslint/types@8.13.0': + resolution: {integrity: sha512-4cyFErJetFLckcThRUFdReWJjVsPCqyBlJTi6IDEpc1GWCIIZRFxVppjWLIMcQhNGhdWJJRYFHpHoDWvMlDzng==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.9.0': - resolution: {integrity: sha512-SjgkvdYyt1FAPhU9c6FiYCXrldwYYlIQLkuc+LfAhCna6ggp96ACncdtlbn8FmnG72tUkXclrDExOpEYf1nfJQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@8.5.0': - resolution: {integrity: sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q==} + '@typescript-eslint/typescript-estree@8.13.0': + resolution: {integrity: sha512-v7SCIGmVsRK2Cy/LTLGN22uea6SaUIlpBcO/gnMGT/7zPtxp90bphcGf4fyrCQl3ZtiBKqVTG32hb668oIYy1g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -2570,86 +2580,67 @@ packages: typescript: optional: true - '@typescript-eslint/typescript-estree@8.9.0': - resolution: {integrity: sha512-9iJYTgKLDG6+iqegehc5+EqE6sqaee7kb8vWpmHZ86EqwDjmlqNNHeqDVqb9duh+BY6WCNHfIGvuVU3Tf9Db0g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/utils@8.5.0': - resolution: {integrity: sha512-6yyGYVL0e+VzGYp60wvkBHiqDWOpT63pdMV2CVG4LVDd5uR6q1qQN/7LafBZtAtNIn/mqXjsSeS5ggv/P0iECw==} + '@typescript-eslint/utils@8.13.0': + resolution: {integrity: sha512-A1EeYOND6Uv250nybnLZapeXpYMl8tkzYUxqmoKAWnI4sei3ihf2XdZVd+vVOmHGcp3t+P7yRrNsyyiXTvShFQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - '@typescript-eslint/utils@8.9.0': - resolution: {integrity: sha512-PKgMmaSo/Yg/F7kIZvrgrWa1+Vwn036CdNUvYFEkYbPwOH4i8xvkaRlu148W3vtheWK9ckKRIz7PBP5oUlkrvQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - - '@typescript-eslint/visitor-keys@8.5.0': - resolution: {integrity: sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/visitor-keys@8.9.0': - resolution: {integrity: sha512-Ht4y38ubk4L5/U8xKUBfKNYGmvKvA1CANoxiTRMM+tOLk3lbF3DvzZCxJCRSE+2GdCMSh6zq9VZJc3asc1XuAA==} + '@typescript-eslint/visitor-keys@8.13.0': + resolution: {integrity: sha512-7N/+lztJqH4Mrf0lb10R/CbI1EaAMMGyF5y0oJvFoAhafwgiRA7TXyd8TFn8FC8k5y2dTsYogg238qavRGNnlw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - '@uppy/companion-client@4.1.0': - resolution: {integrity: sha512-nQ8CQfZcYVBNtFQ6ePj7FDIq38DXlH0YpzP/91LR9gnDVISJKKUuvWfr6tPktj1lRw9FZV8jLmlMKT2ituVKiw==} + '@uppy/companion-client@4.1.1': + resolution: {integrity: sha512-MotCCdnyiLyw34LeyDd8NMxrjD6jvCJA8UCI8eiP2lpLjwJJYyCB2Z0VyO/Wn4yFdh5un2NKwQmtGE0AENHN6Q==} peerDependencies: - '@uppy/core': ^4.2.0 + '@uppy/core': ^4.2.3 - '@uppy/core@4.2.2': - resolution: {integrity: sha512-TfTXngDLHK+gNwbpt1tgKfQ0vQwa7V5ilAnD/VNT+6AGW+/dqGFLZbA6q8xKvVTZ2sUbwDMSWFtqem+G04AhNQ==} + '@uppy/core@4.2.3': + resolution: {integrity: sha512-JSZiTZksrIZeATAtq9+QxXiPl7snfA5HbCn8uL20WJJxLqSff5ctnKmjvj0QmtwctPli00YrryDvEhCMhgmP7g==} - '@uppy/drag-drop@4.0.3': - resolution: {integrity: sha512-k9CySaCNgRge0bZrntmLGNFi2qGVFbprP0oibK3k4FOjrCvbJyN+nNppHwpEbBOPfuh76H94j9zL0OplQVZhZA==} + '@uppy/drag-drop@4.0.4': + resolution: {integrity: sha512-Ibz+NI9OrAFJ9BmuA0LZT9BuKBpIltfsyUIGbMGbNKmtQ1AGGswwgQMnD93RQE0lHC7V1BeU9vG655lNok0CCQ==} peerDependencies: - '@uppy/core': ^4.2.2 + '@uppy/core': ^4.2.3 - '@uppy/progress-bar@4.0.0': - resolution: {integrity: sha512-hCUjlfGWHlvBPQDO5YBH/8HEr+3+ZEobTblBg0Wbn3ecJSiKkSRi0GkDVp3OMnwfqgK2wm8Ve+tR/5Gds7vE0A==} + '@uppy/progress-bar@4.0.1': + resolution: {integrity: sha512-4cJcGVk5OEXZJuw1s0rPHBzatf1bDX0Rqqo4N92bMcX31NKiICvfBct2GZcx5p1VaGHN0I/WUV7pZ0zW9fpg5Q==} peerDependencies: - '@uppy/core': ^4.0.0 + '@uppy/core': ^4.2.3 - '@uppy/store-default@4.1.0': - resolution: {integrity: sha512-z5VSc4PNXpAtrrUPg5hdKJO5Ul7u4ZYLyK+tYzvEgzgR4nLVZmpGzj/d4N90jXpUqEibWKXvevODEB5VlTLHzg==} + '@uppy/store-default@4.1.1': + resolution: {integrity: sha512-VP02Q44Cziw8mLH6v2txToqF2SwsNr+jSxpkvcC7/EaZhG26XnseTd3Ydv2wYxv7YALQY2xhF2/LCXZzzx4fYQ==} - '@uppy/tus@4.1.2': - resolution: {integrity: sha512-wa3Hv2L1hy5Amw1A+MzxFGzTqGvcYrcnCHIMFcQK8dLbxchzunJgMfJI6Wb3YF70LZDerQyYJWBqlhQ2RbrBmg==} + '@uppy/tus@4.1.3': + resolution: {integrity: sha512-oX6Krds1iygLsTbYnvkP7pnGZa2UeiHq0RRxm40IDtVcfeHnNS3lD84VDTYfwxdWzdvfezCHnWBkgNXC+mjWNw==} peerDependencies: - '@uppy/core': ^4.2.2 + '@uppy/core': ^4.2.3 - '@uppy/utils@6.0.3': - resolution: {integrity: sha512-GBVzyAIeVKNe/F3TT63rXR80MSL9ov/FG3BbApO+4wbIt4vai7xpOxGCeTXpW2JjEeOwEb50n1fn92zMCdV9Dg==} + '@uppy/utils@6.0.4': + resolution: {integrity: sha512-EIp1//8+cw7DHPGix8sTp1G1OVopJlC2+p9upKrXXrmvRLFM00n1Xcd2JIZRE89PsrXgQzWdGYeeosCzoPZB2w==} - '@volar/kit@2.4.6': - resolution: {integrity: sha512-OaMtpmLns6IYD1nOSd0NdG/F5KzJ7Jr4B7TLeb4byPzu+ExuuRVeO56Dn1C7Frnw6bGudUQd90cpQAmxdB+RlQ==} + '@volar/kit@2.4.8': + resolution: {integrity: sha512-HY+HTP9sSqj0St9j1N8l85YMu4w0GxCtelzkzZWuq2GVz0+QRYwlyc0mPH7749OknUAdtsdozBR5Ecez55Ncug==} peerDependencies: typescript: '*' - '@volar/language-core@2.4.6': - resolution: {integrity: sha512-FxUfxaB8sCqvY46YjyAAV6c3mMIq/NWQMVvJ+uS4yxr1KzOvyg61gAuOnNvgCvO4TZ7HcLExBEsWcDu4+K4E8A==} + '@volar/language-core@2.4.8': + resolution: {integrity: sha512-K/GxMOXGq997bO00cdFhTNuR85xPxj0BEEAy+BaqqayTmy9Tmhfgmq2wpJcVspRhcwfgPoE2/mEJa26emUhG/g==} - '@volar/language-server@2.4.6': - resolution: {integrity: sha512-ARIbMXapEUPj9UFbZqWqw/iZ+ZuxUcY+vY212+2uutZVo/jrdzhLPu2TfZd9oB9akX8XXuslinT3051DyHLLRA==} + '@volar/language-server@2.4.8': + resolution: {integrity: sha512-3Jd9Y+0Zhwi/zfdRxqoNrm7AxP6lgTsw4Ni9r6eCyWYGVsTnpVwGmlcbiZyDja6anoKZxnaeDatX1jkaHHWaRQ==} - '@volar/language-service@2.4.6': - resolution: {integrity: sha512-wNeEVBgBKgpP1MfMYPrgTf1K8nhOGEh3ac0+9n6ECyk2N03+j0pWCpQ2i99mRWT/POvo1PgizDmYFH8S67bZOA==} + '@volar/language-service@2.4.8': + resolution: {integrity: sha512-9y8X4cdUxXmy4s5HoB8jmOpDIZG7XVFu4iEFvouhZlJX2leCq0pbq5h7dhA+O8My0fne3vtE6cJ4t9nc+8UBZw==} - '@volar/source-map@2.4.6': - resolution: {integrity: sha512-Nsh7UW2ruK+uURIPzjJgF0YRGP5CX9nQHypA2OMqdM2FKy7rh+uv3XgPnWPw30JADbKvZ5HuBzG4gSbVDYVtiw==} + '@volar/source-map@2.4.8': + resolution: {integrity: sha512-jeWJBkC/WivdelMwxKkpFL811uH/jJ1kVxa+c7OvG48DXc3VrP7pplSWPP2W1dLMqBxD+awRlg55FQQfiup4cA==} - '@volar/typescript@2.4.6': - resolution: {integrity: sha512-NMIrA7y5OOqddL9VtngPWYmdQU03htNKFtAYidbYfWA0TOhyGVd9tfcP4TsLWQ+RBWDZCbBqsr8xzU0ZOxYTCQ==} + '@volar/typescript@2.4.8': + resolution: {integrity: sha512-6xkIYJ5xxghVBhVywMoPMidDDAFT1OoQeXwa27HSgJ6AiIKRe61RXLoik+14Z7r0JvnblXVsjsRLmCr42SGzqg==} '@vscode/emmet-helper@2.9.3': resolution: {integrity: sha512-rB39LHWWPQYYlYfpv9qCoZOVioPCftKXXqrsyqN1mTWZM6dTnONT63Db+03vgrBbHzJN45IrgS/AGxw9iiqfEw==} @@ -2733,6 +2724,7 @@ packages: '@xmldom/xmldom@0.7.13': resolution: {integrity: sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==} engines: {node: '>=10.0.0'} + deprecated: this version is no longer supported, please update to at least 0.8.* '@xmldom/xmldom@0.8.10': resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} @@ -2767,11 +2759,6 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} - acorn-import-attributes@1.9.5: - resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} - peerDependencies: - acorn: ^8 - acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -2791,8 +2778,8 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - acorn@8.12.1: - resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} hasBin: true @@ -2866,10 +2853,6 @@ packages: resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} engines: {node: '>=12'} - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -2920,10 +2903,6 @@ packages: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} - arr-union@3.1.0: - resolution: {integrity: sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==} - engines: {node: '>=0.10.0'} - array-bounds@1.0.1: resolution: {integrity: sha512-8wdW3ZGk6UjMPJx/glyEt0sLzzwAE1bhToPsO1W2pbpR2gULyxe3BjSiuJFheP50T/GgODVPz2fuMUmIywt8cQ==} @@ -2978,10 +2957,6 @@ packages: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} engines: {node: '>=0.10.0'} - assign-symbols@1.0.0: - resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==} - engines: {node: '>=0.10.0'} - ast-types@0.13.4: resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} engines: {node: '>=4'} @@ -2999,8 +2974,8 @@ packages: peerDependencies: astro: ^4.0.0-beta || ^3.3.0 - astro@4.16.5: - resolution: {integrity: sha512-v8mKWcEPN7hkfyVsMFWV0F8UgL4GUJKT172KTB7dePO3yJb64HBwCckC0QnA60QQh3jK6+2xwyWEc5QvCeqjtg==} + astro@4.16.9: + resolution: {integrity: sha512-DFYzPZooVArKSGu969BBByUV44tJMVDPGKxgqWNFBaIrkvGljdVUqQSVwD+/iPYACoSkI8BRYvDMEBDkathIUQ==} engines: {node: ^18.17.1 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} hasBin: true @@ -3044,8 +3019,8 @@ packages: babel-plugin-emotion@10.2.2: resolution: {integrity: sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA==} - babel-plugin-formatjs@10.5.18: - resolution: {integrity: sha512-g54iGQ13Jskf9yT34vg6czBtOAIVfW7mVZ6qLsoLASPE2touq06vNjsNkA+CO29AnntdcxKnyVrpiwGB1Kdb2w==} + babel-plugin-formatjs@10.5.24: + resolution: {integrity: sha512-AgyeoTrGFVq6iQQfPvEUl7945uswFPduHKs0JvSSK0d3GXxUXE45wTQSD3JO706gTzl+aVC8BnfPXmQx2a9UGw==} babel-plugin-istanbul@7.0.0: resolution: {integrity: sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw==} @@ -3100,8 +3075,8 @@ packages: bare-path@2.1.3: resolution: {integrity: sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==} - bare-stream@2.3.0: - resolution: {integrity: sha512-pVRWciewGUeCyKEuRxwv06M079r+fRjAQjBEK2P6OYGrO43O+Z0LrPZZEjlc4mB6C2RpZ9AxJ1s7NLEtOHO6eA==} + bare-stream@2.3.2: + resolution: {integrity: sha512-EFZHSIBkDgSHIwj2l2QZfP4U5OcD4xFAOwhSb/vlr9PIqyGJGvB/nfClJbcnh3EY4jtPE4zsb5ztae96bVF79A==} base-64@1.0.0: resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==} @@ -3184,8 +3159,8 @@ packages: bricks.js@1.8.0: resolution: {integrity: sha512-XJsIGxoixpMDo/KoLXR+uQizFVGWNAQy1lLoIwXKxm6/Zpd9QQLSUd0otybbK7wjqX23ZvCXFxnIw+uCXJHo0A==} - browserslist@4.24.0: - resolution: {integrity: sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==} + browserslist@4.24.2: + resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -3221,20 +3196,10 @@ packages: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} - bytes@3.0.0: - resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} - engines: {node: '>= 0.8'} - bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} - bytewise-core@1.2.3: - resolution: {integrity: sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA==} - - bytewise@1.1.0: - resolution: {integrity: sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ==} - cacache@16.1.3: resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -3283,8 +3248,8 @@ packages: caniuse-api@3.0.0: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} - caniuse-lite@1.0.30001668: - resolution: {integrity: sha512-nWLrdxqCdblixUO+27JtGJJE/txpJlyUy5YN1u53wLZkP0emYCo5zgS6QYft7VUYR42LGgi/S5hdLZTrnyIddw==} + caniuse-lite@1.0.30001677: + resolution: {integrity: sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==} canvas-fit@1.5.0: resolution: {integrity: sha512-onIcjRpz69/Hx5bB5HGbYKUF2uC6QT6Gp+pfpGm3A7mPfcluSLV5v4Zu+oflDUwLdUw0rLIBhUbi0v8hM4FJQQ==} @@ -3292,10 +3257,6 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -3551,8 +3512,8 @@ packages: peerDependencies: webpack: ^5.1.0 - compression@1.7.4: - resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==} + compression@1.7.5: + resolution: {integrity: sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==} engines: {node: '>= 0.8.0'} concat-map@0.0.1: @@ -3605,11 +3566,11 @@ packages: resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} engines: {node: '>= 0.8'} - core-js-compat@3.38.1: - resolution: {integrity: sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==} + core-js-compat@3.39.0: + resolution: {integrity: sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==} - core-js@3.38.1: - resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==} + core-js@3.39.0: + resolution: {integrity: sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==} core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -3644,8 +3605,8 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} - css-blank-pseudo@7.0.0: - resolution: {integrity: sha512-v9xXYGdm6LIn4iHEfu3egk/PM1g/yJr8uwTIj6E44kurv5dE/4y3QW7WdVmZ0PVnqfTuK+C0ClZcEEiaKWBL9Q==} + css-blank-pseudo@7.0.1: + resolution: {integrity: sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -3678,8 +3639,8 @@ packages: css-global-keywords@1.0.1: resolution: {integrity: sha512-X1xgQhkZ9n94WDwntqst5D/FKkmiU0GlJSFZSV3kLvyJ1WC5VeyoXDOuleUD+SIuH9C7W05is++0Woh0CGfKjQ==} - css-has-pseudo@7.0.0: - resolution: {integrity: sha512-vO6k9bBt4/eEZ2PeHmS2VXjJga5SBy6O1ESyaOkse5/lvp6piFqg8Sh5KTU7X33M7Uh/oqo+M3EeMktQrZoTCQ==} + css-has-pseudo@7.0.1: + resolution: {integrity: sha512-EOcoyJt+OsuKfCADgLT7gADZI5jMzIe/AeI6MeAYKiFBDmNmM7kk46DtSfMj5AohUJisqVzopBpnQTlvbyaBWg==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -3747,8 +3708,8 @@ packages: resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - css-tree@3.0.0: - resolution: {integrity: sha512-o88DVQ6GzsABn1+6+zo2ct801dBO5OASVyxbbvA2W20ue2puSh/VOuqUj90eUeMSX/xqGqBmOKiRQN7tJOuBXw==} + css-tree@3.0.1: + resolution: {integrity: sha512-8Fxxv+tGhORlshCdCwnNJytvlvq46sOLSYEx2ZIGurahWvMucSRnyjPA3AmrMq4VPRYbHVpWj5VkiVasrM2H4Q==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} css-what@6.1.0: @@ -4011,8 +3972,8 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - devtools-protocol@0.0.1342118: - resolution: {integrity: sha512-75fMas7PkYNDTmDyb6PRJCH7ILmHLp+BhrZGeMsa4bCh40DTxgCz2NRy5UDzII4C5KuD0oBMZ9vXKhEl6UD/3w==} + devtools-protocol@0.0.1354347: + resolution: {integrity: sha512-BlmkSqV0V84E2WnEnoPnwyix57rQxAM5SKJjf4TbYOCGLAWtz8CDH8RIaGOjPgPCXo2Mce3kxSY497OySidY3Q==} diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} @@ -4110,8 +4071,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.38: - resolution: {integrity: sha512-VbeVexmZ1IFh+5EfrYz1I0HTzHVIlJa112UEWhciPyeOcKJGeTv6N8WnG4wsQB81DGCaVEGhpSb6o6a8WYFXXg==} + electron-to-chromium@1.5.51: + resolution: {integrity: sha512-kKeWV57KSS8jH4alKt/jKnvHPmJgBxXzGUSbMd4eQF+iOsVPl7bz2KUmu6eo80eMP8wVioTfTyTzdMgM15WXNg==} element-size@1.1.1: resolution: {integrity: sha512-eaN+GMOq/Q+BIWy0ybsgpcYImjGIdNLyjLFJU4XsLHXYQao5jCNb36GyN6C2qwmDDYSfIBmKpPpr4VnBdLCsPQ==} @@ -4256,6 +4217,12 @@ packages: es6-weak-map@2.0.3: resolution: {integrity: sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==} + esast-util-from-estree@2.0.0: + resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} + + esast-util-from-js@2.0.1: + resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} + esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} @@ -4322,8 +4289,8 @@ packages: eslint-import-resolver-webpack: optional: true - eslint-plugin-formatjs@5.1.0: - resolution: {integrity: sha512-IN3/MKq5XumXqgta7OnvixcEoQCyxLAsNVwzsOQIqQqughcHwkvv7JHiIurt+7V6IjBYDyfTiH7opZggR+t+7w==} + eslint-plugin-formatjs@5.2.2: + resolution: {integrity: sha512-EtLhaz2/6l86s/3cOo5SyNGYnsocStylO3KLIoMJI8Ch2LqejZsd06RR7wMpeHusIN9ogvTlJZV2M54mBlElKg==} peerDependencies: eslint: '9' @@ -4408,6 +4375,9 @@ packages: estree-util-is-identifier-name@3.0.0: resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + estree-util-scope@1.0.0: + resolution: {integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==} + estree-util-to-js@2.0.0: resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} @@ -4468,10 +4438,6 @@ packages: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} engines: {node: '>=0.10.0'} - extend-shallow@3.0.2: - resolution: {integrity: sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==} - engines: {node: '>=0.10.0'} - extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -4781,10 +4747,6 @@ packages: resolution: {integrity: sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==} engines: {node: '>= 14'} - get-value@2.0.6: - resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==} - engines: {node: '>=0.10.0'} - github-slugger@2.0.0: resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} @@ -4847,8 +4809,8 @@ packages: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} - globals@15.11.0: - resolution: {integrity: sha512-yeyNSjdbyVaWurlwCpcA6XNBrHTMIeDdj0/hnvX/OLJ9ekOXYbLsLinH/MucQyGvNnXhidTdNhTtJaffL2sMfw==} + globals@15.12.0: + resolution: {integrity: sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==} engines: {node: '>=18'} globalthis@1.0.4: @@ -4947,10 +4909,6 @@ packages: has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -5095,8 +5053,8 @@ packages: html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - html-webpack-plugin@5.6.0: - resolution: {integrity: sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==} + html-webpack-plugin@5.6.3: + resolution: {integrity: sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==} engines: {node: '>=10.13.0'} peerDependencies: '@rspack/core': 0.x || 1.x @@ -5174,8 +5132,8 @@ packages: resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==} engines: {node: '>=10.18'} - i18next@23.16.0: - resolution: {integrity: sha512-Ni3CG6c14teOogY19YNRl+kYaE/Rb59khy0VyHVn4uOZ97E2E/Yziyi6r3C3s9+wacjdLZiq/LLYyx+Cgd+FCw==} + i18next@23.16.4: + resolution: {integrity: sha512-9NIYBVy9cs4wIqzurf7nLXPyf3R78xYbxExVqHLK9od3038rjpyOEzW+XB130kZ1N4PZ9inTtJ471CRJ4Ituyg==} iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} @@ -5250,6 +5208,10 @@ packages: resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + ini@5.0.0: + resolution: {integrity: sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==} + engines: {node: ^18.17.0 || >=20.5.0} + inline-style-parser@0.1.1: resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} @@ -5271,8 +5233,8 @@ packages: intersection-observer@0.11.0: resolution: {integrity: sha512-KZArj2QVnmdud9zTpKf279m2bbGfG+4/kn16UU0NL3pTVl52ZHiJ9IRNSsnn6jaHrL9EGLFM5eWjTx2fz/+zoQ==} - intl-messageformat@10.7.0: - resolution: {integrity: sha512-2P06M9jFTqJnEQzE072VGPjbAx6ZG1YysgopAwc8ui0ajSjtwX1MeQ6bXFXIzKcNENJTizKkcJIcZ0zlpl1zSg==} + intl-messageformat@10.7.6: + resolution: {integrity: sha512-IsMU/hqyy3FJwNJ0hxDfY2heJ7MteSuFvcnCebxRp67di4Fhx1gKKE+qS0bBwUF8yXkX9SsPUhLeX/B6h5SKUA==} ip-address@9.0.5: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} @@ -5355,10 +5317,6 @@ packages: resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} engines: {node: '>=0.10.0'} - is-extendable@1.0.1: - resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==} - engines: {node: '>=0.10.0'} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -5464,9 +5422,6 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - is-reference@3.0.2: - resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} - is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -5975,8 +5930,8 @@ packages: resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} engines: {node: '>=16'} - markdown-table@3.0.3: - resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} math-log2@1.0.1: resolution: {integrity: sha512-9W0yGtkaMAkf74XGYVy4Dqw3YUMnTNB2eeiw9aQbUl4A3KmuCEHTt2DgAB07ENzOYAjsYSAYufkAq0Zd+jU7zA==} @@ -5994,8 +5949,8 @@ packages: mdast-util-find-and-replace@3.0.1: resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==} - mdast-util-from-markdown@2.0.1: - resolution: {integrity: sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==} + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} mdast-util-gfm-autolink-literal@2.0.1: resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} @@ -6033,8 +5988,8 @@ packages: mdast-util-to-hast@13.2.0: resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} - mdast-util-to-markdown@2.1.0: - resolution: {integrity: sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==} + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} @@ -6045,8 +6000,8 @@ packages: mdn-data@2.0.30: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} - mdn-data@2.10.0: - resolution: {integrity: sha512-qq7C3EtK3yJXMwz1zAab65pjl+UhohqMOctTgcqjLOWABqmwj+me02LSsCuEUxnst9X1lCBpoE0WArGKgdGDzw==} + mdn-data@2.12.1: + resolution: {integrity: sha512-rsfnCbOHjqrhWxwt5/wtSLzpoKTzW7OXdT5lLOIH1OTYhWu9rRJveGq0sKvDZODABH7RX+uoR+DYcpFnq4Tf6Q==} media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} @@ -6228,8 +6183,8 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - mini-css-extract-plugin@2.9.1: - resolution: {integrity: sha512-+Vyi+GCCOHnrJ2VPS+6aPoXN2k2jgUzDRhTFLjjTBn23qyXJXkjUWQgTL+mXpF5/A8ixLdCc6kWsoeOjKGejKQ==} + mini-css-extract-plugin@2.9.2: + resolution: {integrity: sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==} engines: {node: '>= 12.13.0'} peerDependencies: webpack: ^5.0.0 @@ -6350,8 +6305,8 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@5.0.7: - resolution: {integrity: sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==} + nanoid@5.0.8: + resolution: {integrity: sha512-TcJPw+9RV9dibz1hHUzlLVy8N4X9TnwirAjrU08Juo6BNKggzVfP2ZJ/3ZUSq15Xl5i85i+Z89XBO90pB2PghQ==} engines: {node: ^18 || >=20} hasBin: true @@ -6373,6 +6328,10 @@ packages: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} @@ -6549,8 +6508,8 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - ora@8.1.0: - resolution: {integrity: sha512-GQEkNkH/GHOhPFXcqZs3IDahXEQcQxsSjEkK4KvEEST4t7eNzoMjxTzef+EZ+JluDEV+Raoi3WQ2CflnRdSVnQ==} + ora@8.1.1: + resolution: {integrity: sha512-YWielGi1XzG1UTvOaCFaNgEnuhZVMSHYkW/FQ7UX8O26PtlpdM84c0f7wLPlkvx2RfiQmnzd61d/MGxmpQeJPw==} engines: {node: '>=18'} ordered-read-streams@1.0.1: @@ -6604,8 +6563,8 @@ packages: resolution: {integrity: sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==} engines: {node: '>=16.17'} - p-timeout@6.1.2: - resolution: {integrity: sha512-UbD77BuZ9Bc9aABo74gfXhNvzC9Tx7SxtHSh1fxvx3jTLLYvmVhiQZZrJzqqU0jKbN32kb5VOKiLEQI/3bIjgQ==} + p-timeout@6.1.3: + resolution: {integrity: sha512-UJUyfKbwvr/uZSV6btANfb+0t/mOhKV/KXcCUTp8FcQI+v/0d+wXqH4htrW0E4rR6WiEO/EPvUFiV9D5OI4vlw==} engines: {node: '>=14.16'} p-try@2.2.0: @@ -6666,8 +6625,8 @@ packages: parse-unit@1.0.1: resolution: {integrity: sha512-hrqldJHokR3Qj88EIlV/kAyAi/G5R2+R56TBANxNMy0uPlYcttx0jnMW6Yx5KsKPSbC3KddM/7qQm3+0wEXKxg==} - parse5@7.2.0: - resolution: {integrity: sha512-ZkDsAOcxsUMZ4Lz5fVciOehNcJ+Gb8gTzcA4yl3wnc273BAybYWrQ+Ks/OjCjSEpjvQkDSeZbybK9qj2VHHdGA==} + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} @@ -6722,19 +6681,20 @@ packages: performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} - periscopic@3.1.0: - resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} - pick-by-alias@1.2.0: resolution: {integrity: sha512-ESj2+eBxhGrcA1azgHs7lARG5+5iLakc/6nlfbpjcLl00HuuUOIuORhYXN4D1HfvMSKuVtFQjAlnwi1JHEeDIw==} - picocolors@1.1.0: - resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -6776,8 +6736,8 @@ packages: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} - postcss-attribute-case-insensitive@7.0.0: - resolution: {integrity: sha512-ETMUHIw67Kyv9Q81nden/NuJbRh+4/S963giXpfSLd5eaKK8kd1UdAHMVRV/NG/w/N6Cq8B0qZIZbZZWU/67+A==} + postcss-attribute-case-insensitive@7.0.1: + resolution: {integrity: sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -6794,8 +6754,8 @@ packages: peerDependencies: postcss: ^8.4.6 - postcss-color-functional-notation@7.0.3: - resolution: {integrity: sha512-mL3LVOwXr5sRX1N5so7AFCNciaYTNTxzXuv5bDoZ/JunV2NCAzGOuVfyICRKczDPFImoIuL4e0O33/zYap9D0w==} + postcss-color-functional-notation@7.0.5: + resolution: {integrity: sha512-zW97tq5t2sSSSZQcIS4y6NDZj79zVv8hrBIJ4PSFZFmMBcjYqCt8sRXFGIYZohCpfFHmimMNqJje2Qd3qqMNdg==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -6824,26 +6784,26 @@ packages: peerDependencies: postcss: ^8.4.31 - postcss-custom-media@11.0.3: - resolution: {integrity: sha512-h52R7j0/QZP7NgnpsUaqx6wdssplK4U+ZuErvic2StgvXt3v5sPopFH86yjLvqz3jBrj/8Hkvr7Gio1LLRFP0g==} + postcss-custom-media@11.0.5: + resolution: {integrity: sha512-SQHhayVNgDvSAdX9NQ/ygcDQGEY+aSF4b/96z7QUX6mqL5yl/JgG/DywcF6fW9XbnCRE+aVYk+9/nqGuzOPWeQ==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - postcss-custom-properties@14.0.2: - resolution: {integrity: sha512-ZDJLIXa6uT6FlK6mYdzHxr1fr5ec6lPbp/CZ5+7EZedFmfjJx1fvYQhAPCBebuyc1lkketmiA26ZVl2UkPQ9Ig==} + postcss-custom-properties@14.0.4: + resolution: {integrity: sha512-QnW8FCCK6q+4ierwjnmXF9Y9KF8q0JkbgVfvQEMa93x1GT8FvOiUevWCN2YLaOWyByeDX8S6VFbZEeWoAoXs2A==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - postcss-custom-selectors@8.0.2: - resolution: {integrity: sha512-8y2fa+RgYHpVFtvR4h3/dHc7b0iWjT6GOpzWwB8VHJTEBdVNaqOB4FH9koa44hgRyaeDs3KTe3xP9EJf6NLvxQ==} + postcss-custom-selectors@8.0.4: + resolution: {integrity: sha512-ASOXqNvDCE0dAJ/5qixxPeL1aOVGHGW2JwSy7HyjWNbnWTQCl+fDc968HY1jCmZI0+BaYT5CxsOiUhavpG/7eg==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - postcss-dir-pseudo-class@9.0.0: - resolution: {integrity: sha512-T59BG9lURiXmhcJMyKbyjNAK3KCyEQYEhaz9GAETHXfIy9XbGQeyz+H0zIwRJlrP4KKRPJolNYe3QjQPemMjBA==} + postcss-dir-pseudo-class@9.0.1: + resolution: {integrity: sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -6884,14 +6844,14 @@ packages: peerDependencies: postcss: ^8.4.6 - postcss-focus-visible@10.0.0: - resolution: {integrity: sha512-GJjzvTj7JY+zN7wVBQ4osdKX53QLUdr6r2rSEkBUqrEMDKu3fHMHKOY9rirdirbHCx3IETnK25EtpPARR2KWNw==} + postcss-focus-visible@10.0.1: + resolution: {integrity: sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - postcss-focus-within@9.0.0: - resolution: {integrity: sha512-QwflAWUToNZvQLGbc4qJhrQO8yZ5617L6hSNzNWDoqRX4FoIh9fbJbEjy0nvFPciaaOoCaeqcxBwYPbFU0HvBw==} + postcss-focus-within@9.0.1: + resolution: {integrity: sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -6919,8 +6879,8 @@ packages: peerDependencies: postcss: ^8.0.0 - postcss-lab-function@7.0.3: - resolution: {integrity: sha512-yCBscY/dwipfvqqy7rQHbn6k18zYZy9O57JY4fGuibot6wz7pbtzRnhRlWraHBNUs+N4p2KogHv2aBsoB6G+5Q==} + postcss-lab-function@7.0.5: + resolution: {integrity: sha512-q2M8CfQbjHxbwv1GPAny05EVuj0WByUgq/OWKgpfbTHnMchtUqsVQgaW1mztjSZ4UPufwuTLB14fmFGsoTE/VQ==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -7016,8 +6976,8 @@ packages: peerDependencies: postcss: ^8.2 - postcss-nesting@13.0.0: - resolution: {integrity: sha512-TCGQOizyqvEkdeTPM+t6NYwJ3EJszYE/8t8ILxw/YoeUvz2rz7aM8XTAmBWh9/DJjfaaabL88fWrsVHSPF2zgA==} + postcss-nesting@13.0.1: + resolution: {integrity: sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -7110,14 +7070,14 @@ packages: peerDependencies: postcss: '*' - postcss-preset-env@10.0.7: - resolution: {integrity: sha512-aUC/bMT2CULwaZ/RK1Ivzdsyv95DQCJs0dK98RTc9cZKUYIal1+85JdNwik0DXg35BKdRZM2ZwASU17PXoglsw==} + postcss-preset-env@10.0.9: + resolution: {integrity: sha512-mpfJWMAW6szov+ifW9HpNUUZE3BoXoHc4CDzNQHdH2I4CwsqulQ3bpFNUR6zh4tg0BUcqM7UUAbzG4UTel8QYw==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 - postcss-pseudo-class-any-link@10.0.0: - resolution: {integrity: sha512-bde8VE08Gq3ekKDq2BQ0ESOjNX54lrFDK3U9zABPINaqHblbZL/4Wfo5Y2vk6U64yVd/sjDwTzuiisFBpGNNIQ==} + postcss-pseudo-class-any-link@10.0.1: + resolution: {integrity: sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -7148,8 +7108,8 @@ packages: peerDependencies: postcss: ^8.4.31 - postcss-selector-not@8.0.0: - resolution: {integrity: sha512-g/juh7A83GWc3+kWL8BiS3YUIJb3XNqIVKz1kGvgN3OhoGCsPncy1qo/+q61tjy5r87OxBhSY1+hcH3yOhEW+g==} + postcss-selector-not@8.0.1: + resolution: {integrity: sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==} engines: {node: '>=18'} peerDependencies: postcss: ^8.4 @@ -7158,6 +7118,10 @@ packages: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} + postcss-selector-parser@7.0.0: + resolution: {integrity: sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==} + engines: {node: '>=4'} + postcss-simple-vars@7.0.1: resolution: {integrity: sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A==} engines: {node: '>=14.0'} @@ -7294,12 +7258,12 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - puppeteer-core@23.5.3: - resolution: {integrity: sha512-V58MZD/B3CwkYsqSEQlHKbavMJptF04fzhMdUpiCRCmUVhwZNwSGEPhaiZ1f8I3ABQUirg3VNhXVB6Z1ubHXtQ==} + puppeteer-core@23.7.0: + resolution: {integrity: sha512-0kC81k3K6n6Upg/k04xv+Mi8yy62bNAJiK7LCA71zfq2XKEo9WAzas1t6UQiLgaNHtGNKM0d1KbR56p/+mgEiQ==} engines: {node: '>=18'} - puppeteer@23.5.3: - resolution: {integrity: sha512-FghmfBsr/UUpe48OiCg1gV3W4vVfQJKjQehbF07SjnQvEpWcvPTah1nykfGWdOQQ1ydJPIXcajzWN7fliCU3zw==} + puppeteer@23.7.0: + resolution: {integrity: sha512-YTgo0KFe8NtBcI9hCu/xsjPFumEhu8kA7QqLr6Uh79JcEsUcUt+go966NgKYXJ+P3Fuefrzn2SXwV3cyOe/UcQ==} engines: {node: '>=18'} hasBin: true @@ -7396,6 +7360,18 @@ packages: resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==} engines: {node: '>= 10.13.0'} + recma-build-jsx@1.0.0: + resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} + + recma-jsx@1.0.0: + resolution: {integrity: sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==} + + recma-parse@1.0.0: + resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} + + recma-stringify@1.0.0: + resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + redent@4.0.0: resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==} engines: {node: '>=12'} @@ -7413,8 +7389,8 @@ packages: regenerator-transform@0.15.2: resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} - regex@4.3.3: - resolution: {integrity: sha512-r/AadFO7owAq1QJVeZ/nq9jNS1vyZt+6t1p/E59B56Rn2GCya+gr1KSyOzNL/er+r+B7phv5jG2xU2Nz1YkmJg==} + regex@4.4.0: + resolution: {integrity: sha512-uCUSuobNVeqUupowbdZub6ggI5/JZkYyJdDogddJr60L764oxC2pMZov1fQ3wM9bdyzUILDG+Sqx6NAKAz9rKQ==} regexp-tree@0.1.27: resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} @@ -7435,8 +7411,8 @@ packages: resolution: {integrity: sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==} hasBin: true - regjsparser@0.11.1: - resolution: {integrity: sha512-1DHODs4B8p/mQHU9kr+jv8+wIC9mtG4eBHxWxIq5mhjE3D5oORhCc6deRKzTjs9DcfRFmj9BHSDguZklqCGFWQ==} + regjsparser@0.11.2: + resolution: {integrity: sha512-3OGZZ4HoLJkkAZx/48mTXJNlmqTGOzc0o9OWQPuWpkOlXXPbyN6OafCcoXUnBqE2D3f/T5L+pWc1kdEmnfnRsA==} hasBin: true regl-error2d@2.0.12: @@ -7466,6 +7442,9 @@ packages: rehype-raw@7.0.0: resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + rehype-recma@1.0.0: + resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} + rehype-stringify@10.0.1: resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} @@ -7486,8 +7465,8 @@ packages: remark-gfm@4.0.0: resolution: {integrity: sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==} - remark-mdx@3.0.1: - resolution: {integrity: sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA==} + remark-mdx@3.1.0: + resolution: {integrity: sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==} remark-parse@11.0.0: resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} @@ -7614,8 +7593,8 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rollup@4.24.0: - resolution: {integrity: sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==} + rollup@4.24.4: + resolution: {integrity: sha512-vGorVWIsWfX3xbcyAS+I047kFKapHYivmkaT63Smj77XwvLSJos6M1xGqZnBPFQFBRZDOcG1QnYEIxAvTr/HjA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -7721,10 +7700,6 @@ packages: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} - set-value@2.0.1: - resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} - engines: {node: '>=0.10.0'} - setprototypeof@1.1.0: resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} @@ -7756,8 +7731,8 @@ packages: shell-quote@1.8.1: resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} - shiki@1.22.0: - resolution: {integrity: sha512-/t5LlhNs+UOKQCYBtl5ZsH/Vclz73GIqT2yQsCBygr8L/ppTdmpL4w3kPLoZJbMKVWtoG77Ue1feOjZfDxvMkw==} + shiki@1.22.2: + resolution: {integrity: sha512-3IZau0NdGKXhH2bBlUk4w1IHNxPh6A5B2sUpyY+8utLu2j/h1QpFkAaUA1bAMxOWWGtTWcAh531vnS4NJKS/lA==} side-channel@1.0.6: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} @@ -7821,18 +7796,6 @@ packages: resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - sort-asc@0.2.0: - resolution: {integrity: sha512-umMGhjPeHAI6YjABoSTrFp2zaBtXBej1a0yKkuMUyjjqu6FJsTF+JYwCswWDg+zJfk/5npWUUbd33HH/WLzpaA==} - engines: {node: '>=0.10.0'} - - sort-desc@0.2.0: - resolution: {integrity: sha512-NqZqyvL4VPW+RAxxXnB8gvE1kyikh8+pR+T+CXLksVRN9eiQqkQlPwqWYU0mF9Jm7UnctShlxLyAt1CaBOTL1w==} - engines: {node: '>=0.10.0'} - - sort-object@3.0.3: - resolution: {integrity: sha512-nK7WOY8jik6zaG9CRwZTaD5O7ETWDLZYMM12pqY8htll+7dYeqGfEUPcUBHOpSJg2vJOrvFIY2Dl5cX2ih1hAQ==} - engines: {node: '>=0.10.0'} - sortablejs@1.15.3: resolution: {integrity: sha512-zdK3/kwwAK1cJgy1rwl1YtNTbRmc8qW/+vgXf75A7NHag5of4pyI6uK86ktmQETyWRH7IGaE73uZOOBcGxgqZg==} @@ -7893,10 +7856,6 @@ packages: spectrum-colorpicker@1.8.1: resolution: {integrity: sha512-x1picQ5giVso71ESII7jZ3+ZFdit8WthNkzwJqLNdPDPzrltKUQGpTohWyPfSAID+bK1zGdO6bDbSh1S6GoLYA==} - split-string@3.1.0: - resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} - engines: {node: '>=0.10.0'} - sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -8051,6 +8010,11 @@ packages: peerDependencies: stylelint: ^16.1.0 + stylelint-high-performance-animation@1.10.0: + resolution: {integrity: sha512-YzNI+E6taN8pwgaM0INazRg4tw23VA17KNMKUVdOeohpnpSyJLBnLVT9NkRcaCFLodK/67smS5VZK+Qe4Ohrvw==} + peerDependencies: + stylelint: ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + stylelint@16.10.0: resolution: {integrity: sha512-z/8X2rZ52dt2c0stVwI9QL2AFJhLhbPkyfpDFcizs200V/g7v+UYY6SNcB9hKOLcDDX/yGLDsY/pX08sLkz9xQ==} engines: {node: '>=18.12.0'} @@ -8068,10 +8032,6 @@ packages: superscript-text@1.0.0: resolution: {integrity: sha512-gwu8l5MtRZ6koO0icVTlmN5pm7Dhh1+Xpe9O4x6ObMAsW+3jPbW14d1DsBq1F4wiI+WOFjXF35pslgec/G8yCQ==} - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -8169,8 +8129,8 @@ packages: uglify-js: optional: true - terser@5.34.1: - resolution: {integrity: sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==} + terser@5.36.0: + resolution: {integrity: sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==} engines: {node: '>=10'} hasBin: true @@ -8178,8 +8138,8 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} - text-decoder@1.2.0: - resolution: {integrity: sha512-n1yg1mOj9DNpk3NeZOx7T6jchTbyJS3i3cucbNN6FcdPriMZx7NsgrGpWWdWZZGxD7ES1XB+3uoqHMgOKaN+fg==} + text-decoder@1.2.1: + resolution: {integrity: sha512-x9v3H/lTKIJKQQe7RPQkLfKAnc9lUTkWDypIQgTzPJAq+5/GCDHonmshfvlsNSj58yyshbIJJDLmU15qNERrXQ==} text-field-edit@4.1.1: resolution: {integrity: sha512-bgvsU5CFdBm8YuDdpLALNqS7Th8bVaRD5cww9VMYaBYab324iN3XOXvuQxTxp9JSPhHHF6zGCDEyF08JUV2hPw==} @@ -8228,8 +8188,8 @@ packages: tinycolor2@1.6.0: resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} - tinyexec@0.3.0: - resolution: {integrity: sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==} + tinyexec@0.3.1: + resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} tinyqueue@2.0.3: resolution: {integrity: sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==} @@ -8240,21 +8200,17 @@ packages: tippy.js@6.3.7: resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} - tldts-core@6.1.51: - resolution: {integrity: sha512-bu9oCYYWC1iRjx+3UnAjqCsfrWNZV1ghNQf49b3w5xE8J/tNShHTzp5syWJfwGH+pxUgTTLUnzHnfuydW7wmbg==} + tldts-core@6.1.58: + resolution: {integrity: sha512-dR936xmhBm7AeqHIhCWwK765gZ7dFyL+IqLSFAjJbFlUXGMLCb8i2PzlzaOuWBuplBTaBYseSb565nk/ZEM0Bg==} - tldts@6.1.51: - resolution: {integrity: sha512-33lfQoL0JsDogIbZ8fgRyvv77GnRtwkNE/MOKocwUgPO1WrSfsq7+vQRKxRQZai5zd+zg97Iv9fpFQSzHyWdLA==} + tldts@6.1.58: + resolution: {integrity: sha512-MQJrJhjHOYGYb8DobR6Y4AdDbd4TYkyQ+KBDVc5ODzs1cbrvPpfN1IemYi9jfipJ/vR1YWvrDli0hg1y19VRoA==} hasBin: true to-absolute-glob@2.0.2: resolution: {integrity: sha512-rtwLUQEwT8ZeKQbyFJyomBRYXyE16U5VKuy0ftxLMK/PZb2fkOsg5r9kHdauuVDbsNdIBoC/HCthpidamQFXYA==} engines: {node: '>=0.10.0'} - to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - to-float32@1.1.0: resolution: {integrity: sha512-keDnAusn/vc+R3iEiSDw8TOF7gPiTLdK1ArvWtYbJQiVfmRg6i/CAvbKq3uIS0vWroAC7ZecN3DjQKw3aSklUg==} @@ -8308,8 +8264,8 @@ packages: trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - ts-api-utils@1.3.0: - resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + ts-api-utils@1.4.0: + resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==} engines: {node: '>=16'} peerDependencies: typescript: '>=4.2.0' @@ -8341,8 +8297,8 @@ packages: tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} - tslib@2.7.0: - resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} tsscmp@1.0.6: resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} @@ -8430,20 +8386,14 @@ packages: typesafe-path@0.2.2: resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==} - typescript-auto-import-cache@0.3.3: - resolution: {integrity: sha512-ojEC7+Ci1ij9eE6hp8Jl9VUNnsEKzztktP5gtYNRMrTmfXVwA1PITYYAkpxCvvupdSYa/Re51B6KMcv1CTZEUA==} + typescript-auto-import-cache@0.3.5: + resolution: {integrity: sha512-fAIveQKsoYj55CozUiBoj4b/7WpN0i4o74wiGY5JVUEoD0XiqDk1tJqTEjgzL2/AizKQrXxyRosSebyDzBZKjw==} typescript@5.6.3: resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} hasBin: true - typewise-core@1.2.0: - resolution: {integrity: sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==} - - typewise@1.0.3: - resolution: {integrity: sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ==} - uglify-js@3.19.3: resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} engines: {node: '>=0.8.0'} @@ -8462,9 +8412,6 @@ packages: underscore@1.13.7: resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==} - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} @@ -8490,10 +8437,6 @@ packages: unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} - union-value@1.0.1: - resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==} - engines: {node: '>=0.10.0'} - unique-filename@2.0.1: resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -8650,8 +8593,8 @@ packages: resolution: {integrity: sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==} engines: {node: '>= 0.10'} - vite@5.4.9: - resolution: {integrity: sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==} + vite@5.4.10: + resolution: {integrity: sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -8689,36 +8632,36 @@ packages: vite: optional: true - vnu-jar@23.4.11: - resolution: {integrity: sha512-lI5dzBYXtxhilNI7EeQ5iUduYnNBq7YWx4UjfBVLXfBQHnXYZSf3y3bpM0bSyDU6jy/+OyKV7nw4tzpR5lXSZg==} + vnu-jar@24.10.17: + resolution: {integrity: sha512-YT7gNrRY5PiJrI1GavlWRHWIwqq2o52COc6J9QeXPfoldKRiZ9BeGP4shNLLaVfi0naA+/LMksdYWkKCr4pnVg==} engines: {node: '>=0.10'} - volar-service-css@0.0.61: - resolution: {integrity: sha512-Ct9L/w+IB1JU8F4jofcNCGoHy6TF83aiapfZq9A0qYYpq+Kk5dH+ONS+rVZSsuhsunq8UvAuF8Gk6B8IFLfniw==} + volar-service-css@0.0.62: + resolution: {integrity: sha512-JwNyKsH3F8PuzZYuqPf+2e+4CTU8YoyUHEHVnoXNlrLe7wy9U3biomZ56llN69Ris7TTy/+DEX41yVxQpM4qvg==} peerDependencies: '@volar/language-service': ~2.4.0 peerDependenciesMeta: '@volar/language-service': optional: true - volar-service-emmet@0.0.61: - resolution: {integrity: sha512-iiYqBxjjcekqrRruw4COQHZME6EZYWVbkHjHDbULpml3g8HGJHzpAMkj9tXNCPxf36A+f1oUYjsvZt36qPg4cg==} + volar-service-emmet@0.0.62: + resolution: {integrity: sha512-U4dxWDBWz7Pi4plpbXf4J4Z/ss6kBO3TYrACxWNsE29abu75QzVS0paxDDhI6bhqpbDFXlpsDhZ9aXVFpnfGRQ==} peerDependencies: '@volar/language-service': ~2.4.0 peerDependenciesMeta: '@volar/language-service': optional: true - volar-service-html@0.0.61: - resolution: {integrity: sha512-yFE+YmmgqIL5HI4ORqP++IYb1QaGcv+xBboI0WkCxJJ/M35HZj7f5rbT3eQ24ECLXFbFCFanckwyWJVz5KmN3Q==} + volar-service-html@0.0.62: + resolution: {integrity: sha512-Zw01aJsZRh4GTGUjveyfEzEqpULQUdQH79KNEiKVYHZyuGtdBRYCHlrus1sueSNMxwwkuF5WnOHfvBzafs8yyQ==} peerDependencies: '@volar/language-service': ~2.4.0 peerDependenciesMeta: '@volar/language-service': optional: true - volar-service-prettier@0.0.61: - resolution: {integrity: sha512-F612nql5I0IS8HxXemCGvOR2Uxd4XooIwqYVUvk7WSBxP/+xu1jYvE3QJ7EVpl8Ty3S4SxPXYiYTsG3bi+gzIQ==} + volar-service-prettier@0.0.62: + resolution: {integrity: sha512-h2yk1RqRTE+vkYZaI9KYuwpDfOQRrTEMvoHol0yW4GFKc75wWQRrb5n/5abDrzMPrkQbSip8JH2AXbvrRtYh4w==} peerDependencies: '@volar/language-service': ~2.4.0 prettier: ^2.2 || ^3.0 @@ -8728,24 +8671,24 @@ packages: prettier: optional: true - volar-service-typescript-twoslash-queries@0.0.61: - resolution: {integrity: sha512-99FICGrEF0r1E2tV+SvprHPw9Knyg7BdW2fUch0tf59kG+KG+Tj4tL6tUg+cy8f23O/VXlmsWFMIE+bx1dXPnQ==} + volar-service-typescript-twoslash-queries@0.0.62: + resolution: {integrity: sha512-KxFt4zydyJYYI0kFAcWPTh4u0Ha36TASPZkAnNY784GtgajerUqM80nX/W1d0wVhmcOFfAxkVsf/Ed+tiYU7ng==} peerDependencies: '@volar/language-service': ~2.4.0 peerDependenciesMeta: '@volar/language-service': optional: true - volar-service-typescript@0.0.61: - resolution: {integrity: sha512-4kRHxVbW7wFBHZWRU6yWxTgiKETBDIJNwmJUAWeP0mHaKpnDGj/astdRFKqGFRYVeEYl45lcUPhdJyrzanjsdQ==} + volar-service-typescript@0.0.62: + resolution: {integrity: sha512-p7MPi71q7KOsH0eAbZwPBiKPp9B2+qrdHAd6VY5oTo9BUXatsOAdakTm9Yf0DUj6uWBAaOT01BSeVOPwucMV1g==} peerDependencies: '@volar/language-service': ~2.4.0 peerDependenciesMeta: '@volar/language-service': optional: true - volar-service-yaml@0.0.61: - resolution: {integrity: sha512-L+gbDiLDQQ1rZUbJ3mf3doDsoQUa8OZM/xdpk/unMg1Vz24Zmi2Ign8GrZyBD7bRoIQDwOH9gdktGDKzRPpUNw==} + volar-service-yaml@0.0.62: + resolution: {integrity: sha512-k7gvv7sk3wa+nGll3MaSKyjwQsJjIGCHFjVkl3wjaSP2nouKyn9aokGmqjrl39mi88Oy49giog2GkZH526wjig==} peerDependencies: '@volar/language-service': ~2.4.0 peerDependenciesMeta: @@ -8885,8 +8828,8 @@ packages: resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} engines: {node: '>=10.13.0'} - webpack@5.95.0: - resolution: {integrity: sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==} + webpack@5.96.1: + resolution: {integrity: sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -9119,8 +9062,8 @@ packages: engines: {node: '>=8.0.0'} hasBin: true - zod-to-json-schema@3.23.3: - resolution: {integrity: sha512-TYWChTxKQbRJp5ST22o/Irt9KC5nj7CdBKYB/AosCRdj/wxEMvv4NNaj9XVUHDOIp53ZxArGhnw5HMZziPFjog==} + zod-to-json-schema@3.23.5: + resolution: {integrity: sha512-5wlSS0bXfF/BrL4jPAbz9da5hDlDptdEppYfe+x4eIJ7jioqKG9uUxOwPzqof09u/XeVdrgFu29lZi+8XNDJtA==} peerDependencies: zod: ^3.23.3 @@ -9133,8 +9076,8 @@ packages: zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} - zulip-js@2.0.9: - resolution: {integrity: sha512-I8Cjnxa7qTaHwxN6YZ4IOL2IiTz89rD4NZul1t8Hzu+q8muSE4LT2iVAlnCrCut4KEbOZDA+Bsgp0/CtFkUbnA==} + zulip-js@2.1.0: + resolution: {integrity: sha512-kLdxzJZ/FvWHBotUJl7LXCHIkShTjy1FUk5HAWfsal1TM+hw0atCZwgasCpvFDBj01y+39ZEZXgjePaie74Xhg==} zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -9176,7 +9119,7 @@ snapshots: '@astrojs/check@0.9.4(prettier@3.3.3)(typescript@5.6.3)': dependencies: - '@astrojs/language-server': 2.15.0(prettier@3.3.3)(typescript@5.6.3) + '@astrojs/language-server': 2.15.4(prettier@3.3.3)(typescript@5.6.3) chokidar: 4.0.1 kleur: 4.1.5 typescript: 5.6.3 @@ -9189,24 +9132,24 @@ snapshots: '@astrojs/internal-helpers@0.4.1': {} - '@astrojs/language-server@2.15.0(prettier@3.3.3)(typescript@5.6.3)': + '@astrojs/language-server@2.15.4(prettier@3.3.3)(typescript@5.6.3)': dependencies: '@astrojs/compiler': 2.10.3 - '@astrojs/yaml2ts': 0.2.1 + '@astrojs/yaml2ts': 0.2.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@volar/kit': 2.4.6(typescript@5.6.3) - '@volar/language-core': 2.4.6 - '@volar/language-server': 2.4.6 - '@volar/language-service': 2.4.6 + '@volar/kit': 2.4.8(typescript@5.6.3) + '@volar/language-core': 2.4.8 + '@volar/language-server': 2.4.8 + '@volar/language-service': 2.4.8 fast-glob: 3.3.2 muggle-string: 0.4.1 - volar-service-css: 0.0.61(@volar/language-service@2.4.6) - volar-service-emmet: 0.0.61(@volar/language-service@2.4.6) - volar-service-html: 0.0.61(@volar/language-service@2.4.6) - volar-service-prettier: 0.0.61(@volar/language-service@2.4.6)(prettier@3.3.3) - volar-service-typescript: 0.0.61(@volar/language-service@2.4.6) - volar-service-typescript-twoslash-queries: 0.0.61(@volar/language-service@2.4.6) - volar-service-yaml: 0.0.61(@volar/language-service@2.4.6) + volar-service-css: 0.0.62(@volar/language-service@2.4.8) + volar-service-emmet: 0.0.62(@volar/language-service@2.4.8) + volar-service-html: 0.0.62(@volar/language-service@2.4.8) + volar-service-prettier: 0.0.62(@volar/language-service@2.4.8)(prettier@3.3.3) + volar-service-typescript: 0.0.62(@volar/language-service@2.4.8) + volar-service-typescript-twoslash-queries: 0.0.62(@volar/language-service@2.4.8) + volar-service-yaml: 0.0.62(@volar/language-service@2.4.8) vscode-html-languageservice: 5.3.1 vscode-uri: 3.0.8 optionalDependencies: @@ -9228,7 +9171,7 @@ snapshots: remark-parse: 11.0.0 remark-rehype: 11.1.1 remark-smartypants: 3.0.2 - shiki: 1.22.0 + shiki: 1.22.2 unified: 11.0.5 unist-util-remove-position: 5.0.0 unist-util-visit: 5.0.0 @@ -9237,12 +9180,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/mdx@3.1.8(astro@4.16.5(@types/node@20.16.11)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3))': + '@astrojs/mdx@3.1.9(astro@4.16.9(@types/node@22.9.0)(rollup@4.24.4)(terser@5.36.0)(typescript@5.6.3))': dependencies: '@astrojs/markdown-remark': 5.3.0 - '@mdx-js/mdx': 3.0.1 - acorn: 8.12.1 - astro: 4.16.5(@types/node@20.16.11)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) + '@mdx-js/mdx': 3.1.0(acorn@8.14.0) + acorn: 8.14.0 + astro: 4.16.9(@types/node@22.9.0)(rollup@4.24.4)(terser@5.36.0)(typescript@5.6.3) es-module-lexer: 1.5.4 estree-util-visit: 2.0.0 gray-matter: 4.0.3 @@ -9267,23 +9210,24 @@ snapshots: stream-replace-string: 2.0.0 zod: 3.23.8 - '@astrojs/starlight@0.28.3(astro@4.16.5(@types/node@20.16.11)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3))': + '@astrojs/starlight@0.28.6(astro@4.16.9(@types/node@22.9.0)(rollup@4.24.4)(terser@5.36.0)(typescript@5.6.3))': dependencies: - '@astrojs/mdx': 3.1.8(astro@4.16.5(@types/node@20.16.11)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)) + '@astrojs/mdx': 3.1.9(astro@4.16.9(@types/node@22.9.0)(rollup@4.24.4)(terser@5.36.0)(typescript@5.6.3)) '@astrojs/sitemap': 3.2.1 '@pagefind/default-ui': 1.1.1 '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - astro: 4.16.5(@types/node@20.16.11)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) - astro-expressive-code: 0.35.6(astro@4.16.5(@types/node@20.16.11)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)) + astro: 4.16.9(@types/node@22.9.0)(rollup@4.24.4)(terser@5.36.0)(typescript@5.6.3) + astro-expressive-code: 0.35.6(astro@4.16.9(@types/node@22.9.0)(rollup@4.24.4)(terser@5.36.0)(typescript@5.6.3)) bcp-47: 2.1.0 hast-util-from-html: 2.0.3 hast-util-select: 6.0.3 hast-util-to-string: 3.0.1 hastscript: 9.0.0 - i18next: 23.16.0 + i18next: 23.16.4 + js-yaml: 4.1.0 mdast-util-directive: 3.0.0 - mdast-util-to-markdown: 2.1.0 + mdast-util-to-markdown: 2.1.2 mdast-util-to-string: 4.0.0 pagefind: 1.1.1 rehype: 13.0.2 @@ -9307,29 +9251,30 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/yaml2ts@0.2.1': + '@astrojs/yaml2ts@0.2.2': dependencies: yaml: 2.6.0 - '@babel/code-frame@7.25.7': + '@babel/code-frame@7.26.2': dependencies: - '@babel/highlight': 7.25.7 - picocolors: 1.1.0 + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 - '@babel/compat-data@7.25.8': {} + '@babel/compat-data@7.26.2': {} - '@babel/core@7.25.8': + '@babel/core@7.26.0': dependencies: '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.25.7 - '@babel/generator': 7.25.7 - '@babel/helper-compilation-targets': 7.25.7 - '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.8) - '@babel/helpers': 7.25.7 - '@babel/parser': 7.25.8 - '@babel/template': 7.25.7 - '@babel/traverse': 7.25.7 - '@babel/types': 7.25.8 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helpers': 7.26.0 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 convert-source-map: 2.0.0 debug: 4.3.7 gensync: 1.0.0-beta.2 @@ -9338,695 +9283,694 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/eslint-parser@7.25.8(@babel/core@7.25.8)(eslint@8.57.1)': + '@babel/eslint-parser@7.25.9(@babel/core@7.26.0)(eslint@8.57.1)': dependencies: - '@babel/core': 7.25.8 + '@babel/core': 7.26.0 '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 eslint: 8.57.1 eslint-visitor-keys: 2.1.0 semver: 6.3.1 - '@babel/generator@7.25.7': + '@babel/generator@7.26.2': dependencies: - '@babel/types': 7.25.8 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.0.2 - '@babel/helper-annotate-as-pure@7.25.7': + '@babel/helper-annotate-as-pure@7.25.9': dependencies: - '@babel/types': 7.25.8 + '@babel/types': 7.26.0 - '@babel/helper-builder-binary-assignment-operator-visitor@7.25.7': + '@babel/helper-builder-binary-assignment-operator-visitor@7.25.9': dependencies: - '@babel/traverse': 7.25.7 - '@babel/types': 7.25.8 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color - '@babel/helper-compilation-targets@7.25.7': + '@babel/helper-compilation-targets@7.25.9': dependencies: - '@babel/compat-data': 7.25.8 - '@babel/helper-validator-option': 7.25.7 - browserslist: 4.24.0 + '@babel/compat-data': 7.26.2 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.2 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.25.7(@babel/core@7.25.8)': + '@babel/helper-create-class-features-plugin@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-annotate-as-pure': 7.25.7 - '@babel/helper-member-expression-to-functions': 7.25.7 - '@babel/helper-optimise-call-expression': 7.25.7 - '@babel/helper-replace-supers': 7.25.7(@babel/core@7.25.8) - '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 - '@babel/traverse': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/traverse': 7.25.9 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.25.7(@babel/core@7.25.8)': + '@babel/helper-create-regexp-features-plugin@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-annotate-as-pure': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 regexpu-core: 6.1.1 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.25.8)': + '@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-compilation-targets': 7.25.7 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 debug: 4.3.7 lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: - supports-color - '@babel/helper-member-expression-to-functions@7.25.7': + '@babel/helper-member-expression-to-functions@7.25.9': dependencies: - '@babel/traverse': 7.25.7 - '@babel/types': 7.25.8 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@7.25.7': + '@babel/helper-module-imports@7.25.9': dependencies: - '@babel/traverse': 7.25.7 - '@babel/types': 7.25.8 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.25.7(@babel/core@7.25.8)': + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-module-imports': 7.25.7 - '@babel/helper-simple-access': 7.25.7 - '@babel/helper-validator-identifier': 7.25.7 - '@babel/traverse': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/helper-optimise-call-expression@7.25.7': + '@babel/helper-optimise-call-expression@7.25.9': dependencies: - '@babel/types': 7.25.8 + '@babel/types': 7.26.0 - '@babel/helper-plugin-utils@7.25.7': {} + '@babel/helper-plugin-utils@7.25.9': {} - '@babel/helper-remap-async-to-generator@7.25.7(@babel/core@7.25.8)': + '@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-annotate-as-pure': 7.25.7 - '@babel/helper-wrap-function': 7.25.7 - '@babel/traverse': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-wrap-function': 7.25.9 + '@babel/traverse': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.25.7(@babel/core@7.25.8)': + '@babel/helper-replace-supers@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-member-expression-to-functions': 7.25.7 - '@babel/helper-optimise-call-expression': 7.25.7 - '@babel/traverse': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/traverse': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/helper-simple-access@7.25.7': + '@babel/helper-simple-access@7.25.9': dependencies: - '@babel/traverse': 7.25.7 - '@babel/types': 7.25.8 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color - '@babel/helper-skip-transparent-expression-wrappers@7.25.7': + '@babel/helper-skip-transparent-expression-wrappers@7.25.9': dependencies: - '@babel/traverse': 7.25.7 - '@babel/types': 7.25.8 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color - '@babel/helper-string-parser@7.25.7': {} + '@babel/helper-string-parser@7.25.9': {} - '@babel/helper-validator-identifier@7.25.7': {} + '@babel/helper-validator-identifier@7.25.9': {} - '@babel/helper-validator-option@7.25.7': {} + '@babel/helper-validator-option@7.25.9': {} - '@babel/helper-wrap-function@7.25.7': + '@babel/helper-wrap-function@7.25.9': dependencies: - '@babel/template': 7.25.7 - '@babel/traverse': 7.25.7 - '@babel/types': 7.25.8 + '@babel/template': 7.25.9 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color - '@babel/helpers@7.25.7': + '@babel/helpers@7.26.0': dependencies: - '@babel/template': 7.25.7 - '@babel/types': 7.25.8 + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 - '@babel/highlight@7.25.7': + '@babel/parser@7.26.2': dependencies: - '@babel/helper-validator-identifier': 7.25.7 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.1.0 + '@babel/types': 7.26.0 - '@babel/parser@7.25.8': + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/types': 7.25.8 - - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.7(@babel/core@7.25.8)': - dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 - '@babel/traverse': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/traverse': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 - '@babel/plugin-transform-optional-chaining': 7.25.8(@babel/core@7.25.8) + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.0) transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 - '@babel/traverse': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/traverse': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.25.8)': + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 + '@babel/core': 7.26.0 - '@babel/plugin-syntax-import-assertions@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-syntax-import-attributes@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-syntax-jsx@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-syntax-typescript@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.25.8)': + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.8) - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-arrow-functions@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-async-generator-functions@7.25.8(@babel/core@7.25.8)': + '@babel/plugin-transform-async-generator-functions@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 - '@babel/helper-remap-async-to-generator': 7.25.7(@babel/core@7.25.8) - '@babel/traverse': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.0) + '@babel/traverse': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-to-generator@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-module-imports': 7.25.7 - '@babel/helper-plugin-utils': 7.25.7 - '@babel/helper-remap-async-to-generator': 7.25.7(@babel/core@7.25.8) + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-block-scoped-functions@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-block-scoped-functions@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-block-scoping@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-class-properties@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-create-class-features-plugin': 7.25.7(@babel/core@7.25.8) - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-static-block@7.25.8(@babel/core@7.25.8)': + '@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-create-class-features-plugin': 7.25.7(@babel/core@7.25.8) - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-classes@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-annotate-as-pure': 7.25.7 - '@babel/helper-compilation-targets': 7.25.7 - '@babel/helper-plugin-utils': 7.25.7 - '@babel/helper-replace-supers': 7.25.7(@babel/core@7.25.8) - '@babel/traverse': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) + '@babel/traverse': 7.25.9 globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-computed-properties@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 - '@babel/template': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/template': 7.25.9 - '@babel/plugin-transform-destructuring@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-dotall-regex@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.8) - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-duplicate-keys@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.8) - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-dynamic-import@7.25.8(@babel/core@7.25.8)': + '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-exponentiation-operator@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-exponentiation-operator@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-builder-binary-assignment-operator-visitor': 7.25.7 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-export-namespace-from@7.25.8(@babel/core@7.25.8)': + '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-for-of@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-for-of@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-function-name@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-function-name@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-compilation-targets': 7.25.7 - '@babel/helper-plugin-utils': 7.25.7 - '@babel/traverse': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/traverse': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-json-strings@7.25.8(@babel/core@7.25.8)': + '@babel/plugin-transform-json-strings@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-literals@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-literals@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-logical-assignment-operators@7.25.8(@babel/core@7.25.8)': + '@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-member-expression-literals@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-member-expression-literals@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-modules-amd@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-modules-amd@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.8) - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-modules-commonjs@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.8) - '@babel/helper-plugin-utils': 7.25.7 - '@babel/helper-simple-access': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-simple-access': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-modules-systemjs@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.8) - '@babel/helper-plugin-utils': 7.25.7 - '@babel/helper-validator-identifier': 7.25.7 - '@babel/traverse': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-umd@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-modules-umd@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.8) - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-named-capturing-groups-regex@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.8) - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-new-target@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-new-target@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-nullish-coalescing-operator@7.25.8(@babel/core@7.25.8)': + '@babel/plugin-transform-nullish-coalescing-operator@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-numeric-separator@7.25.8(@babel/core@7.25.8)': + '@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-object-rest-spread@7.25.8(@babel/core@7.25.8)': + '@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-compilation-targets': 7.25.7 - '@babel/helper-plugin-utils': 7.25.7 - '@babel/plugin-transform-parameters': 7.25.7(@babel/core@7.25.8) + '@babel/core': 7.26.0 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-object-super@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-object-super@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 - '@babel/helper-replace-supers': 7.25.7(@babel/core@7.25.8) + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-catch-binding@7.25.8(@babel/core@7.25.8)': + '@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-optional-chaining@7.25.8(@babel/core@7.25.8)': + '@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-parameters@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-parameters@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-private-methods@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-create-class-features-plugin': 7.25.7(@babel/core@7.25.8) - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.25.8(@babel/core@7.25.8)': + '@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-annotate-as-pure': 7.25.7 - '@babel/helper-create-class-features-plugin': 7.25.7(@babel/core@7.25.8) - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-property-literals@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-react-jsx@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-annotate-as-pure': 7.25.7 - '@babel/helper-module-imports': 7.25.7 - '@babel/helper-plugin-utils': 7.25.7 - '@babel/plugin-syntax-jsx': 7.25.7(@babel/core@7.25.8) - '@babel/types': 7.25.8 + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-regenerator@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 regenerator-transform: 0.15.2 - '@babel/plugin-transform-reserved-words@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-shorthand-properties@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-spread@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-spread@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-sticky-regex@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-template-literals@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-template-literals@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-typeof-symbol@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-typeof-symbol@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-typescript@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-typescript@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-annotate-as-pure': 7.25.7 - '@babel/helper-create-class-features-plugin': 7.25.7(@babel/core@7.25.8) - '@babel/helper-plugin-utils': 7.25.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 - '@babel/plugin-syntax-typescript': 7.25.7(@babel/core@7.25.8) + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-unicode-escapes@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-unicode-property-regex@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.8) - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-unicode-regex@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.8) - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-unicode-sets-regex@7.25.7(@babel/core@7.25.8)': + '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.8) - '@babel/helper-plugin-utils': 7.25.7 + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 - '@babel/preset-env@7.25.8(@babel/core@7.25.8)': + '@babel/preset-env@7.26.0(@babel/core@7.26.0)': dependencies: - '@babel/compat-data': 7.25.8 - '@babel/core': 7.25.8 - '@babel/helper-compilation-targets': 7.25.7 - '@babel/helper-plugin-utils': 7.25.7 - '@babel/helper-validator-option': 7.25.7 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.25.8) - '@babel/plugin-syntax-import-assertions': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-syntax-import-attributes': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.25.8) - '@babel/plugin-transform-arrow-functions': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-async-generator-functions': 7.25.8(@babel/core@7.25.8) - '@babel/plugin-transform-async-to-generator': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-block-scoped-functions': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-block-scoping': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-class-properties': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-class-static-block': 7.25.8(@babel/core@7.25.8) - '@babel/plugin-transform-classes': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-computed-properties': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-destructuring': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-dotall-regex': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-duplicate-keys': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-dynamic-import': 7.25.8(@babel/core@7.25.8) - '@babel/plugin-transform-exponentiation-operator': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-export-namespace-from': 7.25.8(@babel/core@7.25.8) - '@babel/plugin-transform-for-of': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-function-name': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-json-strings': 7.25.8(@babel/core@7.25.8) - '@babel/plugin-transform-literals': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-logical-assignment-operators': 7.25.8(@babel/core@7.25.8) - '@babel/plugin-transform-member-expression-literals': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-modules-amd': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-modules-commonjs': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-modules-systemjs': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-modules-umd': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-named-capturing-groups-regex': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-new-target': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-nullish-coalescing-operator': 7.25.8(@babel/core@7.25.8) - '@babel/plugin-transform-numeric-separator': 7.25.8(@babel/core@7.25.8) - '@babel/plugin-transform-object-rest-spread': 7.25.8(@babel/core@7.25.8) - '@babel/plugin-transform-object-super': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-optional-catch-binding': 7.25.8(@babel/core@7.25.8) - '@babel/plugin-transform-optional-chaining': 7.25.8(@babel/core@7.25.8) - '@babel/plugin-transform-parameters': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-private-methods': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-private-property-in-object': 7.25.8(@babel/core@7.25.8) - '@babel/plugin-transform-property-literals': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-regenerator': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-reserved-words': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-shorthand-properties': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-spread': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-sticky-regex': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-template-literals': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-typeof-symbol': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-unicode-escapes': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-unicode-property-regex': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-unicode-regex': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-unicode-sets-regex': 7.25.7(@babel/core@7.25.8) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.25.8) - babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.25.8) - babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.25.8) - babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.25.8) - core-js-compat: 3.38.1 + '@babel/compat-data': 7.26.2 + '@babel/core': 7.26.0 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.0) + '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.26.0) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.0) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.26.0) + '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-async-generator-functions': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-block-scoped-functions': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-block-scoping': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-class-static-block': 7.26.0(@babel/core@7.26.0) + '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-dotall-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-exponentiation-operator': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-for-of': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-json-strings': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-member-expression-literals': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-amd': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-systemjs': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-umd': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-new-target': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-nullish-coalescing-operator': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-object-super': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-regenerator': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-regexp-modifiers': 7.26.0(@babel/core@7.26.0) + '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-template-literals': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-typeof-symbol': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.26.0) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.26.0) + babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.26.0) + babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.26.0) + babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.26.0) + core-js-compat: 3.39.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.25.8)': + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 - '@babel/types': 7.25.8 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/types': 7.26.0 esutils: 2.0.3 - '@babel/preset-typescript@7.25.7(@babel/core@7.25.8)': + '@babel/preset-typescript@7.26.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 - '@babel/helper-validator-option': 7.25.7 - '@babel/plugin-syntax-jsx': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-modules-commonjs': 7.25.7(@babel/core@7.25.8) - '@babel/plugin-transform-typescript': 7.25.7(@babel/core@7.25.8) + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-commonjs': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-typescript': 7.25.9(@babel/core@7.26.0) transitivePeerDependencies: - supports-color - '@babel/register@7.25.7(@babel/core@7.25.8)': + '@babel/register@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.25.8 + '@babel/core': 7.26.0 clone-deep: 4.0.1 find-cache-dir: 2.1.0 make-dir: 2.1.0 pirates: 4.0.6 source-map-support: 0.5.21 - '@babel/runtime@7.25.7': + '@babel/runtime@7.26.0': dependencies: regenerator-runtime: 0.14.1 - '@babel/template@7.25.7': + '@babel/template@7.25.9': dependencies: - '@babel/code-frame': 7.25.7 - '@babel/parser': 7.25.8 - '@babel/types': 7.25.8 + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 - '@babel/traverse@7.25.7': + '@babel/traverse@7.25.9': dependencies: - '@babel/code-frame': 7.25.7 - '@babel/generator': 7.25.7 - '@babel/parser': 7.25.8 - '@babel/template': 7.25.7 - '@babel/types': 7.25.8 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 debug: 4.3.7 globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/types@7.25.8': + '@babel/types@7.26.0': dependencies: - '@babel/helper-string-parser': 7.25.7 - '@babel/helper-validator-identifier': 7.25.7 - to-fast-properties: 2.0.0 + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 '@choojs/findup@0.2.1': dependencies: @@ -10038,78 +9982,78 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@csstools/cascade-layer-name-parser@2.0.2(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2)': + '@csstools/cascade-layer-name-parser@2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': dependencies: - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 '@csstools/color-helpers@5.0.1': {} - '@csstools/css-calc@2.0.2(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2)': + '@csstools/css-calc@2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': dependencies: - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 - '@csstools/css-color-parser@3.0.3(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2)': + '@csstools/css-color-parser@3.0.5(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': dependencies: '@csstools/color-helpers': 5.0.1 - '@csstools/css-calc': 2.0.2(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2) - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 + '@csstools/css-calc': 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 - '@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2)': + '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)': dependencies: - '@csstools/css-tokenizer': 3.0.2 + '@csstools/css-tokenizer': 3.0.3 - '@csstools/css-tokenizer@3.0.2': {} + '@csstools/css-tokenizer@3.0.3': {} - '@csstools/media-query-list-parser@3.0.1(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2)': + '@csstools/media-query-list-parser@3.0.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': dependencies: - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 - '@csstools/media-query-list-parser@4.0.0(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2)': + '@csstools/media-query-list-parser@4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': dependencies: - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 - '@csstools/postcss-cascade-layers@5.0.0(postcss@8.4.47)': + '@csstools/postcss-cascade-layers@5.0.1(postcss@8.4.47)': dependencies: - '@csstools/selector-specificity': 4.0.0(postcss-selector-parser@6.1.2) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.0.0) postcss: 8.4.47 - postcss-selector-parser: 6.1.2 + postcss-selector-parser: 7.0.0 - '@csstools/postcss-color-function@4.0.3(postcss@8.4.47)': + '@csstools/postcss-color-function@4.0.5(postcss@8.4.47)': dependencies: - '@csstools/css-color-parser': 3.0.3(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2) - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 + '@csstools/css-color-parser': 3.0.5(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.47) '@csstools/utilities': 2.0.0(postcss@8.4.47) postcss: 8.4.47 - '@csstools/postcss-color-mix-function@3.0.3(postcss@8.4.47)': + '@csstools/postcss-color-mix-function@3.0.5(postcss@8.4.47)': dependencies: - '@csstools/css-color-parser': 3.0.3(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2) - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 + '@csstools/css-color-parser': 3.0.5(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.47) '@csstools/utilities': 2.0.0(postcss@8.4.47) postcss: 8.4.47 - '@csstools/postcss-content-alt-text@2.0.2(postcss@8.4.47)': + '@csstools/postcss-content-alt-text@2.0.4(postcss@8.4.47)': dependencies: - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.47) '@csstools/utilities': 2.0.0(postcss@8.4.47) postcss: 8.4.47 - '@csstools/postcss-exponential-functions@2.0.2(postcss@8.4.47)': + '@csstools/postcss-exponential-functions@2.0.4(postcss@8.4.47)': dependencies: - '@csstools/css-calc': 2.0.2(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2) - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 + '@csstools/css-calc': 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 postcss: 8.4.47 '@csstools/postcss-font-format-keywords@4.0.0(postcss@8.4.47)': @@ -10118,27 +10062,27 @@ snapshots: postcss: 8.4.47 postcss-value-parser: 4.2.0 - '@csstools/postcss-gamut-mapping@2.0.3(postcss@8.4.47)': + '@csstools/postcss-gamut-mapping@2.0.5(postcss@8.4.47)': dependencies: - '@csstools/css-color-parser': 3.0.3(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2) - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 + '@csstools/css-color-parser': 3.0.5(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 postcss: 8.4.47 - '@csstools/postcss-gradients-interpolation-method@5.0.3(postcss@8.4.47)': + '@csstools/postcss-gradients-interpolation-method@5.0.5(postcss@8.4.47)': dependencies: - '@csstools/css-color-parser': 3.0.3(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2) - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 + '@csstools/css-color-parser': 3.0.5(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.47) '@csstools/utilities': 2.0.0(postcss@8.4.47) postcss: 8.4.47 - '@csstools/postcss-hwb-function@4.0.3(postcss@8.4.47)': + '@csstools/postcss-hwb-function@4.0.5(postcss@8.4.47)': dependencies: - '@csstools/css-color-parser': 3.0.3(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2) - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 + '@csstools/css-color-parser': 3.0.5(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.47) '@csstools/utilities': 2.0.0(postcss@8.4.47) postcss: 8.4.47 @@ -10154,16 +10098,16 @@ snapshots: dependencies: postcss: 8.4.47 - '@csstools/postcss-is-pseudo-class@5.0.0(postcss@8.4.47)': + '@csstools/postcss-is-pseudo-class@5.0.1(postcss@8.4.47)': dependencies: - '@csstools/selector-specificity': 4.0.0(postcss-selector-parser@6.1.2) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.0.0) postcss: 8.4.47 - postcss-selector-parser: 6.1.2 + postcss-selector-parser: 7.0.0 - '@csstools/postcss-light-dark-function@2.0.5(postcss@8.4.47)': + '@csstools/postcss-light-dark-function@2.0.7(postcss@8.4.47)': dependencies: - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.47) '@csstools/utilities': 2.0.0(postcss@8.4.47) postcss: 8.4.47 @@ -10185,25 +10129,25 @@ snapshots: postcss: 8.4.47 postcss-value-parser: 4.2.0 - '@csstools/postcss-logical-viewport-units@3.0.2(postcss@8.4.47)': + '@csstools/postcss-logical-viewport-units@3.0.3(postcss@8.4.47)': dependencies: - '@csstools/css-tokenizer': 3.0.2 + '@csstools/css-tokenizer': 3.0.3 '@csstools/utilities': 2.0.0(postcss@8.4.47) postcss: 8.4.47 - '@csstools/postcss-media-minmax@2.0.2(postcss@8.4.47)': + '@csstools/postcss-media-minmax@2.0.4(postcss@8.4.47)': dependencies: - '@csstools/css-calc': 2.0.2(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2) - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 - '@csstools/media-query-list-parser': 4.0.0(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2) + '@csstools/css-calc': 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/media-query-list-parser': 4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) postcss: 8.4.47 - '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.2(postcss@8.4.47)': + '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.4(postcss@8.4.47)': dependencies: - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 - '@csstools/media-query-list-parser': 4.0.0(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/media-query-list-parser': 4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) postcss: 8.4.47 '@csstools/postcss-nested-calc@4.0.0(postcss@8.4.47)': @@ -10217,11 +10161,11 @@ snapshots: postcss: 8.4.47 postcss-value-parser: 4.2.0 - '@csstools/postcss-oklab-function@4.0.3(postcss@8.4.47)': + '@csstools/postcss-oklab-function@4.0.5(postcss@8.4.47)': dependencies: - '@csstools/css-color-parser': 3.0.3(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2) - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 + '@csstools/css-color-parser': 3.0.5(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.47) '@csstools/utilities': 2.0.0(postcss@8.4.47) postcss: 8.4.47 @@ -10231,25 +10175,25 @@ snapshots: postcss: 8.4.47 postcss-value-parser: 4.2.0 - '@csstools/postcss-relative-color-syntax@3.0.3(postcss@8.4.47)': + '@csstools/postcss-relative-color-syntax@3.0.5(postcss@8.4.47)': dependencies: - '@csstools/css-color-parser': 3.0.3(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2) - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 + '@csstools/css-color-parser': 3.0.5(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.47) '@csstools/utilities': 2.0.0(postcss@8.4.47) postcss: 8.4.47 - '@csstools/postcss-scope-pseudo-class@4.0.0(postcss@8.4.47)': + '@csstools/postcss-scope-pseudo-class@4.0.1(postcss@8.4.47)': dependencies: postcss: 8.4.47 - postcss-selector-parser: 6.1.2 + postcss-selector-parser: 7.0.0 - '@csstools/postcss-stepped-value-functions@4.0.2(postcss@8.4.47)': + '@csstools/postcss-stepped-value-functions@4.0.4(postcss@8.4.47)': dependencies: - '@csstools/css-calc': 2.0.2(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2) - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 + '@csstools/css-calc': 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 postcss: 8.4.47 '@csstools/postcss-text-decoration-shorthand@4.0.1(postcss@8.4.47)': @@ -10258,20 +10202,20 @@ snapshots: postcss: 8.4.47 postcss-value-parser: 4.2.0 - '@csstools/postcss-trigonometric-functions@4.0.2(postcss@8.4.47)': + '@csstools/postcss-trigonometric-functions@4.0.4(postcss@8.4.47)': dependencies: - '@csstools/css-calc': 2.0.2(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2) - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 + '@csstools/css-calc': 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 postcss: 8.4.47 '@csstools/postcss-unset-value@4.0.0(postcss@8.4.47)': dependencies: postcss: 8.4.47 - '@csstools/selector-resolve-nested@2.0.0(postcss-selector-parser@6.1.2)': + '@csstools/selector-resolve-nested@3.0.0(postcss-selector-parser@7.0.0)': dependencies: - postcss-selector-parser: 6.1.2 + postcss-selector-parser: 7.0.0 '@csstools/selector-specificity@2.2.0(postcss-selector-parser@6.1.2)': dependencies: @@ -10281,6 +10225,10 @@ snapshots: dependencies: postcss-selector-parser: 6.1.2 + '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.0.0)': + dependencies: + postcss-selector-parser: 7.0.0 + '@csstools/utilities@2.0.0(postcss@8.4.47)': dependencies: postcss: 8.4.47 @@ -10322,13 +10270,13 @@ snapshots: '@emnapi/runtime@1.3.1': dependencies: - tslib: 2.7.0 + tslib: 2.8.1 optional: true '@emotion/babel-plugin@11.12.0': dependencies: - '@babel/helper-module-imports': 7.25.7 - '@babel/runtime': 7.25.7 + '@babel/helper-module-imports': 7.25.9 + '@babel/runtime': 7.26.0 '@emotion/hash': 0.9.2 '@emotion/memoize': 0.9.0 '@emotion/serialize': 1.3.2 @@ -10356,7 +10304,7 @@ snapshots: '@emotion/weak-memoize': 0.4.0 stylis: 4.2.0 - '@emotion/css@11.9.0(@babel/core@7.25.8)': + '@emotion/css@11.9.0(@babel/core@7.26.0)': dependencies: '@emotion/babel-plugin': 11.12.0 '@emotion/cache': 11.13.1 @@ -10364,7 +10312,7 @@ snapshots: '@emotion/sheet': 1.4.0 '@emotion/utils': 1.4.1 optionalDependencies: - '@babel/core': 7.25.8 + '@babel/core': 7.26.0 transitivePeerDependencies: - supports-color @@ -10479,12 +10427,12 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true - '@eslint-community/eslint-utils@4.4.0(eslint@8.57.1)': + '@eslint-community/eslint-utils@4.4.1(eslint@8.57.1)': dependencies: eslint: 8.57.1 eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.11.1': {} + '@eslint-community/regexpp@4.12.1': {} '@eslint/eslintrc@2.1.4': dependencies: @@ -10521,7 +10469,7 @@ snapshots: '@expressive-code/plugin-shiki@0.35.6': dependencies: '@expressive-code/core': 0.35.6 - shiki: 1.22.0 + shiki: 1.22.2 '@expressive-code/plugin-text-markers@0.35.6': dependencies: @@ -10529,65 +10477,65 @@ snapshots: '@fontsource-variable/open-sans@5.1.0': {} - '@formatjs/cli@6.2.15': {} + '@formatjs/cli@6.3.8': {} - '@formatjs/ecma402-abstract@2.2.0': + '@formatjs/ecma402-abstract@2.2.3': dependencies: - '@formatjs/fast-memoize': 2.2.1 - '@formatjs/intl-localematcher': 0.5.5 - tslib: 2.7.0 + '@formatjs/fast-memoize': 2.2.3 + '@formatjs/intl-localematcher': 0.5.7 + tslib: 2.8.1 - '@formatjs/fast-memoize@2.2.1': + '@formatjs/fast-memoize@2.2.3': dependencies: - tslib: 2.7.0 + tslib: 2.8.1 - '@formatjs/icu-messageformat-parser@2.7.10': + '@formatjs/icu-messageformat-parser@2.9.3': dependencies: - '@formatjs/ecma402-abstract': 2.2.0 - '@formatjs/icu-skeleton-parser': 1.8.4 - tslib: 2.7.0 + '@formatjs/ecma402-abstract': 2.2.3 + '@formatjs/icu-skeleton-parser': 1.8.7 + tslib: 2.8.1 - '@formatjs/icu-skeleton-parser@1.8.4': + '@formatjs/icu-skeleton-parser@1.8.7': dependencies: - '@formatjs/ecma402-abstract': 2.2.0 - tslib: 2.7.0 + '@formatjs/ecma402-abstract': 2.2.3 + tslib: 2.8.1 - '@formatjs/intl-displaynames@6.6.10': + '@formatjs/intl-displaynames@6.8.4': dependencies: - '@formatjs/ecma402-abstract': 2.2.0 - '@formatjs/intl-localematcher': 0.5.5 - tslib: 2.7.0 + '@formatjs/ecma402-abstract': 2.2.3 + '@formatjs/intl-localematcher': 0.5.7 + tslib: 2.8.1 - '@formatjs/intl-listformat@7.5.9': + '@formatjs/intl-listformat@7.7.4': dependencies: - '@formatjs/ecma402-abstract': 2.2.0 - '@formatjs/intl-localematcher': 0.5.5 - tslib: 2.7.0 + '@formatjs/ecma402-abstract': 2.2.3 + '@formatjs/intl-localematcher': 0.5.7 + tslib: 2.8.1 - '@formatjs/intl-localematcher@0.5.5': + '@formatjs/intl-localematcher@0.5.7': dependencies: - tslib: 2.7.0 + tslib: 2.8.1 - '@formatjs/intl@2.10.8(typescript@5.6.3)': + '@formatjs/intl@2.10.14(typescript@5.6.3)': dependencies: - '@formatjs/ecma402-abstract': 2.2.0 - '@formatjs/fast-memoize': 2.2.1 - '@formatjs/icu-messageformat-parser': 2.7.10 - '@formatjs/intl-displaynames': 6.6.10 - '@formatjs/intl-listformat': 7.5.9 - intl-messageformat: 10.7.0 - tslib: 2.7.0 + '@formatjs/ecma402-abstract': 2.2.3 + '@formatjs/fast-memoize': 2.2.3 + '@formatjs/icu-messageformat-parser': 2.9.3 + '@formatjs/intl-displaynames': 6.8.4 + '@formatjs/intl-listformat': 7.7.4 + intl-messageformat: 10.7.6 + tslib: 2.8.1 optionalDependencies: typescript: 5.6.3 - '@formatjs/ts-transformer@3.13.16': + '@formatjs/ts-transformer@3.13.22': dependencies: - '@formatjs/icu-messageformat-parser': 2.7.10 - '@types/json-stable-stringify': 1.0.36 - '@types/node': 18.19.55 + '@formatjs/icu-messageformat-parser': 2.9.3 + '@types/json-stable-stringify': 1.1.0 + '@types/node': 22.9.0 chalk: 4.1.2 json-stable-stringify: 1.1.1 - tslib: 2.7.0 + tslib: 2.8.1 typescript: 5.6.3 '@gar/promisify@1.1.3': {} @@ -10599,7 +10547,7 @@ snapshots: '@giphy/js-analytics@5.0.0': dependencies: '@giphy/js-types': 5.1.0 - '@giphy/js-util': 5.1.0 + '@giphy/js-util': 5.2.0 append-query: 2.1.1 throttle-debounce: 3.0.1 @@ -10609,14 +10557,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@giphy/js-components@5.13.0(@babel/core@7.25.8)': + '@giphy/js-components@5.13.0(@babel/core@7.26.0)': dependencies: - '@emotion/css': 11.9.0(@babel/core@7.25.8) + '@emotion/css': 11.9.0(@babel/core@7.26.0) '@giphy/js-analytics': 5.0.0 '@giphy/js-brand': 3.0.0 '@giphy/js-fetch-api': 5.6.0 '@giphy/js-types': 5.1.0 - '@giphy/js-util': 5.1.0 + '@giphy/js-util': 5.2.0 bricks.js: 1.8.0 intersection-observer: 0.11.0 preact: 10.4.8 @@ -10628,11 +10576,11 @@ snapshots: '@giphy/js-fetch-api@5.6.0': dependencies: '@giphy/js-types': 5.1.0 - '@giphy/js-util': 5.1.0 + '@giphy/js-util': 5.2.0 '@giphy/js-types@5.1.0': {} - '@giphy/js-util@5.1.0': + '@giphy/js-util@5.2.0': dependencies: '@giphy/js-types': 5.1.0 uuid: 9.0.1 @@ -10745,7 +10693,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.16.11 + '@types/node': 22.9.0 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -10778,21 +10726,21 @@ snapshots: '@jsdevtools/ono@7.1.3': {} - '@jsonjoy.com/base64@1.1.2(tslib@2.7.0)': + '@jsonjoy.com/base64@1.1.2(tslib@2.8.1)': dependencies: - tslib: 2.7.0 + tslib: 2.8.1 - '@jsonjoy.com/json-pack@1.1.0(tslib@2.7.0)': + '@jsonjoy.com/json-pack@1.1.0(tslib@2.8.1)': dependencies: - '@jsonjoy.com/base64': 1.1.2(tslib@2.7.0) - '@jsonjoy.com/util': 1.5.0(tslib@2.7.0) + '@jsonjoy.com/base64': 1.1.2(tslib@2.8.1) + '@jsonjoy.com/util': 1.5.0(tslib@2.8.1) hyperdyperid: 1.2.0 - thingies: 1.21.0(tslib@2.7.0) - tslib: 2.7.0 + thingies: 1.21.0(tslib@2.8.1) + tslib: 2.8.1 - '@jsonjoy.com/util@1.5.0(tslib@2.7.0)': + '@jsonjoy.com/util@1.5.0(tslib@2.8.1)': dependencies: - tslib: 2.7.0 + tslib: 2.8.1 '@koa/bodyparser@5.1.1(koa@2.15.3)': dependencies: @@ -10832,7 +10780,7 @@ snapshots: '@mapbox/whoots-js@3.1.0': {} - '@maplibre/maplibre-gl-style-spec@20.3.1': + '@maplibre/maplibre-gl-style-spec@20.4.0': dependencies: '@mapbox/jsonlint-lines-primitives': 2.0.2 '@mapbox/unitbezier': 0.0.1 @@ -10840,10 +10788,9 @@ snapshots: minimist: 1.2.8 quickselect: 2.0.0 rw: 1.3.3 - sort-object: 3.0.3 tinyqueue: 3.0.0 - '@mdx-js/mdx@3.0.1': + '@mdx-js/mdx@3.1.0(acorn@8.14.0)': dependencies: '@types/estree': 1.0.6 '@types/estree-jsx': 1.0.5 @@ -10851,15 +10798,16 @@ snapshots: '@types/mdx': 2.0.13 collapse-white-space: 2.1.0 devlop: 1.1.0 - estree-util-build-jsx: 3.0.1 estree-util-is-identifier-name: 3.0.0 - estree-util-to-js: 2.0.0 + estree-util-scope: 1.0.0 estree-walker: 3.0.3 - hast-util-to-estree: 3.1.0 hast-util-to-jsx-runtime: 2.3.2 markdown-extensions: 2.0.0 - periscopic: 3.1.0 - remark-mdx: 3.0.1 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.0(acorn@8.14.0) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0 + remark-mdx: 3.1.0 remark-parse: 11.0.0 remark-rehype: 11.1.1 source-map: 0.7.4 @@ -10869,6 +10817,7 @@ snapshots: unist-util-visit: 5.0.0 vfile: 6.0.3 transitivePeerDependencies: + - acorn - supports-color '@mixmark-io/domino@2.2.0': {} @@ -10979,7 +10928,7 @@ snapshots: '@popperjs/core@2.11.8': {} - '@puppeteer/browsers@2.4.0': + '@puppeteer/browsers@2.4.1': dependencies: debug: 4.3.7 extract-zip: 2.0.1 @@ -10992,132 +10941,138 @@ snapshots: transitivePeerDependencies: - supports-color - '@rollup/pluginutils@5.1.2(rollup@4.24.0)': + '@rollup/pluginutils@5.1.3(rollup@4.24.4)': dependencies: '@types/estree': 1.0.6 estree-walker: 2.0.2 - picomatch: 2.3.1 + picomatch: 4.0.2 optionalDependencies: - rollup: 4.24.0 + rollup: 4.24.4 - '@rollup/rollup-android-arm-eabi@4.24.0': + '@rollup/rollup-android-arm-eabi@4.24.4': optional: true - '@rollup/rollup-android-arm64@4.24.0': + '@rollup/rollup-android-arm64@4.24.4': optional: true - '@rollup/rollup-darwin-arm64@4.24.0': + '@rollup/rollup-darwin-arm64@4.24.4': optional: true - '@rollup/rollup-darwin-x64@4.24.0': + '@rollup/rollup-darwin-x64@4.24.4': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.24.0': + '@rollup/rollup-freebsd-arm64@4.24.4': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.24.0': + '@rollup/rollup-freebsd-x64@4.24.4': optional: true - '@rollup/rollup-linux-arm64-gnu@4.24.0': + '@rollup/rollup-linux-arm-gnueabihf@4.24.4': optional: true - '@rollup/rollup-linux-arm64-musl@4.24.0': + '@rollup/rollup-linux-arm-musleabihf@4.24.4': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': + '@rollup/rollup-linux-arm64-gnu@4.24.4': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.24.0': + '@rollup/rollup-linux-arm64-musl@4.24.4': optional: true - '@rollup/rollup-linux-s390x-gnu@4.24.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.24.4': optional: true - '@rollup/rollup-linux-x64-gnu@4.24.0': + '@rollup/rollup-linux-riscv64-gnu@4.24.4': optional: true - '@rollup/rollup-linux-x64-musl@4.24.0': + '@rollup/rollup-linux-s390x-gnu@4.24.4': optional: true - '@rollup/rollup-win32-arm64-msvc@4.24.0': + '@rollup/rollup-linux-x64-gnu@4.24.4': optional: true - '@rollup/rollup-win32-ia32-msvc@4.24.0': + '@rollup/rollup-linux-x64-musl@4.24.4': optional: true - '@rollup/rollup-win32-x64-msvc@4.24.0': + '@rollup/rollup-win32-arm64-msvc@4.24.4': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.24.4': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.24.4': optional: true '@rtsao/scc@1.1.0': {} - '@sentry-internal/browser-utils@8.34.0': + '@sentry-internal/browser-utils@8.37.1': dependencies: - '@sentry/core': 8.34.0 - '@sentry/types': 8.34.0 - '@sentry/utils': 8.34.0 + '@sentry/core': 8.37.1 + '@sentry/types': 8.37.1 + '@sentry/utils': 8.37.1 - '@sentry-internal/feedback@8.34.0': + '@sentry-internal/feedback@8.37.1': dependencies: - '@sentry/core': 8.34.0 - '@sentry/types': 8.34.0 - '@sentry/utils': 8.34.0 + '@sentry/core': 8.37.1 + '@sentry/types': 8.37.1 + '@sentry/utils': 8.37.1 - '@sentry-internal/replay-canvas@8.34.0': + '@sentry-internal/replay-canvas@8.37.1': dependencies: - '@sentry-internal/replay': 8.34.0 - '@sentry/core': 8.34.0 - '@sentry/types': 8.34.0 - '@sentry/utils': 8.34.0 + '@sentry-internal/replay': 8.37.1 + '@sentry/core': 8.37.1 + '@sentry/types': 8.37.1 + '@sentry/utils': 8.37.1 - '@sentry-internal/replay@8.34.0': + '@sentry-internal/replay@8.37.1': dependencies: - '@sentry-internal/browser-utils': 8.34.0 - '@sentry/core': 8.34.0 - '@sentry/types': 8.34.0 - '@sentry/utils': 8.34.0 + '@sentry-internal/browser-utils': 8.37.1 + '@sentry/core': 8.37.1 + '@sentry/types': 8.37.1 + '@sentry/utils': 8.37.1 - '@sentry/browser@8.34.0': + '@sentry/browser@8.37.1': dependencies: - '@sentry-internal/browser-utils': 8.34.0 - '@sentry-internal/feedback': 8.34.0 - '@sentry-internal/replay': 8.34.0 - '@sentry-internal/replay-canvas': 8.34.0 - '@sentry/core': 8.34.0 - '@sentry/types': 8.34.0 - '@sentry/utils': 8.34.0 + '@sentry-internal/browser-utils': 8.37.1 + '@sentry-internal/feedback': 8.37.1 + '@sentry-internal/replay': 8.37.1 + '@sentry-internal/replay-canvas': 8.37.1 + '@sentry/core': 8.37.1 + '@sentry/types': 8.37.1 + '@sentry/utils': 8.37.1 - '@sentry/core@8.34.0': + '@sentry/core@8.37.1': dependencies: - '@sentry/types': 8.34.0 - '@sentry/utils': 8.34.0 + '@sentry/types': 8.37.1 + '@sentry/utils': 8.37.1 - '@sentry/types@8.34.0': {} + '@sentry/types@8.37.1': {} - '@sentry/utils@8.34.0': + '@sentry/utils@8.37.1': dependencies: - '@sentry/types': 8.34.0 + '@sentry/types': 8.37.1 - '@shikijs/core@1.22.0': + '@shikijs/core@1.22.2': dependencies: - '@shikijs/engine-javascript': 1.22.0 - '@shikijs/engine-oniguruma': 1.22.0 - '@shikijs/types': 1.22.0 + '@shikijs/engine-javascript': 1.22.2 + '@shikijs/engine-oniguruma': 1.22.2 + '@shikijs/types': 1.22.2 '@shikijs/vscode-textmate': 9.3.0 '@types/hast': 3.0.4 hast-util-to-html: 9.0.3 - '@shikijs/engine-javascript@1.22.0': + '@shikijs/engine-javascript@1.22.2': dependencies: - '@shikijs/types': 1.22.0 + '@shikijs/types': 1.22.2 '@shikijs/vscode-textmate': 9.3.0 oniguruma-to-js: 0.4.3 - '@shikijs/engine-oniguruma@1.22.0': + '@shikijs/engine-oniguruma@1.22.2': dependencies: - '@shikijs/types': 1.22.0 + '@shikijs/types': 1.22.2 '@shikijs/vscode-textmate': 9.3.0 - '@shikijs/types@1.22.0': + '@shikijs/types@1.22.2': dependencies: '@shikijs/vscode-textmate': 9.3.0 '@types/hast': 3.0.4 @@ -11147,26 +11102,26 @@ snapshots: '@turf/helpers': 7.1.0 '@turf/meta': 7.1.0 '@types/geojson': 7946.0.14 - tslib: 2.7.0 + tslib: 2.8.1 '@turf/bbox@7.1.0': dependencies: '@turf/helpers': 7.1.0 '@turf/meta': 7.1.0 '@types/geojson': 7946.0.14 - tslib: 2.7.0 + tslib: 2.8.1 '@turf/centroid@7.1.0': dependencies: '@turf/helpers': 7.1.0 '@turf/meta': 7.1.0 '@types/geojson': 7946.0.14 - tslib: 2.7.0 + tslib: 2.8.1 '@turf/helpers@7.1.0': dependencies: '@types/geojson': 7946.0.14 - tslib: 2.7.0 + tslib: 2.8.1 '@turf/meta@7.1.0': dependencies: @@ -11175,7 +11130,7 @@ snapshots: '@types/accepts@1.3.7': dependencies: - '@types/node': 20.16.11 + '@types/node': 22.9.0 '@types/acorn@4.0.6': dependencies: @@ -11185,15 +11140,15 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.25.8 - '@babel/types': 7.25.8 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.6 '@types/babel__generator@7.6.8': dependencies: - '@babel/types': 7.25.8 + '@babel/types': 7.26.0 '@types/babel__helper-plugin-utils@7.10.3': dependencies: @@ -11201,37 +11156,37 @@ snapshots: '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.25.8 - '@babel/types': 7.25.8 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 '@types/babel__traverse@7.20.6': dependencies: - '@babel/types': 7.25.8 + '@babel/types': 7.26.0 '@types/blueimp-md5@2.18.2': {} '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 - '@types/node': 20.16.11 + '@types/node': 22.9.0 '@types/bonjour@3.5.13': dependencies: - '@types/node': 20.16.11 + '@types/node': 22.9.0 '@types/co-body@6.1.3': dependencies: - '@types/node': 20.16.11 - '@types/qs': 6.9.16 + '@types/node': 22.9.0 + '@types/qs': 6.9.17 '@types/connect-history-api-fallback@1.5.4': dependencies: - '@types/express-serve-static-core': 5.0.0 - '@types/node': 20.16.11 + '@types/express-serve-static-core': 5.0.1 + '@types/node': 22.9.0 '@types/connect@3.4.38': dependencies: - '@types/node': 20.16.11 + '@types/node': 22.9.0 '@types/content-disposition@0.5.8': {} @@ -11242,12 +11197,17 @@ snapshots: '@types/connect': 3.4.38 '@types/express': 5.0.0 '@types/keygrip': 1.0.6 - '@types/node': 20.16.11 + '@types/node': 22.9.0 '@types/debug@4.1.12': dependencies: '@types/ms': 0.7.34 + '@types/eslint-scope@3.7.7': + dependencies: + '@types/eslint': 9.6.1 + '@types/estree': 1.0.6 + '@types/eslint@9.6.1': dependencies: '@types/estree': 1.0.6 @@ -11261,15 +11221,15 @@ snapshots: '@types/express-serve-static-core@4.19.6': dependencies: - '@types/node': 20.16.11 - '@types/qs': 6.9.16 + '@types/node': 22.9.0 + '@types/qs': 6.9.17 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 - '@types/express-serve-static-core@5.0.0': + '@types/express-serve-static-core@5.0.1': dependencies: - '@types/node': 20.16.11 - '@types/qs': 6.9.16 + '@types/node': 22.9.0 + '@types/qs': 6.9.17 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -11277,14 +11237,14 @@ snapshots: dependencies: '@types/body-parser': 1.19.5 '@types/express-serve-static-core': 4.19.6 - '@types/qs': 6.9.16 + '@types/qs': 6.9.17 '@types/serve-static': 1.15.7 '@types/express@5.0.0': dependencies: '@types/body-parser': 1.19.5 - '@types/express-serve-static-core': 5.0.0 - '@types/qs': 6.9.16 + '@types/express-serve-static-core': 5.0.1 + '@types/qs': 6.9.17 '@types/serve-static': 1.15.7 '@types/geojson-vt@3.2.5': @@ -11299,13 +11259,13 @@ snapshots: '@types/html-minifier-terser@6.1.0': {} - '@types/http-assert@1.5.5': {} + '@types/http-assert@1.5.6': {} '@types/http-errors@2.0.4': {} '@types/http-proxy@1.17.15': dependencies: - '@types/node': 20.16.11 + '@types/node': 22.9.0 '@types/is-url@1.2.32': {} @@ -11321,17 +11281,17 @@ snapshots: '@types/jquery.validation@1.17.0': dependencies: - '@types/jquery': 3.5.31 + '@types/jquery': 3.5.32 - '@types/jquery@3.5.31': + '@types/jquery@3.5.32': dependencies: - '@types/sizzle': 2.3.8 + '@types/sizzle': 2.3.9 '@types/js-cookie@3.0.6': {} '@types/json-schema@7.0.15': {} - '@types/json-stable-stringify@1.0.36': {} + '@types/json-stable-stringify@1.1.0': {} '@types/json5@0.0.29': {} @@ -11348,17 +11308,17 @@ snapshots: '@types/accepts': 1.3.7 '@types/content-disposition': 0.5.8 '@types/cookies': 0.9.0 - '@types/http-assert': 1.5.5 + '@types/http-assert': 1.5.6 '@types/http-errors': 2.0.4 '@types/keygrip': 1.0.6 '@types/koa-compose': 3.2.8 - '@types/node': 20.16.11 + '@types/node': 22.9.0 '@types/lodash-es@4.17.12': dependencies: - '@types/lodash': 4.17.10 + '@types/lodash': 4.17.13 - '@types/lodash@4.17.10': {} + '@types/lodash@4.17.13': {} '@types/mapbox__point-geometry@0.1.4': {} @@ -11390,15 +11350,11 @@ snapshots: '@types/node-forge@1.3.11': dependencies: - '@types/node': 20.16.11 + '@types/node': 22.9.0 '@types/node@17.0.45': {} - '@types/node@18.19.55': - dependencies: - undici-types: 5.26.5 - - '@types/node@20.16.11': + '@types/node@22.9.0': dependencies: undici-types: 6.19.8 @@ -11408,11 +11364,11 @@ snapshots: '@types/pbf@3.0.5': {} - '@types/picomatch@2.3.4': {} + '@types/picomatch@3.0.1': {} - '@types/plotly.js@2.33.4': {} + '@types/plotly.js@2.33.5': {} - '@types/qs@6.9.16': {} + '@types/qs@6.9.17': {} '@types/range-parser@1.2.7': {} @@ -11420,12 +11376,12 @@ snapshots: '@types/sax@1.2.7': dependencies: - '@types/node': 20.16.11 + '@types/node': 22.9.0 '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 20.16.11 + '@types/node': 22.9.0 '@types/serve-index@1.9.4': dependencies: @@ -11434,20 +11390,20 @@ snapshots: '@types/serve-static@1.15.7': dependencies: '@types/http-errors': 2.0.4 - '@types/node': 20.16.11 + '@types/node': 22.9.0 '@types/send': 0.17.4 - '@types/sizzle@2.3.8': {} + '@types/sizzle@2.3.9': {} '@types/sockjs@0.3.36': dependencies: - '@types/node': 20.16.11 + '@types/node': 22.9.0 '@types/sortablejs@1.15.8': {} '@types/spectrum@1.8.7': dependencies: - '@types/jquery': 3.5.31 + '@types/jquery': 3.5.32 '@types/tinycolor2': 1.4.6 '@types/supercluster@7.1.3': @@ -11466,9 +11422,9 @@ snapshots: '@types/unist@3.0.3': {} - '@types/ws@8.5.12': + '@types/ws@8.5.13': dependencies: - '@types/node': 20.16.11 + '@types/node': 22.9.0 '@types/yargs-parser@21.0.3': {} @@ -11478,33 +11434,33 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 20.16.11 + '@types/node': 22.9.0 optional: true - '@typescript-eslint/eslint-plugin@8.9.0(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/eslint-plugin@8.13.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)': dependencies: - '@eslint-community/regexpp': 4.11.1 - '@typescript-eslint/parser': 8.9.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/scope-manager': 8.9.0 - '@typescript-eslint/type-utils': 8.9.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/utils': 8.9.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 8.9.0 + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.13.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/scope-manager': 8.13.0 + '@typescript-eslint/type-utils': 8.13.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/utils': 8.13.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.13.0 eslint: 8.57.1 graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.6.3) + ts-api-utils: 1.4.0(typescript@5.6.3) optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3)': dependencies: - '@typescript-eslint/scope-manager': 8.9.0 - '@typescript-eslint/types': 8.9.0 - '@typescript-eslint/typescript-estree': 8.9.0(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 8.9.0 + '@typescript-eslint/scope-manager': 8.13.0 + '@typescript-eslint/types': 8.13.0 + '@typescript-eslint/typescript-estree': 8.13.0(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.13.0 debug: 4.3.7 eslint: 8.57.1 optionalDependencies: @@ -11512,158 +11468,120 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.5.0': + '@typescript-eslint/scope-manager@8.13.0': dependencies: - '@typescript-eslint/types': 8.5.0 - '@typescript-eslint/visitor-keys': 8.5.0 + '@typescript-eslint/types': 8.13.0 + '@typescript-eslint/visitor-keys': 8.13.0 - '@typescript-eslint/scope-manager@8.9.0': + '@typescript-eslint/type-utils@8.13.0(eslint@8.57.1)(typescript@5.6.3)': dependencies: - '@typescript-eslint/types': 8.9.0 - '@typescript-eslint/visitor-keys': 8.9.0 - - '@typescript-eslint/type-utils@8.9.0(eslint@8.57.1)(typescript@5.6.3)': - dependencies: - '@typescript-eslint/typescript-estree': 8.9.0(typescript@5.6.3) - '@typescript-eslint/utils': 8.9.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/typescript-estree': 8.13.0(typescript@5.6.3) + '@typescript-eslint/utils': 8.13.0(eslint@8.57.1)(typescript@5.6.3) debug: 4.3.7 - ts-api-utils: 1.3.0(typescript@5.6.3) + ts-api-utils: 1.4.0(typescript@5.6.3) optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: - eslint - supports-color - '@typescript-eslint/types@8.5.0': {} + '@typescript-eslint/types@8.13.0': {} - '@typescript-eslint/types@8.9.0': {} - - '@typescript-eslint/typescript-estree@8.5.0(typescript@5.6.3)': + '@typescript-eslint/typescript-estree@8.13.0(typescript@5.6.3)': dependencies: - '@typescript-eslint/types': 8.5.0 - '@typescript-eslint/visitor-keys': 8.5.0 + '@typescript-eslint/types': 8.13.0 + '@typescript-eslint/visitor-keys': 8.13.0 debug: 4.3.7 fast-glob: 3.3.2 is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 - ts-api-utils: 1.3.0(typescript@5.6.3) + ts-api-utils: 1.4.0(typescript@5.6.3) optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.9.0(typescript@5.6.3)': + '@typescript-eslint/utils@8.13.0(eslint@8.57.1)(typescript@5.6.3)': dependencies: - '@typescript-eslint/types': 8.9.0 - '@typescript-eslint/visitor-keys': 8.9.0 - debug: 4.3.7 - fast-glob: 3.3.2 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.6.3 - ts-api-utils: 1.3.0(typescript@5.6.3) - optionalDependencies: - typescript: 5.6.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.5.0(eslint@8.57.1)(typescript@5.6.3)': - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) - '@typescript-eslint/scope-manager': 8.5.0 - '@typescript-eslint/types': 8.5.0 - '@typescript-eslint/typescript-estree': 8.5.0(typescript@5.6.3) + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@typescript-eslint/scope-manager': 8.13.0 + '@typescript-eslint/types': 8.13.0 + '@typescript-eslint/typescript-estree': 8.13.0(typescript@5.6.3) eslint: 8.57.1 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/utils@8.9.0(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/visitor-keys@8.13.0': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) - '@typescript-eslint/scope-manager': 8.9.0 - '@typescript-eslint/types': 8.9.0 - '@typescript-eslint/typescript-estree': 8.9.0(typescript@5.6.3) - eslint: 8.57.1 - transitivePeerDependencies: - - supports-color - - typescript - - '@typescript-eslint/visitor-keys@8.5.0': - dependencies: - '@typescript-eslint/types': 8.5.0 - eslint-visitor-keys: 3.4.3 - - '@typescript-eslint/visitor-keys@8.9.0': - dependencies: - '@typescript-eslint/types': 8.9.0 + '@typescript-eslint/types': 8.13.0 eslint-visitor-keys: 3.4.3 '@ungap/structured-clone@1.2.0': {} - '@uppy/companion-client@4.1.0(@uppy/core@4.2.2)': + '@uppy/companion-client@4.1.1(@uppy/core@4.2.3)': dependencies: - '@uppy/core': 4.2.2 - '@uppy/utils': 6.0.3 + '@uppy/core': 4.2.3 + '@uppy/utils': 6.0.4 namespace-emitter: 2.0.1 p-retry: 6.2.0 - '@uppy/core@4.2.2': + '@uppy/core@4.2.3': dependencies: '@transloadit/prettier-bytes': 0.3.4 - '@uppy/store-default': 4.1.0 - '@uppy/utils': 6.0.3 + '@uppy/store-default': 4.1.1 + '@uppy/utils': 6.0.4 lodash: 4.17.21 mime-match: 1.0.2 namespace-emitter: 2.0.1 - nanoid: 5.0.7 + nanoid: 5.0.8 preact: 10.24.3 - '@uppy/drag-drop@4.0.3(@uppy/core@4.2.2)': + '@uppy/drag-drop@4.0.4(@uppy/core@4.2.3)': dependencies: - '@uppy/core': 4.2.2 - '@uppy/utils': 6.0.3 + '@uppy/core': 4.2.3 + '@uppy/utils': 6.0.4 preact: 10.24.3 - '@uppy/progress-bar@4.0.0(@uppy/core@4.2.2)': + '@uppy/progress-bar@4.0.1(@uppy/core@4.2.3)': dependencies: - '@uppy/core': 4.2.2 - '@uppy/utils': 6.0.3 + '@uppy/core': 4.2.3 + '@uppy/utils': 6.0.4 preact: 10.24.3 - '@uppy/store-default@4.1.0': {} + '@uppy/store-default@4.1.1': {} - '@uppy/tus@4.1.2(@uppy/core@4.2.2)': + '@uppy/tus@4.1.3(@uppy/core@4.2.3)': dependencies: - '@uppy/companion-client': 4.1.0(@uppy/core@4.2.2) - '@uppy/core': 4.2.2 - '@uppy/utils': 6.0.3 + '@uppy/companion-client': 4.1.1(@uppy/core@4.2.3) + '@uppy/core': 4.2.3 + '@uppy/utils': 6.0.4 tus-js-client: 4.2.3 - '@uppy/utils@6.0.3': + '@uppy/utils@6.0.4': dependencies: lodash: 4.17.21 preact: 10.24.3 - '@volar/kit@2.4.6(typescript@5.6.3)': + '@volar/kit@2.4.8(typescript@5.6.3)': dependencies: - '@volar/language-service': 2.4.6 - '@volar/typescript': 2.4.6 + '@volar/language-service': 2.4.8 + '@volar/typescript': 2.4.8 typesafe-path: 0.2.2 typescript: 5.6.3 vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.0.8 - '@volar/language-core@2.4.6': + '@volar/language-core@2.4.8': dependencies: - '@volar/source-map': 2.4.6 + '@volar/source-map': 2.4.8 - '@volar/language-server@2.4.6': + '@volar/language-server@2.4.8': dependencies: - '@volar/language-core': 2.4.6 - '@volar/language-service': 2.4.6 - '@volar/typescript': 2.4.6 + '@volar/language-core': 2.4.8 + '@volar/language-service': 2.4.8 + '@volar/typescript': 2.4.8 path-browserify: 1.0.1 request-light: 0.7.0 vscode-languageserver: 9.0.1 @@ -11671,18 +11589,18 @@ snapshots: vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.0.8 - '@volar/language-service@2.4.6': + '@volar/language-service@2.4.8': dependencies: - '@volar/language-core': 2.4.6 + '@volar/language-core': 2.4.8 vscode-languageserver-protocol: 3.17.5 vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.0.8 - '@volar/source-map@2.4.6': {} + '@volar/source-map@2.4.8': {} - '@volar/typescript@2.4.6': + '@volar/typescript@2.4.8': dependencies: - '@volar/language-core': 2.4.6 + '@volar/language-core': 2.4.8 path-browserify: 1.0.1 vscode-uri: 3.0.8 @@ -11788,22 +11706,22 @@ snapshots: '@webassemblyjs/ast': 1.12.1 '@xtuc/long': 4.2.2 - '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.95.0)': + '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.96.1)': dependencies: - webpack: 5.95.0(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.95.0) + webpack: 5.96.1(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.96.1) - '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.95.0)': + '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.96.1)': dependencies: - webpack: 5.95.0(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.95.0) + webpack: 5.96.1(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.96.1) - '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack-dev-server@5.1.0)(webpack@5.95.0)': + '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack-dev-server@5.1.0)(webpack@5.96.1)': dependencies: - webpack: 5.95.0(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.95.0) + webpack: 5.96.1(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.96.1) optionalDependencies: - webpack-dev-server: 5.1.0(webpack-cli@5.1.4)(webpack@5.95.0) + webpack-dev-server: 5.1.0(webpack-cli@5.1.4)(webpack@5.96.1) '@xmldom/xmldom@0.7.13': {} @@ -11834,23 +11752,19 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 - acorn-import-attributes@1.9.5(acorn@8.12.1): + acorn-jsx@5.3.2(acorn@8.14.0): dependencies: - acorn: 8.12.1 - - acorn-jsx@5.3.2(acorn@8.12.1): - dependencies: - acorn: 8.12.1 + acorn: 8.14.0 acorn-walk@8.3.4: dependencies: - acorn: 8.12.1 + acorn: 8.14.0 acorn@7.4.1: {} acorn@8.11.3: {} - acorn@8.12.1: {} + acorn@8.14.0: {} agent-base@6.0.2: dependencies: @@ -11920,10 +11834,6 @@ snapshots: ansi-regex@6.1.0: {} - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -11968,8 +11878,6 @@ snapshots: aria-query@5.3.2: {} - arr-union@3.1.0: {} - array-bounds@1.0.1: {} array-buffer-byte-length@1.0.1: @@ -12038,35 +11946,33 @@ snapshots: arrify@1.0.1: {} - assign-symbols@1.0.0: {} - ast-types@0.13.4: dependencies: - tslib: 2.7.0 + tslib: 2.8.1 astral-regex@2.0.0: {} astring@1.9.0: {} - astro-expressive-code@0.35.6(astro@4.16.5(@types/node@20.16.11)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3)): + astro-expressive-code@0.35.6(astro@4.16.9(@types/node@22.9.0)(rollup@4.24.4)(terser@5.36.0)(typescript@5.6.3)): dependencies: - astro: 4.16.5(@types/node@20.16.11)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3) + astro: 4.16.9(@types/node@22.9.0)(rollup@4.24.4)(terser@5.36.0)(typescript@5.6.3) rehype-expressive-code: 0.35.6 - astro@4.16.5(@types/node@20.16.11)(rollup@4.24.0)(terser@5.34.1)(typescript@5.6.3): + astro@4.16.9(@types/node@22.9.0)(rollup@4.24.4)(terser@5.36.0)(typescript@5.6.3): dependencies: '@astrojs/compiler': 2.10.3 '@astrojs/internal-helpers': 0.4.1 '@astrojs/markdown-remark': 5.3.0 '@astrojs/telemetry': 3.1.0 - '@babel/core': 7.25.8 - '@babel/plugin-transform-react-jsx': 7.25.7(@babel/core@7.25.8) - '@babel/types': 7.25.8 + '@babel/core': 7.26.0 + '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/types': 7.26.0 '@oslojs/encoding': 1.1.0 - '@rollup/pluginutils': 5.1.2(rollup@4.24.0) + '@rollup/pluginutils': 5.1.3(rollup@4.24.4) '@types/babel__core': 7.20.5 '@types/cookie': 0.6.0 - acorn: 8.12.1 + acorn: 8.14.0 aria-query: 5.3.2 axobject-query: 4.1.0 boxen: 8.0.1 @@ -12097,25 +12003,25 @@ snapshots: micromatch: 4.0.8 mrmime: 2.0.0 neotraverse: 0.6.18 - ora: 8.1.0 + ora: 8.1.1 p-limit: 6.1.0 p-queue: 8.0.1 preferred-pm: 4.0.0 prompts: 2.4.2 rehype: 13.0.2 semver: 7.6.3 - shiki: 1.22.0 - tinyexec: 0.3.0 + shiki: 1.22.2 + tinyexec: 0.3.1 tsconfck: 3.1.4(typescript@5.6.3) unist-util-visit: 5.0.0 vfile: 6.0.3 - vite: 5.4.9(@types/node@20.16.11)(terser@5.34.1) - vitefu: 1.0.3(vite@5.4.9(@types/node@20.16.11)(terser@5.34.1)) + vite: 5.4.10(@types/node@22.9.0)(terser@5.36.0) + vitefu: 1.0.3(vite@5.4.10(@types/node@22.9.0)(terser@5.36.0)) which-pm: 3.0.0 xxhash-wasm: 1.0.2 yargs-parser: 21.1.1 zod: 3.23.8 - zod-to-json-schema: 3.23.3(zod@3.23.8) + zod-to-json-schema: 3.23.5(zod@3.23.8) zod-to-ts: 1.2.0(typescript@5.6.3)(zod@3.23.8) optionalDependencies: sharp: 0.33.5 @@ -12138,11 +12044,11 @@ snapshots: autoprefixer@10.4.20(postcss@8.4.47): dependencies: - browserslist: 4.24.0 - caniuse-lite: 1.0.30001668 + browserslist: 4.24.2 + caniuse-lite: 1.0.30001677 fraction.js: 4.3.7 normalize-range: 0.1.2 - picocolors: 1.1.0 + picocolors: 1.1.1 postcss: 8.4.47 postcss-value-parser: 4.2.0 @@ -12158,16 +12064,16 @@ snapshots: b4a@1.6.7: {} - babel-loader@9.2.1(@babel/core@7.25.8)(webpack@5.95.0): + babel-loader@9.2.1(@babel/core@7.26.0)(webpack@5.96.1): dependencies: - '@babel/core': 7.25.8 + '@babel/core': 7.26.0 find-cache-dir: 4.0.0 schema-utils: 4.2.0 - webpack: 5.95.0(webpack-cli@5.1.4) + webpack: 5.96.1(webpack-cli@5.1.4) babel-plugin-emotion@10.2.2: dependencies: - '@babel/helper-module-imports': 7.25.7 + '@babel/helper-module-imports': 7.25.9 '@emotion/hash': 0.8.0 '@emotion/memoize': 0.7.4 '@emotion/serialize': 0.11.16 @@ -12180,26 +12086,26 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-formatjs@10.5.18: + babel-plugin-formatjs@10.5.24: dependencies: - '@babel/core': 7.25.8 - '@babel/helper-plugin-utils': 7.25.7 - '@babel/plugin-syntax-jsx': 7.25.7(@babel/core@7.25.8) - '@babel/traverse': 7.25.7 - '@babel/types': 7.25.8 - '@formatjs/icu-messageformat-parser': 2.7.10 - '@formatjs/ts-transformer': 3.13.16 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 + '@formatjs/icu-messageformat-parser': 2.9.3 + '@formatjs/ts-transformer': 3.13.22 '@types/babel__core': 7.20.5 '@types/babel__helper-plugin-utils': 7.10.3 '@types/babel__traverse': 7.20.6 - tslib: 2.7.0 + tslib: 2.8.1 transitivePeerDependencies: - supports-color - ts-jest babel-plugin-istanbul@7.0.0: dependencies: - '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-plugin-utils': 7.25.9 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 6.0.3 @@ -12209,37 +12115,37 @@ snapshots: babel-plugin-macros@2.8.0: dependencies: - '@babel/runtime': 7.25.7 + '@babel/runtime': 7.26.0 cosmiconfig: 6.0.0 resolve: 1.22.8 babel-plugin-macros@3.1.0: dependencies: - '@babel/runtime': 7.25.7 + '@babel/runtime': 7.26.0 cosmiconfig: 7.1.0 resolve: 1.22.8 - babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.25.8): + babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.26.0): dependencies: - '@babel/compat-data': 7.25.8 - '@babel/core': 7.25.8 - '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.25.8) + '@babel/compat-data': 7.26.2 + '@babel/core': 7.26.0 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.26.0) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.25.8): + babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.26.0): dependencies: - '@babel/core': 7.25.8 - '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.25.8) - core-js-compat: 3.38.1 + '@babel/core': 7.26.0 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.26.0) + core-js-compat: 3.39.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.25.8): + babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.26.0): dependencies: - '@babel/core': 7.25.8 - '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.25.8) + '@babel/core': 7.26.0 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.26.0) transitivePeerDependencies: - supports-color @@ -12260,7 +12166,7 @@ snapshots: dependencies: bare-events: 2.5.0 bare-path: 2.1.3 - bare-stream: 2.3.0 + bare-stream: 2.3.2 optional: true bare-os@2.4.4: @@ -12271,9 +12177,8 @@ snapshots: bare-os: 2.4.4 optional: true - bare-stream@2.3.0: + bare-stream@2.3.2: dependencies: - b4a: 1.6.7 streamx: 2.20.1 optional: true @@ -12372,12 +12277,12 @@ snapshots: dependencies: knot.js: 1.1.5 - browserslist@4.24.0: + browserslist@4.24.2: dependencies: - caniuse-lite: 1.0.30001668 - electron-to-chromium: 1.5.38 + caniuse-lite: 1.0.30001677 + electron-to-chromium: 1.5.51 node-releases: 2.0.18 - update-browserslist-db: 1.1.1(browserslist@4.24.0) + update-browserslist-db: 1.1.1(browserslist@4.24.2) buffer-crc32@0.2.13: {} @@ -12412,19 +12317,8 @@ snapshots: dependencies: run-applescript: 7.0.0 - bytes@3.0.0: {} - bytes@3.1.2: {} - bytewise-core@1.2.3: - dependencies: - typewise-core: 1.2.0 - - bytewise@1.1.0: - dependencies: - bytewise-core: 1.2.3 - typewise: 1.0.3 - cacache@16.1.3: dependencies: '@npmcli/fs': 2.1.2 @@ -12475,7 +12369,7 @@ snapshots: camel-case@4.1.2: dependencies: pascal-case: 3.1.2 - tslib: 2.7.0 + tslib: 2.8.1 camelcase-keys@7.0.2: dependencies: @@ -12494,12 +12388,12 @@ snapshots: caniuse-api@3.0.0: dependencies: - browserslist: 4.24.0 - caniuse-lite: 1.0.30001668 + browserslist: 4.24.2 + caniuse-lite: 1.0.30001677 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 - caniuse-lite@1.0.30001668: {} + caniuse-lite@1.0.30001677: {} canvas-fit@1.5.0: dependencies: @@ -12507,12 +12401,6 @@ snapshots: ccount@2.0.1: {} - chalk@2.4.2: - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -12548,9 +12436,9 @@ snapshots: chrome-trace-event@1.0.4: {} - chromium-bidi@0.8.0(devtools-protocol@0.0.1342118): + chromium-bidi@0.8.0(devtools-protocol@0.0.1354347): dependencies: - devtools-protocol: 0.0.1342118 + devtools-protocol: 0.0.1354347 mitt: 3.0.1 urlpattern-polyfill: 10.0.0 zod: 3.23.8 @@ -12752,20 +12640,20 @@ snapshots: dependencies: mime-db: 1.53.0 - compression-webpack-plugin@11.1.0(webpack@5.95.0): + compression-webpack-plugin@11.1.0(webpack@5.96.1): dependencies: schema-utils: 4.2.0 serialize-javascript: 6.0.2 - webpack: 5.95.0(webpack-cli@5.1.4) + webpack: 5.96.1(webpack-cli@5.1.4) - compression@1.7.4: + compression@1.7.5: dependencies: - accepts: 1.3.8 - bytes: 3.0.0 + bytes: 3.1.2 compressible: 2.0.18 debug: 2.6.9 + negotiator: 0.6.4 on-headers: 1.0.2 - safe-buffer: 5.1.2 + safe-buffer: 5.2.1 vary: 1.1.2 transitivePeerDependencies: - supports-color @@ -12813,11 +12701,11 @@ snapshots: depd: 2.0.0 keygrip: 1.1.0 - core-js-compat@3.38.1: + core-js-compat@3.39.0: dependencies: - browserslist: 4.24.0 + browserslist: 4.24.2 - core-js@3.38.1: {} + core-js@3.39.0: {} core-util-is@1.0.3: {} @@ -12863,10 +12751,10 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - css-blank-pseudo@7.0.0(postcss@8.4.47): + css-blank-pseudo@7.0.1(postcss@8.4.47): dependencies: postcss: 8.4.47 - postcss-selector-parser: 6.1.2 + postcss-selector-parser: 7.0.0 css-declaration-sorter@7.2.0(postcss@8.4.47): dependencies: @@ -12896,14 +12784,14 @@ snapshots: css-global-keywords@1.0.1: {} - css-has-pseudo@7.0.0(postcss@8.4.47): + css-has-pseudo@7.0.1(postcss@8.4.47): dependencies: - '@csstools/selector-specificity': 4.0.0(postcss-selector-parser@6.1.2) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.0.0) postcss: 8.4.47 - postcss-selector-parser: 6.1.2 + postcss-selector-parser: 7.0.0 postcss-value-parser: 4.2.0 - css-loader@7.1.2(webpack@5.95.0): + css-loader@7.1.2(webpack@5.96.1): dependencies: icss-utils: 5.1.0(postcss@8.4.47) postcss: 8.4.47 @@ -12914,9 +12802,9 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.6.3 optionalDependencies: - webpack: 5.95.0(webpack-cli@5.1.4) + webpack: 5.96.1(webpack-cli@5.1.4) - css-minimizer-webpack-plugin@7.0.0(clean-css@5.3.3)(webpack@5.95.0): + css-minimizer-webpack-plugin@7.0.0(clean-css@5.3.3)(webpack@5.96.1): dependencies: '@jridgewell/trace-mapping': 0.3.25 cssnano: 7.0.6(postcss@8.4.47) @@ -12924,7 +12812,7 @@ snapshots: postcss: 8.4.47 schema-utils: 4.2.0 serialize-javascript: 6.0.2 - webpack: 5.95.0(webpack-cli@5.1.4) + webpack: 5.96.1(webpack-cli@5.1.4) optionalDependencies: clean-css: 5.3.3 @@ -12962,9 +12850,9 @@ snapshots: mdn-data: 2.0.30 source-map-js: 1.2.1 - css-tree@3.0.0: + css-tree@3.0.1: dependencies: - mdn-data: 2.10.0 + mdn-data: 2.12.1 source-map-js: 1.2.1 css-what@6.1.0: {} @@ -12979,7 +12867,7 @@ snapshots: cssnano-preset-default@7.0.6(postcss@8.4.47): dependencies: - browserslist: 4.24.0 + browserslist: 4.24.2 css-declaration-sorter: 7.2.0(postcss@8.4.47) cssnano-utils: 5.0.0(postcss@8.4.47) postcss: 8.4.47 @@ -13213,7 +13101,7 @@ snapshots: dependencies: dequal: 2.0.3 - devtools-protocol@0.0.1342118: {} + devtools-protocol@0.0.1354347: {} diff@4.0.2: {} @@ -13286,7 +13174,7 @@ snapshots: dot-case@3.0.4: dependencies: no-case: 3.0.4 - tslib: 2.7.0 + tslib: 2.8.1 draw-svg-path@1.0.0: dependencies: @@ -13314,7 +13202,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.38: {} + electron-to-chromium@1.5.51: {} element-size@1.1.1: {} @@ -13510,6 +13398,20 @@ snapshots: es6-iterator: 2.0.3 es6-symbol: 3.1.4 + esast-util-from-estree@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + unist-util-position-from-estree: 2.0.0 + + esast-util-from-js@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + acorn: 8.14.0 + esast-util-from-estree: 2.0.0 + vfile-message: 4.0.2 + esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -13566,11 +13468,11 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-webpack@0.13.9(eslint-plugin-import@2.31.0)(webpack@5.95.0): + eslint-import-resolver-webpack@0.13.9(eslint-plugin-import@2.31.0)(webpack@5.96.1): dependencies: debug: 3.2.7 enhanced-resolve: 0.9.1 - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-webpack@0.13.9)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-webpack@0.13.9)(eslint@8.57.1) find-root: 1.1.0 hasown: 2.0.2 interpret: 1.4.0 @@ -13579,40 +13481,40 @@ snapshots: lodash: 4.17.21 resolve: 2.0.0-next.5 semver: 5.7.2 - webpack: 5.95.0(webpack-cli@5.1.4) + webpack: 5.96.1(webpack-cli@5.1.4) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-webpack@0.13.9)(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-webpack@0.13.9)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.9.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/parser': 8.13.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-webpack: 0.13.9(eslint-plugin-import@2.31.0)(webpack@5.95.0) + eslint-import-resolver-webpack: 0.13.9(eslint-plugin-import@2.31.0)(webpack@5.96.1) transitivePeerDependencies: - supports-color - eslint-plugin-formatjs@5.1.0(eslint@8.57.1): + eslint-plugin-formatjs@5.2.2(eslint@8.57.1)(typescript@5.6.3): dependencies: - '@formatjs/icu-messageformat-parser': 2.7.10 - '@formatjs/ts-transformer': 3.13.16 + '@formatjs/icu-messageformat-parser': 2.9.3 + '@formatjs/ts-transformer': 3.13.22 '@types/eslint': 9.6.1 - '@types/picomatch': 2.3.4 - '@typescript-eslint/utils': 8.5.0(eslint@8.57.1)(typescript@5.6.3) + '@types/picomatch': 3.0.1 + '@typescript-eslint/utils': 8.13.0(eslint@8.57.1)(typescript@5.6.3) emoji-regex: 10.4.0 eslint: 8.57.1 magic-string: 0.30.12 - picomatch: 2.3.1 - tslib: 2.7.0 - typescript: 5.6.3 + picomatch: 4.0.2 + tslib: 2.8.1 unicode-emoji-utils: 1.2.0 transitivePeerDependencies: - supports-color - ts-jest + - typescript - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-webpack@0.13.9)(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-webpack@0.13.9)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -13623,7 +13525,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.9.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-webpack@0.13.9)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-webpack@0.13.9)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -13635,7 +13537,7 @@ snapshots: string.prototype.trimend: 1.0.8 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.9.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/parser': 8.13.0(eslint@8.57.1)(typescript@5.6.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -13647,14 +13549,14 @@ snapshots: eslint-plugin-unicorn@56.0.0(eslint@8.57.1): dependencies: - '@babel/helper-validator-identifier': 7.25.7 - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) + '@babel/helper-validator-identifier': 7.25.9 + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) ci-info: 4.0.0 clean-regexp: 1.0.0 - core-js-compat: 3.38.1 + core-js-compat: 3.39.0 eslint: 8.57.1 esquery: 1.6.0 - globals: 15.11.0 + globals: 15.12.0 indent-string: 4.0.0 is-builtin-module: 3.2.1 jsesc: 3.0.2 @@ -13681,8 +13583,8 @@ snapshots: eslint@8.57.1: dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) - '@eslint-community/regexpp': 4.11.1 + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.1 '@eslint/eslintrc': 2.1.4 '@eslint/js': 8.57.1 '@humanwhocodes/config-array': 0.13.0 @@ -13731,8 +13633,8 @@ snapshots: espree@9.6.1: dependencies: - acorn: 8.12.1 - acorn-jsx: 5.3.2(acorn@8.12.1) + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) eslint-visitor-keys: 3.4.3 esprima@4.0.1: {} @@ -13762,6 +13664,11 @@ snapshots: estree-util-is-identifier-name@3.0.0: {} + estree-util-scope@1.0.0: + dependencies: + '@types/estree': 1.0.6 + devlop: 1.1.0 + estree-util-to-js@2.0.0: dependencies: '@types/estree-jsx': 1.0.5 @@ -13798,9 +13705,9 @@ snapshots: exponential-backoff@3.1.1: {} - expose-loader@5.0.0(webpack@5.95.0): + expose-loader@5.0.0(webpack@5.96.1): dependencies: - webpack: 5.95.0(webpack-cli@5.1.4) + webpack: 5.96.1(webpack-cli@5.1.4) express@4.21.1: dependencies: @@ -13853,11 +13760,6 @@ snapshots: dependencies: is-extendable: 0.1.1 - extend-shallow@3.0.2: - dependencies: - assign-symbols: 1.0.0 - is-extendable: 1.0.1 - extend@3.0.2: {} extract-zip@2.0.1: @@ -14202,8 +14104,6 @@ snapshots: transitivePeerDependencies: - supports-color - get-value@2.0.6: {} - github-slugger@2.0.0: {} gl-mat4@1.2.0: {} @@ -14307,7 +14207,7 @@ snapshots: dependencies: type-fest: 0.20.2 - globals@15.11.0: {} + globals@15.12.0: {} globalthis@1.0.4: dependencies: @@ -14451,8 +14351,6 @@ snapshots: has-bigints@1.0.2: {} - has-flag@3.0.0: {} - has-flag@4.0.0: {} has-hover@1.0.1: @@ -14506,7 +14404,7 @@ snapshots: '@types/hast': 3.0.4 devlop: 1.1.0 hast-util-from-parse5: 8.0.1 - parse5: 7.2.0 + parse5: 7.2.1 vfile: 6.0.3 vfile-message: 4.0.2 @@ -14562,7 +14460,7 @@ snapshots: hast-util-to-parse5: 8.0.0 html-void-elements: 3.0.0 mdast-util-to-hast: 13.2.0 - parse5: 7.2.0 + parse5: 7.2.1 unist-util-position: 5.0.0 unist-util-visit: 5.0.0 vfile: 6.0.3 @@ -14720,13 +14618,13 @@ snapshots: he: 1.2.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.34.1 + terser: 5.36.0 html-tags@3.3.1: {} html-void-elements@3.0.0: {} - html-webpack-plugin@5.6.0(webpack@5.95.0): + html-webpack-plugin@5.6.3(webpack@5.96.1): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -14734,7 +14632,7 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.1 optionalDependencies: - webpack: 5.95.0(webpack-cli@5.1.4) + webpack: 5.96.1(webpack-cli@5.1.4) html-whitespace-sensitive-tag-names@3.0.1: {} @@ -14834,9 +14732,9 @@ snapshots: hyperdyperid@1.2.0: {} - i18next@23.16.0: + i18next@23.16.4: dependencies: - '@babel/runtime': 7.25.7 + '@babel/runtime': 7.26.0 iconv-lite@0.4.24: dependencies: @@ -14891,6 +14789,8 @@ snapshots: ini@4.1.3: {} + ini@5.0.0: {} + inline-style-parser@0.1.1: {} inline-style-parser@0.2.4: {} @@ -14907,12 +14807,12 @@ snapshots: intersection-observer@0.11.0: {} - intl-messageformat@10.7.0: + intl-messageformat@10.7.6: dependencies: - '@formatjs/ecma402-abstract': 2.2.0 - '@formatjs/fast-memoize': 2.2.1 - '@formatjs/icu-messageformat-parser': 2.7.10 - tslib: 2.7.0 + '@formatjs/ecma402-abstract': 2.2.3 + '@formatjs/fast-memoize': 2.2.3 + '@formatjs/icu-messageformat-parser': 2.9.3 + tslib: 2.8.1 ip-address@9.0.5: dependencies: @@ -14985,10 +14885,6 @@ snapshots: is-extendable@0.1.1: {} - is-extendable@1.0.1: - dependencies: - is-plain-object: 2.0.4 - is-extglob@2.1.1: {} is-finite@1.1.0: {} @@ -15057,10 +14953,6 @@ snapshots: is-potential-custom-element-name@1.0.1: {} - is-reference@3.0.2: - dependencies: - '@types/estree': 1.0.6 - is-regex@1.1.4: dependencies: call-bind: 1.0.7 @@ -15157,8 +15049,8 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.25.8 - '@babel/parser': 7.25.8 + '@babel/core': 7.26.0 + '@babel/parser': 7.26.2 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 7.6.3 @@ -15196,7 +15088,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.16.11 + '@types/node': 22.9.0 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -15204,13 +15096,13 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.16.11 + '@types/node': 22.9.0 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@29.7.0: dependencies: - '@types/node': 20.16.11 + '@types/node': 22.9.0 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -15253,7 +15145,7 @@ snapshots: https-proxy-agent: 7.0.5 is-potential-custom-element-name: 1.0.1 nwsapi: 2.2.13 - parse5: 7.2.0 + parse5: 7.2.1 rrweb-cssom: 0.7.1 saxes: 6.0.0 symbol-tree: 3.2.4 @@ -15386,7 +15278,7 @@ snapshots: launch-editor@2.9.1: dependencies: - picocolors: 1.1.0 + picocolors: 1.1.1 shell-quote: 1.8.1 lazystream@1.0.1: @@ -15524,7 +15416,7 @@ snapshots: lower-case@2.0.2: dependencies: - tslib: 2.7.0 + tslib: 2.8.1 lru-cache@5.1.1: dependencies: @@ -15542,8 +15434,8 @@ snapshots: magicast@0.3.5: dependencies: - '@babel/parser': 7.25.8 - '@babel/types': 7.25.8 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 source-map-js: 1.2.1 make-dir@2.1.0: @@ -15575,7 +15467,7 @@ snapshots: minipass-fetch: 2.1.2 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 - negotiator: 0.6.3 + negotiator: 0.6.4 promise-retry: 2.0.1 socks-proxy-agent: 7.0.0 ssri: 9.0.1 @@ -15625,7 +15517,7 @@ snapshots: '@mapbox/unitbezier': 0.0.1 '@mapbox/vector-tile': 1.3.1 '@mapbox/whoots-js': 3.1.0 - '@maplibre/maplibre-gl-style-spec': 20.3.1 + '@maplibre/maplibre-gl-style-spec': 20.4.0 '@types/geojson': 7946.0.14 '@types/geojson-vt': 3.2.5 '@types/mapbox__point-geometry': 0.1.4 @@ -15647,7 +15539,7 @@ snapshots: markdown-extensions@2.0.0: {} - markdown-table@3.0.3: {} + markdown-table@3.0.4: {} math-log2@1.0.1: {} @@ -15664,8 +15556,8 @@ snapshots: '@types/mdast': 4.0.4 '@types/unist': 3.0.3 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.1 - mdast-util-to-markdown: 2.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 parse-entities: 4.0.1 stringify-entities: 4.0.4 unist-util-visit-parents: 6.0.1 @@ -15679,7 +15571,7 @@ snapshots: unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 - mdast-util-from-markdown@2.0.1: + mdast-util-from-markdown@2.0.2: dependencies: '@types/mdast': 4.0.4 '@types/unist': 3.0.3 @@ -15708,8 +15600,8 @@ snapshots: dependencies: '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.1 - mdast-util-to-markdown: 2.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 micromark-util-normalize-identifier: 2.0.0 transitivePeerDependencies: - supports-color @@ -15717,8 +15609,8 @@ snapshots: mdast-util-gfm-strikethrough@2.0.0: dependencies: '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.1 - mdast-util-to-markdown: 2.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -15726,9 +15618,9 @@ snapshots: dependencies: '@types/mdast': 4.0.4 devlop: 1.1.0 - markdown-table: 3.0.3 - mdast-util-from-markdown: 2.0.1 - mdast-util-to-markdown: 2.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -15736,20 +15628,20 @@ snapshots: dependencies: '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.1 - mdast-util-to-markdown: 2.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color mdast-util-gfm@3.0.0: dependencies: - mdast-util-from-markdown: 2.0.1 + mdast-util-from-markdown: 2.0.2 mdast-util-gfm-autolink-literal: 2.0.1 mdast-util-gfm-footnote: 2.0.0 mdast-util-gfm-strikethrough: 2.0.0 mdast-util-gfm-table: 2.0.0 mdast-util-gfm-task-list-item: 2.0.0 - mdast-util-to-markdown: 2.1.0 + mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -15759,8 +15651,8 @@ snapshots: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.1 - mdast-util-to-markdown: 2.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -15772,8 +15664,8 @@ snapshots: '@types/unist': 3.0.3 ccount: 2.0.1 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.1 - mdast-util-to-markdown: 2.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 parse-entities: 4.0.1 stringify-entities: 4.0.4 unist-util-stringify-position: 4.0.0 @@ -15783,11 +15675,11 @@ snapshots: mdast-util-mdx@3.0.0: dependencies: - mdast-util-from-markdown: 2.0.1 + mdast-util-from-markdown: 2.0.2 mdast-util-mdx-expression: 2.0.1 mdast-util-mdx-jsx: 3.1.3 mdast-util-mdxjs-esm: 2.0.1 - mdast-util-to-markdown: 2.1.0 + mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -15797,8 +15689,8 @@ snapshots: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.1 - mdast-util-to-markdown: 2.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -15819,13 +15711,14 @@ snapshots: unist-util-visit: 5.0.0 vfile: 6.0.3 - mdast-util-to-markdown@2.1.0: + mdast-util-to-markdown@2.1.2: dependencies: '@types/mdast': 4.0.4 '@types/unist': 3.0.3 longest-streak: 3.1.0 mdast-util-phrasing: 4.1.0 mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.0 micromark-util-decode-string: 2.0.0 unist-util-visit: 5.0.0 zwitch: 2.0.4 @@ -15838,16 +15731,16 @@ snapshots: mdn-data@2.0.30: {} - mdn-data@2.10.0: {} + mdn-data@2.12.1: {} media-typer@0.3.0: {} memfs@4.14.0: dependencies: - '@jsonjoy.com/json-pack': 1.1.0(tslib@2.7.0) - '@jsonjoy.com/util': 1.5.0(tslib@2.7.0) - tree-dump: 1.0.2(tslib@2.7.0) - tslib: 2.7.0 + '@jsonjoy.com/json-pack': 1.1.0(tslib@2.8.1) + '@jsonjoy.com/util': 1.5.0(tslib@2.8.1) + tree-dump: 1.0.2(tslib@2.8.1) + tslib: 2.8.1 memory-fs@0.2.0: {} @@ -16008,8 +15901,8 @@ snapshots: micromark-extension-mdxjs@3.0.0: dependencies: - acorn: 8.12.1 - acorn-jsx: 5.3.2(acorn@8.12.1) + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) micromark-extension-mdx-expression: 3.0.0 micromark-extension-mdx-jsx: 3.0.1 micromark-extension-mdx-md: 2.0.0 @@ -16179,11 +16072,11 @@ snapshots: min-indent@1.0.1: {} - mini-css-extract-plugin@2.9.1(webpack@5.95.0): + mini-css-extract-plugin@2.9.2(webpack@5.96.1): dependencies: schema-utils: 4.2.0 tapable: 2.2.1 - webpack: 5.95.0(webpack-cli@5.1.4) + webpack: 5.96.1(webpack-cli@5.1.4) minimalistic-assert@1.0.1: {} @@ -16289,7 +16182,7 @@ snapshots: nanoid@3.3.7: {} - nanoid@5.0.7: {} + nanoid@5.0.8: {} native-promise-only@0.8.1: {} @@ -16309,6 +16202,8 @@ snapshots: negotiator@0.6.3: {} + negotiator@0.6.4: {} + neo-async@2.6.2: {} neotraverse@0.6.18: {} @@ -16326,7 +16221,7 @@ snapshots: no-case@3.0.4: dependencies: lower-case: 2.0.2 - tslib: 2.7.0 + tslib: 2.8.1 node-fetch@2.7.0(encoding@0.1.13): dependencies: @@ -16502,7 +16397,7 @@ snapshots: oniguruma-to-js@0.4.3: dependencies: - regex: 4.3.3 + regex: 4.4.0 only@0.0.2: {} @@ -16541,7 +16436,7 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - ora@8.1.0: + ora@8.1.1: dependencies: chalk: 5.3.0 cli-cursor: 5.0.0 @@ -16600,7 +16495,7 @@ snapshots: p-queue@8.0.1: dependencies: eventemitter3: 5.0.1 - p-timeout: 6.1.2 + p-timeout: 6.1.3 p-retry@6.2.0: dependencies: @@ -16608,7 +16503,7 @@ snapshots: is-network-error: 1.1.0 retry: 0.13.1 - p-timeout@6.1.2: {} + p-timeout@6.1.3: {} p-try@2.2.0: {} @@ -16658,7 +16553,7 @@ snapshots: param-case@3.0.4: dependencies: dot-case: 3.0.4 - tslib: 2.7.0 + tslib: 2.8.1 parent-module@1.0.1: dependencies: @@ -16679,7 +16574,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.25.7 + '@babel/code-frame': 7.26.2 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -16701,7 +16596,7 @@ snapshots: parse-unit@1.0.1: {} - parse5@7.2.0: + parse5@7.2.1: dependencies: entities: 4.5.0 @@ -16710,7 +16605,7 @@ snapshots: pascal-case@3.1.2: dependencies: no-case: 3.0.4 - tslib: 2.7.0 + tslib: 2.8.1 path-browserify@1.0.1: {} @@ -16741,18 +16636,14 @@ snapshots: performance-now@2.1.0: {} - periscopic@3.1.0: - dependencies: - '@types/estree': 1.0.6 - estree-walker: 3.0.3 - is-reference: 3.0.2 - pick-by-alias@1.2.0: {} - picocolors@1.1.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} + picomatch@4.0.2: {} + pify@2.3.0: {} pify@4.0.1: {} @@ -16771,7 +16662,7 @@ snapshots: dependencies: find-up: 6.3.0 - plotly.js@2.35.2(mapbox-gl@1.13.3)(webpack@5.95.0): + plotly.js@2.35.2(mapbox-gl@1.13.3)(webpack@5.96.1): dependencies: '@plotly/d3': 3.8.2 '@plotly/d3-sankey': 0.7.2 @@ -16787,7 +16678,7 @@ snapshots: color-parse: 2.0.0 color-rgba: 2.1.1 country-regex: 1.1.0 - css-loader: 7.1.2(webpack@5.95.0) + css-loader: 7.1.2(webpack@5.96.1) d3-force: 1.2.1 d3-format: 1.4.5 d3-geo: 1.12.1 @@ -16817,7 +16708,7 @@ snapshots: regl-scatter2d: 3.3.1 regl-splom: 1.0.14 strongly-connected-components: 1.0.1 - style-loader: 4.0.0(webpack@5.95.0) + style-loader: 4.0.0(webpack@5.96.1) superscript-text: 1.0.0 svg-path-sdf: 1.1.3 tinycolor2: 1.6.0 @@ -16839,10 +16730,10 @@ snapshots: possible-typed-array-names@1.0.0: {} - postcss-attribute-case-insensitive@7.0.0(postcss@8.4.47): + postcss-attribute-case-insensitive@7.0.1(postcss@8.4.47): dependencies: postcss: 8.4.47 - postcss-selector-parser: 6.1.2 + postcss-selector-parser: 7.0.0 postcss-calc@10.0.2(postcss@8.4.47): dependencies: @@ -16855,11 +16746,11 @@ snapshots: postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-color-functional-notation@7.0.3(postcss@8.4.47): + postcss-color-functional-notation@7.0.5(postcss@8.4.47): dependencies: - '@csstools/css-color-parser': 3.0.3(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2) - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 + '@csstools/css-color-parser': 3.0.5(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.47) '@csstools/utilities': 2.0.0(postcss@8.4.47) postcss: 8.4.47 @@ -16878,7 +16769,7 @@ snapshots: postcss-colormin@7.0.2(postcss@8.4.47): dependencies: - browserslist: 4.24.0 + browserslist: 4.24.2 caniuse-api: 3.0.0 colord: 2.9.3 postcss: 8.4.47 @@ -16886,39 +16777,39 @@ snapshots: postcss-convert-values@7.0.4(postcss@8.4.47): dependencies: - browserslist: 4.24.0 + browserslist: 4.24.2 postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-custom-media@11.0.3(postcss@8.4.47): + postcss-custom-media@11.0.5(postcss@8.4.47): dependencies: - '@csstools/cascade-layer-name-parser': 2.0.2(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2) - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 - '@csstools/media-query-list-parser': 4.0.0(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2) + '@csstools/cascade-layer-name-parser': 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/media-query-list-parser': 4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) postcss: 8.4.47 - postcss-custom-properties@14.0.2(postcss@8.4.47): + postcss-custom-properties@14.0.4(postcss@8.4.47): dependencies: - '@csstools/cascade-layer-name-parser': 2.0.2(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2) - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 + '@csstools/cascade-layer-name-parser': 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 '@csstools/utilities': 2.0.0(postcss@8.4.47) postcss: 8.4.47 postcss-value-parser: 4.2.0 - postcss-custom-selectors@8.0.2(postcss@8.4.47): + postcss-custom-selectors@8.0.4(postcss@8.4.47): dependencies: - '@csstools/cascade-layer-name-parser': 2.0.2(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2) - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 + '@csstools/cascade-layer-name-parser': 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 postcss: 8.4.47 - postcss-selector-parser: 6.1.2 + postcss-selector-parser: 7.0.0 - postcss-dir-pseudo-class@9.0.0(postcss@8.4.47): + postcss-dir-pseudo-class@9.0.1(postcss@8.4.47): dependencies: postcss: 8.4.47 - postcss-selector-parser: 6.1.2 + postcss-selector-parser: 7.0.0 postcss-discard-comments@7.0.3(postcss@8.4.47): dependencies: @@ -16949,15 +16840,15 @@ snapshots: postcss: 8.4.47 postcss-nesting: 10.2.0(postcss@8.4.47) - postcss-focus-visible@10.0.0(postcss@8.4.47): + postcss-focus-visible@10.0.1(postcss@8.4.47): dependencies: postcss: 8.4.47 - postcss-selector-parser: 6.1.2 + postcss-selector-parser: 7.0.0 - postcss-focus-within@9.0.0(postcss@8.4.47): + postcss-focus-within@9.0.1(postcss@8.4.47): dependencies: postcss: 8.4.47 - postcss-selector-parser: 6.1.2 + postcss-selector-parser: 7.0.0 postcss-font-variant@5.0.0(postcss@8.4.47): dependencies: @@ -16980,23 +16871,23 @@ snapshots: read-cache: 1.0.0 resolve: 1.22.8 - postcss-lab-function@7.0.3(postcss@8.4.47): + postcss-lab-function@7.0.5(postcss@8.4.47): dependencies: - '@csstools/css-color-parser': 3.0.3(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2) - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 + '@csstools/css-color-parser': 3.0.5(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.47) '@csstools/utilities': 2.0.0(postcss@8.4.47) postcss: 8.4.47 - postcss-loader@8.1.1(postcss@8.4.47)(typescript@5.6.3)(webpack@5.95.0): + postcss-loader@8.1.1(postcss@8.4.47)(typescript@5.6.3)(webpack@5.96.1): dependencies: cosmiconfig: 9.0.0(typescript@5.6.3) jiti: 1.21.6 postcss: 8.4.47 semver: 7.6.3 optionalDependencies: - webpack: 5.95.0(webpack-cli@5.1.4) + webpack: 5.96.1(webpack-cli@5.1.4) transitivePeerDependencies: - typescript @@ -17013,7 +16904,7 @@ snapshots: postcss-merge-rules@7.0.4(postcss@8.4.47): dependencies: - browserslist: 4.24.0 + browserslist: 4.24.2 caniuse-api: 3.0.0 cssnano-utils: 5.0.0(postcss@8.4.47) postcss: 8.4.47 @@ -17033,7 +16924,7 @@ snapshots: postcss-minify-params@7.0.2(postcss@8.4.47): dependencies: - browserslist: 4.24.0 + browserslist: 4.24.2 cssnano-utils: 5.0.0(postcss@8.4.47) postcss: 8.4.47 postcss-value-parser: 4.2.0 @@ -17076,12 +16967,12 @@ snapshots: postcss: 8.4.47 postcss-selector-parser: 6.1.2 - postcss-nesting@13.0.0(postcss@8.4.47): + postcss-nesting@13.0.1(postcss@8.4.47): dependencies: - '@csstools/selector-resolve-nested': 2.0.0(postcss-selector-parser@6.1.2) - '@csstools/selector-specificity': 4.0.0(postcss-selector-parser@6.1.2) + '@csstools/selector-resolve-nested': 3.0.0(postcss-selector-parser@7.0.0) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.0.0) postcss: 8.4.47 - postcss-selector-parser: 6.1.2 + postcss-selector-parser: 7.0.0 postcss-normalize-charset@7.0.0(postcss@8.4.47): dependencies: @@ -17114,7 +17005,7 @@ snapshots: postcss-normalize-unicode@7.0.2(postcss@8.4.47): dependencies: - browserslist: 4.24.0 + browserslist: 4.24.2 postcss: 8.4.47 postcss-value-parser: 4.2.0 @@ -17156,79 +17047,79 @@ snapshots: dependencies: postcss: 8.4.47 - postcss-preset-env@10.0.7(postcss@8.4.47): + postcss-preset-env@10.0.9(postcss@8.4.47): dependencies: - '@csstools/postcss-cascade-layers': 5.0.0(postcss@8.4.47) - '@csstools/postcss-color-function': 4.0.3(postcss@8.4.47) - '@csstools/postcss-color-mix-function': 3.0.3(postcss@8.4.47) - '@csstools/postcss-content-alt-text': 2.0.2(postcss@8.4.47) - '@csstools/postcss-exponential-functions': 2.0.2(postcss@8.4.47) + '@csstools/postcss-cascade-layers': 5.0.1(postcss@8.4.47) + '@csstools/postcss-color-function': 4.0.5(postcss@8.4.47) + '@csstools/postcss-color-mix-function': 3.0.5(postcss@8.4.47) + '@csstools/postcss-content-alt-text': 2.0.4(postcss@8.4.47) + '@csstools/postcss-exponential-functions': 2.0.4(postcss@8.4.47) '@csstools/postcss-font-format-keywords': 4.0.0(postcss@8.4.47) - '@csstools/postcss-gamut-mapping': 2.0.3(postcss@8.4.47) - '@csstools/postcss-gradients-interpolation-method': 5.0.3(postcss@8.4.47) - '@csstools/postcss-hwb-function': 4.0.3(postcss@8.4.47) + '@csstools/postcss-gamut-mapping': 2.0.5(postcss@8.4.47) + '@csstools/postcss-gradients-interpolation-method': 5.0.5(postcss@8.4.47) + '@csstools/postcss-hwb-function': 4.0.5(postcss@8.4.47) '@csstools/postcss-ic-unit': 4.0.0(postcss@8.4.47) '@csstools/postcss-initial': 2.0.0(postcss@8.4.47) - '@csstools/postcss-is-pseudo-class': 5.0.0(postcss@8.4.47) - '@csstools/postcss-light-dark-function': 2.0.5(postcss@8.4.47) + '@csstools/postcss-is-pseudo-class': 5.0.1(postcss@8.4.47) + '@csstools/postcss-light-dark-function': 2.0.7(postcss@8.4.47) '@csstools/postcss-logical-float-and-clear': 3.0.0(postcss@8.4.47) '@csstools/postcss-logical-overflow': 2.0.0(postcss@8.4.47) '@csstools/postcss-logical-overscroll-behavior': 2.0.0(postcss@8.4.47) '@csstools/postcss-logical-resize': 3.0.0(postcss@8.4.47) - '@csstools/postcss-logical-viewport-units': 3.0.2(postcss@8.4.47) - '@csstools/postcss-media-minmax': 2.0.2(postcss@8.4.47) - '@csstools/postcss-media-queries-aspect-ratio-number-values': 3.0.2(postcss@8.4.47) + '@csstools/postcss-logical-viewport-units': 3.0.3(postcss@8.4.47) + '@csstools/postcss-media-minmax': 2.0.4(postcss@8.4.47) + '@csstools/postcss-media-queries-aspect-ratio-number-values': 3.0.4(postcss@8.4.47) '@csstools/postcss-nested-calc': 4.0.0(postcss@8.4.47) '@csstools/postcss-normalize-display-values': 4.0.0(postcss@8.4.47) - '@csstools/postcss-oklab-function': 4.0.3(postcss@8.4.47) + '@csstools/postcss-oklab-function': 4.0.5(postcss@8.4.47) '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.47) - '@csstools/postcss-relative-color-syntax': 3.0.3(postcss@8.4.47) - '@csstools/postcss-scope-pseudo-class': 4.0.0(postcss@8.4.47) - '@csstools/postcss-stepped-value-functions': 4.0.2(postcss@8.4.47) + '@csstools/postcss-relative-color-syntax': 3.0.5(postcss@8.4.47) + '@csstools/postcss-scope-pseudo-class': 4.0.1(postcss@8.4.47) + '@csstools/postcss-stepped-value-functions': 4.0.4(postcss@8.4.47) '@csstools/postcss-text-decoration-shorthand': 4.0.1(postcss@8.4.47) - '@csstools/postcss-trigonometric-functions': 4.0.2(postcss@8.4.47) + '@csstools/postcss-trigonometric-functions': 4.0.4(postcss@8.4.47) '@csstools/postcss-unset-value': 4.0.0(postcss@8.4.47) autoprefixer: 10.4.20(postcss@8.4.47) - browserslist: 4.24.0 - css-blank-pseudo: 7.0.0(postcss@8.4.47) - css-has-pseudo: 7.0.0(postcss@8.4.47) + browserslist: 4.24.2 + css-blank-pseudo: 7.0.1(postcss@8.4.47) + css-has-pseudo: 7.0.1(postcss@8.4.47) css-prefers-color-scheme: 10.0.0(postcss@8.4.47) cssdb: 8.1.2 postcss: 8.4.47 - postcss-attribute-case-insensitive: 7.0.0(postcss@8.4.47) + postcss-attribute-case-insensitive: 7.0.1(postcss@8.4.47) postcss-clamp: 4.1.0(postcss@8.4.47) - postcss-color-functional-notation: 7.0.3(postcss@8.4.47) + postcss-color-functional-notation: 7.0.5(postcss@8.4.47) postcss-color-hex-alpha: 10.0.0(postcss@8.4.47) postcss-color-rebeccapurple: 10.0.0(postcss@8.4.47) - postcss-custom-media: 11.0.3(postcss@8.4.47) - postcss-custom-properties: 14.0.2(postcss@8.4.47) - postcss-custom-selectors: 8.0.2(postcss@8.4.47) - postcss-dir-pseudo-class: 9.0.0(postcss@8.4.47) + postcss-custom-media: 11.0.5(postcss@8.4.47) + postcss-custom-properties: 14.0.4(postcss@8.4.47) + postcss-custom-selectors: 8.0.4(postcss@8.4.47) + postcss-dir-pseudo-class: 9.0.1(postcss@8.4.47) postcss-double-position-gradients: 6.0.0(postcss@8.4.47) - postcss-focus-visible: 10.0.0(postcss@8.4.47) - postcss-focus-within: 9.0.0(postcss@8.4.47) + postcss-focus-visible: 10.0.1(postcss@8.4.47) + postcss-focus-within: 9.0.1(postcss@8.4.47) postcss-font-variant: 5.0.0(postcss@8.4.47) postcss-gap-properties: 6.0.0(postcss@8.4.47) postcss-image-set-function: 7.0.0(postcss@8.4.47) - postcss-lab-function: 7.0.3(postcss@8.4.47) + postcss-lab-function: 7.0.5(postcss@8.4.47) postcss-logical: 8.0.0(postcss@8.4.47) - postcss-nesting: 13.0.0(postcss@8.4.47) + postcss-nesting: 13.0.1(postcss@8.4.47) postcss-opacity-percentage: 3.0.0(postcss@8.4.47) postcss-overflow-shorthand: 6.0.0(postcss@8.4.47) postcss-page-break: 3.0.4(postcss@8.4.47) postcss-place: 10.0.0(postcss@8.4.47) - postcss-pseudo-class-any-link: 10.0.0(postcss@8.4.47) + postcss-pseudo-class-any-link: 10.0.1(postcss@8.4.47) postcss-replace-overflow-wrap: 4.0.0(postcss@8.4.47) - postcss-selector-not: 8.0.0(postcss@8.4.47) + postcss-selector-not: 8.0.1(postcss@8.4.47) - postcss-pseudo-class-any-link@10.0.0(postcss@8.4.47): + postcss-pseudo-class-any-link@10.0.1(postcss@8.4.47): dependencies: postcss: 8.4.47 - postcss-selector-parser: 6.1.2 + postcss-selector-parser: 7.0.0 postcss-reduce-initial@7.0.2(postcss@8.4.47): dependencies: - browserslist: 4.24.0 + browserslist: 4.24.2 caniuse-api: 3.0.0 postcss: 8.4.47 @@ -17247,16 +17138,21 @@ snapshots: dependencies: postcss: 8.4.47 - postcss-selector-not@8.0.0(postcss@8.4.47): + postcss-selector-not@8.0.1(postcss@8.4.47): dependencies: postcss: 8.4.47 - postcss-selector-parser: 6.1.2 + postcss-selector-parser: 7.0.0 postcss-selector-parser@6.1.2: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 + postcss-selector-parser@7.0.0: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + postcss-simple-vars@7.0.1(postcss@8.4.47): dependencies: postcss: 8.4.47 @@ -17277,7 +17173,7 @@ snapshots: postcss@8.4.47: dependencies: nanoid: 3.3.7 - picocolors: 1.1.0 + picocolors: 1.1.1 source-map-js: 1.2.1 potpack@1.0.2: {} @@ -17393,12 +17289,12 @@ snapshots: punycode@2.3.1: {} - puppeteer-core@23.5.3(patch_hash=arlsztxuq6xlcowulqo36wnkoa): + puppeteer-core@23.7.0: dependencies: - '@puppeteer/browsers': 2.4.0 - chromium-bidi: 0.8.0(devtools-protocol@0.0.1342118) + '@puppeteer/browsers': 2.4.1 + chromium-bidi: 0.8.0(devtools-protocol@0.0.1354347) debug: 4.3.7 - devtools-protocol: 0.0.1342118 + devtools-protocol: 0.0.1354347 typed-query-selector: 2.12.0 ws: 8.18.0 transitivePeerDependencies: @@ -17406,13 +17302,13 @@ snapshots: - supports-color - utf-8-validate - puppeteer@23.5.3(typescript@5.6.3): + puppeteer@23.7.0(typescript@5.6.3): dependencies: - '@puppeteer/browsers': 2.4.0 - chromium-bidi: 0.8.0(devtools-protocol@0.0.1342118) + '@puppeteer/browsers': 2.4.1 + chromium-bidi: 0.8.0(devtools-protocol@0.0.1354347) cosmiconfig: 9.0.0(typescript@5.6.3) - devtools-protocol: 0.0.1342118 - puppeteer-core: 23.5.3(patch_hash=arlsztxuq6xlcowulqo36wnkoa) + devtools-protocol: 0.0.1354347 + puppeteer-core: 23.7.0 typed-query-selector: 2.12.0 transitivePeerDependencies: - bufferutil @@ -17533,6 +17429,36 @@ snapshots: dependencies: resolve: 1.22.8 + recma-build-jsx@1.0.0: + dependencies: + '@types/estree': 1.0.6 + estree-util-build-jsx: 3.0.1 + vfile: 6.0.3 + + recma-jsx@1.0.0(acorn@8.14.0): + dependencies: + acorn-jsx: 5.3.2(acorn@8.14.0) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - acorn + + recma-parse@1.0.0: + dependencies: + '@types/estree': 1.0.6 + esast-util-from-js: 2.0.1 + unified: 11.0.5 + vfile: 6.0.3 + + recma-stringify@1.0.0: + dependencies: + '@types/estree': 1.0.6 + estree-util-to-js: 2.0.0 + unified: 11.0.5 + vfile: 6.0.3 + redent@4.0.0: dependencies: indent-string: 5.0.0 @@ -17548,9 +17474,9 @@ snapshots: regenerator-transform@0.15.2: dependencies: - '@babel/runtime': 7.25.7 + '@babel/runtime': 7.26.0 - regex@4.3.3: {} + regex@4.4.0: {} regexp-tree@0.1.27: {} @@ -17566,7 +17492,7 @@ snapshots: regenerate: 1.4.2 regenerate-unicode-properties: 10.2.0 regjsgen: 0.8.0 - regjsparser: 0.11.1 + regjsparser: 0.11.2 unicode-match-property-ecmascript: 2.0.0 unicode-match-property-value-ecmascript: 2.2.0 @@ -17576,7 +17502,7 @@ snapshots: dependencies: jsesc: 0.5.0 - regjsparser@0.11.1: + regjsparser@0.11.2: dependencies: jsesc: 3.0.2 @@ -17656,6 +17582,14 @@ snapshots: hast-util-raw: 9.0.4 vfile: 6.0.3 + rehype-recma@1.0.0: + dependencies: + '@types/estree': 1.0.6 + '@types/hast': 3.0.4 + hast-util-to-estree: 3.1.0 + transitivePeerDependencies: + - supports-color + rehype-stringify@10.0.1: dependencies: '@types/hast': 3.0.4 @@ -17695,7 +17629,7 @@ snapshots: transitivePeerDependencies: - supports-color - remark-mdx@3.0.1: + remark-mdx@3.1.0: dependencies: mdast-util-mdx: 3.0.0 micromark-extension-mdxjs: 3.0.0 @@ -17705,7 +17639,7 @@ snapshots: remark-parse@11.0.0: dependencies: '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.1 + mdast-util-from-markdown: 2.0.2 micromark-util-types: 2.0.0 unified: 11.0.5 transitivePeerDependencies: @@ -17729,7 +17663,7 @@ snapshots: remark-stringify@11.0.0: dependencies: '@types/mdast': 4.0.4 - mdast-util-to-markdown: 2.1.0 + mdast-util-to-markdown: 2.1.2 unified: 11.0.5 remove-bom-buffer@3.0.0: @@ -17843,26 +17777,28 @@ snapshots: dependencies: glob: 7.2.3 - rollup@4.24.0: + rollup@4.24.4: dependencies: '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.24.0 - '@rollup/rollup-android-arm64': 4.24.0 - '@rollup/rollup-darwin-arm64': 4.24.0 - '@rollup/rollup-darwin-x64': 4.24.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.24.0 - '@rollup/rollup-linux-arm-musleabihf': 4.24.0 - '@rollup/rollup-linux-arm64-gnu': 4.24.0 - '@rollup/rollup-linux-arm64-musl': 4.24.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.24.0 - '@rollup/rollup-linux-riscv64-gnu': 4.24.0 - '@rollup/rollup-linux-s390x-gnu': 4.24.0 - '@rollup/rollup-linux-x64-gnu': 4.24.0 - '@rollup/rollup-linux-x64-musl': 4.24.0 - '@rollup/rollup-win32-arm64-msvc': 4.24.0 - '@rollup/rollup-win32-ia32-msvc': 4.24.0 - '@rollup/rollup-win32-x64-msvc': 4.24.0 + '@rollup/rollup-android-arm-eabi': 4.24.4 + '@rollup/rollup-android-arm64': 4.24.4 + '@rollup/rollup-darwin-arm64': 4.24.4 + '@rollup/rollup-darwin-x64': 4.24.4 + '@rollup/rollup-freebsd-arm64': 4.24.4 + '@rollup/rollup-freebsd-x64': 4.24.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.24.4 + '@rollup/rollup-linux-arm-musleabihf': 4.24.4 + '@rollup/rollup-linux-arm64-gnu': 4.24.4 + '@rollup/rollup-linux-arm64-musl': 4.24.4 + '@rollup/rollup-linux-powerpc64le-gnu': 4.24.4 + '@rollup/rollup-linux-riscv64-gnu': 4.24.4 + '@rollup/rollup-linux-s390x-gnu': 4.24.4 + '@rollup/rollup-linux-x64-gnu': 4.24.4 + '@rollup/rollup-linux-x64-musl': 4.24.4 + '@rollup/rollup-win32-arm64-msvc': 4.24.4 + '@rollup/rollup-win32-ia32-msvc': 4.24.4 + '@rollup/rollup-win32-x64-msvc': 4.24.4 fsevents: 2.3.3 rrweb-cssom@0.7.1: {} @@ -17996,13 +17932,6 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 - set-value@2.0.1: - dependencies: - extend-shallow: 2.0.1 - is-extendable: 0.1.1 - is-plain-object: 2.0.4 - split-string: 3.1.0 - setprototypeof@1.1.0: {} setprototypeof@1.2.0: {} @@ -18049,12 +17978,12 @@ snapshots: shell-quote@1.8.1: {} - shiki@1.22.0: + shiki@1.22.2: dependencies: - '@shikijs/core': 1.22.0 - '@shikijs/engine-javascript': 1.22.0 - '@shikijs/engine-oniguruma': 1.22.0 - '@shikijs/types': 1.22.0 + '@shikijs/core': 1.22.2 + '@shikijs/engine-javascript': 1.22.2 + '@shikijs/engine-oniguruma': 1.22.2 + '@shikijs/types': 1.22.2 '@shikijs/vscode-textmate': 9.3.0 '@types/hast': 3.0.4 @@ -18137,19 +18066,6 @@ snapshots: ip-address: 9.0.5 smart-buffer: 4.2.0 - sort-asc@0.2.0: {} - - sort-desc@0.2.0: {} - - sort-object@3.0.3: - dependencies: - bytewise: 1.1.0 - get-value: 2.0.6 - is-extendable: 0.1.1 - sort-asc: 0.2.0 - sort-desc: 0.2.0 - union-value: 1.0.1 - sortablejs@1.15.3: {} sorttable@1.0.2: {} @@ -18219,10 +18135,6 @@ snapshots: spectrum-colorpicker@1.8.1: {} - split-string@3.1.0: - dependencies: - extend-shallow: 3.0.2 - sprintf-js@1.0.3: {} sprintf-js@1.1.3: {} @@ -18271,7 +18183,7 @@ snapshots: dependencies: fast-fifo: 1.3.2 queue-tick: 1.0.1 - text-decoder: 1.2.0 + text-decoder: 1.2.1 optionalDependencies: bare-events: 2.5.0 @@ -18353,9 +18265,9 @@ snapshots: strongly-connected-components@1.0.1: {} - style-loader@4.0.0(webpack@5.95.0): + style-loader@4.0.0(webpack@5.96.1): dependencies: - webpack: 5.95.0(webpack-cli@5.1.4) + webpack: 5.96.1(webpack-cli@5.1.4) style-to-object@0.4.4: dependencies: @@ -18367,7 +18279,7 @@ snapshots: stylehacks@7.0.4(postcss@8.4.47): dependencies: - browserslist: 4.24.0 + browserslist: 4.24.2 postcss: 8.4.47 postcss-selector-parser: 6.1.2 @@ -18380,18 +18292,23 @@ snapshots: stylelint: 16.10.0(typescript@5.6.3) stylelint-config-recommended: 14.0.1(stylelint@16.10.0(typescript@5.6.3)) + stylelint-high-performance-animation@1.10.0(stylelint@16.10.0(typescript@5.6.3)): + dependencies: + postcss-value-parser: 4.2.0 + stylelint: 16.10.0(typescript@5.6.3) + stylelint@16.10.0(typescript@5.6.3): dependencies: - '@csstools/css-parser-algorithms': 3.0.2(@csstools/css-tokenizer@3.0.2) - '@csstools/css-tokenizer': 3.0.2 - '@csstools/media-query-list-parser': 3.0.1(@csstools/css-parser-algorithms@3.0.2(@csstools/css-tokenizer@3.0.2))(@csstools/css-tokenizer@3.0.2) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/media-query-list-parser': 3.0.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) '@csstools/selector-specificity': 4.0.0(postcss-selector-parser@6.1.2) '@dual-bundle/import-meta-resolve': 4.1.0 balanced-match: 2.0.0 colord: 2.9.3 cosmiconfig: 9.0.0(typescript@5.6.3) css-functions-list: 3.2.3 - css-tree: 3.0.0 + css-tree: 3.0.1 debug: 4.3.7 fast-glob: 3.3.2 fastest-levenshtein: 1.0.16 @@ -18408,7 +18325,7 @@ snapshots: meow: 13.2.0 micromatch: 4.0.8 normalize-path: 3.0.0 - picocolors: 1.1.0 + picocolors: 1.1.1 postcss: 8.4.47 postcss-resolve-nested-selector: 0.1.6 postcss-safe-parser: 7.0.1(postcss@8.4.47) @@ -18436,10 +18353,6 @@ snapshots: superscript-text@1.0.0: {} - supports-color@5.5.0: - dependencies: - has-flag: 3.0.0 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -18503,7 +18416,7 @@ snapshots: css-tree: 2.3.1 css-what: 6.1.0 csso: 5.0.5 - picocolors: 1.1.0 + picocolors: 1.1.1 svgpath@2.6.0: {} @@ -18554,19 +18467,19 @@ snapshots: dependencies: bintrees: 1.0.2 - terser-webpack-plugin@5.3.10(webpack@5.95.0): + terser-webpack-plugin@5.3.10(webpack@5.96.1): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 - terser: 5.34.1 - webpack: 5.95.0(webpack-cli@5.1.4) + terser: 5.36.0 + webpack: 5.96.1(webpack-cli@5.1.4) - terser@5.34.1: + terser@5.36.0: dependencies: '@jridgewell/source-map': 0.3.6 - acorn: 8.12.1 + acorn: 8.14.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -18576,9 +18489,7 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 - text-decoder@1.2.0: - dependencies: - b4a: 1.6.7 + text-decoder@1.2.1: {} text-field-edit@4.1.1: {} @@ -18588,9 +18499,9 @@ snapshots: textarea-caret@3.1.0: {} - thingies@1.21.0(tslib@2.7.0): + thingies@1.21.0(tslib@2.8.1): dependencies: - tslib: 2.7.0 + tslib: 2.8.1 throttle-debounce@3.0.1: {} @@ -18621,7 +18532,7 @@ snapshots: tinycolor2@1.6.0: {} - tinyexec@0.3.0: {} + tinyexec@0.3.1: {} tinyqueue@2.0.3: {} @@ -18631,19 +18542,17 @@ snapshots: dependencies: '@popperjs/core': 2.11.8 - tldts-core@6.1.51: {} + tldts-core@6.1.58: {} - tldts@6.1.51: + tldts@6.1.58: dependencies: - tldts-core: 6.1.51 + tldts-core: 6.1.58 to-absolute-glob@2.0.2: dependencies: is-absolute: 1.0.0 is-negated-glob: 1.0.0 - to-fast-properties@2.0.0: {} - to-float32@1.1.0: {} to-px@1.0.1: @@ -18666,7 +18575,7 @@ snapshots: tough-cookie@5.0.0: dependencies: - tldts: 6.1.51 + tldts: 6.1.58 tr46@0.0.3: {} @@ -18674,9 +18583,9 @@ snapshots: dependencies: punycode: 2.3.1 - tree-dump@1.0.2(tslib@2.7.0): + tree-dump@1.0.2(tslib@2.8.1): dependencies: - tslib: 2.7.0 + tslib: 2.8.1 trim-lines@3.0.1: {} @@ -18686,19 +18595,19 @@ snapshots: trough@2.2.0: {} - ts-api-utils@1.3.0(typescript@5.6.3): + ts-api-utils@1.4.0(typescript@5.6.3): dependencies: typescript: 5.6.3 - ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3): + ts-node@10.9.2(@types/node@22.9.0)(typescript@5.6.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.16.11 - acorn: 8.12.1 + '@types/node': 22.9.0 + acorn: 8.14.0 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 @@ -18719,7 +18628,7 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 - tslib@2.7.0: {} + tslib@2.8.1: {} tsscmp@1.0.6: {} @@ -18824,18 +18733,12 @@ snapshots: typesafe-path@0.2.2: {} - typescript-auto-import-cache@0.3.3: + typescript-auto-import-cache@0.3.5: dependencies: semver: 7.6.3 typescript@5.6.3: {} - typewise-core@1.2.0: {} - - typewise@1.0.3: - dependencies: - typewise-core: 1.2.0 - uglify-js@3.19.3: optional: true @@ -18855,8 +18758,6 @@ snapshots: underscore@1.13.7: {} - undici-types@5.26.5: {} - undici-types@6.19.8: {} unicode-canonical-property-names-ecmascript@2.0.1: {} @@ -18884,13 +18785,6 @@ snapshots: trough: 2.2.0 vfile: 6.0.3 - union-value@1.0.1: - dependencies: - arr-union: 3.1.0 - get-value: 2.0.6 - is-extendable: 0.1.1 - set-value: 2.0.1 - unique-filename@2.0.1: dependencies: unique-slug: 3.0.0 @@ -18956,11 +18850,11 @@ snapshots: unquote@1.1.1: {} - update-browserslist-db@1.1.1(browserslist@4.24.0): + update-browserslist-db@1.1.1(browserslist@4.24.2): dependencies: - browserslist: 4.24.0 + browserslist: 4.24.2 escalade: 3.2.0 - picocolors: 1.1.0 + picocolors: 1.1.1 update-diff@1.1.0: {} @@ -18970,12 +18864,12 @@ snapshots: url-join@4.0.1: {} - url-loader@4.1.1(webpack@5.95.0): + url-loader@4.1.1(webpack@5.96.1): dependencies: loader-utils: 2.0.4 mime-types: 2.1.35 schema-utils: 3.3.0 - webpack: 5.95.0(webpack-cli@5.1.4) + webpack: 5.96.1(webpack-cli@5.1.4) url-parse@1.5.10: dependencies: @@ -19075,77 +18969,77 @@ snapshots: remove-trailing-separator: 1.1.0 replace-ext: 1.0.1 - vite@5.4.9(@types/node@20.16.11)(terser@5.34.1): + vite@5.4.10(@types/node@22.9.0)(terser@5.36.0): dependencies: esbuild: 0.21.5 postcss: 8.4.47 - rollup: 4.24.0 + rollup: 4.24.4 optionalDependencies: - '@types/node': 20.16.11 + '@types/node': 22.9.0 fsevents: 2.3.3 - terser: 5.34.1 + terser: 5.36.0 - vitefu@1.0.3(vite@5.4.9(@types/node@20.16.11)(terser@5.34.1)): + vitefu@1.0.3(vite@5.4.10(@types/node@22.9.0)(terser@5.36.0)): optionalDependencies: - vite: 5.4.9(@types/node@20.16.11)(terser@5.34.1) + vite: 5.4.10(@types/node@22.9.0)(terser@5.36.0) - vnu-jar@23.4.11: {} + vnu-jar@24.10.17: {} - volar-service-css@0.0.61(@volar/language-service@2.4.6): + volar-service-css@0.0.62(@volar/language-service@2.4.8): dependencies: vscode-css-languageservice: 6.3.1 vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.0.8 optionalDependencies: - '@volar/language-service': 2.4.6 + '@volar/language-service': 2.4.8 - volar-service-emmet@0.0.61(@volar/language-service@2.4.6): + volar-service-emmet@0.0.62(@volar/language-service@2.4.8): dependencies: '@emmetio/css-parser': 0.4.0 '@emmetio/html-matcher': 1.3.0 '@vscode/emmet-helper': 2.9.3 vscode-uri: 3.0.8 optionalDependencies: - '@volar/language-service': 2.4.6 + '@volar/language-service': 2.4.8 - volar-service-html@0.0.61(@volar/language-service@2.4.6): + volar-service-html@0.0.62(@volar/language-service@2.4.8): dependencies: vscode-html-languageservice: 5.3.1 vscode-languageserver-textdocument: 1.0.12 vscode-uri: 3.0.8 optionalDependencies: - '@volar/language-service': 2.4.6 + '@volar/language-service': 2.4.8 - volar-service-prettier@0.0.61(@volar/language-service@2.4.6)(prettier@3.3.3): + volar-service-prettier@0.0.62(@volar/language-service@2.4.8)(prettier@3.3.3): dependencies: vscode-uri: 3.0.8 optionalDependencies: - '@volar/language-service': 2.4.6 + '@volar/language-service': 2.4.8 prettier: 3.3.3 - volar-service-typescript-twoslash-queries@0.0.61(@volar/language-service@2.4.6): + volar-service-typescript-twoslash-queries@0.0.62(@volar/language-service@2.4.8): dependencies: vscode-uri: 3.0.8 optionalDependencies: - '@volar/language-service': 2.4.6 + '@volar/language-service': 2.4.8 - volar-service-typescript@0.0.61(@volar/language-service@2.4.6): + volar-service-typescript@0.0.62(@volar/language-service@2.4.8): dependencies: path-browserify: 1.0.1 semver: 7.6.3 - typescript-auto-import-cache: 0.3.3 + typescript-auto-import-cache: 0.3.5 vscode-languageserver-textdocument: 1.0.12 vscode-nls: 5.2.0 vscode-uri: 3.0.8 optionalDependencies: - '@volar/language-service': 2.4.6 + '@volar/language-service': 2.4.8 - volar-service-yaml@0.0.61(@volar/language-service@2.4.6): + volar-service-yaml@0.0.62(@volar/language-service@2.4.8): dependencies: vscode-uri: 3.0.8 yaml-language-server: 1.15.0 optionalDependencies: - '@volar/language-service': 2.4.6 + '@volar/language-service': 2.4.8 vscode-css-languageservice@6.3.1: dependencies: @@ -19252,12 +19146,12 @@ snapshots: lodash.get: 4.4.2 lodash.topairs: 4.3.0 - webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.95.0): + webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.96.1): dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4)(webpack@5.95.0) - '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4)(webpack@5.95.0) - '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack-dev-server@5.1.0)(webpack@5.95.0) + '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4)(webpack@5.96.1) + '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4)(webpack@5.96.1) + '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack-dev-server@5.1.0)(webpack@5.96.1) colorette: 2.0.20 commander: 10.0.1 cross-spawn: 7.0.3 @@ -19266,12 +19160,12 @@ snapshots: import-local: 3.2.0 interpret: 3.1.1 rechoir: 0.8.0 - webpack: 5.95.0(webpack-cli@5.1.4) + webpack: 5.96.1(webpack-cli@5.1.4) webpack-merge: 5.10.0 optionalDependencies: - webpack-dev-server: 5.1.0(webpack-cli@5.1.4)(webpack@5.95.0) + webpack-dev-server: 5.1.0(webpack-cli@5.1.4)(webpack@5.96.1) - webpack-dev-middleware@7.4.2(webpack@5.95.0): + webpack-dev-middleware@7.4.2(webpack@5.96.1): dependencies: colorette: 2.0.20 memfs: 4.14.0 @@ -19280,9 +19174,9 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.2.0 optionalDependencies: - webpack: 5.95.0(webpack-cli@5.1.4) + webpack: 5.96.1(webpack-cli@5.1.4) - webpack-dev-server@5.1.0(webpack-cli@5.1.4)(webpack@5.95.0): + webpack-dev-server@5.1.0(webpack-cli@5.1.4)(webpack@5.96.1): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 @@ -19290,12 +19184,12 @@ snapshots: '@types/serve-index': 1.9.4 '@types/serve-static': 1.15.7 '@types/sockjs': 0.3.36 - '@types/ws': 8.5.12 + '@types/ws': 8.5.13 ansi-html-community: 0.0.8 bonjour-service: 1.2.1 chokidar: 3.6.0 colorette: 2.0.20 - compression: 1.7.4 + compression: 1.7.5 connect-history-api-fallback: 2.0.0 express: 4.21.1 graceful-fs: 4.2.11 @@ -19310,11 +19204,11 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.2(webpack@5.95.0) + webpack-dev-middleware: 7.4.2(webpack@5.96.1) ws: 8.18.0 optionalDependencies: - webpack: 5.95.0(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.95.0) + webpack: 5.96.1(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.96.1) transitivePeerDependencies: - bufferutil - debug @@ -19329,15 +19223,15 @@ snapshots: webpack-sources@3.2.3: {} - webpack@5.95.0(webpack-cli@5.1.4): + webpack@5.96.1(webpack-cli@5.1.4): dependencies: + '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.6 '@webassemblyjs/ast': 1.12.1 '@webassemblyjs/wasm-edit': 1.12.1 '@webassemblyjs/wasm-parser': 1.12.1 - acorn: 8.12.1 - acorn-import-attributes: 1.9.5(acorn@8.12.1) - browserslist: 4.24.0 + acorn: 8.14.0 + browserslist: 4.24.2 chrome-trace-event: 1.0.4 enhanced-resolve: 5.17.1 es-module-lexer: 1.5.4 @@ -19351,11 +19245,11 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(webpack@5.95.0) + terser-webpack-plugin: 5.3.10(webpack@5.96.1) watchpack: 2.4.2 webpack-sources: 3.2.3 optionalDependencies: - webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.95.0) + webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.96.1) transitivePeerDependencies: - '@swc/core' - esbuild @@ -19598,7 +19492,7 @@ snapshots: optionalDependencies: commander: 9.5.0 - zod-to-json-schema@3.23.3(zod@3.23.8): + zod-to-json-schema@3.23.5(zod@3.23.8): dependencies: zod: 3.23.8 @@ -19609,10 +19503,10 @@ snapshots: zod@3.23.8: {} - zulip-js@2.0.9(encoding@0.1.13): + zulip-js@2.1.0(encoding@0.1.13): dependencies: - '@babel/runtime': 7.25.7 - ini: 1.3.8 + '@babel/runtime': 7.26.0 + ini: 5.0.0 isomorphic-fetch: 3.0.0(encoding@0.1.13) isomorphic-form-data: 2.0.0 transitivePeerDependencies: diff --git a/puppet/zulip/manifests/profile/postgresql.pp b/puppet/zulip/manifests/profile/postgresql.pp index 933af589b6..40d223a924 100644 --- a/puppet/zulip/manifests/profile/postgresql.pp +++ b/puppet/zulip/manifests/profile/postgresql.pp @@ -30,7 +30,7 @@ class zulip::profile::postgresql { group => 'postgres', } - if $version in ['12','13','14'] { + if $version in ['13','14'] { $postgresql_conf_file = "${zulip::postgresql_base::postgresql_confdir}/postgresql.conf" file { $postgresql_conf_file: ensure => file, diff --git a/puppet/zulip/templates/postgresql/12/postgresql.conf.template.erb b/puppet/zulip/templates/postgresql/12/postgresql.conf.template.erb deleted file mode 100644 index 7c0fea8fd0..0000000000 --- a/puppet/zulip/templates/postgresql/12/postgresql.conf.template.erb +++ /dev/null @@ -1,812 +0,0 @@ -# ----------------------------- -# PostgreSQL configuration file -# ----------------------------- -# -# This file consists of lines of the form: -# -# name = value -# -# (The "=" is optional.) Whitespace may be used. Comments are introduced with -# "#" anywhere on a line. The complete list of parameter names and allowed -# values can be found in the PostgreSQL documentation. -# -# The commented-out settings shown in this file represent the default values. -# Re-commenting a setting is NOT sufficient to revert it to the default value; -# you need to reload the server. -# -# This file is read on server startup and when the server receives a SIGHUP -# signal. If you edit the file on a running system, you have to SIGHUP the -# server for the changes to take effect, run "pg_ctl reload", or execute -# "SELECT pg_reload_conf()". Some parameters, which are marked below, -# require a server shutdown and restart to take effect. -# -# Any parameter can also be given as a command-line option to the server, e.g., -# "postgres -c log_connections=on". Some parameters can be changed at run time -# with the "SET" SQL command. -# -# Memory units: kB = kilobytes Time units: ms = milliseconds -# MB = megabytes s = seconds -# GB = gigabytes min = minutes -# TB = terabytes h = hours -# d = days - - -#------------------------------------------------------------------------------ -# FILE LOCATIONS -#------------------------------------------------------------------------------ - -# The default values of these variables are driven from the -D command-line -# option or PGDATA environment variable, represented here as ConfigDir. - -data_directory = '<%= scope["zulip::postgresql_base::postgresql_datadir"] %>' # use data in another directory - # (change requires restart) -hba_file = '<%= scope["zulip::postgresql_base::postgresql_confdir"] %>/pg_hba.conf' # host-based authentication file - # (change requires restart) -ident_file = '<%= scope["zulip::postgresql_base::postgresql_confdir"] %>/pg_ident.conf' # ident configuration file - # (change requires restart) - -# If external_pid_file is not explicitly set, no extra PID file is written. -external_pid_file = '/var/run/postgresql/<%= scope["zulip::postgresql_common::version"] %>-main.pid' # write an extra PID file - # (change requires restart) - - -#------------------------------------------------------------------------------ -# CONNECTIONS AND AUTHENTICATION -#------------------------------------------------------------------------------ - -# - Connection Settings - - -#listen_addresses = 'localhost' # what IP address(es) to listen on; - # comma-separated list of addresses; - # defaults to 'localhost'; use '*' for all - # (change requires restart) -port = 5432 # (change requires restart) -max_connections = 100 # (change requires restart) -#superuser_reserved_connections = 3 # (change requires restart) -unix_socket_directories = '/var/run/postgresql' # comma-separated list of directories - # (change requires restart) -#unix_socket_group = '' # (change requires restart) -#unix_socket_permissions = 0777 # begin with 0 to use octal notation - # (change requires restart) -#bonjour = off # advertise server via Bonjour - # (change requires restart) -#bonjour_name = '' # defaults to the computer name - # (change requires restart) - -# - TCP settings - -# see "man 7 tcp" for details - -#tcp_keepalives_idle = 0 # TCP_KEEPIDLE, in seconds; - # 0 selects the system default -#tcp_keepalives_interval = 0 # TCP_KEEPINTVL, in seconds; - # 0 selects the system default -#tcp_keepalives_count = 0 # TCP_KEEPCNT; - # 0 selects the system default -#tcp_user_timeout = 0 # TCP_USER_TIMEOUT, in milliseconds; - # 0 selects the system default - -# - Authentication - - -#authentication_timeout = 1min # 1s-600s -#password_encryption = md5 # md5 or scram-sha-256 -#db_user_namespace = off - -# GSSAPI using Kerberos -#krb_server_keyfile = '' -#krb_caseins_users = off - -# - SSL - - -ssl = on -#ssl_ca_file = '' -ssl_cert_file = '/etc/ssl/certs/ssl-cert-snakeoil.pem' -#ssl_crl_file = '' -ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key' -#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers -#ssl_prefer_server_ciphers = on -#ssl_ecdh_curve = 'prime256v1' -#ssl_min_protocol_version = 'TLSv1' -#ssl_max_protocol_version = '' -#ssl_dh_params_file = '' -#ssl_passphrase_command = '' -#ssl_passphrase_command_supports_reload = off - - -#------------------------------------------------------------------------------ -# RESOURCE USAGE (except WAL) -#------------------------------------------------------------------------------ - -# - Memory - - -shared_buffers = 128MB # min 128kB - # (change requires restart) -#huge_pages = try # on, off, or try - # (change requires restart) -#temp_buffers = 8MB # min 800kB -#max_prepared_transactions = 0 # zero disables the feature - # (change requires restart) -# Caution: it is not advisable to set max_prepared_transactions nonzero unless -# you actively intend to use prepared transactions. -#work_mem = 4MB # min 64kB -#maintenance_work_mem = 64MB # min 1MB -#autovacuum_work_mem = -1 # min 1MB, or -1 to use maintenance_work_mem -#max_stack_depth = 2MB # min 100kB -#shared_memory_type = mmap # the default is the first option - # supported by the operating system: - # mmap - # sysv - # windows - # (change requires restart) -dynamic_shared_memory_type = posix # the default is the first option - # supported by the operating system: - # posix - # sysv - # windows - # mmap - # (change requires restart) - -# - Disk - - -#temp_file_limit = -1 # limits per-process temp file space - # in kB, or -1 for no limit - -# - Kernel Resources - - -#max_files_per_process = 1000 # min 25 - # (change requires restart) - -# - Cost-Based Vacuum Delay - - -#vacuum_cost_delay = 0 # 0-100 milliseconds (0 disables) -#vacuum_cost_page_hit = 1 # 0-10000 credits -#vacuum_cost_page_miss = 10 # 0-10000 credits -#vacuum_cost_page_dirty = 20 # 0-10000 credits -#vacuum_cost_limit = 200 # 1-10000 credits - -# - Background Writer - - -#bgwriter_delay = 200ms # 10-10000ms between rounds -#bgwriter_lru_maxpages = 100 # max buffers written/round, 0 disables -#bgwriter_lru_multiplier = 2.0 # 0-10.0 multiplier on buffers scanned/round -#bgwriter_flush_after = 512kB # measured in pages, 0 disables - -# - Asynchronous Behavior - - -#effective_io_concurrency = 1 # 1-1000; 0 disables prefetching -#max_worker_processes = 8 # (change requires restart) -#max_parallel_maintenance_workers = 2 # taken from max_parallel_workers -#max_parallel_workers_per_gather = 2 # taken from max_parallel_workers -#parallel_leader_participation = on -#max_parallel_workers = 8 # maximum number of max_worker_processes that - # can be used in parallel operations -#old_snapshot_threshold = -1 # 1min-60d; -1 disables; 0 is immediate - # (change requires restart) -#backend_flush_after = 0 # measured in pages, 0 disables - - -#------------------------------------------------------------------------------ -# WRITE-AHEAD LOG -#------------------------------------------------------------------------------ - -# - Settings - - -#wal_level = replica # minimal, replica, or logical - # (change requires restart) -#fsync = on # flush data to disk for crash safety - # (turning this off can cause - # unrecoverable data corruption) -#synchronous_commit = on # synchronization level; - # off, local, remote_write, remote_apply, or on -#wal_sync_method = fsync # the default is the first option - # supported by the operating system: - # open_datasync - # fdatasync (default on Linux) - # fsync - # fsync_writethrough - # open_sync -#full_page_writes = on # recover from partial page writes -#wal_compression = off # enable compression of full-page writes -#wal_log_hints = off # also do full page writes of non-critical updates - # (change requires restart) -#wal_init_zero = on # zero-fill new WAL files -#wal_recycle = on # recycle WAL files -#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers - # (change requires restart) -#wal_writer_delay = 200ms # 1-10000 milliseconds -#wal_writer_flush_after = 1MB # measured in pages, 0 disables - -#commit_delay = 0 # range 0-100000, in microseconds -#commit_siblings = 5 # range 1-1000 - -# - Checkpoints - - -#checkpoint_timeout = 5min # range 30s-1d -max_wal_size = 1GB -min_wal_size = 80MB -#checkpoint_completion_target = 0.5 # checkpoint target duration, 0.0 - 1.0 -#checkpoint_flush_after = 256kB # measured in pages, 0 disables -#checkpoint_warning = 30s # 0 disables - -# - Archiving - - -#archive_mode = off # enables archiving; off, on, or always - # (change requires restart) -#archive_command = '' # command to use to archive a logfile segment - # placeholders: %p = path of file to archive - # %f = file name only - # e.g. 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f' -#archive_timeout = 0 # force a logfile segment switch after this - # number of seconds; 0 disables - -# - Archive Recovery - - -# These are only used in recovery mode. - -#restore_command = '' # command to use to restore an archived logfile segment - # placeholders: %p = path of file to restore - # %f = file name only - # e.g. 'cp /mnt/server/archivedir/%f %p' - # (change requires restart) -#archive_cleanup_command = '' # command to execute at every restartpoint -#recovery_end_command = '' # command to execute at completion of recovery - -# - Recovery Target - - -# Set these only when performing a targeted recovery. - -#recovery_target = '' # 'immediate' to end recovery as soon as a - # consistent state is reached - # (change requires restart) -#recovery_target_name = '' # the named restore point to which recovery will proceed - # (change requires restart) -#recovery_target_time = '' # the time stamp up to which recovery will proceed - # (change requires restart) -#recovery_target_xid = '' # the transaction ID up to which recovery will proceed - # (change requires restart) -#recovery_target_lsn = '' # the WAL LSN up to which recovery will proceed - # (change requires restart) -#recovery_target_inclusive = on # Specifies whether to stop: - # just after the specified recovery target (on) - # just before the recovery target (off) - # (change requires restart) -#recovery_target_timeline = 'latest' # 'current', 'latest', or timeline ID - # (change requires restart) -#recovery_target_action = 'pause' # 'pause', 'promote', 'shutdown' - # (change requires restart) - - -#------------------------------------------------------------------------------ -# REPLICATION -#------------------------------------------------------------------------------ - -# - Sending Servers - - -# Set these on the master and on any standby that will send replication data. - -#max_wal_senders = 10 # max number of walsender processes - # (change requires restart) -#wal_keep_segments = 0 # in logfile segments; 0 disables -#wal_sender_timeout = 60s # in milliseconds; 0 disables - -#max_replication_slots = 10 # max number of replication slots - # (change requires restart) -#track_commit_timestamp = off # collect timestamp of transaction commit - # (change requires restart) - -# - Master Server - - -# These settings are ignored on a standby server. - -#synchronous_standby_names = '' # standby servers that provide sync rep - # method to choose sync standbys, number of sync standbys, - # and comma-separated list of application_name - # from standby(s); '*' = all -#vacuum_defer_cleanup_age = 0 # number of xacts by which cleanup is delayed - -# - Standby Servers - - -# These settings are ignored on a master server. - -#primary_conninfo = '' # connection string to sending server - # (change requires restart) -#primary_slot_name = '' # replication slot on sending server - # (change requires restart) -#promote_trigger_file = '' # file name whose presence ends recovery -#hot_standby = on # "off" disallows queries during recovery - # (change requires restart) -#max_standby_archive_delay = 30s # max delay before canceling queries - # when reading WAL from archive; - # -1 allows indefinite delay -#max_standby_streaming_delay = 30s # max delay before canceling queries - # when reading streaming WAL; - # -1 allows indefinite delay -#wal_receiver_status_interval = 10s # send replies at least this often - # 0 disables -#hot_standby_feedback = off # send info from standby to prevent - # query conflicts -#wal_receiver_timeout = 60s # time that receiver waits for - # communication from master - # in milliseconds; 0 disables -#wal_retrieve_retry_interval = 5s # time to wait before retrying to - # retrieve WAL after a failed attempt -#recovery_min_apply_delay = 0 # minimum delay for applying changes during recovery - -# - Subscribers - - -# These settings are ignored on a publisher. - -#max_logical_replication_workers = 4 # taken from max_worker_processes - # (change requires restart) -#max_sync_workers_per_subscription = 2 # taken from max_logical_replication_workers - - -#------------------------------------------------------------------------------ -# QUERY TUNING -#------------------------------------------------------------------------------ - -# - Planner Method Configuration - - -#enable_bitmapscan = on -#enable_hashagg = on -#enable_hashjoin = on -#enable_indexscan = on -#enable_indexonlyscan = on -#enable_material = on -#enable_mergejoin = on -#enable_nestloop = on -#enable_parallel_append = on -#enable_seqscan = on -#enable_sort = on -#enable_tidscan = on -#enable_partitionwise_join = off -#enable_partitionwise_aggregate = off -#enable_parallel_hash = on -#enable_partition_pruning = on - -# - Planner Cost Constants - - -#seq_page_cost = 1.0 # measured on an arbitrary scale -#random_page_cost = 4.0 # same scale as above -#cpu_tuple_cost = 0.01 # same scale as above -#cpu_index_tuple_cost = 0.005 # same scale as above -#cpu_operator_cost = 0.0025 # same scale as above -#parallel_tuple_cost = 0.1 # same scale as above -#parallel_setup_cost = 1000.0 # same scale as above - -#jit_above_cost = 100000 # perform JIT compilation if available - # and query more expensive than this; - # -1 disables -#jit_inline_above_cost = 500000 # inline small functions if query is - # more expensive than this; -1 disables -#jit_optimize_above_cost = 500000 # use expensive JIT optimizations if - # query is more expensive than this; - # -1 disables - -#min_parallel_table_scan_size = 8MB -#min_parallel_index_scan_size = 512kB -#effective_cache_size = 4GB - -# - Genetic Query Optimizer - - -#geqo = on -#geqo_threshold = 12 -#geqo_effort = 5 # range 1-10 -#geqo_pool_size = 0 # selects default based on effort -#geqo_generations = 0 # selects default based on effort -#geqo_selection_bias = 2.0 # range 1.5-2.0 -#geqo_seed = 0.0 # range 0.0-1.0 - -# - Other Planner Options - - -#default_statistics_target = 100 # range 1-10000 -#constraint_exclusion = partition # on, off, or partition -#cursor_tuple_fraction = 0.1 # range 0.0-1.0 -#from_collapse_limit = 8 -#join_collapse_limit = 8 # 1 disables collapsing of explicit - # JOIN clauses -#force_parallel_mode = off -#jit = on # allow JIT compilation -#plan_cache_mode = auto # auto, force_generic_plan or - # force_custom_plan - - -#------------------------------------------------------------------------------ -# REPORTING AND LOGGING -#------------------------------------------------------------------------------ - -# - Where to Log - - -#log_destination = 'stderr' # Valid values are combinations of - # stderr, csvlog, syslog, and eventlog, - # depending on platform. csvlog - # requires logging_collector to be on. - -# This is used when logging to stderr: -#logging_collector = off # Enable capturing of stderr and csvlog - # into log files. Required to be on for - # csvlogs. - # (change requires restart) - -# These are only used if logging_collector is on: -#log_directory = 'log' # directory where log files are written, - # can be absolute or relative to PGDATA -#log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' # log file name pattern, - # can include strftime() escapes -#log_file_mode = 0600 # creation mode for log files, - # begin with 0 to use octal notation -#log_truncate_on_rotation = off # If on, an existing log file with the - # same name as the new log file will be - # truncated rather than appended to. - # But such truncation only occurs on - # time-driven rotation, not on restarts - # or size-driven rotation. Default is - # off, meaning append to existing files - # in all cases. -#log_rotation_age = 1d # Automatic rotation of logfiles will - # happen after that time. 0 disables. -#log_rotation_size = 10MB # Automatic rotation of logfiles will - # happen after that much log output. - # 0 disables. - -# These are relevant when logging to syslog: -#syslog_facility = 'LOCAL0' -#syslog_ident = 'postgres' -#syslog_sequence_numbers = on -#syslog_split_messages = on - -# This is only relevant when logging to eventlog (win32): -# (change requires restart) -#event_source = 'PostgreSQL' - -# - When to Log - - -#log_min_messages = warning # values in order of decreasing detail: - # debug5 - # debug4 - # debug3 - # debug2 - # debug1 - # info - # notice - # warning - # error - # log - # fatal - # panic - -#log_min_error_statement = error # values in order of decreasing detail: - # debug5 - # debug4 - # debug3 - # debug2 - # debug1 - # info - # notice - # warning - # error - # log - # fatal - # panic (effectively off) - -#log_min_duration_statement = -1 # -1 is disabled, 0 logs all statements - # and their durations, > 0 logs only - # statements running at least this number - # of milliseconds - -#log_transaction_sample_rate = 0.0 # Fraction of transactions whose statements - # are logged regardless of their duration. 1.0 logs all - # statements from all transactions, 0.0 never logs. - -# - What to Log - - -#debug_print_parse = off -#debug_print_rewritten = off -#debug_print_plan = off -#debug_pretty_print = on -#log_checkpoints = off -#log_connections = off -#log_disconnections = off -#log_duration = off -#log_error_verbosity = default # terse, default, or verbose messages -#log_hostname = off -log_line_prefix = '%m [%p] %q%u@%d ' # special values: - # %a = application name - # %u = user name - # %d = database name - # %r = remote host and port - # %h = remote host - # %p = process ID - # %t = timestamp without milliseconds - # %m = timestamp with milliseconds - # %n = timestamp with milliseconds (as a Unix epoch) - # %i = command tag - # %e = SQL state - # %c = session ID - # %l = session line number - # %s = session start timestamp - # %v = virtual transaction ID - # %x = transaction ID (0 if none) - # %q = stop here in non-session - # processes - # %% = '%' - # e.g. '<%%u%%%d> ' -#log_lock_waits = off # log lock waits >= deadlock_timeout -#log_statement = 'none' # none, ddl, mod, all -#log_replication_commands = off -#log_temp_files = -1 # log temporary files equal or larger - # than the specified size in kilobytes; - # -1 disables, 0 logs all temp files -log_timezone = 'UTC' - -#------------------------------------------------------------------------------ -# PROCESS TITLE -#------------------------------------------------------------------------------ - -cluster_name = '<%= scope["zulip::postgresql_common::version"] %>/main' # added to process titles if nonempty - # (change requires restart) -#update_process_title = on - - -#------------------------------------------------------------------------------ -# STATISTICS -#------------------------------------------------------------------------------ - -# - Query and Index Statistics Collector - - -#track_activities = on -#track_counts = on -#track_io_timing = off -#track_functions = none # none, pl, all -#track_activity_query_size = 1024 # (change requires restart) -stats_temp_directory = '/var/run/postgresql/<%= scope["zulip::postgresql_common::version"] %>-main.pg_stat_tmp' - - -# - Monitoring - - -#log_parser_stats = off -#log_planner_stats = off -#log_executor_stats = off -#log_statement_stats = off - - -#------------------------------------------------------------------------------ -# AUTOVACUUM -#------------------------------------------------------------------------------ - -#autovacuum = on # Enable autovacuum subprocess? 'on' - # requires track_counts to also be on. -#log_autovacuum_min_duration = -1 # -1 disables, 0 logs all actions and - # their durations, > 0 logs only - # actions running at least this number - # of milliseconds. -#autovacuum_max_workers = 3 # max number of autovacuum subprocesses - # (change requires restart) -#autovacuum_naptime = 1min # time between autovacuum runs -#autovacuum_vacuum_threshold = 50 # min number of row updates before - # vacuum -#autovacuum_analyze_threshold = 50 # min number of row updates before - # analyze -#autovacuum_vacuum_scale_factor = 0.2 # fraction of table size before vacuum -#autovacuum_analyze_scale_factor = 0.1 # fraction of table size before analyze -#autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum - # (change requires restart) -#autovacuum_multixact_freeze_max_age = 400000000 # maximum multixact age - # before forced vacuum - # (change requires restart) -#autovacuum_vacuum_cost_delay = 2ms # default vacuum cost delay for - # autovacuum, in milliseconds; - # -1 means use vacuum_cost_delay -#autovacuum_vacuum_cost_limit = -1 # default vacuum cost limit for - # autovacuum, -1 means use - # vacuum_cost_limit - - -#------------------------------------------------------------------------------ -# CLIENT CONNECTION DEFAULTS -#------------------------------------------------------------------------------ - -# - Statement Behavior - - -#client_min_messages = notice # values in order of decreasing detail: - # debug5 - # debug4 - # debug3 - # debug2 - # debug1 - # log - # notice - # warning - # error -#search_path = '"$user", public' # schema names -#row_security = on -#default_tablespace = '' # a tablespace name, '' uses the default -#temp_tablespaces = '' # a list of tablespace names, '' uses - # only default tablespace -#default_table_access_method = 'heap' -#check_function_bodies = on -#default_transaction_isolation = 'read committed' -#default_transaction_read_only = off -#default_transaction_deferrable = off -#session_replication_role = 'origin' -#statement_timeout = 0 # in milliseconds, 0 is disabled -#lock_timeout = 0 # in milliseconds, 0 is disabled -#idle_in_transaction_session_timeout = 0 # in milliseconds, 0 is disabled -#vacuum_freeze_min_age = 50000000 -#vacuum_freeze_table_age = 150000000 -#vacuum_multixact_freeze_min_age = 5000000 -#vacuum_multixact_freeze_table_age = 150000000 -#vacuum_cleanup_index_scale_factor = 0.1 # fraction of total number of tuples - # before index cleanup, 0 always performs - # index cleanup -#bytea_output = 'hex' # hex, escape -#xmlbinary = 'base64' -#xmloption = 'content' -#gin_fuzzy_search_limit = 0 -#gin_pending_list_limit = 4MB - -# - Locale and Formatting - - -datestyle = 'iso, mdy' -#intervalstyle = 'postgres' -timezone = 'UTC' -#timezone_abbreviations = 'Default' # Select the set of available time zone - # abbreviations. Currently, there are - # Default - # Australia (historical usage) - # India - # You can create your own file in - # share/timezonesets/. -#extra_float_digits = 1 # min -15, max 3; any value >0 actually - # selects precise output mode -#client_encoding = sql_ascii # actually, defaults to database - # encoding - -# These settings are initialized by initdb, but they can be changed. -lc_messages = 'C.UTF-8' # locale for system error message - # strings -lc_monetary = 'C.UTF-8' # locale for monetary formatting -lc_numeric = 'C.UTF-8' # locale for number formatting -lc_time = 'C.UTF-8' # locale for time formatting - -# default configuration for text search -default_text_search_config = 'pg_catalog.english' - -# - Shared Library Preloading - - -#shared_preload_libraries = '' # (change requires restart) -#local_preload_libraries = '' -#session_preload_libraries = '' -#jit_provider = 'llvmjit' # JIT library to use - -# - Other Defaults - - -#dynamic_library_path = '$libdir' - - -#------------------------------------------------------------------------------ -# LOCK MANAGEMENT -#------------------------------------------------------------------------------ - -#deadlock_timeout = 1s -#max_locks_per_transaction = 64 # min 10 - # (change requires restart) -#max_pred_locks_per_transaction = 64 # min 10 - # (change requires restart) -#max_pred_locks_per_relation = -2 # negative values mean - # (max_pred_locks_per_transaction - # / -max_pred_locks_per_relation) - 1 -#max_pred_locks_per_page = 2 # min 0 - - -#------------------------------------------------------------------------------ -# VERSION AND PLATFORM COMPATIBILITY -#------------------------------------------------------------------------------ - -# - Previous PostgreSQL Versions - - -#array_nulls = on -#backslash_quote = safe_encoding # on, off, or safe_encoding -#escape_string_warning = on -#lo_compat_privileges = off -#operator_precedence_warning = off -#quote_all_identifiers = off -#standard_conforming_strings = on -#synchronize_seqscans = on - -# - Other Platforms and Clients - - -#transform_null_equals = off - - -#------------------------------------------------------------------------------ -# ERROR HANDLING -#------------------------------------------------------------------------------ - -#exit_on_error = off # terminate session on any error? -#restart_after_crash = on # reinitialize after backend crash? -#data_sync_retry = off # retry or panic on failure to fsync - # data? - # (change requires restart) - - -#------------------------------------------------------------------------------ -# CONFIG FILE INCLUDES -#------------------------------------------------------------------------------ - -# These options allow settings to be loaded from files other than the -# default postgresql.conf. Note that these are directives, not variable -# assignments, so they can usefully be given more than once. - -include_dir = 'conf.d' # include files ending in '.conf' from - # a directory, e.g., 'conf.d' -#include_if_exists = '...' # include file only if it exists -#include = '...' # include file - - -#------------------------------------------------------------------------------ -# CUSTOMIZED OPTIONS -#------------------------------------------------------------------------------ - -# Add settings for extensions here -timezone = 'UTC' -default_text_search_config = 'zulip.english_us_search' - -log_destination = 'stderr' -logging_collector = on -log_directory = '/var/log/postgresql' -log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' -log_rotation_age = 7d -log_rotation_size = 100MB -log_min_duration_statement = 500 -log_line_prefix = '%m [%c]: [%l-1] ' -log_checkpoints = on -log_lock_waits = on -log_temp_files = 0 -log_autovacuum_min_duration = 100 - -autovacuum_freeze_max_age = 2000000000 -vacuum_freeze_min_age = 1000000000 -vacuum_freeze_table_age = 1800000000 - -# Performance settings -max_connections = 1000 -maintenance_work_mem = <%= scope["zulip::profile::postgresql::maintenance_work_mem"] %>MB -effective_cache_size = <%= scope["zulip::profile::postgresql::effective_cache_size"] %>MB -work_mem = <%= scope["zulip::profile::postgresql::work_mem"] %>MB -shared_buffers = <%= scope["zulip::profile::postgresql::shared_buffers"] %>MB -wal_buffers = 4MB -checkpoint_completion_target = 0.7 -<% unless @random_page_cost.nil? -%> -random_page_cost = <%= @random_page_cost %> -<% end -%> -<% unless @effective_io_concurrency.nil? -%> -effective_io_concurrency = <%= @effective_io_concurrency %> -<% end -%> - -<% unless @listen_addresses.nil? -%> -listen_addresses = <%= @listen_addresses %> -<% end -%> - -<% if @s3_backups_bucket != '' -%> -# WAL backups to S3 (may also be used for replication) -archive_mode = on -archive_command = '/usr/bin/timeout 10m /usr/local/bin/env-wal-g wal-push %p' -restore_command = '/usr/local/bin/env-wal-g wal-fetch "%f" "%p"' -<% end -%> -<% unless @replication_primary.nil? || @replication_user.nil? -%> -# Streaming replication -primary_conninfo = 'host=<%= @replication_primary %> user=<%= @replication_user -%> -<% if @replication_password != '' %> password=<%= @replication_password %><% end -%> -<% unless @ssl_mode.nil? %> sslmode=<%= @ssl_mode %><% end -%> -' -<% end -%> - -<% unless @ssl_cert_file.nil? -%> -ssl_cert_file = '<%= @ssl_cert_file %>' # (change requires restart) -<% end -%> -<% unless @ssl_key_file.nil? -%> -ssl_key_file = '<%= @ssl_key_file %>' # (change requires restart) -<% end -%> -<% unless @ssl_ca_file.nil? -%> -ssl_ca_file = '<%= @ssl_ca_file %>' # (change requires restart) -<% end -%> diff --git a/requirements/common.in b/requirements/common.in index 79c403aa03..e511a9ee7f 100644 --- a/requirements/common.in +++ b/requirements/common.in @@ -189,7 +189,7 @@ django-scim2 circuitbreaker # Runtime monkeypatching of django-stubs generics -https://github.com/zulip/django-stubs/archive/a006b9993130cc2c1e80f2eb55d959422337554a.zip#egg=django-stubs-ext==5.1.0+git&subdirectory=ext # https://github.com/typeddjango/django-stubs/pull/2408, https://github.com/typeddjango/django-stubs/pull/2411 +django-stubs-ext # Structured data representation with parsing. pydantic diff --git a/requirements/dev.in b/requirements/dev.in index 73d2f41757..058e893b48 100644 --- a/requirements/dev.in +++ b/requirements/dev.in @@ -51,7 +51,7 @@ python-digitalocean pip-tools<6.3.0 # https://github.com/jazzband/pip-tools/pull/1455 breaks our hack for installing specific commits from Git # zulip's linting framework - zulint -https://github.com/zulip/zulint/archive/a070f3a349bc0c7ce09ad097b1d3e419b42a6126.zip#egg=zulint==1.0.0+git +https://github.com/zulip/zulint/archive/9be0a32bf75a9d8738b005f0b880567fff64e943.zip#egg=zulint==1.0.0+git -r mypy.in diff --git a/requirements/dev.txt b/requirements/dev.txt index 6a0d999d1c..2520284099 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -114,9 +114,9 @@ aiohttp==3.10.10 \ # -r requirements/dev.in # aiohttp-retry # twilio -aiohttp-retry==2.8.3 \ - --hash=sha256:3aeeead8f6afe48272db93ced9440cf4eda8b6fd7ee2abb25357b7eb28525b45 \ - --hash=sha256:9a8e637e31682ad36e1ff9f8bcba912fcfc7d7041722bc901a4b948da4d71ea9 +aiohttp-retry==2.9.0 \ + --hash=sha256:7661af92471e9a96c69d9b8f32021360272073397e6a15bc44c1726b12f46056 \ + --hash=sha256:92c47f1580040208bac95d9a8389a87227ef22758530f2e3f4683395e42c41b5 # via twilio aiosignal==1.3.1 \ --hash=sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc \ @@ -241,26 +241,26 @@ boltons==21.0.0 \ # face # glom # semgrep -boto3==1.35.44 \ - --hash=sha256:18416d07b41e6094101a44f8b881047dcec6b846dad0b9f83b9bbf2f0cd93d07 \ - --hash=sha256:7f8e8a252458d584d8cf7877c372c4f74ec103356eedf43d2dd9e479f47f3639 +boto3==1.35.53 \ + --hash=sha256:a9c0955df0b52b43749d81bde159343a40ea2a3537a46049336fe8193871b18e \ + --hash=sha256:f4124548bb831e13504e805f2fbbfcee06df42fffea0655862c6eb9b95d6d1be # via # -r requirements/common.in # moto -boto3-stubs[s3,ses,sns,sqs]==1.35.44 \ - --hash=sha256:03b8f717692e85003539135c5553c3d8591c2475f9c9860e4e0b8a139c94b5ff \ - --hash=sha256:8268c64f6480d9cdd9fcc01082ea5bea96a33e5967ee23d90dcbd3153ec9ffe6 +boto3-stubs[s3,ses,sns,sqs]==1.35.53 \ + --hash=sha256:43172fe0f6950c6a5310856cd21664f5d8bf2a561d2969bf5694ba851a5fc905 \ + --hash=sha256:502b9c90b5a03e9fc7e12a6f1aeb8a4ce6caf169c32b23a902b3910ce4160a58 # via -r requirements/mypy.in -botocore==1.35.44 \ - --hash=sha256:1fcd97b966ad8a88de4106fe1bd3bbd6d8dadabe99bbd4a6aadcf11cb6c66b39 \ - --hash=sha256:55388e80624401d017a9a2b8109afd94814f7e666b53e28fce51375cfa8d9326 +botocore==1.35.53 \ + --hash=sha256:12869640f2f9fab3152ea312a6906d5bc6ae15522cc74b6367ee1c273269a28b \ + --hash=sha256:e610ae076ad1eaed5680d3990493659bbabdffd67b15c61d8373a23e4bc41062 # via # boto3 # moto # s3transfer -botocore-stubs==1.35.44 \ - --hash=sha256:09e499ac11d4e63abf7c847da2d3c2e24c31a9ec18db01f6db97a3953585b8e6 \ - --hash=sha256:96172c75186cd63658c4f130d993db7da52f0df5f248799e87b70c374ee16739 +botocore-stubs==1.35.53 \ + --hash=sha256:09c65f0dd2a91a6d9915bca483d14645efdf0d267971fcedd0d87d474d66071b \ + --hash=sha256:ba85b7f233527abfb60fae3e121519e5b748b0486756a0d4cfccfcc3dd47e649 # via boto3-stubs bracex==2.5.post1 \ --hash=sha256:12c50952415bfa773d2d9ccb8e79651b8cdb1f31a42f6091b804f6ba2b4a66b6 \ @@ -648,9 +648,9 @@ defusedxml==0.7.1 \ # scrapy # semgrep # social-auth-core -disposable-email-domains==0.0.107 \ - --hash=sha256:1a7b891f644b1234dd2555de5208bcb35a52e32aaef156ff03ad2cc02f8564ed \ - --hash=sha256:a5e5f267d6fe1288840ba67e03816e7478a3ec308948c9ae5aa7f7cfcc31b13c +disposable-email-domains==0.0.108 \ + --hash=sha256:4427cb11962dbfc5bb5afddbb6381fb7072eb7abf941bae4c37e414b544ae3c3 \ + --hash=sha256:57a3ae6cfb03ef29f93c6aa069137f57b60cbcb8295d9f82b0158e357b32c799 # via -r requirements/common.in distro==1.9.0 \ --hash=sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed \ @@ -701,11 +701,13 @@ django-scim2==0.19.1 \ --hash=sha256:8126111160e76a880f6699babc5259f0345c9316c8018ce5dcc3f7579ccb9e89 \ --hash=sha256:845eaa64e72e4ddfe2aa54d21865721f8c1d59ecd9c06e72341eeb03ed6ba94e # via -r requirements/common.in -https://github.com/zulip/django-stubs/archive/a006b9993130cc2c1e80f2eb55d959422337554a.zip#egg=django-stubs==5.1.0+git \ - --hash=sha256:5b04dac961197bb6e7eeb1a453caceec3dfb29bdec85f67bb3c561ed961e01f3 +django-stubs==5.1.1 \ + --hash=sha256:126d354bbdff4906c4e93e6361197f6fbfb6231c3df6def85a291dae6f9f577b \ + --hash=sha256:c4dc64260bd72e6d32b9e536e8dd0d9247922f0271f82d1d5132a18f24b388ac # via -r requirements/mypy.in -https://github.com/zulip/django-stubs/archive/a006b9993130cc2c1e80f2eb55d959422337554a.zip#egg=django-stubs-ext==5.1.0+git&subdirectory=ext \ - --hash=sha256:5b04dac961197bb6e7eeb1a453caceec3dfb29bdec85f67bb3c561ed961e01f3 +django-stubs-ext==5.1.1 \ + --hash=sha256:3907f99e178c93323e2ce908aef8352adb8c047605161f8d9e5e7b4efb5a6a9c \ + --hash=sha256:db7364e4f50ae7e5360993dbd58a3a57ea4b2e7e5bab0fbd525ccdb3e7975d1c # via # -r requirements/common.in # django-stubs @@ -750,84 +752,99 @@ firebase-admin==6.5.0 \ --hash=sha256:e716dde1447f0a1cd1523be76ff872df33c4e1a3c079564ace033b2ad60bcc4f \ --hash=sha256:fe34ee3ca0e625c5156b3931ca4b4b69b5fc344dbe51bba9706ff674ce277898 # via -r requirements/common.in -frozenlist==1.4.1 \ - --hash=sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7 \ - --hash=sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98 \ - --hash=sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad \ - --hash=sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5 \ - --hash=sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae \ - --hash=sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e \ - --hash=sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a \ - --hash=sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701 \ - --hash=sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d \ - --hash=sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6 \ - --hash=sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6 \ - --hash=sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106 \ - --hash=sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75 \ - --hash=sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868 \ - --hash=sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a \ - --hash=sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0 \ - --hash=sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1 \ - --hash=sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826 \ - --hash=sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec \ - --hash=sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6 \ - --hash=sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950 \ - --hash=sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19 \ - --hash=sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0 \ - --hash=sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8 \ - --hash=sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a \ - --hash=sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09 \ - --hash=sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86 \ - --hash=sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c \ - --hash=sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5 \ - --hash=sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b \ - --hash=sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b \ - --hash=sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d \ - --hash=sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0 \ - --hash=sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea \ - --hash=sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776 \ - --hash=sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a \ - --hash=sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897 \ - --hash=sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7 \ - --hash=sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09 \ - --hash=sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9 \ - --hash=sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe \ - --hash=sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd \ - --hash=sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742 \ - --hash=sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09 \ - --hash=sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0 \ - --hash=sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932 \ - --hash=sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1 \ - --hash=sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a \ - --hash=sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49 \ - --hash=sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d \ - --hash=sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7 \ - --hash=sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480 \ - --hash=sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89 \ - --hash=sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e \ - --hash=sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b \ - --hash=sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82 \ - --hash=sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb \ - --hash=sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068 \ - --hash=sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8 \ - --hash=sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b \ - --hash=sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb \ - --hash=sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2 \ - --hash=sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11 \ - --hash=sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b \ - --hash=sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc \ - --hash=sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0 \ - --hash=sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497 \ - --hash=sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17 \ - --hash=sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0 \ - --hash=sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2 \ - --hash=sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439 \ - --hash=sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5 \ - --hash=sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac \ - --hash=sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825 \ - --hash=sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887 \ - --hash=sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced \ - --hash=sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74 +frozenlist==1.5.0 \ + --hash=sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e \ + --hash=sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf \ + --hash=sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6 \ + --hash=sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a \ + --hash=sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d \ + --hash=sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f \ + --hash=sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28 \ + --hash=sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b \ + --hash=sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9 \ + --hash=sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2 \ + --hash=sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec \ + --hash=sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2 \ + --hash=sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c \ + --hash=sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336 \ + --hash=sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4 \ + --hash=sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d \ + --hash=sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b \ + --hash=sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c \ + --hash=sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10 \ + --hash=sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08 \ + --hash=sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942 \ + --hash=sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8 \ + --hash=sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f \ + --hash=sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10 \ + --hash=sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5 \ + --hash=sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6 \ + --hash=sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21 \ + --hash=sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c \ + --hash=sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d \ + --hash=sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923 \ + --hash=sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608 \ + --hash=sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de \ + --hash=sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17 \ + --hash=sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0 \ + --hash=sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f \ + --hash=sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641 \ + --hash=sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c \ + --hash=sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a \ + --hash=sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0 \ + --hash=sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9 \ + --hash=sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab \ + --hash=sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f \ + --hash=sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3 \ + --hash=sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a \ + --hash=sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784 \ + --hash=sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604 \ + --hash=sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d \ + --hash=sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5 \ + --hash=sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03 \ + --hash=sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e \ + --hash=sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953 \ + --hash=sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee \ + --hash=sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d \ + --hash=sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817 \ + --hash=sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3 \ + --hash=sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039 \ + --hash=sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f \ + --hash=sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9 \ + --hash=sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf \ + --hash=sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76 \ + --hash=sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba \ + --hash=sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171 \ + --hash=sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb \ + --hash=sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439 \ + --hash=sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631 \ + --hash=sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972 \ + --hash=sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d \ + --hash=sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869 \ + --hash=sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9 \ + --hash=sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411 \ + --hash=sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723 \ + --hash=sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2 \ + --hash=sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b \ + --hash=sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99 \ + --hash=sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e \ + --hash=sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840 \ + --hash=sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3 \ + --hash=sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb \ + --hash=sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3 \ + --hash=sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0 \ + --hash=sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca \ + --hash=sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45 \ + --hash=sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e \ + --hash=sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f \ + --hash=sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5 \ + --hash=sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307 \ + --hash=sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e \ + --hash=sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2 \ + --hash=sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778 \ + --hash=sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a \ + --hash=sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30 \ + --hash=sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a # via # aiohttp # aiosignal @@ -843,18 +860,18 @@ glom==22.1.0 \ --hash=sha256:1510c6587a8f9c64a246641b70033cbc5ebde99f02ad245693678038e821aeb5 \ --hash=sha256:5339da206bf3532e01a83a35aca202960ea885156986d190574b779598e9e772 # via semgrep -google-api-core[grpc]==2.21.0 \ - --hash=sha256:4a152fd11a9f774ea606388d423b68aa7e6d6a0ffe4c8266f74979613ec09f81 \ - --hash=sha256:6869eacb2a37720380ba5898312af79a4d30b8bca1548fb4093e0697dc4bdf5d +google-api-core[grpc]==2.22.0 \ + --hash=sha256:26f8d76b96477db42b55fd02a33aae4a42ec8b86b98b94969b7333a2c828bf35 \ + --hash=sha256:a6652b6bd51303902494998626653671703c420f6f4c88cfd3f50ed723e9d021 # via # firebase-admin # google-api-python-client # google-cloud-core # google-cloud-firestore # google-cloud-storage -google-api-python-client==2.149.0 \ - --hash=sha256:1a5232e9cfed8c201799d9327e4d44dc7ea7daa3c6e1627fca41aa201539c0da \ - --hash=sha256:b9d68c6b14ec72580d66001bd33c5816b78e2134b93ccc5cf8f624516b561750 +google-api-python-client==2.151.0 \ + --hash=sha256:4427b2f47cd88b0355d540c2c52215f68c337f3bc9d6aae1ceeae4525977504c \ + --hash=sha256:a9d26d630810ed4631aea21d1de3e42072f98240aaf184a8a1a874a371115034 # via firebase-admin google-auth==2.35.0 \ --hash=sha256:25df55f327ef021de8be50bad0dfd4a916ad0de96da86cd05661c9297723ad3f \ @@ -1055,68 +1072,68 @@ greenlet==3.1.1 \ # via # -r requirements/common.in # sqlalchemy -grpcio==1.67.0 \ - --hash=sha256:014dfc020e28a0d9be7e93a91f85ff9f4a87158b7df9952fe23cc42d29d31e1e \ - --hash=sha256:0892dd200ece4822d72dd0952f7112c542a487fc48fe77568deaaa399c1e717d \ - --hash=sha256:0bb94e66cd8f0baf29bd3184b6aa09aeb1a660f9ec3d85da615c5003154bc2bf \ - --hash=sha256:0c69bf11894cad9da00047f46584d5758d6ebc9b5950c0dc96fec7e0bce5cde9 \ - --hash=sha256:15c05a26a0f7047f720da41dc49406b395c1470eef44ff7e2c506a47ac2c0591 \ - --hash=sha256:16724ffc956ea42967f5758c2f043faef43cb7e48a51948ab593570570d1e68b \ - --hash=sha256:227316b5631260e0bef8a3ce04fa7db4cc81756fea1258b007950b6efc90c05d \ - --hash=sha256:2b7183c80b602b0ad816315d66f2fb7887614ead950416d60913a9a71c12560d \ - --hash=sha256:2f55c1e0e2ae9bdd23b3c63459ee4c06d223b68aeb1961d83c48fb63dc29bc03 \ - --hash=sha256:30d47dbacfd20cbd0c8be9bfa52fdb833b395d4ec32fe5cff7220afc05d08571 \ - --hash=sha256:323741b6699cd2b04a71cb38f502db98f90532e8a40cb675393d248126a268af \ - --hash=sha256:3840994689cc8cbb73d60485c594424ad8adb56c71a30d8948d6453083624b52 \ - --hash=sha256:391df8b0faac84d42f5b8dfc65f5152c48ed914e13c522fd05f2aca211f8bfad \ - --hash=sha256:42199e704095b62688998c2d84c89e59a26a7d5d32eed86d43dc90e7a3bd04aa \ - --hash=sha256:54d16383044e681f8beb50f905249e4e7261dd169d4aaf6e52eab67b01cbbbe2 \ - --hash=sha256:5a1e03c3102b6451028d5dc9f8591131d6ab3c8a0e023d94c28cb930ed4b5f81 \ - --hash=sha256:62492bd534979e6d7127b8a6b29093161a742dee3875873e01964049d5250a74 \ - --hash=sha256:662c8e105c5e5cee0317d500eb186ed7a93229586e431c1bf0c9236c2407352c \ - --hash=sha256:682968427a63d898759474e3b3178d42546e878fdce034fd7474ef75143b64e3 \ - --hash=sha256:74b900566bdf68241118f2918d312d3bf554b2ce0b12b90178091ea7d0a17b3d \ - --hash=sha256:77196216d5dd6f99af1c51e235af2dd339159f657280e65ce7e12c1a8feffd1d \ - --hash=sha256:7f200aca719c1c5dc72ab68be3479b9dafccdf03df530d137632c534bb6f1ee3 \ - --hash=sha256:7fc1d2b9fd549264ae585026b266ac2db53735510a207381be509c315b4af4e8 \ - --hash=sha256:82e5bd4b67b17c8c597273663794a6a46a45e44165b960517fe6d8a2f7f16d23 \ - --hash=sha256:8c9a35b8bc50db35ab8e3e02a4f2a35cfba46c8705c3911c34ce343bd777813a \ - --hash=sha256:985b2686f786f3e20326c4367eebdaed3e7aa65848260ff0c6644f817042cb15 \ - --hash=sha256:9d75641a2fca9ae1ae86454fd25d4c298ea8cc195dbc962852234d54a07060ad \ - --hash=sha256:a4e95e43447a02aa603abcc6b5e727d093d161a869c83b073f50b9390ecf0fa8 \ - --hash=sha256:a6b9a5c18863fd4b6624a42e2712103fb0f57799a3b29651c0e5b8119a519d65 \ - --hash=sha256:aa8d025fae1595a207b4e47c2e087cb88d47008494db258ac561c00877d4c8f8 \ - --hash=sha256:ac11ecb34a86b831239cc38245403a8de25037b448464f95c3315819e7519772 \ - --hash=sha256:ae6de510f670137e755eb2a74b04d1041e7210af2444103c8c95f193340d17ee \ - --hash=sha256:b2a44e572fb762c668e4812156b81835f7aba8a721b027e2d4bb29fb50ff4d33 \ - --hash=sha256:b6eb68493a05d38b426604e1dc93bfc0137c4157f7ab4fac5771fd9a104bbaa6 \ - --hash=sha256:b9bca3ca0c5e74dea44bf57d27e15a3a3996ce7e5780d61b7c72386356d231db \ - --hash=sha256:bd79929b3bb96b54df1296cd3bf4d2b770bd1df6c2bdf549b49bab286b925cdc \ - --hash=sha256:c4c425f440fb81f8d0237c07b9322fc0fb6ee2b29fbef5f62a322ff8fcce240d \ - --hash=sha256:cb204a742997277da678611a809a8409657b1398aaeebf73b3d9563b7d154c13 \ - --hash=sha256:cf51d28063338608cd8d3cd64677e922134837902b70ce00dad7f116e3998210 \ - --hash=sha256:cfd9306511fdfc623a1ba1dc3bc07fbd24e6cfbe3c28b4d1e05177baa2f99617 \ - --hash=sha256:cff8e54d6a463883cda2fab94d2062aad2f5edd7f06ae3ed030f2a74756db365 \ - --hash=sha256:d01793653248f49cf47e5695e0a79805b1d9d4eacef85b310118ba1dfcd1b955 \ - --hash=sha256:d4ea4509d42c6797539e9ec7496c15473177ce9abc89bc5c71e7abe50fc25737 \ - --hash=sha256:d90cfdafcf4b45a7a076e3e2a58e7bc3d59c698c4f6470b0bb13a4d869cf2273 \ - --hash=sha256:e090b2553e0da1c875449c8e75073dd4415dd71c9bde6a406240fdf4c0ee467c \ - --hash=sha256:e91d154689639932305b6ea6f45c6e46bb51ecc8ea77c10ef25aa77f75443ad4 \ - --hash=sha256:eef1dce9d1a46119fd09f9a992cf6ab9d9178b696382439446ca5f399d7b96fe \ - --hash=sha256:efe32b45dd6d118f5ea2e5deaed417d8a14976325c93812dd831908522b402c9 \ - --hash=sha256:f4d613fbf868b2e2444f490d18af472ccb47660ea3df52f068c9c8801e1f3e85 \ - --hash=sha256:f55f077685f61f0fbd06ea355142b71e47e4a26d2d678b3ba27248abfe67163a \ - --hash=sha256:f623c57a5321461c84498a99dddf9d13dac0e40ee056d884d6ec4ebcab647a78 \ - --hash=sha256:f6bd2ab135c64a4d1e9e44679a616c9bc944547357c830fafea5c3caa3de5153 \ - --hash=sha256:f95e15db43e75a534420e04822df91f645664bf4ad21dfaad7d51773c80e6bb4 \ - --hash=sha256:fd6bc27861e460fe28e94226e3673d46e294ca4673d46b224428d197c5935e69 \ - --hash=sha256:fe89295219b9c9e47780a0f1c75ca44211e706d1c598242249fe717af3385ec8 +grpcio==1.67.1 \ + --hash=sha256:01f616a964e540638af5130469451cf580ba8c7329f45ca998ab66e0c7dcdb04 \ + --hash=sha256:0489063974d1452436139501bf6b180f63d4977223ee87488fe36858c5725292 \ + --hash=sha256:0e6f255980afef598a9e64a24efce87b625e3e3c80a45162d111a461a9f92955 \ + --hash=sha256:0f3e49c738396e93b7ba9016e153eb09e0778e776df6090c1b8c91877cc1c426 \ + --hash=sha256:178f5db771c4f9a9facb2ab37a434c46cb9be1a75e820f187ee3d1e7805c4f65 \ + --hash=sha256:1a65b503d008f066e994f34f456e0647e5ceb34cfcec5ad180b1b44020ad4970 \ + --hash=sha256:1d7616d2ded471231c701489190379e0c311ee0a6c756f3c03e6a62b95a7146e \ + --hash=sha256:24e8a26dbfc5274d7474c27759b54486b8de23c709d76695237515bc8b5baeab \ + --hash=sha256:267d1745894200e4c604958da5f856da6293f063327cb049a51fe67348e4f953 \ + --hash=sha256:299b3d8c4f790c6bcca485f9963b4846dd92cf6f1b65d3697145d005c80f9fe8 \ + --hash=sha256:3b6c16489326d79ead41689c4b84bc40d522c9a7617219f4ad94bc7f448c5085 \ + --hash=sha256:3dc2ed4cabea4dc14d5e708c2b426205956077cc5de419b4d4079315017e9732 \ + --hash=sha256:43112046864317498a33bdc4797ae6a268c36345a910de9b9c17159d8346602f \ + --hash=sha256:4422581cdc628f77302270ff839a44f4c24fdc57887dc2a45b7e53d8fc2376af \ + --hash=sha256:4e7b904484a634a0fff132958dabdb10d63e0927398273917da3ee103e8d1f78 \ + --hash=sha256:5721e66a594a6c4204458004852719b38f3d5522082be9061d6510b455c90afc \ + --hash=sha256:5db70d32d6703b89912af16d6d45d78406374a8b8ef0d28140351dd0ec610e98 \ + --hash=sha256:5ed601c4c6008429e3d247ddb367fe8c7259c355757448d7c1ef7bd4a6739e8e \ + --hash=sha256:60336bff760fbb47d7e86165408126f1dded184448e9a4c892189eb7c9d3f90f \ + --hash=sha256:608d87d1bdabf9e2868b12338cd38a79969eaf920c89d698ead08f48de9c0f9e \ + --hash=sha256:60e6a4dcf5af7bbc36fd9f81c9f372e8ae580870a9e4b6eafe948cd334b81cf3 \ + --hash=sha256:638354e698fd0c6c76b04540a850bf1db27b4d2515a19fcd5cf645c48d3eb1ed \ + --hash=sha256:699e964923b70f3101393710793289e42845791ea07565654ada0969522d0a38 \ + --hash=sha256:7818c0454027ae3384235a65210bbf5464bd715450e30a3d40385453a85a70cb \ + --hash=sha256:786a5b18544622bfb1e25cc08402bd44ea83edfb04b93798d85dca4d1a0b5be5 \ + --hash=sha256:804c6457c3cd3ec04fe6006c739579b8d35c86ae3298ffca8de57b493524b771 \ + --hash=sha256:80b866f73224b0634f4312a4674c1be21b2b4afa73cb20953cbbb73a6b36c3cc \ + --hash=sha256:85f69fdc1d28ce7cff8de3f9c67db2b0ca9ba4449644488c1e0303c146135ddb \ + --hash=sha256:85f862069b86a305497e74d0dc43c02de3d1d184fc2c180993aa8aa86fbd19b8 \ + --hash=sha256:8a00efecde9d6fcc3ab00c13f816313c040a28450e5e25739c24f432fc6d3c75 \ + --hash=sha256:8a23cbcc5bb11ea7dc6163078be36c065db68d915c24f5faa4f872c573bb400f \ + --hash=sha256:8b0341d66a57f8a3119b77ab32207072be60c9bf79760fa609c5609f2deb1f3f \ + --hash=sha256:917e8d8994eed1d86b907ba2a61b9f0aef27a2155bca6cbb322430fc7135b7bb \ + --hash=sha256:95b5f2b857856ed78d72da93cd7d09b6db8ef30102e5e7fe0961fe4d9f7d48e8 \ + --hash=sha256:9e838cad2176ebd5d4a8bb03955138d6589ce9e2ce5d51c3ada34396dbd2dba8 \ + --hash=sha256:9fd042de4a82e3e7aca44008ee2fb5da01b3e5adb316348c21980f7f58adc311 \ + --hash=sha256:a25bdea92b13ff4d7790962190bf6bf5c4639876e01c0f3dda70fc2769616335 \ + --hash=sha256:a6703916c43b1d468d0756c8077b12017a9fcb6a1ef13faf49e67d20d7ebda62 \ + --hash=sha256:a93deda571a1bf94ec1f6fcda2872dad3ae538700d94dc283c672a3b508ba3af \ + --hash=sha256:aa0162e56fd10a5547fac8774c4899fc3e18c1aa4a4759d0ce2cd00d3696ea6b \ + --hash=sha256:b49359977c6ec9f5d0573ea4e0071ad278ef905aa74e420acc73fd28ce39e9ce \ + --hash=sha256:beee96c8c0b1a75d556fe57b92b58b4347c77a65781ee2ac749d550f2a365dc1 \ + --hash=sha256:c7a01337407dd89005527623a4a72c5c8e2894d22bead0895306b23c6695698f \ + --hash=sha256:c9b929f13677b10f63124c1a410994a401cdd85214ad83ab67cc077fc7e480f0 \ + --hash=sha256:cdc491ae35a13535fd9196acb5afe1af37c8237df2e54427be3eecda3653127e \ + --hash=sha256:e279330bef1744040db8fc432becc8a727b84f456ab62b744d3fdb83f327e121 \ + --hash=sha256:e29ca27bec8e163dca0c98084040edec3bc49afd10f18b412f483cc68c712744 \ + --hash=sha256:e7d1797a8a3845437d327145959a2c0c47c05947c9eef5ff1a4c80e499dcc6fa \ + --hash=sha256:ea33986b70f83844cd00814cee4451055cd8cab36f00ac64a31f5bb09b31919e \ + --hash=sha256:ec74ef02010186185de82cc594058a3ccd8d86821842bbac9873fd4a2cf8be8d \ + --hash=sha256:f26b0b547eb8d00e195274cdfc63ce64c8fc2d3e2d00b12bf468ece41a0423a0 \ + --hash=sha256:f5a27dddefe0e2357d3e617b9079b4bfdc91341a91565111a21ed6ebbc51b22d \ + --hash=sha256:f5b76ff64aaac53fede0cc93abf57894ab2a7362986ba22243d06218b93efe46 \ + --hash=sha256:f9fff78ba10d4250bfc07a01bd6254a6d87dc67f9627adece85c0b2ed754fa96 \ + --hash=sha256:fa0c739ad8b1996bd24823950e3cb5152ae91fca1c09cc791190bf1627ffefba # via # google-api-core # grpcio-status -grpcio-status==1.67.0 \ - --hash=sha256:0e79e2e01ba41a6ca6ed9d7a825323c511fe1653a646f8014c7e3c8132527acc \ - --hash=sha256:c3e5a86fa007e9e263cd5f988a8a907484da4caab582874ea2a4a6092734046b +grpcio-status==1.67.1 \ + --hash=sha256:16e6c085950bdacac97c779e6a502ea671232385e6e37f258884d6883392c2bd \ + --hash=sha256:2bf38395e028ceeecfd8866b081f61628114b384da7d51ae064ddc8d766a5d11 # via google-api-core h2==4.1.0 \ --hash=sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d \ @@ -1165,12 +1182,9 @@ incremental==24.7.2 \ --hash=sha256:8cb2c3431530bec48ad70513931a760f446ad6c25e8333ca5d95e24b0ed7b8fe \ --hash=sha256:fb4f1d47ee60efe87d4f6f0ebb5f70b9760db2b2574c59c8e8912be4ebd464c9 # via twisted -intervaltree==3.1.0 \ - --hash=sha256:902b1b88936918f9b2a19e0e5eb7ccb430ae45cde4f39ea4b36932920d33952d - # via pyre-check -ipython==8.28.0 \ - --hash=sha256:0d0d15ca1e01faeb868ef56bc7ee5a0de5bd66885735682e8a322ae289a13d1a \ - --hash=sha256:530ef1e7bb693724d3cdc37287c80b07ad9b25986c007a53aa1857272dac3f35 +ipython==8.29.0 \ + --hash=sha256:0188a1bd83267192123ccea7f4a8ed0a78910535dbaa3f37671dca76ebd429c8 \ + --hash=sha256:40b60e15b22591450eef73e40a027cf77bd652e757523eebc5bd7c7c498290eb # via -r requirements/common.in isodate==0.7.2 \ --hash=sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15 \ @@ -1791,45 +1805,45 @@ multidict==6.1.0 \ # via # aiohttp # yarl -mypy==1.12.1 \ - --hash=sha256:02dcfe270c6ea13338210908f8cadc8d31af0f04cee8ca996438fe6a97b4ec66 \ - --hash=sha256:0dcc1e843d58f444fce19da4cce5bd35c282d4bde232acdeca8279523087088a \ - --hash=sha256:0e6fe449223fa59fbee351db32283838a8fee8059e0028e9e6494a03802b4004 \ - --hash=sha256:1230048fec1380faf240be6385e709c8570604d2d27ec6ca7e573e3bc09c3735 \ - --hash=sha256:186e0c8346efc027ee1f9acf5ca734425fc4f7dc2b60144f0fbe27cc19dc7931 \ - --hash=sha256:19bf51f87a295e7ab2894f1d8167622b063492d754e69c3c2fed6563268cb42a \ - --hash=sha256:20db6eb1ca3d1de8ece00033b12f793f1ea9da767334b7e8c626a4872090cf02 \ - --hash=sha256:389e307e333879c571029d5b93932cf838b811d3f5395ed1ad05086b52148fb0 \ - --hash=sha256:3d7d4371829184e22fda4015278fbfdef0327a4b955a483012bd2d423a788801 \ - --hash=sha256:427878aa54f2e2c5d8db31fa9010c599ed9f994b3b49e64ae9cd9990c40bd635 \ - --hash=sha256:4ee5932370ccf7ebf83f79d1c157a5929d7ea36313027b0d70a488493dc1b179 \ - --hash=sha256:5fcde63ea2c9f69d6be859a1e6dd35955e87fa81de95bc240143cf00de1f7f81 \ - --hash=sha256:673ba1140a478b50e6d265c03391702fa11a5c5aff3f54d69a62a48da32cb811 \ - --hash=sha256:8135ffec02121a75f75dc97c81af7c14aa4ae0dda277132cfcd6abcd21551bfd \ - --hash=sha256:843826966f1d65925e8b50d2b483065c51fc16dc5d72647e0236aae51dc8d77e \ - --hash=sha256:94b2048a95a21f7a9ebc9fbd075a4fcd310410d078aa0228dbbad7f71335e042 \ - --hash=sha256:96af62050971c5241afb4701c15189ea9507db89ad07794a4ee7b4e092dc0627 \ - --hash=sha256:9fb83a7be97c498176fb7486cafbb81decccaef1ac339d837c377b0ce3743a7f \ - --hash=sha256:9fe20f89da41a95e14c34b1ddb09c80262edcc295ad891f22cc4b60013e8f78d \ - --hash=sha256:a5a437c9102a6a252d9e3a63edc191a3aed5f2fcb786d614722ee3f4472e33f6 \ - --hash=sha256:a7b76fa83260824300cc4834a3ab93180db19876bce59af921467fd03e692810 \ - --hash=sha256:b16fe09f9c741d85a2e3b14a5257a27a4f4886c171d562bc5a5e90d8591906b8 \ - --hash=sha256:b947097fae68004b8328c55161ac9db7d3566abfef72d9d41b47a021c2fba6b1 \ - --hash=sha256:ce561a09e3bb9863ab77edf29ae3a50e65685ad74bba1431278185b7e5d5486e \ - --hash=sha256:d34167d43613ffb1d6c6cdc0cc043bb106cac0aa5d6a4171f77ab92a3c758bcc \ - --hash=sha256:d54d840f6c052929f4a3d2aab2066af0f45a020b085fe0e40d4583db52aab4e4 \ - --hash=sha256:d90da248f4c2dba6c44ddcfea94bb361e491962f05f41990ff24dbd09969ce20 \ - --hash=sha256:dc6e2a2195a290a7fd5bac3e60b586d77fc88e986eba7feced8b778c373f9afe \ - --hash=sha256:de5b2a8988b4e1269a98beaf0e7cc71b510d050dce80c343b53b4955fff45f19 \ - --hash=sha256:e10ba7de5c616e44ad21005fa13450cd0de7caaa303a626147d45307492e4f2d \ - --hash=sha256:f59f1dfbf497d473201356966e353ef09d4daec48caeacc0254db8ef633a28a5 \ - --hash=sha256:f5b3936f7a6d0e8280c9bdef94c7ce4847f5cdfc258fbb2c29a8c1711e8bb96d +mypy[faster-cache]==1.13.0 \ + --hash=sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc \ + --hash=sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e \ + --hash=sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f \ + --hash=sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74 \ + --hash=sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a \ + --hash=sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2 \ + --hash=sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b \ + --hash=sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73 \ + --hash=sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e \ + --hash=sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d \ + --hash=sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d \ + --hash=sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6 \ + --hash=sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca \ + --hash=sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d \ + --hash=sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5 \ + --hash=sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62 \ + --hash=sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a \ + --hash=sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc \ + --hash=sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7 \ + --hash=sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb \ + --hash=sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7 \ + --hash=sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732 \ + --hash=sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80 \ + --hash=sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a \ + --hash=sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc \ + --hash=sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2 \ + --hash=sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0 \ + --hash=sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24 \ + --hash=sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7 \ + --hash=sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b \ + --hash=sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372 \ + --hash=sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8 # via # -r requirements/mypy.in # sqlalchemy -mypy-boto3-s3==1.35.42 \ - --hash=sha256:2d040c05d68a161d91c4b72926127034da28ade98d480def6a9569ab7655cddd \ - --hash=sha256:4e47094a43012be18e8ce858d6ec50f6505ba1d5364ca611093d9f736a38e8b2 +mypy-boto3-s3==1.35.46 \ + --hash=sha256:34d19dfba400f5b9bd6b64f09eb8f8eedef60545b410a3753fe99fec0c41ba78 \ + --hash=sha256:f0087a3765d103b2db565cd8065ebc2b0f70f2dd4e92c132f64b8945dd869940 # via # -r requirements/common.in # boto3-stubs @@ -1886,65 +1900,68 @@ openapi-spec-validator==0.7.1 \ --hash=sha256:3c81825043f24ccbcd2f4b149b11e8231abce5ba84f37065e14ec947d8f4e959 \ --hash=sha256:8577b85a8268685da6f8aa30990b83b7960d4d1117e901d451b5d572605e5ec7 # via openapi-core -orjson==3.10.9 \ - --hash=sha256:01f5fef452b4d7615f2e94153479370a4b59e0c964efb32dd902978f807a45cd \ - --hash=sha256:060e020d85d0ec145bc1b536b1fd9c10a0519c91991ead9724d6f759ebe26b9a \ - --hash=sha256:063ca59d93d93d1387f0c4bb766c6d4f5b0e423fe7c366d0bd4401a56d1669d1 \ - --hash=sha256:0a9fc7a6cf2b229ddc323e136df13b3fb4466c50d84ed600cd0898223dd2fea3 \ - --hash=sha256:0ab6e3ad10e964392f0e838751bcce2ef9c8fa8be7deddffff83088e5791566d \ - --hash=sha256:0bf37bf0ca538065c34efe1803378b2dadd7e05b06610a086c2857f15ee59e12 \ - --hash=sha256:0c4f5e0360b7f0aba91dafe12469108109a0e8973956d4a9865ca262a6881406 \ - --hash=sha256:0e492b93e122264c2dc78700859122631a4715bda88fabf57d9226954cfe7ec5 \ - --hash=sha256:12e2efe81356b8448f1cd130f8d75d3718de583112d71f2e2f8baa81bd835bb9 \ - --hash=sha256:1471c3274b1a4a9b8f4b9ed6effaea9ad885796373797515c44b365b375c256d \ - --hash=sha256:1b3069b7e2f57f3eef2282029b9c2ba21f08a55f1018e483663a3356f046af4c \ - --hash=sha256:1c3a1e845916a3739ab4162bb48dee66e0e727a19faf397176a7db0d9826cc3c \ - --hash=sha256:2314846e1029a2d2b899140f350eaaf3a73281df43ba84ac44d94ca861b5b269 \ - --hash=sha256:2920c8754f1aedc98bd357ec172af18ce48f5f1017a92244c85fe41d16d3c6e0 \ - --hash=sha256:2ea7a98f3295ed8adb6730a5788cc78dafea28300d19932a1d2143457f7db802 \ - --hash=sha256:41d8cac575acd15918903d74cfaabb5dbe57b357b93341332f647d1013928dcc \ - --hash=sha256:43ad5560db54331c007dc38be5ba7706cb72974a29ae8227019d89305d750a6f \ - --hash=sha256:485358fe9892d6bfd88e5885b66bf88496e1842c8f35f61682ff9928b12a6cf0 \ - --hash=sha256:68ef65223baab00f469c8698f771ab3e6ccf6af2a987e77de5b566b4ec651150 \ - --hash=sha256:6f130848205fea90a2cb9fa2b11cafff9a9f31f4efad225800bc8b9e4a702f24 \ - --hash=sha256:6fdf8d32b6d94019dc15163542d345e9ce4c4661f56b318608aa3088a1a3a23b \ - --hash=sha256:71f73439999fe662843da3607cdf6e75b1551c330f487e5801d463d969091c63 \ - --hash=sha256:731e8859fc99b398c286320726906404091141e9223dd5e9e6917f7e32e1cc68 \ - --hash=sha256:74f5a7a7f282d326be71b722b0c350da7af6f5f15b9378da177e0e4a09bd91a3 \ - --hash=sha256:75b061c11f5aab979a95927a76394b4a85e3e4d63d0a2a16b56a4f7c6503afab \ - --hash=sha256:77d277fa138d4bf145e8b24042004891c188c52ac8492724a183f42b0031cf0c \ - --hash=sha256:7ae82992c00b480c3cc7dac6739324554be8c5d8e858a90044928506a3333ef4 \ - --hash=sha256:7d9d83a91168aa48309acba804e393b7d9216b66f15e38f339b9fbb00db8986d \ - --hash=sha256:80e0c013e50cf7198319d8137931684eb9f32daa067e8276d9dbdd4010bb4add \ - --hash=sha256:938b7fcd79cf06fe348fb24b6163fbaa2fdc9fbed8b1f06318f24467f1487e63 \ - --hash=sha256:95361c4197c7ce9afdf56255de6f4e2474c39d16a277cce31d1b99a2520486d8 \ - --hash=sha256:9d989152df8f60a76867354e0e08d896292ab9fb96a7ef89a5b3838de174522c \ - --hash=sha256:a04f912c32463386ba117591c99a3d9e40b3b69bed9c5123d89dff06f0f5a4b0 \ - --hash=sha256:a377186a11b48c55969e34f0aa414c2826a234f212d6f2b312ba512e3cdb2c6f \ - --hash=sha256:a4948961b6bce1e2086b2cf0b56cc454cdab589d40c7f85be71fb5a5556c51d3 \ - --hash=sha256:ae82ca347829ca47431767b079f96bb977f592189250ccdede676339a80c8982 \ - --hash=sha256:b4289b5d1f88fd05dcafdd7a1f3b17bb722e77712b7618f98e86bdda560e0a1a \ - --hash=sha256:b61b08f6397f004570fd6a840f4a58946b63b4c7029408cdedb45fe85c7d17f7 \ - --hash=sha256:bdce39f96149a74fddeb2674c54f1da5e57724d32952eb6df2ac719b66d453cc \ - --hash=sha256:bfba9605e85bfd19b83a21c2c25c2bed2000d5f097f3fa3ad5b5f8a7263a3148 \ - --hash=sha256:c378074e0c46035dc66e57006993233ec66bf8487d501bab41649b4b7289ed4d \ - --hash=sha256:c7fa3ff6a0d9d15a0d0d2254cca16cd919156a18423654ce5574591392fe9914 \ - --hash=sha256:ca54e6f320e33c8a6e471c424ee16576361d905c15d69e134c2906d3fcb31795 \ - --hash=sha256:cc32a9e43c7693011ccde6f8eff8cba75ca0d2a55de11092faa4a716101e67f5 \ - --hash=sha256:d11383701d4b58e795039b662ada46987744293d57bfa2719e7379b8d67bc796 \ - --hash=sha256:d6ae1b1733e4528e45675ed09a732b6ac37d716bce2facaf467f84ce774adecd \ - --hash=sha256:e0014038a17a1fe273da0a5489787677ef5a64566ab383ad6d929e44ed5683f4 \ - --hash=sha256:e1e91b90c0c26bd79593967c1adef421bcff88c9e723d49c93bb7ad8af80bc6b \ - --hash=sha256:e29bbf08d907756c145a3a3a1f7ce2f11f15e3edbd3342842589d6030981b76f \ - --hash=sha256:e403429e2947a059545e305d97e4b0eb90d3bb44b396d6f327d7ae2018391e13 \ - --hash=sha256:e9ff9521b5be0340c8e686bcfe2619777fd7583f71e7b494601cc91ad3919d2e \ - --hash=sha256:f11949024f785ace1a516db32fa6255f6227226b2c988abf66f5aee61d43d8f7 \ - --hash=sha256:f3bd9df47385b8fabb3b2ee1e83f9960b8accc1905be971a1c257f16c32b491e \ - --hash=sha256:f52d993504827503411df2d60e60acf52885561458d6273f99ecd172f31c4352 \ - --hash=sha256:f9a9eb03a29c9b30b6c8bb35e5fa20d96589a76e0042005be59b7c3af10a7e43 \ - --hash=sha256:fd5083906825d7f5d23089425ce5424d783d6294020bcabb8518a3e1f97833e5 \ - --hash=sha256:fe91c2259c4a859356b6db1c6e649b40577492f66d483da8b8af6da0f87c00e3 - # via -r requirements/common.in +orjson==3.10.10 \ + --hash=sha256:019481fa9ea5ff13b5d5d95e6fd5ab25ded0810c80b150c2c7b1cc8660b662a7 \ + --hash=sha256:081b3fc6a86d72efeb67c13d0ea7c030017bd95f9868b1e329a376edc456153b \ + --hash=sha256:0c25908eb86968613216f3db4d3003f1c45d78eb9046b71056ca327ff92bdbd4 \ + --hash=sha256:0dd57eff09894938b4c86d4b871a479260f9e156fa7f12f8cad4b39ea8028bb5 \ + --hash=sha256:1dcbb0ca5fafb2b378b2c74419480ab2486326974826bbf6588f4dc62137570a \ + --hash=sha256:218cb0bc03340144b6328a9ff78f0932e642199ac184dd74b01ad691f42f93ff \ + --hash=sha256:23458d31fa50ec18e0ec4b0b4343730928296b11111df5f547c75913714116b2 \ + --hash=sha256:23776265c5215ec532de6238a52707048401a568f0fa0d938008e92a147fe2c7 \ + --hash=sha256:24ac62336da9bda1bd93c0491eff0613003b48d3cb5d01470842e7b52a40d5b4 \ + --hash=sha256:2787cd9dedc591c989f3facd7e3e86508eafdc9536a26ec277699c0aa63c685b \ + --hash=sha256:37949383c4df7b4337ce82ee35b6d7471e55195efa7dcb45ab8226ceadb0fe3b \ + --hash=sha256:384cd13579a1b4cd689d218e329f459eb9ddc504fa48c5a83ef4889db7fd7a4f \ + --hash=sha256:3b2625cb37b8fb42e2147404e5ff7ef08712099197a9cd38895006d7053e69d6 \ + --hash=sha256:44bffae68c291f94ff5a9b4149fe9d1bdd4cd0ff0fb575bcea8351d48db629a1 \ + --hash=sha256:5a059afddbaa6dd733b5a2d76a90dbc8af790b993b1b5cb97a1176ca713b5df8 \ + --hash=sha256:6514449d2c202a75183f807bc755167713297c69f1db57a89a1ef4a0170ee269 \ + --hash=sha256:65f9886d3bae65be026219c0a5f32dbbe91a9e6272f56d092ab22561ad0ea33b \ + --hash=sha256:672f9874a8a8fb9bb1b771331d31ba27f57702c8106cdbadad8bda5d10bc1019 \ + --hash=sha256:68b65c93617bcafa7f04b74ae8bc2cc214bd5cb45168a953256ff83015c6747d \ + --hash=sha256:6f9b5c59f7e2a1a410f971c5ebc68f1995822837cd10905ee255f96074537ee6 \ + --hash=sha256:730ed5350147db7beb23ddaf072f490329e90a1d059711d364b49fe352ec987b \ + --hash=sha256:75c38f5647e02d423807d252ce4528bf6a95bd776af999cb1fb48867ed01d1f6 \ + --hash=sha256:766f21487a53aee8524b97ca9582d5c6541b03ab6210fbaf10142ae2f3ced2aa \ + --hash=sha256:78bee66a988f1a333dc0b6257503d63553b1957889c17b2c4ed72385cd1b96ae \ + --hash=sha256:7948cfb909353fce2135dcdbe4521a5e7e1159484e0bb024c1722f272488f2b8 \ + --hash=sha256:804b18e2b88022c8905bb79bd2cbe59c0cd014b9328f43da8d3b28441995cda4 \ + --hash=sha256:829700cc18503efc0cf502d630f612884258020d98a317679cd2054af0259568 \ + --hash=sha256:848ea3b55ab5ccc9d7bbd420d69432628b691fba3ca8ae3148c35156cbd282aa \ + --hash=sha256:8564f48f3620861f5ef1e080ce7cd122ee89d7d6dacf25fcae675ff63b4d6e05 \ + --hash=sha256:879e99486c0fbb256266c7c6a67ff84f46035e4f8749ac6317cc83dacd7f993a \ + --hash=sha256:8cc2a654c08755cef90b468ff17c102e2def0edd62898b2486767204a7f5cc9c \ + --hash=sha256:9972572a1d042ec9ee421b6da69f7cc823da5962237563fa548ab17f152f0b9b \ + --hash=sha256:a12f2003695b10817f0fa8b8fca982ed7f5761dcb0d93cff4f2f9f6709903fd7 \ + --hash=sha256:a8f4bf5f1c85bea2170800020d53a8877812892697f9c2de73d576c9307a8a5f \ + --hash=sha256:aaf29ce0bb5d3320824ec3d1508652421000ba466abd63bdd52c64bcce9eb1fa \ + --hash=sha256:b3be81c42f1242cbed03cbb3973501fcaa2675a0af638f8be494eaf37143d999 \ + --hash=sha256:b788a579b113acf1c57e0a68e558be71d5d09aa67f62ca1f68e01117e550a998 \ + --hash=sha256:bca84df16d6b49325a4084fd8b2fe2229cb415e15c46c529f868c3387bb1339d \ + --hash=sha256:c14ce70e8f39bd71f9f80423801b5d10bf93d1dceffdecd04df0f64d2c69bc01 \ + --hash=sha256:c5bf161a32b479034098c5b81f2608f09167ad2fa1c06abd4e527ea6bf4837a9 \ + --hash=sha256:d5ef198bafdef4aa9d49a4165ba53ffdc0a9e1c7b6f76178572ab33118afea25 \ + --hash=sha256:d78e4cacced5781b01d9bc0f0cd8b70b906a0e109825cb41c1b03f9c41e4ce86 \ + --hash=sha256:d9bbd3a4b92256875cb058c3381b782649b9a3c68a4aa9a2fff020c2f9cfc1be \ + --hash=sha256:dbde6d70cd95ab4d11ea8ac5e738e30764e510fc54d777336eec09bb93b8576c \ + --hash=sha256:dbf3c20c6a7db69df58672a0d5815647ecf78c8e62a4d9bd284e8621c1fe5ccb \ + --hash=sha256:dc6993ab1c2ae7dd0711161e303f1db69062955ac2668181bfdf2dd410e65258 \ + --hash=sha256:dddd5516bcc93e723d029c1633ae79c4417477b4f57dad9bfeeb6bc0315e654a \ + --hash=sha256:e0ceb5e0e8c4f010ac787d29ae6299846935044686509e2f0f06ed441c1ca949 \ + --hash=sha256:e2277ec2cea3775640dc81ab5195bb5b2ada2fe0ea6eee4677474edc75ea6785 \ + --hash=sha256:e27b4c6437315df3024f0835887127dac2a0a3ff643500ec27088d2588fa5ae1 \ + --hash=sha256:e3e67b537ac0c835b25b5f7d40d83816abd2d3f4c0b0866ee981a045287a54f3 \ + --hash=sha256:e4d0d9fe174cc7a5bdce2e6c378bcdb4c49b2bf522a8f996aa586020e1b96cee \ + --hash=sha256:e6eb2598df518281ba0cbc30d24c5b06124ccf7e19169e883c14e0831217a0bc \ + --hash=sha256:e8e28406f97fc2ea0c6150f4c1b6e8261453318930b334abc419214c82314f85 \ + --hash=sha256:eb0a42831372ec2b05acc9ee45af77bcaccbd91257345f93780a8e654efc75db \ + --hash=sha256:f0c4f37f8bf3f1075c6cc8dd8a9f843689a4b618628f8812d0a71e6968b95ffd \ + --hash=sha256:f1d647ca8d62afeb774340a343c7fc023efacfd3a39f70c798991063f0c681dd \ + --hash=sha256:ff38c5fb749347768a603be1fb8a31856458af839f31f064c5aa74aca5be9efe + # via + # -r requirements/common.in + # mypy packaging==24.1 \ --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 @@ -1988,9 +2005,9 @@ pexpect==4.9.0 \ --hash=sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523 \ --hash=sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f # via ipython -phonenumberslite==8.13.47 \ - --hash=sha256:9a4d040f4ef9ea5cbbd907f6fe9a52313d46191051e3a9994102c05082a9db67 \ - --hash=sha256:baf770804c056a122c76f0d29d3a85bd3111c511c5350548e1c3355449b824e9 +phonenumberslite==8.13.48 \ + --hash=sha256:9548bc4c3a7c4d67f7945ba0286e9a37c3ee4ea5531f7ea2518d1cbdc857f50f \ + --hash=sha256:db060fd07421306f8fbc4332ca16b8785df0fa70371d3ed632519546c1a11274 # via django-two-factor-auth pika==1.3.2 \ --hash=sha256:0779a7c1fafd805672796085560d290213a465e4f6f76a6fb19e378d8041a14f \ @@ -2120,24 +2137,24 @@ protego==0.3.1 \ --hash=sha256:2fbe8e9b7a7dbc5016a932b14c98d236aad4c29290bbe457b8d2779666ef7a41 \ --hash=sha256:e94430d0d25cbbf239bc849d86c5e544fbde531fcccfa059953c7da344a1712c # via scrapy -proto-plus==1.24.0 \ - --hash=sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445 \ - --hash=sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12 +proto-plus==1.25.0 \ + --hash=sha256:c91fc4a65074ade8e458e95ef8bac34d4008daa7cce4a12d6707066fca648961 \ + --hash=sha256:fbb17f57f7bd05a68b7707e745e26528b0b3c34e378db91eef93912c54982d91 # via # google-api-core # google-cloud-firestore -protobuf==5.28.2 \ - --hash=sha256:2c69461a7fcc8e24be697624c09a839976d82ae75062b11a0972e41fd2cd9132 \ - --hash=sha256:35cfcb15f213449af7ff6198d6eb5f739c37d7e4f1c09b5d0641babf2cc0c68f \ - --hash=sha256:52235802093bd8a2811abbe8bf0ab9c5f54cca0a751fdd3f6ac2a21438bffece \ - --hash=sha256:59379674ff119717404f7454647913787034f03fe7049cbef1d74a97bb4593f0 \ - --hash=sha256:5e8a95246d581eef20471b5d5ba010d55f66740942b95ba9b872d918c459452f \ - --hash=sha256:87317e9bcda04a32f2ee82089a204d3a2f0d3c8aeed16568c7daf4756e4f1fe0 \ - --hash=sha256:8ddc60bf374785fb7cb12510b267f59067fa10087325b8e1855b898a0d81d276 \ - --hash=sha256:a8b9403fc70764b08d2f593ce44f1d2920c5077bf7d311fefec999f8c40f78b7 \ - --hash=sha256:c0ea0123dac3399a2eeb1a1443d82b7afc9ff40241433296769f7da42d142ec3 \ - --hash=sha256:ca53faf29896c526863366a52a8f4d88e69cd04ec9571ed6082fa117fac3ab36 \ - --hash=sha256:eeea10f3dc0ac7e6b4933d32db20662902b4ab81bf28df12218aa389e9c2102d +protobuf==5.28.3 \ + --hash=sha256:0c4eec6f987338617072592b97943fdbe30d019c56126493111cf24344c1cc24 \ + --hash=sha256:135658402f71bbd49500322c0f736145731b16fc79dc8f367ab544a17eab4535 \ + --hash=sha256:27b246b3723692bf1068d5734ddaf2fccc2cdd6e0c9b47fe099244d80200593b \ + --hash=sha256:3e6101d095dfd119513cde7259aa703d16c6bbdfae2554dfe5cfdbe94e32d548 \ + --hash=sha256:3fa2de6b8b29d12c61911505d893afe7320ce7ccba4df913e2971461fa36d584 \ + --hash=sha256:64badbc49180a5e401f373f9ce7ab1d18b63f7dd4a9cdc43c92b9f0b481cef7b \ + --hash=sha256:70585a70fc2dd4818c51287ceef5bdba6387f88a578c86d47bb34669b5552c36 \ + --hash=sha256:712319fbdddb46f21abb66cd33cb9e491a5763b2febd8f228251add221981135 \ + --hash=sha256:91fba8f445723fcf400fdbe9ca796b19d3b1242cd873907979b9ed71e4afe868 \ + --hash=sha256:a3f6857551e53ce35e60b403b8a27b0295f7d6eb63d10484f12bc6879c715687 \ + --hash=sha256:cee1757663fa32a1ee673434fcf3bf24dd54763c79690201208bafec62f19eed # via # google-api-core # google-cloud-firestore @@ -2442,18 +2459,18 @@ pypng==0.20220715.0 \ --hash=sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c \ --hash=sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1 # via qrcode -pyre-check==0.9.22 \ - --hash=sha256:4bbd61dad5669dfef00e875bf8a573866595ecbd8240f595339a9781e8a1e22e \ - --hash=sha256:d331e2687e194fa22505e0724b1536e61bf06fddc5416d7b83d542d2270c91ce \ - --hash=sha256:e082f926dff71661959535c3936fca5ad40a44858b5fd3e99009a616a1b57083 +pyre-check==0.9.23 \ + --hash=sha256:3f4baf99145e06af416a2444e50b9e90b183585c053ab476004729ed9ba6902c \ + --hash=sha256:6362f0d8af2d513c90fc863a142009d8d7cbf0aa762ec37cad194684bd962ae5 \ + --hash=sha256:71ae076a75293a6fbb9025c3aa1e7a81a4dfd7a6da8a884f4c39deed2e4e3f3a # via -r requirements/dev.in pyre-extensions==0.0.31 \ --hash=sha256:5f89e0c786afdd4dc15753b2318975b02789ea997daa69ab40ff5ba4f438a8a0 \ --hash=sha256:945806dd33027856cf6e41c9c4adacffab2b56993f8762ff4ea7826f95db0481 # via pyre-check -python-binary-memcached==0.31.2 \ - --hash=sha256:290f70451e277df6a39aa0eea3cb6ca2eefcf5d601f957cf2ec1d353d7676c03 \ - --hash=sha256:e5b93d54429e835cab7d5b33988649f9748344aa49adaed8eed94b37e714d562 +python-binary-memcached==0.31.3 \ + --hash=sha256:aa7c7e1a8ef27a3e89f5f1823e04c58d891ef824ce8ca26ba683be5460f17c0a \ + --hash=sha256:e173091db6d2d7d244218ad2732edb4b99988de0894cb544d7b94dbedf2ca416 # via # -r requirements/common.in # django-bmemcached @@ -2574,9 +2591,9 @@ queuelib==1.7.0 \ --hash=sha256:2855162096cf0230510890b354379ea1c0ff19d105d3147d349d2433bb222b08 \ --hash=sha256:b07aaa2410caac3a0021ee4f4026acdac992b0fb9a2cbeb34a918617df3c12a7 # via scrapy -redis==5.1.1 \ - --hash=sha256:f6c997521fedbae53387307c5d0bf784d9acc28d9f1d058abeac566ec4dbed72 \ - --hash=sha256:f8ea06b7482a668c6475ae202ed8d9bcaa409f6e87fb77ed1043d912afd62e24 +redis==5.2.0 \ + --hash=sha256:0b1087665a771b1ff2e003aa5bdd354f15a70c9e25d5a7dbf9c722c16528a7b0 \ + --hash=sha256:ae174f2bb3b1bf2b09d54bf3e51fbc1469cf6c10aa03e21141f51969801a7897 # via -r requirements/common.in referencing==0.35.1 \ --hash=sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c \ @@ -2728,114 +2745,114 @@ rfc3339-validator==0.1.4 \ --hash=sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b \ --hash=sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa # via openapi-schema-validator -rich==13.9.2 \ - --hash=sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c \ - --hash=sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1 +rich==13.9.3 \ + --hash=sha256:9836f5096eb2172c9e77df411c1b009bace4193d6a481d534fea75ebba758283 \ + --hash=sha256:bc1e01b899537598cf02579d2b9f4a415104d3fc439313a7a2c165d76557a08e # via semgrep -rpds-py==0.20.0 \ - --hash=sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c \ - --hash=sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585 \ - --hash=sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5 \ - --hash=sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6 \ - --hash=sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef \ - --hash=sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2 \ - --hash=sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29 \ - --hash=sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318 \ - --hash=sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b \ - --hash=sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399 \ - --hash=sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739 \ - --hash=sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee \ - --hash=sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174 \ - --hash=sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a \ - --hash=sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344 \ - --hash=sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2 \ - --hash=sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03 \ - --hash=sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5 \ - --hash=sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22 \ - --hash=sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e \ - --hash=sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96 \ - --hash=sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91 \ - --hash=sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752 \ - --hash=sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075 \ - --hash=sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253 \ - --hash=sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee \ - --hash=sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad \ - --hash=sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5 \ - --hash=sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce \ - --hash=sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7 \ - --hash=sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b \ - --hash=sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8 \ - --hash=sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57 \ - --hash=sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3 \ - --hash=sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec \ - --hash=sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209 \ - --hash=sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921 \ - --hash=sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045 \ - --hash=sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074 \ - --hash=sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580 \ - --hash=sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7 \ - --hash=sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5 \ - --hash=sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3 \ - --hash=sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0 \ - --hash=sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24 \ - --hash=sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139 \ - --hash=sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db \ - --hash=sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc \ - --hash=sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789 \ - --hash=sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f \ - --hash=sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2 \ - --hash=sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c \ - --hash=sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232 \ - --hash=sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6 \ - --hash=sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c \ - --hash=sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29 \ - --hash=sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489 \ - --hash=sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94 \ - --hash=sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751 \ - --hash=sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2 \ - --hash=sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda \ - --hash=sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9 \ - --hash=sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51 \ - --hash=sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c \ - --hash=sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8 \ - --hash=sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989 \ - --hash=sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511 \ - --hash=sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1 \ - --hash=sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2 \ - --hash=sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150 \ - --hash=sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c \ - --hash=sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965 \ - --hash=sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f \ - --hash=sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58 \ - --hash=sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b \ - --hash=sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f \ - --hash=sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d \ - --hash=sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821 \ - --hash=sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de \ - --hash=sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121 \ - --hash=sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855 \ - --hash=sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272 \ - --hash=sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60 \ - --hash=sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02 \ - --hash=sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1 \ - --hash=sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140 \ - --hash=sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879 \ - --hash=sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940 \ - --hash=sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364 \ - --hash=sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4 \ - --hash=sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e \ - --hash=sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420 \ - --hash=sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5 \ - --hash=sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24 \ - --hash=sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c \ - --hash=sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf \ - --hash=sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f \ - --hash=sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e \ - --hash=sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab \ - --hash=sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08 \ - --hash=sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92 \ - --hash=sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a \ - --hash=sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8 +rpds-py==0.20.1 \ + --hash=sha256:02a0629ec053fc013808a85178524e3cb63a61dbc35b22499870194a63578fb9 \ + --hash=sha256:07924c1b938798797d60c6308fa8ad3b3f0201802f82e4a2c41bb3fafb44cc28 \ + --hash=sha256:07f59760ef99f31422c49038964b31c4dfcfeb5d2384ebfc71058a7c9adae2d2 \ + --hash=sha256:0a3a1e9ee9728b2c1734f65d6a1d376c6f2f6fdcc13bb007a08cc4b1ff576dc5 \ + --hash=sha256:0a90c373ea2975519b58dece25853dbcb9779b05cc46b4819cb1917e3b3215b6 \ + --hash=sha256:0ad56edabcdb428c2e33bbf24f255fe2b43253b7d13a2cdbf05de955217313e6 \ + --hash=sha256:0b581f47257a9fce535c4567782a8976002d6b8afa2c39ff616edf87cbeff712 \ + --hash=sha256:0f8f741b6292c86059ed175d80eefa80997125b7c478fb8769fd9ac8943a16c0 \ + --hash=sha256:0fc212779bf8411667234b3cdd34d53de6c2b8b8b958e1e12cb473a5f367c338 \ + --hash=sha256:13c56de6518e14b9bf6edde23c4c39dac5b48dcf04160ea7bce8fca8397cdf86 \ + --hash=sha256:142c0a5124d9bd0e2976089484af5c74f47bd3298f2ed651ef54ea728d2ea42c \ + --hash=sha256:14511a539afee6f9ab492b543060c7491c99924314977a55c98bfa2ee29ce78c \ + --hash=sha256:15a842bb369e00295392e7ce192de9dcbf136954614124a667f9f9f17d6a216f \ + --hash=sha256:16d4477bcb9fbbd7b5b0e4a5d9b493e42026c0bf1f06f723a9353f5153e75d30 \ + --hash=sha256:1791ff70bc975b098fe6ecf04356a10e9e2bd7dc21fa7351c1742fdeb9b4966f \ + --hash=sha256:19b73643c802f4eaf13d97f7855d0fb527fbc92ab7013c4ad0e13a6ae0ed23bd \ + --hash=sha256:200a23239781f46149e6a415f1e870c5ef1e712939fe8fa63035cd053ac2638e \ + --hash=sha256:2249280b870e6a42c0d972339e9cc22ee98730a99cd7f2f727549af80dd5a963 \ + --hash=sha256:2b431c777c9653e569986ecf69ff4a5dba281cded16043d348bf9ba505486f36 \ + --hash=sha256:2cc3712a4b0b76a1d45a9302dd2f53ff339614b1c29603a911318f2357b04dd2 \ + --hash=sha256:2fbb0ffc754490aff6dabbf28064be47f0f9ca0b9755976f945214965b3ace7e \ + --hash=sha256:32b922e13d4c0080d03e7b62991ad7f5007d9cd74e239c4b16bc85ae8b70252d \ + --hash=sha256:36785be22066966a27348444b40389f8444671630063edfb1a2eb04318721e17 \ + --hash=sha256:37fe0f12aebb6a0e3e17bb4cd356b1286d2d18d2e93b2d39fe647138458b4bcb \ + --hash=sha256:3aea7eed3e55119635a74bbeb80b35e776bafccb70d97e8ff838816c124539f1 \ + --hash=sha256:3c6afcf2338e7f374e8edc765c79fbcb4061d02b15dd5f8f314a4af2bdc7feb5 \ + --hash=sha256:3ccb8ac2d3c71cda472b75af42818981bdacf48d2e21c36331b50b4f16930163 \ + --hash=sha256:3d089d0b88996df627693639d123c8158cff41c0651f646cd8fd292c7da90eaf \ + --hash=sha256:3dd645e2b0dcb0fd05bf58e2e54c13875847687d0b71941ad2e757e5d89d4356 \ + --hash=sha256:3e310838a5801795207c66c73ea903deda321e6146d6f282e85fa7e3e4854804 \ + --hash=sha256:42cbde7789f5c0bcd6816cb29808e36c01b960fb5d29f11e052215aa85497c93 \ + --hash=sha256:483b29f6f7ffa6af845107d4efe2e3fa8fb2693de8657bc1849f674296ff6a5a \ + --hash=sha256:4888e117dd41b9d34194d9e31631af70d3d526efc363085e3089ab1a62c32ed1 \ + --hash=sha256:49fe9b04b6fa685bd39237d45fad89ba19e9163a1ccaa16611a812e682913496 \ + --hash=sha256:4a5a844f68776a7715ecb30843b453f07ac89bad393431efbf7accca3ef599c1 \ + --hash=sha256:4a916087371afd9648e1962e67403c53f9c49ca47b9680adbeef79da3a7811b0 \ + --hash=sha256:4f676e21db2f8c72ff0936f895271e7a700aa1f8d31b40e4e43442ba94973899 \ + --hash=sha256:518d2ca43c358929bf08f9079b617f1c2ca6e8848f83c1225c88caeac46e6cbc \ + --hash=sha256:5265505b3d61a0f56618c9b941dc54dc334dc6e660f1592d112cd103d914a6db \ + --hash=sha256:55cd1fa4ecfa6d9f14fbd97ac24803e6f73e897c738f771a9fe038f2f11ff07c \ + --hash=sha256:58b1d5dd591973d426cbb2da5e27ba0339209832b2f3315928c9790e13f159e8 \ + --hash=sha256:59240685e7da61fb78f65a9f07f8108e36a83317c53f7b276b4175dc44151684 \ + --hash=sha256:5b48e790e0355865197ad0aca8cde3d8ede347831e1959e158369eb3493d2191 \ + --hash=sha256:5d4eea0761e37485c9b81400437adb11c40e13ef513375bbd6973e34100aeb06 \ + --hash=sha256:648386ddd1e19b4a6abab69139b002bc49ebf065b596119f8f37c38e9ecee8ff \ + --hash=sha256:653647b8838cf83b2e7e6a0364f49af96deec64d2a6578324db58380cff82aca \ + --hash=sha256:6740a3e8d43a32629bb9b009017ea5b9e713b7210ba48ac8d4cb6d99d86c8ee8 \ + --hash=sha256:6889469bfdc1eddf489729b471303739bf04555bb151fe8875931f8564309afc \ + --hash=sha256:68cb0a499f2c4a088fd2f521453e22ed3527154136a855c62e148b7883b99f9a \ + --hash=sha256:6aa97af1558a9bef4025f8f5d8c60d712e0a3b13a2fe875511defc6ee77a1ab7 \ + --hash=sha256:6b73c67850ca7cae0f6c56f71e356d7e9fa25958d3e18a64927c2d930859b8e4 \ + --hash=sha256:6c8e9340ce5a52f95fa7d3b552b35c7e8f3874d74a03a8a69279fd5fca5dc751 \ + --hash=sha256:6ca91093a4a8da4afae7fe6a222c3b53ee4eef433ebfee4d54978a103435159e \ + --hash=sha256:754bbed1a4ca48479e9d4182a561d001bbf81543876cdded6f695ec3d465846b \ + --hash=sha256:762703bdd2b30983c1d9e62b4c88664df4a8a4d5ec0e9253b0231171f18f6d75 \ + --hash=sha256:78f0b6877bfce7a3d1ff150391354a410c55d3cdce386f862926a4958ad5ab7e \ + --hash=sha256:7a07ced2b22f0cf0b55a6a510078174c31b6d8544f3bc00c2bcee52b3d613f74 \ + --hash=sha256:7dca7081e9a0c3b6490a145593f6fe3173a94197f2cb9891183ef75e9d64c425 \ + --hash=sha256:7e21b7031e17c6b0e445f42ccc77f79a97e2687023c5746bfb7a9e45e0921b84 \ + --hash=sha256:7f5179583d7a6cdb981151dd349786cbc318bab54963a192692d945dd3f6435d \ + --hash=sha256:83cba698cfb3c2c5a7c3c6bac12fe6c6a51aae69513726be6411076185a8b24a \ + --hash=sha256:842c19a6ce894493563c3bd00d81d5100e8e57d70209e84d5491940fdb8b9e3a \ + --hash=sha256:84b8382a90539910b53a6307f7c35697bc7e6ffb25d9c1d4e998a13e842a5e83 \ + --hash=sha256:8ba6f89cac95c0900d932c9efb7f0fb6ca47f6687feec41abcb1bd5e2bd45535 \ + --hash=sha256:8bbe951244a838a51289ee53a6bae3a07f26d4e179b96fc7ddd3301caf0518eb \ + --hash=sha256:925d176a549f4832c6f69fa6026071294ab5910e82a0fe6c6228fce17b0706bd \ + --hash=sha256:92b68b79c0da2a980b1c4197e56ac3dd0c8a149b4603747c4378914a68706979 \ + --hash=sha256:93da1d3db08a827eda74356f9f58884adb254e59b6664f64cc04cdff2cc19b0d \ + --hash=sha256:95f3b65d2392e1c5cec27cff08fdc0080270d5a1a4b2ea1d51d5f4a2620ff08d \ + --hash=sha256:9c4cb04a16b0f199a8c9bf807269b2f63b7b5b11425e4a6bd44bd6961d28282c \ + --hash=sha256:a624cc00ef2158e04188df5e3016385b9353638139a06fb77057b3498f794782 \ + --hash=sha256:a649dfd735fff086e8a9d0503a9f0c7d01b7912a333c7ae77e1515c08c146dad \ + --hash=sha256:a94e52537a0e0a85429eda9e49f272ada715506d3b2431f64b8a3e34eb5f3e75 \ + --hash=sha256:aa7ac11e294304e615b43f8c441fee5d40094275ed7311f3420d805fde9b07b4 \ + --hash=sha256:b41b6321805c472f66990c2849e152aff7bc359eb92f781e3f606609eac877ad \ + --hash=sha256:b71b8666eeea69d6363248822078c075bac6ed135faa9216aa85f295ff009b1e \ + --hash=sha256:b9c2fe36d1f758b28121bef29ed1dee9b7a2453e997528e7d1ac99b94892527c \ + --hash=sha256:bb63804105143c7e24cee7db89e37cb3f3941f8e80c4379a0b355c52a52b6780 \ + --hash=sha256:be5ef2f1fc586a7372bfc355986226484e06d1dc4f9402539872c8bb99e34b01 \ + --hash=sha256:c142b88039b92e7e0cb2552e8967077e3179b22359e945574f5e2764c3953dcf \ + --hash=sha256:c14937af98c4cc362a1d4374806204dd51b1e12dded1ae30645c298e5a5c4cb1 \ + --hash=sha256:ca449520e7484534a2a44faf629362cae62b660601432d04c482283c47eaebab \ + --hash=sha256:cd945871335a639275eee904caef90041568ce3b42f402c6959b460d25ae8732 \ + --hash=sha256:d0b937b2a1988f184a3e9e577adaa8aede21ec0b38320d6009e02bd026db04fa \ + --hash=sha256:d126b52e4a473d40232ec2052a8b232270ed1f8c9571aaf33f73a14cc298c24f \ + --hash=sha256:d8761c3c891cc51e90bc9926d6d2f59b27beaf86c74622c8979380a29cc23ac3 \ + --hash=sha256:d9ecb51120de61e4604650666d1f2b68444d46ae18fd492245a08f53ad2b7711 \ + --hash=sha256:da584ff96ec95e97925174eb8237e32f626e7a1a97888cdd27ee2f1f24dd0ad8 \ + --hash=sha256:dbcf360c9e3399b056a238523146ea77eeb2a596ce263b8814c900263e46031a \ + --hash=sha256:dbddc10776ca7ebf2a299c41a4dde8ea0d8e3547bfd731cb87af2e8f5bf8962d \ + --hash=sha256:dc73505153798c6f74854aba69cc75953888cf9866465196889c7cdd351e720c \ + --hash=sha256:e13de156137b7095442b288e72f33503a469aa1980ed856b43c353ac86390519 \ + --hash=sha256:e1791c4aabd117653530dccd24108fa03cc6baf21f58b950d0a73c3b3b29a350 \ + --hash=sha256:e75ba609dba23f2c95b776efb9dd3f0b78a76a151e96f96cc5b6b1b0004de66f \ + --hash=sha256:e79059d67bea28b53d255c1437b25391653263f0e69cd7dec170d778fdbca95e \ + --hash=sha256:ecd27a66740ffd621d20b9a2f2b5ee4129a56e27bfb9458a3bcc2e45794c96cb \ + --hash=sha256:f009c69bc8c53db5dfab72ac760895dc1f2bc1b62ab7408b253c8d1ec52459fc \ + --hash=sha256:f16bc1334853e91ddaaa1217045dd7be166170beec337576818461268a3de67f \ + --hash=sha256:f19169781dddae7478a32301b499b2858bc52fc45a112955e798ee307e294977 \ + --hash=sha256:fa3060d885657abc549b2a0f8e1b79699290e5d83845141717c6c90c2df38311 \ + --hash=sha256:fa41a64ac5b08b292906e248549ab48b69c5428f3987b09689ab2441f267d04d \ + --hash=sha256:fbf15aff64a163db29a91ed0868af181d6f68ec1a3a7d5afcfe4501252840bad \ + --hash=sha256:fe00a9057d100e69b4ae4a094203a708d65b0f345ed546fdef86498bf5390982 # via # jsonschema # referencing @@ -2853,6 +2870,9 @@ ruamel.yaml.clib==0.2.12 \ --hash=sha256:0b7e75b4965e1d4690e93021adfcecccbca7d61c7bddd8e22406ef2ff20d74ef \ --hash=sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5 \ --hash=sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632 \ + --hash=sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6 \ + --hash=sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680 \ + --hash=sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf \ --hash=sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da \ --hash=sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6 \ --hash=sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a \ @@ -2860,7 +2880,9 @@ ruamel.yaml.clib==0.2.12 \ --hash=sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6 \ --hash=sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f \ --hash=sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd \ + --hash=sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2 \ --hash=sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52 \ + --hash=sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd \ --hash=sha256:943f32bc9dedb3abff9879edc134901df92cfce2c3d5c9348f172f62eb2d771d \ --hash=sha256:95c3829bb364fdb8e0332c9931ecf57d9be3519241323c5274bd82f709cebc0c \ --hash=sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6 \ @@ -2871,7 +2893,12 @@ ruamel.yaml.clib==0.2.12 \ --hash=sha256:bc5f1e1c28e966d61d2519f2a3d451ba989f9ea0f2307de7bc45baa526de9e45 \ --hash=sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4 \ --hash=sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12 \ + --hash=sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31 \ + --hash=sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642 \ --hash=sha256:d84318609196d6bd6da0edfa25cedfbabd8dbde5140a0a23af29ad4b8f91fb1e \ + --hash=sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285 \ + --hash=sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed \ + --hash=sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1 \ --hash=sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7 \ --hash=sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3 \ --hash=sha256:e7e3736715fbf53e9be2a79eb4db68e4ed857017344d697e8b9749444ae57475 \ @@ -2880,25 +2907,25 @@ ruamel.yaml.clib==0.2.12 \ --hash=sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987 \ --hash=sha256:fd5415dded15c3822597455bc02bcd66e81ef8b7a48cb71a33628fc9fdde39df # via ruamel.yaml -ruff==0.7.0 \ - --hash=sha256:0cdf20c2b6ff98e37df47b2b0bd3a34aaa155f59a11182c1303cce79be715628 \ - --hash=sha256:10842f69c245e78d6adec7e1db0a7d9ddc2fff0621d730e61657b64fa36f207e \ - --hash=sha256:194d6c46c98c73949a106425ed40a576f52291c12bc21399eb8f13a0f7073495 \ - --hash=sha256:1eb54986f770f49edb14f71d33312d79e00e629a57387382200b1ef12d6a4ef9 \ - --hash=sha256:211d877674e9373d4bb0f1c80f97a0201c61bcd1e9d045b6e9726adc42c156aa \ - --hash=sha256:214b88498684e20b6b2b8852c01d50f0651f3cc6118dfa113b4def9f14faaf06 \ - --hash=sha256:47a86360cf62d9cd53ebfb0b5eb0e882193fc191c6d717e8bef4462bc3b9ea2b \ - --hash=sha256:496494d350c7fdeb36ca4ef1c9f21d80d182423718782222c29b3e72b3512737 \ - --hash=sha256:4b406c2dce5be9bad59f2de26139a86017a517e6bcd2688da515481c05a2cb11 \ - --hash=sha256:630fce3fefe9844e91ea5bbf7ceadab4f9981f42b704fae011bb8efcaf5d84be \ - --hash=sha256:82c2579b82b9973a110fab281860403b397c08c403de92de19568f32f7178598 \ - --hash=sha256:9af971fe85dcd5eaed8f585ddbc6bdbe8c217fb8fcf510ea6bca5bdfff56040e \ - --hash=sha256:ab7d98c7eed355166f367597e513a6c82408df4181a937628dbec79abb2a1fe4 \ - --hash=sha256:b641c7f16939b7d24b7bfc0be4102c56562a18281f84f635604e8a6989948914 \ - --hash=sha256:d71672336e46b34e0c90a790afeac8a31954fd42872c1f6adaea1dff76fd44f9 \ - --hash=sha256:dc452ba6f2bb9cf8726a84aa877061a2462afe9ae0ea1d411c53d226661c601d \ - --hash=sha256:f6c968509f767776f524a8430426539587d5ec5c662f6addb6aa25bc2e8195ec \ - --hash=sha256:ff4aabfbaaba880e85d394603b9e75d32b0693152e16fa659a3064a85df7fce2 +ruff==0.7.1 \ + --hash=sha256:19aa200ec824c0f36d0c9114c8ec0087082021732979a359d6f3c390a6ff2a37 \ + --hash=sha256:27c1c52a8d199a257ff1e5582d078eab7145129aa02721815ca8fa4f9612dc35 \ + --hash=sha256:32f1e8a192e261366c702c5fb2ece9f68d26625f198a25c408861c16dc2dea9c \ + --hash=sha256:344cc2b0814047dc8c3a8ff2cd1f3d808bb23c6658db830d25147339d9bf9ea7 \ + --hash=sha256:4316bbf69d5a859cc937890c7ac7a6551252b6a01b1d2c97e8fc96e45a7c8b4a \ + --hash=sha256:56aad830af8a9db644e80098fe4984a948e2b6fc2e73891538f43bbe478461b8 \ + --hash=sha256:588a34e1ef2ea55b4ddfec26bbe76bc866e92523d8c6cdec5e8aceefeff02d99 \ + --hash=sha256:658304f02f68d3a83c998ad8bf91f9b4f53e93e5412b8f2388359d55869727fd \ + --hash=sha256:699085bf05819588551b11751eff33e9ca58b1b86a6843e1b082a7de40da1565 \ + --hash=sha256:79d3af9dca4c56043e738a4d6dd1e9444b6d6c10598ac52d146e331eb155a8ad \ + --hash=sha256:8422104078324ea250886954e48f1373a8fe7de59283d747c3a7eca050b4e378 \ + --hash=sha256:94fc32f9cdf72dc75c451e5f072758b118ab8100727168a3df58502b43a599ca \ + --hash=sha256:985818742b833bffa543a84d1cc11b5e6871de1b4e0ac3060a59a2bae3969250 \ + --hash=sha256:9d8a41d4aa2dad1575adb98a82870cf5db5f76b2938cf2206c22c940034a36f4 \ + --hash=sha256:b517a2011333eb7ce2d402652ecaa0ac1a30c114fbbd55c6b8ee466a7f600ee9 \ + --hash=sha256:c5c121b46abde94a505175524e51891f829414e093cd8326d6e741ecfc0a9112 \ + --hash=sha256:cb1bc5ed9403daa7da05475d615739cc0212e861b7306f314379d958592aaa89 \ + --hash=sha256:f38c41fcde1728736b4eb2b18850f6d1e3eedd9678c914dede554a70d5241307 # via -r requirements/dev.in s3transfer==0.10.3 \ --hash=sha256:263ed587a5803c6c708d3ce44dc4dfedaab4c1a32e8329bab818933d79ddcf5d \ @@ -2923,9 +2950,9 @@ sentry-sdk==2.17.0 \ --hash=sha256:625955884b862cc58748920f9e21efdfb8e0d4f98cca4ab0d3918576d5b606ad \ --hash=sha256:dd0a05352b78ffeacced73a94e86f38b32e2eae15fff5f30ca5abb568a72eacf # via -r requirements/common.in -service-identity==24.1.0 \ - --hash=sha256:6829c9d62fb832c2e1c435629b0a8c476e1929881f28bee4d20bc24161009221 \ - --hash=sha256:a28caf8130c8a5c1c7a6f5293faaf239bbfb7751e4862436920ee6f2616f568a +service-identity==24.2.0 \ + --hash=sha256:6b047fbd8a84fd0bb0d55ebce4031e400562b9196e1e0d3e0fe2b8a59f6d4a85 \ + --hash=sha256:b8683ba13f0d39c6cd5d625d2c5f65421d6d707b013b375c355751557cbe8e09 # via scrapy sh==2.1.0 \ --hash=sha256:7e27301c574bec8ca5bf6f211851357526455ee97cd27a7c4c6cc5e2375399cb \ @@ -2964,10 +2991,6 @@ social-auth-core[azuread,openidconnect,saml]==4.5.4 \ # via # -r requirements/common.in # social-auth-app-django -sortedcontainers==2.4.0 \ - --hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \ - --hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0 - # via intervaltree soupsieve==2.6 \ --hash=sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb \ --hash=sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9 @@ -3077,9 +3100,9 @@ stack-data==0.6.3 \ --hash=sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9 \ --hash=sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695 # via ipython -stripe==11.1.1 \ - --hash=sha256:0bbdfe54a09728fc54db6bb099b2f440ffc111d07d9674b0f04bfd0d3c1cbdcf \ - --hash=sha256:e79e02238d0ec7c89a64986af941dcae41e4857489b7cc83497acce9def356e5 +stripe==11.2.0 \ + --hash=sha256:4c53d61d7b596070324bfa5d7215843145fe5466e48973d828aab41ad209b5ce \ + --hash=sha256:dec812eabc95488862be40e6c799acdaf2e1225d686490a793f949fab745fdd0 # via -r requirements/common.in tabulate==0.9.0 \ --hash=sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c \ @@ -3193,21 +3216,21 @@ traitlets==5.14.3 \ # via # ipython # matplotlib-inline -twilio==9.3.4 \ - --hash=sha256:2cae99f0f7aecbd9da02fa59ad8f11b360db4a9281fc3fb3237ad50be21d8a9b \ - --hash=sha256:38a6ab04752f44313dcf736eae45236a901528d3f53dfc21d3afd33539243c7f +twilio==9.3.6 \ + --hash=sha256:c5d7f4cfeb50a7928397b8f819c8f7fb2bb956a1a2cabbda1df1d7a40f9ce1d7 \ + --hash=sha256:d42691f7fe1faaa5ba82942f169bfea4d7f01a0a542a456d82018fb49bd1f5b2 # via django-two-factor-auth -twisted==24.7.0 \ - --hash=sha256:5a60147f044187a127ec7da96d170d49bcce50c6fd36f594e60f4587eff4d394 \ - --hash=sha256:734832ef98108136e222b5230075b1079dad8a3fc5637319615619a7725b0c81 +twisted==24.10.0 \ + --hash=sha256:02951299672595fea0f70fa2d5f7b5e3d56836157eda68859a6ad6492d36756e \ + --hash=sha256:67aa7c8aa94387385302acf44ade12967c747858c8bcce0f11d38077a11c5326 # via scrapy typeguard==2.13.3 \ --hash=sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4 \ --hash=sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1 # via testslide -types-awscrt==0.22.0 \ - --hash=sha256:67a660c90bad360c339f6a79310cc17094d12472042c7ca5a41450aaf5fc9a54 \ - --hash=sha256:b2c196bbd3226bab42d80fae13c34548de9ddc195f5a366d79c15d18e5897aa9 +types-awscrt==0.23.0 \ + --hash=sha256:3fd1edeac923d1956c0e907c973fb83bda465beae7f054716b371b293f9b5fdc \ + --hash=sha256:517d9d06f19cf58d778ca90ad01e52e0489466bf70dcf78c7f47f74fdf151a60 # via botocore-stubs types-beautifulsoup4==4.12.0.20241020 \ --hash=sha256:158370d08d0cd448bd11b132a50ff5279237a5d4b5837beba074de152a513059 \ @@ -3299,9 +3322,9 @@ types-s3transfer==0.10.3 \ --hash=sha256:d34c5a82f531af95bb550927136ff5b737a1ed3087f90a59d545591dfde5b4cc \ --hash=sha256:f761b2876ac4c208e6c6b75cdf5f6939009768be9950c545b11b0225e7703ee7 # via boto3-stubs -types-setuptools==75.2.0.20241019 \ - --hash=sha256:2e48ff3acd4919471e80d5e3f049cce5c177e108d5d36d2d4cee3fa4d4104258 \ - --hash=sha256:86ea31b5f6df2c6b8f2dc8ae3f72b213607f62549b6fa2ed5866e5299f968694 +types-setuptools==75.2.0.20241025 \ + --hash=sha256:2949913a518d5285ce00a3b7d88961c80a6e72ffb8f3da0a3f5650ea533bd45e \ + --hash=sha256:6721ac0f1a620321e2ccd87a9a747c4a383dc381f78d894ce37f2455b45fcf1c # via # types-cffi # types-pygments @@ -3369,8 +3392,8 @@ urllib3==2.2.3 \ # semgrep # sentry-sdk # types-requests -uwsgi==2.0.27 \ - --hash=sha256:3ee5bfb7e6e9c93478c22aa8183eef35b95a2d5b14cca16172e67f135565c458 +uwsgi==2.0.28 \ + --hash=sha256:79ca1891ef2df14508ab0471ee8c0eb94bd2d51d03f32f90c4bbe557ab1e99d0 # via -r requirements/prod.in virtualenv-clone==0.5.7 \ --hash=sha256:418ee935c36152f8f153c79824bb93eaf6f0f7984bae31d3f48f350b9183501a \ @@ -3394,9 +3417,9 @@ webencodings==0.5.1 \ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 # via html5lib -werkzeug==3.0.4 \ - --hash=sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c \ - --hash=sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306 +werkzeug==3.1.0 \ + --hash=sha256:208a2e31a4a54c8b3d2244f2079ca1d3851629a7a7d546646059c64fb746023a \ + --hash=sha256:6f2a0d38f25ba5a75c36c45b4ae350c7a23b57e3b974e9eb2d6851f2c648c00d # via # moto # openapi-core @@ -3470,170 +3493,170 @@ xmltodict==0.14.2 \ --hash=sha256:201e7c28bb210e374999d1dde6382923ab0ed1a8a5faeece48ab525b7810a553 \ --hash=sha256:20cc7d723ed729276e808f26fb6b3599f786cbc37e06c65e192ba77c40f20aac # via moto -yarl==1.15.5 \ - --hash=sha256:00bb3a559d7bd006a5302ecd7e409916939106a8cdbe31f4eb5e5b9ffcca57ea \ - --hash=sha256:0327081978fe186c3390dd4f73f95f825d0bb9c74967e22c2a1a87735974d8f5 \ - --hash=sha256:041bafaa82b77fd4ec2826d42a55461ec86d999adf7ed9644eef7e8a9febb366 \ - --hash=sha256:06ec070a2d71415f90dbe9d70af3158e7da97a128519dba2d1581156ee27fb92 \ - --hash=sha256:07a4b53abe85813c538b9cdbb02909ebe3734e3af466a587df516e960d500cc8 \ - --hash=sha256:0a843e692f9d5402b3455653f4607dc521de2385f01c5cad7ba4a87c46e2ea8d \ - --hash=sha256:10bfe0bef4cf5ea0383886beda004071faadedf2647048b9f876664284c5b60d \ - --hash=sha256:18940191ec9a83bbfe63eea61c3e9d12474bb910d5613bce8fa46e84a80b75b2 \ - --hash=sha256:19e2a4b2935f95fad0949f420514c5d862f5f18058fbbfd8854f496a97d9fd87 \ - --hash=sha256:1a6b6e95bc621c11cf9ff21012173337e789f2461ebc3b4e5bf65c74ef69adb8 \ - --hash=sha256:1f5a1ca6eaabfe62718b87eac06d9a47b30cf92ffa065fee9196d3ecd24a3cf1 \ - --hash=sha256:21050b6cd569980fe20ceeab4baeb900d3f7247270475e42bafe117416a5496c \ - --hash=sha256:2597a589859b94d0a5e2f5d30fee95081867926e57cb751f8b44a7dd92da4e79 \ - --hash=sha256:294c742a273f44511f14b03a9e06b66094dcdf4bbb75a5e23fead548fd5310ae \ - --hash=sha256:2eeb9ba53c055740cd282ae9d34eb7970d65e73a46f15adec4b0c1b0f2e55cc2 \ - --hash=sha256:30ca64521f1a96b72886dd9e8652f16eab11891b4572dcfcfc1ad6d6ccb27abd \ - --hash=sha256:325e2beb2cd8654b276e7686a3cd203628dd3fe32d5c616e632bc35a2901fb16 \ - --hash=sha256:34816f1d833433a16c4832562a050b0a60eac53dcb71b2032e6ebff82d74b6a7 \ - --hash=sha256:362da97ad4360e4ef1dd24ccdd3bceb18332da7f40026a42f49b7edd686e31c3 \ - --hash=sha256:3b30f13fac56598474071a4f1ecd66c78fdaf2f8619042d7ca135f72dbb348cf \ - --hash=sha256:44088ec0be82fba118ed29b6b429f80bf295297727adae4c257ac297e01e8bcd \ - --hash=sha256:44359c52af9c383e5107f3b6301446fc8269599721fa42fafb2afb5f31a42dcb \ - --hash=sha256:4ac83b307cc4b8907345b52994055c6c3c2601ceb6fcb94c5ed6a93c6b4e8257 \ - --hash=sha256:5093a453176a4fad4f9c3006f507cf300546190bb3e27944275a37cfd6323a65 \ - --hash=sha256:524b3bb7dff320e305bc979c65eddc0342548c56ea9241502f907853fe53c408 \ - --hash=sha256:5848500b6a01497560969e8c3a7eb1b2570853c74a0ca6f67ebaf6064106c49b \ - --hash=sha256:5882faa2a6e684f65ee44f18c701768749a950cbd5e72db452fc07805f6bdec0 \ - --hash=sha256:5b8af4165e097ff84d9bbb97bb4f4d7f71b9c1c9565a2d0e27d93e5f92dae220 \ - --hash=sha256:5c3ac5bdcc1375c8ee52784adf94edbce37c471dd2100a117cfef56fe8dbc2b4 \ - --hash=sha256:5d6be369488d503c8edc14e2f63d71ab2a607041ad216a8ad444fa18e8dea792 \ - --hash=sha256:5fadcf532fd9f6cbad71485ef8c2462dd9a91d3efc72ca01eb0970792c92552a \ - --hash=sha256:607683991bab8607e5158cd290dd8fdaa613442aeab802fe1c237d3a3eee7358 \ - --hash=sha256:625f31d6650829fba4030b4e7bdb2d69e41510dddfa29a1da27076c199521757 \ - --hash=sha256:63d46606b20f80a6476f1044bab78e1a69c2e0747f174583e2f12fc70bad2170 \ - --hash=sha256:6493da9ba5c551978c679ab04856c2cf8f79c316e8ec8c503460a135705edc3b \ - --hash=sha256:6563394492c96cb57f4dff0c69c63d2b28b5469c59c66f35a1e6451583cd0ab4 \ - --hash=sha256:68d21d0563d82aaf46163eac529adac301b20be3181b8a2811f7bd5615466055 \ - --hash=sha256:68e837b3edfcd037f9706157e7cb8efda832de6248c7d9e893e2638356dfae5d \ - --hash=sha256:6b3d2767bd64c62909ea33525b954ba05c8f9726bfdf2141d175da4e344f19ae \ - --hash=sha256:6e2c674cfe4c03ad7a4d536b1f808221f0d11a360486b4b032d2557c0bd633ad \ - --hash=sha256:70d074d5a96e0954fe6db81ff356f4361397da1cda3f7c127fc0902f671a087e \ - --hash=sha256:71730658be0b5de7c570a9795d7404c577b2313c1db370407092c66f70e04ccb \ - --hash=sha256:73143dd279e641543da52c55652ad7b4c7c5f79e797f124f58f04cc060f14271 \ - --hash=sha256:75d04ba8ed335042328086e643e01165e0c24598216f72da709b375930ae3bdb \ - --hash=sha256:7825506fbee4055265528ec3532a8197ff26fc53d4978917a4c8ddbb4c1667d7 \ - --hash=sha256:7983290ede3aaa2c9620879530849532529b4dcbf5b12a0b6a91163a773eadb9 \ - --hash=sha256:7abd7d15aedb3961a967cc65f8144dbbca42e3626a21c5f4f29919cf43eeafb9 \ - --hash=sha256:8249147ee81c1cf4d1dc6f26ba28a1b9d92751529f83c308ad02164bb93abd0d \ - --hash=sha256:86648c53b10c53db8b967a75fb41e0c89dbec7398f6525e34af2b6c456bb0ac0 \ - --hash=sha256:8669a110f655c9eb22f16fb68a7d4942020aeaa09f1def584a80183e3e89953c \ - --hash=sha256:8b7dd6983c81523f9de0ae6334c3b7a3cb33283936e0525f80c4f713f54a9bb6 \ - --hash=sha256:8fc727f0fb388debc771eaa7091c092bd2e8b6b4741b73354b8efadcf96d6031 \ - --hash=sha256:9162ea117ce8bad8ebc95b7376b4135988acd888d2cf4702f8281e3c11f8b81f \ - --hash=sha256:94189746c5ad62e1014a16298130e696fe593d031d442ef135fb7787b7a1f820 \ - --hash=sha256:94ab1185900f43760d5487c8e49f5f1a66f864e36092f282f1813597479b9dfa \ - --hash=sha256:96ce879799fee124d241ea3b84448378f638e290c49493d00b706f3fd57ec22b \ - --hash=sha256:9aa054d97033beac9cb9b19b7c0b8784b85b12cd17879087ca6bffba57884e02 \ - --hash=sha256:9c2d1109c8d92059314cc34dd8f0a31f74b720dc140744923ed7ca228bf9b491 \ - --hash=sha256:a082dc948045606f62dca0228ab24f13737180b253378d6443f5b2b9ef8beefe \ - --hash=sha256:a7d317fb80bc17ed4b34a9aad8b80cef34bea0993654f3e8566daf323def7ef9 \ - --hash=sha256:b06d8b05d0fafef204d635a4711283ddbf19c7c0facdc61b4b775f6e47e2d4be \ - --hash=sha256:b1217102a455e3ac9ac293081093f21f0183e978c7692171ff669fee5296fa28 \ - --hash=sha256:b6c57972a406ea0f61e3f28f2b3a780fb71fbe1d82d267afe5a2f889a83ee7e7 \ - --hash=sha256:b997a806846c00d1f41d6a251803732837771b2091bead7566f68820e317bfe7 \ - --hash=sha256:bb129f77ddaea2d8e6e00417b8d907448de3407af4eddacca0a515574ad71493 \ - --hash=sha256:bb707859218e8335447b210f41a755e7b1367c33e87add884128bba144694a7f \ - --hash=sha256:c166ad987265bb343be58cdf4fbc4478cc1d81f2246d2be9a15f94393b269faa \ - --hash=sha256:c884dfa56b050f718ea3cbbfd972e29a6f07f63a7449b10d9a20d64f7eec92e2 \ - --hash=sha256:cbf36099a9b407e1456dbf55844743a98603fcba32d2a46fb3a698d926facf1b \ - --hash=sha256:cd529e637cd23204bd82072f6637cff7af2516ad2c132e8f3342cbc84871f7d1 \ - --hash=sha256:d3309ee667f2d9c7ac9ecf44620d6b274bfdd8065b8c5019ff6795dd887b8fed \ - --hash=sha256:d56980374a10c74255fcea6ebcfb0aeca7166d212ee9fd7e823ddef35fb62ad0 \ - --hash=sha256:d7fa4b033e2f267e37aabcc36949fa89f9f1716a723395912147f9cf3fb437c7 \ - --hash=sha256:da48cdff56b01ea4282a6d04b83b07a2088351a4a3ff7aacc1e7e9b6b04b90b9 \ - --hash=sha256:de6917946dc6bc237d4b354e38aa13a232e0c7948fdbdb160edee3862e9d735f \ - --hash=sha256:e27861251d9c094f641d39a8a78dd2371fb9a252ea2f689d1ad353a31d46a0bc \ - --hash=sha256:e652aa9f8dfa808bc5b2da4d1f4e286cf1d640570fdfa72ffc0c1d16ba114651 \ - --hash=sha256:e8aa19c39cb20bfb16f0266df175a6004943122cf20707fbf0cacc21f6468a25 \ - --hash=sha256:ed9c72d5361cfd5af5ccadffa8f8077f4929640e1f938aa0f4b92c5a24996ac5 \ - --hash=sha256:f7de0d4b6b4d8a77e422eb54d765255c0ec6883ee03b8fd537101633948619d7 \ - --hash=sha256:fcfd663dc88465ebe41c7c938bdc91c4b01cda96a0d64bf38fd66c1877323771 \ - --hash=sha256:fd56de8b645421ff09c993fdb0ee9c5a3b50d290a8f55793b500d99b34d0c1ce +yarl==1.17.1 \ + --hash=sha256:06157fb3c58f2736a5e47c8fcbe1afc8b5de6fb28b14d25574af9e62150fcaac \ + --hash=sha256:067a63fcfda82da6b198fa73079b1ca40b7c9b7994995b6ee38acda728b64d47 \ + --hash=sha256:0b1794853124e2f663f0ea54efb0340b457f08d40a1cef78edfa086576179c91 \ + --hash=sha256:0bdff5e0995522706c53078f531fb586f56de9c4c81c243865dd5c66c132c3b5 \ + --hash=sha256:117ed8b3732528a1e41af3aa6d4e08483c2f0f2e3d3d7dca7cf538b3516d93df \ + --hash=sha256:14bc88baa44e1f84164a392827b5defb4fa8e56b93fecac3d15315e7c8e5d8b3 \ + --hash=sha256:1654ec814b18be1af2c857aa9000de7a601400bd4c9ca24629b18486c2e35463 \ + --hash=sha256:16bca6678a83657dd48df84b51bd56a6c6bd401853aef6d09dc2506a78484c7b \ + --hash=sha256:1a3b91c44efa29e6c8ef8a9a2b583347998e2ba52c5d8280dbd5919c02dfc3b5 \ + --hash=sha256:1a52a1ffdd824fb1835272e125385c32fd8b17fbdefeedcb4d543cc23b332d74 \ + --hash=sha256:1ce36ded585f45b1e9bb36d0ae94765c6608b43bd2e7f5f88079f7a85c61a4d3 \ + --hash=sha256:299f11b44d8d3a588234adbe01112126010bd96d9139c3ba7b3badd9829261c3 \ + --hash=sha256:2b24ec55fad43e476905eceaf14f41f6478780b870eda5d08b4d6de9a60b65b4 \ + --hash=sha256:2d374d70fdc36f5863b84e54775452f68639bc862918602d028f89310a034ab0 \ + --hash=sha256:2d9f0606baaec5dd54cb99667fcf85183a7477f3766fbddbe3f385e7fc253299 \ + --hash=sha256:2e7ba4c9377e48fb7b20dedbd473cbcbc13e72e1826917c185157a137dac9df2 \ + --hash=sha256:2f0a6423295a0d282d00e8701fe763eeefba8037e984ad5de44aa349002562ac \ + --hash=sha256:327828786da2006085a4d1feb2594de6f6d26f8af48b81eb1ae950c788d97f61 \ + --hash=sha256:380e6c38ef692b8fd5a0f6d1fa8774d81ebc08cfbd624b1bca62a4d4af2f9931 \ + --hash=sha256:3b74ff4767d3ef47ffe0cd1d89379dc4d828d4873e5528976ced3b44fe5b0a21 \ + --hash=sha256:3e844be8d536afa129366d9af76ed7cb8dfefec99f5f1c9e4f8ae542279a6dc3 \ + --hash=sha256:459e81c2fb920b5f5df744262d1498ec2c8081acdcfe18181da44c50f51312f7 \ + --hash=sha256:46ddf6e0b975cd680eb83318aa1d321cb2bf8d288d50f1754526230fcf59ba96 \ + --hash=sha256:482c122b72e3c5ec98f11457aeb436ae4aecca75de19b3d1de7cf88bc40db82f \ + --hash=sha256:561c87fea99545ef7d692403c110b2f99dced6dff93056d6e04384ad3bc46243 \ + --hash=sha256:578d00c9b7fccfa1745a44f4eddfdc99d723d157dad26764538fbdda37209857 \ + --hash=sha256:58c8e9620eb82a189c6c40cb6b59b4e35b2ee68b1f2afa6597732a2b467d7e8f \ + --hash=sha256:5b29beab10211a746f9846baa39275e80034e065460d99eb51e45c9a9495bcca \ + --hash=sha256:5d1d42556b063d579cae59e37a38c61f4402b47d70c29f0ef15cee1acaa64488 \ + --hash=sha256:5f236cb5999ccd23a0ab1bd219cfe0ee3e1c1b65aaf6dd3320e972f7ec3a39da \ + --hash=sha256:62a91aefff3d11bf60e5956d340eb507a983a7ec802b19072bb989ce120cd948 \ + --hash=sha256:64cc6e97f14cf8a275d79c5002281f3040c12e2e4220623b5759ea7f9868d6a5 \ + --hash=sha256:6f4c9156c4d1eb490fe374fb294deeb7bc7eaccda50e23775b2354b6a6739934 \ + --hash=sha256:7294e38f9aa2e9f05f765b28ffdc5d81378508ce6dadbe93f6d464a8c9594473 \ + --hash=sha256:7615058aabad54416ddac99ade09a5510cf77039a3b903e94e8922f25ed203d7 \ + --hash=sha256:7e48cdb8226644e2fbd0bdb0a0f87906a3db07087f4de77a1b1b1ccfd9e93685 \ + --hash=sha256:7f63d176a81555984e91f2c84c2a574a61cab7111cc907e176f0f01538e9ff6e \ + --hash=sha256:7f6595c852ca544aaeeb32d357e62c9c780eac69dcd34e40cae7b55bc4fb1147 \ + --hash=sha256:7fac95714b09da9278a0b52e492466f773cfe37651cf467a83a1b659be24bf71 \ + --hash=sha256:81713b70bea5c1386dc2f32a8f0dab4148a2928c7495c808c541ee0aae614d67 \ + --hash=sha256:846dd2e1243407133d3195d2d7e4ceefcaa5f5bf7278f0a9bda00967e6326b04 \ + --hash=sha256:84c063af19ef5130084db70ada40ce63a84f6c1ef4d3dbc34e5e8c4febb20822 \ + --hash=sha256:881764d610e3269964fc4bb3c19bb6fce55422828e152b885609ec176b41cf11 \ + --hash=sha256:8994b29c462de9a8fce2d591028b986dbbe1b32f3ad600b2d3e1c482c93abad6 \ + --hash=sha256:8c79e9d7e3d8a32d4824250a9c6401194fb4c2ad9a0cec8f6a96e09a582c2cc0 \ + --hash=sha256:8ee427208c675f1b6e344a1f89376a9613fc30b52646a04ac0c1f6587c7e46ec \ + --hash=sha256:949681f68e0e3c25377462be4b658500e85ca24323d9619fdc41f68d46a1ffda \ + --hash=sha256:9e275792097c9f7e80741c36de3b61917aebecc08a67ae62899b074566ff8556 \ + --hash=sha256:9fb815155aac6bfa8d86184079652c9715c812d506b22cfa369196ef4e99d1b4 \ + --hash=sha256:a2a64e62c7a0edd07c1c917b0586655f3362d2c2d37d474db1a509efb96fea1c \ + --hash=sha256:a7ac5b4984c468ce4f4a553df281450df0a34aefae02e58d77a0847be8d1e11f \ + --hash=sha256:aa46dce75078fceaf7cecac5817422febb4355fbdda440db55206e3bd288cfb8 \ + --hash=sha256:ae3476e934b9d714aa8000d2e4c01eb2590eee10b9d8cd03e7983ad65dfbfcba \ + --hash=sha256:b0341e6d9a0c0e3cdc65857ef518bb05b410dbd70d749a0d33ac0f39e81a4258 \ + --hash=sha256:b40d1bf6e6f74f7c0a567a9e5e778bbd4699d1d3d2c0fe46f4b717eef9e96b95 \ + --hash=sha256:b5c4804e4039f487e942c13381e6c27b4b4e66066d94ef1fae3f6ba8b953f383 \ + --hash=sha256:b5d6a6c9602fd4598fa07e0389e19fe199ae96449008d8304bf5d47cb745462e \ + --hash=sha256:b5f1ac7359e17efe0b6e5fec21de34145caef22b260e978336f325d5c84e6938 \ + --hash=sha256:c0167540094838ee9093ef6cc2c69d0074bbf84a432b4995835e8e5a0d984374 \ + --hash=sha256:c180ac742a083e109c1a18151f4dd8675f32679985a1c750d2ff806796165b55 \ + --hash=sha256:c73df5b6e8fabe2ddb74876fb82d9dd44cbace0ca12e8861ce9155ad3c886139 \ + --hash=sha256:c7e177c619342e407415d4f35dec63d2d134d951e24b5166afcdfd1362828e17 \ + --hash=sha256:cbad927ea8ed814622305d842c93412cb47bd39a496ed0f96bfd42b922b4a217 \ + --hash=sha256:cc353841428d56b683a123a813e6a686e07026d6b1c5757970a877195f880c2d \ + --hash=sha256:cc7c92c1baa629cb03ecb0c3d12564f172218fb1739f54bf5f3881844daadc6d \ + --hash=sha256:cc7d768260f4ba4ea01741c1b5fe3d3a6c70eb91c87f4c8761bbcce5181beafe \ + --hash=sha256:d0eea830b591dbc68e030c86a9569826145df485b2b4554874b07fea1275a199 \ + --hash=sha256:d216e5d9b8749563c7f2c6f7a0831057ec844c68b4c11cb10fc62d4fd373c26d \ + --hash=sha256:d401f07261dc5aa36c2e4efc308548f6ae943bfff20fcadb0a07517a26b196d8 \ + --hash=sha256:d6324274b4e0e2fa1b3eccb25997b1c9ed134ff61d296448ab8269f5ac068c4c \ + --hash=sha256:d8a8b74d843c2638f3864a17d97a4acda58e40d3e44b6303b8cc3d3c44ae2d29 \ + --hash=sha256:d9b6b28a57feb51605d6ae5e61a9044a31742db557a3b851a74c13bc61de5172 \ + --hash=sha256:de599af166970d6a61accde358ec9ded821234cbbc8c6413acfec06056b8e860 \ + --hash=sha256:e594b22688d5747b06e957f1ef822060cb5cb35b493066e33ceac0cf882188b7 \ + --hash=sha256:e5b078134f48552c4d9527db2f7da0b5359abd49393cdf9794017baec7506170 \ + --hash=sha256:eb6dce402734575e1a8cc0bb1509afca508a400a57ce13d306ea2c663bad1138 \ + --hash=sha256:f1790a4b1e8e8e028c391175433b9c8122c39b46e1663228158e61e6f915bf06 \ + --hash=sha256:f5efe0661b9fcd6246f27957f6ae1c0eb29bc60552820f01e970b4996e016004 \ + --hash=sha256:f9cbfbc5faca235fbdf531b93aa0f9f005ec7d267d9d738761a4d42b744ea159 \ + --hash=sha256:fbea1751729afe607d84acfd01efd95e3b31db148a181a441984ce9b3d3469da \ + --hash=sha256:fca4b4307ebe9c3ec77a084da3a9d1999d164693d16492ca2b64594340999988 \ + --hash=sha256:ff5c6771c7e3511a06555afa317879b7db8d640137ba55d6ab0d0c50425cab75 # via aiohttp -zope-interface==7.1.0 \ - --hash=sha256:07add15de0cc7e69917f7d286b64d54125c950aeb43efed7a5ea7172f000fbc1 \ - --hash=sha256:0ac20581fc6cd7c754f6dff0ae06fedb060fa0e9ea6309d8be8b2701d9ea51c4 \ - --hash=sha256:124149e2d42067b9c6597f4dafdc7a0983d0163868f897b7bb5dc850b14f9a87 \ - --hash=sha256:27cfb5205d68b12682b6e55ab8424662d96e8ead19550aad0796b08dd2c9a45e \ - --hash=sha256:2a29ac607e970b5576547f0e3589ec156e04de17af42839eedcf478450687317 \ - --hash=sha256:2b6a4924f5bad9fe21d99f66a07da60d75696a136162427951ec3cb223a5570d \ - --hash=sha256:2bd9e9f366a5df08ebbdc159f8224904c1c5ce63893984abb76954e6fbe4381a \ - --hash=sha256:3bcff5c09d0215f42ba64b49205a278e44413d9bf9fa688fd9e42bfe472b5f4f \ - --hash=sha256:3f005869a1a05e368965adb2075f97f8ee9a26c61898a9e52a9764d93774f237 \ - --hash=sha256:4a00ead2e24c76436e1b457a5132d87f83858330f6c923640b7ef82d668525d1 \ - --hash=sha256:4af4a12b459a273b0b34679a5c3dc5e34c1847c3dd14a628aa0668e19e638ea2 \ - --hash=sha256:5501e772aff595e3c54266bc1bfc5858e8f38974ce413a8f1044aae0f32a83a3 \ - --hash=sha256:5e28ea0bc4b084fc93a483877653a033062435317082cdc6388dec3438309faf \ - --hash=sha256:5e956b1fd7f3448dd5e00f273072e73e50dfafcb35e4227e6d5af208075593c9 \ - --hash=sha256:5fcf379b875c610b5a41bc8a891841533f98de0520287d7f85e25386cd10d3e9 \ - --hash=sha256:6159e767d224d8f18deff634a1d3722e68d27488c357f62ebeb5f3e2f5288b1f \ - --hash=sha256:661d5df403cd3c5b8699ac480fa7f58047a3253b029db690efa0c3cf209993ef \ - --hash=sha256:711eebc77f2092c6a8b304bad0b81a6ce3cf5490b25574e7309fbc07d881e3af \ - --hash=sha256:80a3c00b35f6170be5454b45abe2719ea65919a2f09e8a6e7b1362312a872cd3 \ - --hash=sha256:848b6fa92d7c8143646e64124ed46818a0049a24ecc517958c520081fd147685 \ - --hash=sha256:91b6c30689cfd87c8f264acb2fc16ad6b3c72caba2aec1bf189314cf1a84ca33 \ - --hash=sha256:9733a9a0f94ef53d7aa64661811b20875b5bc6039034c6e42fb9732170130573 \ - --hash=sha256:9940d5bc441f887c5f375ec62bcf7e7e495a2d5b1da97de1184a88fb567f06af \ - --hash=sha256:9e3e48f3dea21c147e1b10c132016cb79af1159facca9736d231694ef5a740a8 \ - --hash=sha256:a14c9decf0eb61e0892631271d500c1e306c7b6901c998c7035e194d9150fdd1 \ - --hash=sha256:a735f82d2e3ed47ca01a20dfc4c779b966b16352650a8036ab3955aad151ed8a \ - --hash=sha256:a99240b1d02dc469f6afbe7da1bf617645e60290c272968f4e53feec18d7dce8 \ - --hash=sha256:b7b25db127db3e6b597c5f74af60309c4ad65acd826f89609662f0dc33a54728 \ - --hash=sha256:b936d61dbe29572fd2cfe13e30b925e5383bed1aba867692670f5a2a2eb7b4e9 \ - --hash=sha256:bec001798ab62c3fc5447162bf48496ae9fba02edc295a9e10a0b0c639a6452e \ - --hash=sha256:cc8a318162123eddbdf22fcc7b751288ce52e4ad096d3766ff1799244352449d \ - --hash=sha256:d0a45b5af9f72c805ee668d1479480ca85169312211bed6ed18c343e39307d5f \ - --hash=sha256:e53c291debef523b09e1fe3dffe5f35dde164f1c603d77f770b88a1da34b7ed6 \ - --hash=sha256:ec1ef1fdb6f014d5886b97e52b16d0f852364f447d2ab0f0c6027765777b6667 \ - --hash=sha256:ec59fe53db7d32abb96c6d4efeed84aab4a7c38c62d7a901a9b20c09dd936e7a \ - --hash=sha256:f245d039f72e6f802902375755846f5de1ee1e14c3e8736c078565599bcab621 \ - --hash=sha256:ff115ef91c0eeac69cd92daeba36a9d8e14daee445b504eeea2b1c0b55821984 +zope-interface==7.1.1 \ + --hash=sha256:0de23bcb93401994ea00bc5c677ef06d420340ac0a4e9c10d80e047b9ce5af3f \ + --hash=sha256:179ad46ece518c9084cb272e4a69d266b659f7f8f48e51706746c2d8a426433e \ + --hash=sha256:190eeec67e023d5aac54d183fa145db0b898664234234ac54643a441da434616 \ + --hash=sha256:1a2ed0852c25950cf430067f058f8d98df6288502ac313861d9803fe7691a9b3 \ + --hash=sha256:1c4e1b4c06d9abd1037c088dae1566c85f344a3e6ae4350744c3f7f7259d9c67 \ + --hash=sha256:1d0e23c6b746eb8ce04573cc47bcac60961ac138885d207bd6f57e27a1431ae8 \ + --hash=sha256:2317e1d4dba68203a5227ea3057f9078ec9376275f9700086b8f0ffc0b358e1b \ + --hash=sha256:2d553e02b68c0ea5a226855f02edbc9eefd99f6a8886fa9f9bdf999d77f46585 \ + --hash=sha256:3603ef82a9920bd0bfb505423cb7e937498ad971ad5a6141841e8f76d2fd5446 \ + --hash=sha256:3defc925c4b22ac1272d544a49c6ba04c3eefcce3200319ee1be03d9270306dd \ + --hash=sha256:3e59f175e868f856a77c0a77ba001385c377df2104fdbda6b9f99456a01e102a \ + --hash=sha256:4284d664ef0ff7b709836d4de7b13d80873dc5faeffc073abdb280058bfac5e3 \ + --hash=sha256:55c373becbd36a44d0c9be1d5271422fdaa8562d158fb44b4192297b3c67096c \ + --hash=sha256:5836b8fb044c6e75ba34dfaabc602493019eadfa0faf6ff25f4c4c356a71a853 \ + --hash=sha256:5cdb7e7e5524b76d3ec037c1d81a9e2c7457b240fd4cb0a2476b65c3a5a6c81f \ + --hash=sha256:6650bd56ef350d37c8baccfd3ee8a0483ed6f8666e641e4b9ae1a1827b79f9e5 \ + --hash=sha256:7395f13533318f150ee72adb55b29284b16e73b6d5f02ab21f173b3e83f242b8 \ + --hash=sha256:7720322763aceb5e0a7cadcc38c67b839efe599f0887cbf6c003c55b1458c501 \ + --hash=sha256:7cd5e3d910ac87652a09f6e5db8e41bc3b49cf08ddd2d73d30afc644801492cd \ + --hash=sha256:81744a7e61b598ebcf4722ac56a7a4f50502432b5b4dc7eb29075a89cf82d029 \ + --hash=sha256:84e87eba6b77a3af187bae82d8de1a7c208c2a04ec9f6bd444fd091b811ad92e \ + --hash=sha256:8d0fe45be57b5219aa4b96e846631c04615d5ef068146de5a02ccd15c185321f \ + --hash=sha256:9595e478047ce752b35cfa221d7601a5283ccdaab40422e0dc1d4a334c70f580 \ + --hash=sha256:99c14f0727c978639139e6cad7a60e82b7720922678d75aacb90cf4ef74a068c \ + --hash=sha256:9b1eed7670d564f1025d7cda89f99f216c30210e42e95de466135be0b4a499d9 \ + --hash=sha256:9fad9bd5502221ab179f13ea251cb30eef7cf65023156967f86673aff54b53a0 \ + --hash=sha256:ad339509dcfbbc99bf8e147db6686249c4032f26586699ec4c82f6e5909c9fe2 \ + --hash=sha256:bcbeb44fc16e0078b3b68a95e43f821ae34dcbf976dde6985141838a5f23dd3d \ + --hash=sha256:c8e7b05dc6315a193cceaec071cc3cf1c180cea28808ccded0b1283f1c38ba73 \ + --hash=sha256:ca95594d936ee349620900be5b46c0122a1ff6ce42d7d5cb2cf09dc84071ef16 \ + --hash=sha256:d029fac6a80edae80f79c37e5e3abfa92968fe921886139b3ee470a1b177321a \ + --hash=sha256:d17e7fc814eaab93409b80819fd6d30342844345c27f3bc3c4b43c2425a8d267 \ + --hash=sha256:d6821ef9870f32154da873fcde439274f99814ea452dd16b99fa0b66345c4b6b \ + --hash=sha256:e6503534b52bb1720ace9366ee30838a58a3413d3e197512f3338c8f34b5d89d \ + --hash=sha256:ed1df8cc01dd1e3970666a7370b8bfc7457371c58ba88c57bd5bca17ab198053 \ + --hash=sha256:f1d52d052355e0c5c89e0630dd2ff7c0b823fd5f56286a663e92444761b35e25 \ + --hash=sha256:f85b290e5b8b11814efb0d004d8ce6c9a483c35c462e8d9bf84abb93e79fa770 # via twisted -zope.interface==7.1.0 \ - --hash=sha256:07add15de0cc7e69917f7d286b64d54125c950aeb43efed7a5ea7172f000fbc1 \ - --hash=sha256:0ac20581fc6cd7c754f6dff0ae06fedb060fa0e9ea6309d8be8b2701d9ea51c4 \ - --hash=sha256:124149e2d42067b9c6597f4dafdc7a0983d0163868f897b7bb5dc850b14f9a87 \ - --hash=sha256:27cfb5205d68b12682b6e55ab8424662d96e8ead19550aad0796b08dd2c9a45e \ - --hash=sha256:2a29ac607e970b5576547f0e3589ec156e04de17af42839eedcf478450687317 \ - --hash=sha256:2b6a4924f5bad9fe21d99f66a07da60d75696a136162427951ec3cb223a5570d \ - --hash=sha256:2bd9e9f366a5df08ebbdc159f8224904c1c5ce63893984abb76954e6fbe4381a \ - --hash=sha256:3bcff5c09d0215f42ba64b49205a278e44413d9bf9fa688fd9e42bfe472b5f4f \ - --hash=sha256:3f005869a1a05e368965adb2075f97f8ee9a26c61898a9e52a9764d93774f237 \ - --hash=sha256:4a00ead2e24c76436e1b457a5132d87f83858330f6c923640b7ef82d668525d1 \ - --hash=sha256:4af4a12b459a273b0b34679a5c3dc5e34c1847c3dd14a628aa0668e19e638ea2 \ - --hash=sha256:5501e772aff595e3c54266bc1bfc5858e8f38974ce413a8f1044aae0f32a83a3 \ - --hash=sha256:5e28ea0bc4b084fc93a483877653a033062435317082cdc6388dec3438309faf \ - --hash=sha256:5e956b1fd7f3448dd5e00f273072e73e50dfafcb35e4227e6d5af208075593c9 \ - --hash=sha256:5fcf379b875c610b5a41bc8a891841533f98de0520287d7f85e25386cd10d3e9 \ - --hash=sha256:6159e767d224d8f18deff634a1d3722e68d27488c357f62ebeb5f3e2f5288b1f \ - --hash=sha256:661d5df403cd3c5b8699ac480fa7f58047a3253b029db690efa0c3cf209993ef \ - --hash=sha256:711eebc77f2092c6a8b304bad0b81a6ce3cf5490b25574e7309fbc07d881e3af \ - --hash=sha256:80a3c00b35f6170be5454b45abe2719ea65919a2f09e8a6e7b1362312a872cd3 \ - --hash=sha256:848b6fa92d7c8143646e64124ed46818a0049a24ecc517958c520081fd147685 \ - --hash=sha256:91b6c30689cfd87c8f264acb2fc16ad6b3c72caba2aec1bf189314cf1a84ca33 \ - --hash=sha256:9733a9a0f94ef53d7aa64661811b20875b5bc6039034c6e42fb9732170130573 \ - --hash=sha256:9940d5bc441f887c5f375ec62bcf7e7e495a2d5b1da97de1184a88fb567f06af \ - --hash=sha256:9e3e48f3dea21c147e1b10c132016cb79af1159facca9736d231694ef5a740a8 \ - --hash=sha256:a14c9decf0eb61e0892631271d500c1e306c7b6901c998c7035e194d9150fdd1 \ - --hash=sha256:a735f82d2e3ed47ca01a20dfc4c779b966b16352650a8036ab3955aad151ed8a \ - --hash=sha256:a99240b1d02dc469f6afbe7da1bf617645e60290c272968f4e53feec18d7dce8 \ - --hash=sha256:b7b25db127db3e6b597c5f74af60309c4ad65acd826f89609662f0dc33a54728 \ - --hash=sha256:b936d61dbe29572fd2cfe13e30b925e5383bed1aba867692670f5a2a2eb7b4e9 \ - --hash=sha256:bec001798ab62c3fc5447162bf48496ae9fba02edc295a9e10a0b0c639a6452e \ - --hash=sha256:cc8a318162123eddbdf22fcc7b751288ce52e4ad096d3766ff1799244352449d \ - --hash=sha256:d0a45b5af9f72c805ee668d1479480ca85169312211bed6ed18c343e39307d5f \ - --hash=sha256:e53c291debef523b09e1fe3dffe5f35dde164f1c603d77f770b88a1da34b7ed6 \ - --hash=sha256:ec1ef1fdb6f014d5886b97e52b16d0f852364f447d2ab0f0c6027765777b6667 \ - --hash=sha256:ec59fe53db7d32abb96c6d4efeed84aab4a7c38c62d7a901a9b20c09dd936e7a \ - --hash=sha256:f245d039f72e6f802902375755846f5de1ee1e14c3e8736c078565599bcab621 \ - --hash=sha256:ff115ef91c0eeac69cd92daeba36a9d8e14daee445b504eeea2b1c0b55821984 +zope.interface==7.1.1 \ + --hash=sha256:0de23bcb93401994ea00bc5c677ef06d420340ac0a4e9c10d80e047b9ce5af3f \ + --hash=sha256:179ad46ece518c9084cb272e4a69d266b659f7f8f48e51706746c2d8a426433e \ + --hash=sha256:190eeec67e023d5aac54d183fa145db0b898664234234ac54643a441da434616 \ + --hash=sha256:1a2ed0852c25950cf430067f058f8d98df6288502ac313861d9803fe7691a9b3 \ + --hash=sha256:1c4e1b4c06d9abd1037c088dae1566c85f344a3e6ae4350744c3f7f7259d9c67 \ + --hash=sha256:1d0e23c6b746eb8ce04573cc47bcac60961ac138885d207bd6f57e27a1431ae8 \ + --hash=sha256:2317e1d4dba68203a5227ea3057f9078ec9376275f9700086b8f0ffc0b358e1b \ + --hash=sha256:2d553e02b68c0ea5a226855f02edbc9eefd99f6a8886fa9f9bdf999d77f46585 \ + --hash=sha256:3603ef82a9920bd0bfb505423cb7e937498ad971ad5a6141841e8f76d2fd5446 \ + --hash=sha256:3defc925c4b22ac1272d544a49c6ba04c3eefcce3200319ee1be03d9270306dd \ + --hash=sha256:3e59f175e868f856a77c0a77ba001385c377df2104fdbda6b9f99456a01e102a \ + --hash=sha256:4284d664ef0ff7b709836d4de7b13d80873dc5faeffc073abdb280058bfac5e3 \ + --hash=sha256:55c373becbd36a44d0c9be1d5271422fdaa8562d158fb44b4192297b3c67096c \ + --hash=sha256:5836b8fb044c6e75ba34dfaabc602493019eadfa0faf6ff25f4c4c356a71a853 \ + --hash=sha256:5cdb7e7e5524b76d3ec037c1d81a9e2c7457b240fd4cb0a2476b65c3a5a6c81f \ + --hash=sha256:6650bd56ef350d37c8baccfd3ee8a0483ed6f8666e641e4b9ae1a1827b79f9e5 \ + --hash=sha256:7395f13533318f150ee72adb55b29284b16e73b6d5f02ab21f173b3e83f242b8 \ + --hash=sha256:7720322763aceb5e0a7cadcc38c67b839efe599f0887cbf6c003c55b1458c501 \ + --hash=sha256:7cd5e3d910ac87652a09f6e5db8e41bc3b49cf08ddd2d73d30afc644801492cd \ + --hash=sha256:81744a7e61b598ebcf4722ac56a7a4f50502432b5b4dc7eb29075a89cf82d029 \ + --hash=sha256:84e87eba6b77a3af187bae82d8de1a7c208c2a04ec9f6bd444fd091b811ad92e \ + --hash=sha256:8d0fe45be57b5219aa4b96e846631c04615d5ef068146de5a02ccd15c185321f \ + --hash=sha256:9595e478047ce752b35cfa221d7601a5283ccdaab40422e0dc1d4a334c70f580 \ + --hash=sha256:99c14f0727c978639139e6cad7a60e82b7720922678d75aacb90cf4ef74a068c \ + --hash=sha256:9b1eed7670d564f1025d7cda89f99f216c30210e42e95de466135be0b4a499d9 \ + --hash=sha256:9fad9bd5502221ab179f13ea251cb30eef7cf65023156967f86673aff54b53a0 \ + --hash=sha256:ad339509dcfbbc99bf8e147db6686249c4032f26586699ec4c82f6e5909c9fe2 \ + --hash=sha256:bcbeb44fc16e0078b3b68a95e43f821ae34dcbf976dde6985141838a5f23dd3d \ + --hash=sha256:c8e7b05dc6315a193cceaec071cc3cf1c180cea28808ccded0b1283f1c38ba73 \ + --hash=sha256:ca95594d936ee349620900be5b46c0122a1ff6ce42d7d5cb2cf09dc84071ef16 \ + --hash=sha256:d029fac6a80edae80f79c37e5e3abfa92968fe921886139b3ee470a1b177321a \ + --hash=sha256:d17e7fc814eaab93409b80819fd6d30342844345c27f3bc3c4b43c2425a8d267 \ + --hash=sha256:d6821ef9870f32154da873fcde439274f99814ea452dd16b99fa0b66345c4b6b \ + --hash=sha256:e6503534b52bb1720ace9366ee30838a58a3413d3e197512f3338c8f34b5d89d \ + --hash=sha256:ed1df8cc01dd1e3970666a7370b8bfc7457371c58ba88c57bd5bca17ab198053 \ + --hash=sha256:f1d52d052355e0c5c89e0630dd2ff7c0b823fd5f56286a663e92444761b35e25 \ + --hash=sha256:f85b290e5b8b11814efb0d004d8ce6c9a483c35c462e8d9bf84abb93e79fa770 # via scrapy -https://github.com/zulip/zulint/archive/a070f3a349bc0c7ce09ad097b1d3e419b42a6126.zip#egg=zulint==1.0.0+git \ - --hash=sha256:bdd302d0c0b571bad48100822394fb871b92827009bec3b4dcaffe7222c45901 +https://github.com/zulip/zulint/archive/9be0a32bf75a9d8738b005f0b880567fff64e943.zip#egg=zulint==1.0.0+git \ + --hash=sha256:bd4e712d95e69562074ae4f878f0d645caf8a9a7fcee7c88d0c058299e7c8c67 # via -r requirements/dev.in https://github.com/zulip/python-zulip-api/archive/0.9.0.zip#egg=zulip==0.9.0+git&subdirectory=zulip \ --hash=sha256:71ea94192f1a0de1c45fd177e827b65fe4970c4fc8565991fd066b4de77eaccb @@ -3648,8 +3671,8 @@ zxcvbn==4.4.28 \ # via -r requirements/common.in # The following packages are considered to be unsafe in a requirements file: -https://github.com/zulip/pip/archive/39e8b5ecfcd8d19872f103d680713bc8e9de9d00.zip#egg=pip==20.3.4+git \ - --hash=sha256:2c8ae0783fea3e2b98b422d443b19c443779f0881882f905e0728be7b4043d1b +https://github.com/zulip/pip/archive/50e61dcc78d0da8a041e4fecc566f40b2b0604df.zip#egg=pip==20.3.4+git \ + --hash=sha256:35e47938b7e2a91523359b54a53c62fc2aeed5adaaba24437a33cbd9a4641574 # via # -r requirements/pip.in # pip-tools diff --git a/requirements/mypy.in b/requirements/mypy.in index ddb27d5d7e..85499a7362 100644 --- a/requirements/mypy.in +++ b/requirements/mypy.in @@ -1,10 +1,10 @@ # After editing this file, you MUST afterward run # /tools/update-locked-requirements to update requirements/dev.txt. # See requirements/README.md for more detail. -mypy +mypy[faster-cache] boto3-stubs[s3,ses,sns,sqs] -https://github.com/zulip/django-stubs/archive/a006b9993130cc2c1e80f2eb55d959422337554a.zip#egg=django-stubs==5.1.0+git # https://github.com/typeddjango/django-stubs/pull/2408, https://github.com/typeddjango/django-stubs/pull/2411 +django-stubs lxml-stubs SQLAlchemy[mypy] types-beautifulsoup4 diff --git a/requirements/pip.in b/requirements/pip.in index 077c8d2cbf..781ece7a51 100644 --- a/requirements/pip.in +++ b/requirements/pip.in @@ -1,3 +1,3 @@ -https://github.com/zulip/pip/archive/39e8b5ecfcd8d19872f103d680713bc8e9de9d00.zip#egg=pip==20.3.4+git # Our hack for installing specific commits from Git requires --use-deprecated=legacy-resolver: https://github.com/pypa/pip/issues/5780 -setuptools +https://github.com/zulip/pip/archive/50e61dcc78d0da8a041e4fecc566f40b2b0604df.zip#egg=pip==20.3.4+git # Our hack for installing specific commits from Git requires --use-deprecated=legacy-resolver: https://github.com/pypa/pip/issues/5780 +setuptools<71.0.2 # Newer setuptools fools old pip into thinking typing_extensions is already installed wheel diff --git a/requirements/pip.txt b/requirements/pip.txt index be013dc10b..a988f35adf 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -13,8 +13,8 @@ wheel==0.44.0 \ # via -r requirements/pip.in # The following packages are considered to be unsafe in a requirements file: -https://github.com/zulip/pip/archive/39e8b5ecfcd8d19872f103d680713bc8e9de9d00.zip#egg=pip==20.3.4+git \ - --hash=sha256:2c8ae0783fea3e2b98b422d443b19c443779f0881882f905e0728be7b4043d1b +https://github.com/zulip/pip/archive/50e61dcc78d0da8a041e4fecc566f40b2b0604df.zip#egg=pip==20.3.4+git \ + --hash=sha256:35e47938b7e2a91523359b54a53c62fc2aeed5adaaba24437a33cbd9a4641574 # via -r requirements/pip.in setuptools==71.0.0 \ --hash=sha256:98da3b8aca443b9848a209ae4165e2edede62633219afa493a58fbba57f72e2e \ diff --git a/requirements/prod.txt b/requirements/prod.txt index a9f0b3f047..00ae2510cf 100644 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -113,9 +113,9 @@ aiohttp==3.10.10 \ # via # aiohttp-retry # twilio -aiohttp-retry==2.8.3 \ - --hash=sha256:3aeeead8f6afe48272db93ced9440cf4eda8b6fd7ee2abb25357b7eb28525b45 \ - --hash=sha256:9a8e637e31682ad36e1ff9f8bcba912fcfc7d7041722bc901a4b948da4d71ea9 +aiohttp-retry==2.9.0 \ + --hash=sha256:7661af92471e9a96c69d9b8f32021360272073397e6a15bc44c1726b12f46056 \ + --hash=sha256:92c47f1580040208bac95d9a8389a87227ef22758530f2e3f4683395e42c41b5 # via twilio aiosignal==1.3.1 \ --hash=sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc \ @@ -188,13 +188,13 @@ beautifulsoup4==4.12.3 \ # -r requirements/common.in # pyoembed # zulip-bots -boto3==1.35.44 \ - --hash=sha256:18416d07b41e6094101a44f8b881047dcec6b846dad0b9f83b9bbf2f0cd93d07 \ - --hash=sha256:7f8e8a252458d584d8cf7877c372c4f74ec103356eedf43d2dd9e479f47f3639 +boto3==1.35.53 \ + --hash=sha256:a9c0955df0b52b43749d81bde159343a40ea2a3537a46049336fe8193871b18e \ + --hash=sha256:f4124548bb831e13504e805f2fbbfcee06df42fffea0655862c6eb9b95d6d1be # via -r requirements/common.in -botocore==1.35.44 \ - --hash=sha256:1fcd97b966ad8a88de4106fe1bd3bbd6d8dadabe99bbd4a6aadcf11cb6c66b39 \ - --hash=sha256:55388e80624401d017a9a2b8109afd94814f7e666b53e28fce51375cfa8d9326 +botocore==1.35.53 \ + --hash=sha256:12869640f2f9fab3152ea312a6906d5bc6ae15522cc74b6367ee1c273269a28b \ + --hash=sha256:e610ae076ad1eaed5680d3990493659bbabdffd67b15c61d8373a23e4bc41062 # via # boto3 # s3transfer @@ -475,9 +475,9 @@ defusedxml==0.7.1 \ # -r requirements/common.in # python3-openid # social-auth-core -disposable-email-domains==0.0.107 \ - --hash=sha256:1a7b891f644b1234dd2555de5208bcb35a52e32aaef156ff03ad2cc02f8564ed \ - --hash=sha256:a5e5f267d6fe1288840ba67e03816e7478a3ec308948c9ae5aa7f7cfcc31b13c +disposable-email-domains==0.0.108 \ + --hash=sha256:4427cb11962dbfc5bb5afddbb6381fb7072eb7abf941bae4c37e414b544ae3c3 \ + --hash=sha256:57a3ae6cfb03ef29f93c6aa069137f57b60cbcb8295d9f82b0158e357b32c799 # via -r requirements/common.in distro==1.9.0 \ --hash=sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed \ @@ -527,8 +527,9 @@ django-scim2==0.19.1 \ --hash=sha256:8126111160e76a880f6699babc5259f0345c9316c8018ce5dcc3f7579ccb9e89 \ --hash=sha256:845eaa64e72e4ddfe2aa54d21865721f8c1d59ecd9c06e72341eeb03ed6ba94e # via -r requirements/common.in -https://github.com/zulip/django-stubs/archive/a006b9993130cc2c1e80f2eb55d959422337554a.zip#egg=django-stubs-ext==5.1.0+git&subdirectory=ext \ - --hash=sha256:5b04dac961197bb6e7eeb1a453caceec3dfb29bdec85f67bb3c561ed961e01f3 +django-stubs-ext==5.1.1 \ + --hash=sha256:3907f99e178c93323e2ce908aef8352adb8c047605161f8d9e5e7b4efb5a6a9c \ + --hash=sha256:db7364e4f50ae7e5360993dbd58a3a57ea4b2e7e5bab0fbd525ccdb3e7975d1c # via -r requirements/common.in django-two-factor-auth[call,phonenumberslite,sms]==1.17.0 \ --hash=sha256:622e78b0d6cf12eeafa239665d99c1221c399228f2f902fe478aea7759995e0e \ @@ -550,84 +551,99 @@ firebase-admin==6.5.0 \ --hash=sha256:e716dde1447f0a1cd1523be76ff872df33c4e1a3c079564ace033b2ad60bcc4f \ --hash=sha256:fe34ee3ca0e625c5156b3931ca4b4b69b5fc344dbe51bba9706ff674ce277898 # via -r requirements/common.in -frozenlist==1.4.1 \ - --hash=sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7 \ - --hash=sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98 \ - --hash=sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad \ - --hash=sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5 \ - --hash=sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae \ - --hash=sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e \ - --hash=sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a \ - --hash=sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701 \ - --hash=sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d \ - --hash=sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6 \ - --hash=sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6 \ - --hash=sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106 \ - --hash=sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75 \ - --hash=sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868 \ - --hash=sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a \ - --hash=sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0 \ - --hash=sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1 \ - --hash=sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826 \ - --hash=sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec \ - --hash=sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6 \ - --hash=sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950 \ - --hash=sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19 \ - --hash=sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0 \ - --hash=sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8 \ - --hash=sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a \ - --hash=sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09 \ - --hash=sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86 \ - --hash=sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c \ - --hash=sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5 \ - --hash=sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b \ - --hash=sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b \ - --hash=sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d \ - --hash=sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0 \ - --hash=sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea \ - --hash=sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776 \ - --hash=sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a \ - --hash=sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897 \ - --hash=sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7 \ - --hash=sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09 \ - --hash=sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9 \ - --hash=sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe \ - --hash=sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd \ - --hash=sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742 \ - --hash=sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09 \ - --hash=sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0 \ - --hash=sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932 \ - --hash=sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1 \ - --hash=sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a \ - --hash=sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49 \ - --hash=sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d \ - --hash=sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7 \ - --hash=sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480 \ - --hash=sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89 \ - --hash=sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e \ - --hash=sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b \ - --hash=sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82 \ - --hash=sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb \ - --hash=sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068 \ - --hash=sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8 \ - --hash=sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b \ - --hash=sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb \ - --hash=sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2 \ - --hash=sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11 \ - --hash=sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b \ - --hash=sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc \ - --hash=sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0 \ - --hash=sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497 \ - --hash=sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17 \ - --hash=sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0 \ - --hash=sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2 \ - --hash=sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439 \ - --hash=sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5 \ - --hash=sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac \ - --hash=sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825 \ - --hash=sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887 \ - --hash=sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced \ - --hash=sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74 +frozenlist==1.5.0 \ + --hash=sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e \ + --hash=sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf \ + --hash=sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6 \ + --hash=sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a \ + --hash=sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d \ + --hash=sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f \ + --hash=sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28 \ + --hash=sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b \ + --hash=sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9 \ + --hash=sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2 \ + --hash=sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec \ + --hash=sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2 \ + --hash=sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c \ + --hash=sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336 \ + --hash=sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4 \ + --hash=sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d \ + --hash=sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b \ + --hash=sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c \ + --hash=sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10 \ + --hash=sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08 \ + --hash=sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942 \ + --hash=sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8 \ + --hash=sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f \ + --hash=sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10 \ + --hash=sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5 \ + --hash=sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6 \ + --hash=sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21 \ + --hash=sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c \ + --hash=sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d \ + --hash=sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923 \ + --hash=sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608 \ + --hash=sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de \ + --hash=sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17 \ + --hash=sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0 \ + --hash=sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f \ + --hash=sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641 \ + --hash=sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c \ + --hash=sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a \ + --hash=sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0 \ + --hash=sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9 \ + --hash=sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab \ + --hash=sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f \ + --hash=sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3 \ + --hash=sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a \ + --hash=sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784 \ + --hash=sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604 \ + --hash=sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d \ + --hash=sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5 \ + --hash=sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03 \ + --hash=sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e \ + --hash=sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953 \ + --hash=sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee \ + --hash=sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d \ + --hash=sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817 \ + --hash=sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3 \ + --hash=sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039 \ + --hash=sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f \ + --hash=sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9 \ + --hash=sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf \ + --hash=sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76 \ + --hash=sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba \ + --hash=sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171 \ + --hash=sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb \ + --hash=sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439 \ + --hash=sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631 \ + --hash=sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972 \ + --hash=sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d \ + --hash=sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869 \ + --hash=sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9 \ + --hash=sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411 \ + --hash=sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723 \ + --hash=sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2 \ + --hash=sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b \ + --hash=sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99 \ + --hash=sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e \ + --hash=sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840 \ + --hash=sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3 \ + --hash=sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb \ + --hash=sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3 \ + --hash=sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0 \ + --hash=sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca \ + --hash=sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45 \ + --hash=sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e \ + --hash=sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f \ + --hash=sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5 \ + --hash=sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307 \ + --hash=sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e \ + --hash=sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2 \ + --hash=sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778 \ + --hash=sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a \ + --hash=sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30 \ + --hash=sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a # via # aiohttp # aiosignal @@ -635,18 +651,18 @@ future==1.0.0 \ --hash=sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216 \ --hash=sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05 # via python-twitter -google-api-core[grpc]==2.21.0 \ - --hash=sha256:4a152fd11a9f774ea606388d423b68aa7e6d6a0ffe4c8266f74979613ec09f81 \ - --hash=sha256:6869eacb2a37720380ba5898312af79a4d30b8bca1548fb4093e0697dc4bdf5d +google-api-core[grpc]==2.22.0 \ + --hash=sha256:26f8d76b96477db42b55fd02a33aae4a42ec8b86b98b94969b7333a2c828bf35 \ + --hash=sha256:a6652b6bd51303902494998626653671703c420f6f4c88cfd3f50ed723e9d021 # via # firebase-admin # google-api-python-client # google-cloud-core # google-cloud-firestore # google-cloud-storage -google-api-python-client==2.149.0 \ - --hash=sha256:1a5232e9cfed8c201799d9327e4d44dc7ea7daa3c6e1627fca41aa201539c0da \ - --hash=sha256:b9d68c6b14ec72580d66001bd33c5816b78e2134b93ccc5cf8f624516b561750 +google-api-python-client==2.151.0 \ + --hash=sha256:4427b2f47cd88b0355d540c2c52215f68c337f3bc9d6aae1ceeae4525977504c \ + --hash=sha256:a9d26d630810ed4631aea21d1de3e42072f98240aaf184a8a1a874a371115034 # via firebase-admin google-auth==2.35.0 \ --hash=sha256:25df55f327ef021de8be50bad0dfd4a916ad0de96da86cd05661c9297723ad3f \ @@ -847,68 +863,68 @@ greenlet==3.1.1 \ # via # -r requirements/common.in # sqlalchemy -grpcio==1.67.0 \ - --hash=sha256:014dfc020e28a0d9be7e93a91f85ff9f4a87158b7df9952fe23cc42d29d31e1e \ - --hash=sha256:0892dd200ece4822d72dd0952f7112c542a487fc48fe77568deaaa399c1e717d \ - --hash=sha256:0bb94e66cd8f0baf29bd3184b6aa09aeb1a660f9ec3d85da615c5003154bc2bf \ - --hash=sha256:0c69bf11894cad9da00047f46584d5758d6ebc9b5950c0dc96fec7e0bce5cde9 \ - --hash=sha256:15c05a26a0f7047f720da41dc49406b395c1470eef44ff7e2c506a47ac2c0591 \ - --hash=sha256:16724ffc956ea42967f5758c2f043faef43cb7e48a51948ab593570570d1e68b \ - --hash=sha256:227316b5631260e0bef8a3ce04fa7db4cc81756fea1258b007950b6efc90c05d \ - --hash=sha256:2b7183c80b602b0ad816315d66f2fb7887614ead950416d60913a9a71c12560d \ - --hash=sha256:2f55c1e0e2ae9bdd23b3c63459ee4c06d223b68aeb1961d83c48fb63dc29bc03 \ - --hash=sha256:30d47dbacfd20cbd0c8be9bfa52fdb833b395d4ec32fe5cff7220afc05d08571 \ - --hash=sha256:323741b6699cd2b04a71cb38f502db98f90532e8a40cb675393d248126a268af \ - --hash=sha256:3840994689cc8cbb73d60485c594424ad8adb56c71a30d8948d6453083624b52 \ - --hash=sha256:391df8b0faac84d42f5b8dfc65f5152c48ed914e13c522fd05f2aca211f8bfad \ - --hash=sha256:42199e704095b62688998c2d84c89e59a26a7d5d32eed86d43dc90e7a3bd04aa \ - --hash=sha256:54d16383044e681f8beb50f905249e4e7261dd169d4aaf6e52eab67b01cbbbe2 \ - --hash=sha256:5a1e03c3102b6451028d5dc9f8591131d6ab3c8a0e023d94c28cb930ed4b5f81 \ - --hash=sha256:62492bd534979e6d7127b8a6b29093161a742dee3875873e01964049d5250a74 \ - --hash=sha256:662c8e105c5e5cee0317d500eb186ed7a93229586e431c1bf0c9236c2407352c \ - --hash=sha256:682968427a63d898759474e3b3178d42546e878fdce034fd7474ef75143b64e3 \ - --hash=sha256:74b900566bdf68241118f2918d312d3bf554b2ce0b12b90178091ea7d0a17b3d \ - --hash=sha256:77196216d5dd6f99af1c51e235af2dd339159f657280e65ce7e12c1a8feffd1d \ - --hash=sha256:7f200aca719c1c5dc72ab68be3479b9dafccdf03df530d137632c534bb6f1ee3 \ - --hash=sha256:7fc1d2b9fd549264ae585026b266ac2db53735510a207381be509c315b4af4e8 \ - --hash=sha256:82e5bd4b67b17c8c597273663794a6a46a45e44165b960517fe6d8a2f7f16d23 \ - --hash=sha256:8c9a35b8bc50db35ab8e3e02a4f2a35cfba46c8705c3911c34ce343bd777813a \ - --hash=sha256:985b2686f786f3e20326c4367eebdaed3e7aa65848260ff0c6644f817042cb15 \ - --hash=sha256:9d75641a2fca9ae1ae86454fd25d4c298ea8cc195dbc962852234d54a07060ad \ - --hash=sha256:a4e95e43447a02aa603abcc6b5e727d093d161a869c83b073f50b9390ecf0fa8 \ - --hash=sha256:a6b9a5c18863fd4b6624a42e2712103fb0f57799a3b29651c0e5b8119a519d65 \ - --hash=sha256:aa8d025fae1595a207b4e47c2e087cb88d47008494db258ac561c00877d4c8f8 \ - --hash=sha256:ac11ecb34a86b831239cc38245403a8de25037b448464f95c3315819e7519772 \ - --hash=sha256:ae6de510f670137e755eb2a74b04d1041e7210af2444103c8c95f193340d17ee \ - --hash=sha256:b2a44e572fb762c668e4812156b81835f7aba8a721b027e2d4bb29fb50ff4d33 \ - --hash=sha256:b6eb68493a05d38b426604e1dc93bfc0137c4157f7ab4fac5771fd9a104bbaa6 \ - --hash=sha256:b9bca3ca0c5e74dea44bf57d27e15a3a3996ce7e5780d61b7c72386356d231db \ - --hash=sha256:bd79929b3bb96b54df1296cd3bf4d2b770bd1df6c2bdf549b49bab286b925cdc \ - --hash=sha256:c4c425f440fb81f8d0237c07b9322fc0fb6ee2b29fbef5f62a322ff8fcce240d \ - --hash=sha256:cb204a742997277da678611a809a8409657b1398aaeebf73b3d9563b7d154c13 \ - --hash=sha256:cf51d28063338608cd8d3cd64677e922134837902b70ce00dad7f116e3998210 \ - --hash=sha256:cfd9306511fdfc623a1ba1dc3bc07fbd24e6cfbe3c28b4d1e05177baa2f99617 \ - --hash=sha256:cff8e54d6a463883cda2fab94d2062aad2f5edd7f06ae3ed030f2a74756db365 \ - --hash=sha256:d01793653248f49cf47e5695e0a79805b1d9d4eacef85b310118ba1dfcd1b955 \ - --hash=sha256:d4ea4509d42c6797539e9ec7496c15473177ce9abc89bc5c71e7abe50fc25737 \ - --hash=sha256:d90cfdafcf4b45a7a076e3e2a58e7bc3d59c698c4f6470b0bb13a4d869cf2273 \ - --hash=sha256:e090b2553e0da1c875449c8e75073dd4415dd71c9bde6a406240fdf4c0ee467c \ - --hash=sha256:e91d154689639932305b6ea6f45c6e46bb51ecc8ea77c10ef25aa77f75443ad4 \ - --hash=sha256:eef1dce9d1a46119fd09f9a992cf6ab9d9178b696382439446ca5f399d7b96fe \ - --hash=sha256:efe32b45dd6d118f5ea2e5deaed417d8a14976325c93812dd831908522b402c9 \ - --hash=sha256:f4d613fbf868b2e2444f490d18af472ccb47660ea3df52f068c9c8801e1f3e85 \ - --hash=sha256:f55f077685f61f0fbd06ea355142b71e47e4a26d2d678b3ba27248abfe67163a \ - --hash=sha256:f623c57a5321461c84498a99dddf9d13dac0e40ee056d884d6ec4ebcab647a78 \ - --hash=sha256:f6bd2ab135c64a4d1e9e44679a616c9bc944547357c830fafea5c3caa3de5153 \ - --hash=sha256:f95e15db43e75a534420e04822df91f645664bf4ad21dfaad7d51773c80e6bb4 \ - --hash=sha256:fd6bc27861e460fe28e94226e3673d46e294ca4673d46b224428d197c5935e69 \ - --hash=sha256:fe89295219b9c9e47780a0f1c75ca44211e706d1c598242249fe717af3385ec8 +grpcio==1.67.1 \ + --hash=sha256:01f616a964e540638af5130469451cf580ba8c7329f45ca998ab66e0c7dcdb04 \ + --hash=sha256:0489063974d1452436139501bf6b180f63d4977223ee87488fe36858c5725292 \ + --hash=sha256:0e6f255980afef598a9e64a24efce87b625e3e3c80a45162d111a461a9f92955 \ + --hash=sha256:0f3e49c738396e93b7ba9016e153eb09e0778e776df6090c1b8c91877cc1c426 \ + --hash=sha256:178f5db771c4f9a9facb2ab37a434c46cb9be1a75e820f187ee3d1e7805c4f65 \ + --hash=sha256:1a65b503d008f066e994f34f456e0647e5ceb34cfcec5ad180b1b44020ad4970 \ + --hash=sha256:1d7616d2ded471231c701489190379e0c311ee0a6c756f3c03e6a62b95a7146e \ + --hash=sha256:24e8a26dbfc5274d7474c27759b54486b8de23c709d76695237515bc8b5baeab \ + --hash=sha256:267d1745894200e4c604958da5f856da6293f063327cb049a51fe67348e4f953 \ + --hash=sha256:299b3d8c4f790c6bcca485f9963b4846dd92cf6f1b65d3697145d005c80f9fe8 \ + --hash=sha256:3b6c16489326d79ead41689c4b84bc40d522c9a7617219f4ad94bc7f448c5085 \ + --hash=sha256:3dc2ed4cabea4dc14d5e708c2b426205956077cc5de419b4d4079315017e9732 \ + --hash=sha256:43112046864317498a33bdc4797ae6a268c36345a910de9b9c17159d8346602f \ + --hash=sha256:4422581cdc628f77302270ff839a44f4c24fdc57887dc2a45b7e53d8fc2376af \ + --hash=sha256:4e7b904484a634a0fff132958dabdb10d63e0927398273917da3ee103e8d1f78 \ + --hash=sha256:5721e66a594a6c4204458004852719b38f3d5522082be9061d6510b455c90afc \ + --hash=sha256:5db70d32d6703b89912af16d6d45d78406374a8b8ef0d28140351dd0ec610e98 \ + --hash=sha256:5ed601c4c6008429e3d247ddb367fe8c7259c355757448d7c1ef7bd4a6739e8e \ + --hash=sha256:60336bff760fbb47d7e86165408126f1dded184448e9a4c892189eb7c9d3f90f \ + --hash=sha256:608d87d1bdabf9e2868b12338cd38a79969eaf920c89d698ead08f48de9c0f9e \ + --hash=sha256:60e6a4dcf5af7bbc36fd9f81c9f372e8ae580870a9e4b6eafe948cd334b81cf3 \ + --hash=sha256:638354e698fd0c6c76b04540a850bf1db27b4d2515a19fcd5cf645c48d3eb1ed \ + --hash=sha256:699e964923b70f3101393710793289e42845791ea07565654ada0969522d0a38 \ + --hash=sha256:7818c0454027ae3384235a65210bbf5464bd715450e30a3d40385453a85a70cb \ + --hash=sha256:786a5b18544622bfb1e25cc08402bd44ea83edfb04b93798d85dca4d1a0b5be5 \ + --hash=sha256:804c6457c3cd3ec04fe6006c739579b8d35c86ae3298ffca8de57b493524b771 \ + --hash=sha256:80b866f73224b0634f4312a4674c1be21b2b4afa73cb20953cbbb73a6b36c3cc \ + --hash=sha256:85f69fdc1d28ce7cff8de3f9c67db2b0ca9ba4449644488c1e0303c146135ddb \ + --hash=sha256:85f862069b86a305497e74d0dc43c02de3d1d184fc2c180993aa8aa86fbd19b8 \ + --hash=sha256:8a00efecde9d6fcc3ab00c13f816313c040a28450e5e25739c24f432fc6d3c75 \ + --hash=sha256:8a23cbcc5bb11ea7dc6163078be36c065db68d915c24f5faa4f872c573bb400f \ + --hash=sha256:8b0341d66a57f8a3119b77ab32207072be60c9bf79760fa609c5609f2deb1f3f \ + --hash=sha256:917e8d8994eed1d86b907ba2a61b9f0aef27a2155bca6cbb322430fc7135b7bb \ + --hash=sha256:95b5f2b857856ed78d72da93cd7d09b6db8ef30102e5e7fe0961fe4d9f7d48e8 \ + --hash=sha256:9e838cad2176ebd5d4a8bb03955138d6589ce9e2ce5d51c3ada34396dbd2dba8 \ + --hash=sha256:9fd042de4a82e3e7aca44008ee2fb5da01b3e5adb316348c21980f7f58adc311 \ + --hash=sha256:a25bdea92b13ff4d7790962190bf6bf5c4639876e01c0f3dda70fc2769616335 \ + --hash=sha256:a6703916c43b1d468d0756c8077b12017a9fcb6a1ef13faf49e67d20d7ebda62 \ + --hash=sha256:a93deda571a1bf94ec1f6fcda2872dad3ae538700d94dc283c672a3b508ba3af \ + --hash=sha256:aa0162e56fd10a5547fac8774c4899fc3e18c1aa4a4759d0ce2cd00d3696ea6b \ + --hash=sha256:b49359977c6ec9f5d0573ea4e0071ad278ef905aa74e420acc73fd28ce39e9ce \ + --hash=sha256:beee96c8c0b1a75d556fe57b92b58b4347c77a65781ee2ac749d550f2a365dc1 \ + --hash=sha256:c7a01337407dd89005527623a4a72c5c8e2894d22bead0895306b23c6695698f \ + --hash=sha256:c9b929f13677b10f63124c1a410994a401cdd85214ad83ab67cc077fc7e480f0 \ + --hash=sha256:cdc491ae35a13535fd9196acb5afe1af37c8237df2e54427be3eecda3653127e \ + --hash=sha256:e279330bef1744040db8fc432becc8a727b84f456ab62b744d3fdb83f327e121 \ + --hash=sha256:e29ca27bec8e163dca0c98084040edec3bc49afd10f18b412f483cc68c712744 \ + --hash=sha256:e7d1797a8a3845437d327145959a2c0c47c05947c9eef5ff1a4c80e499dcc6fa \ + --hash=sha256:ea33986b70f83844cd00814cee4451055cd8cab36f00ac64a31f5bb09b31919e \ + --hash=sha256:ec74ef02010186185de82cc594058a3ccd8d86821842bbac9873fd4a2cf8be8d \ + --hash=sha256:f26b0b547eb8d00e195274cdfc63ce64c8fc2d3e2d00b12bf468ece41a0423a0 \ + --hash=sha256:f5a27dddefe0e2357d3e617b9079b4bfdc91341a91565111a21ed6ebbc51b22d \ + --hash=sha256:f5b76ff64aaac53fede0cc93abf57894ab2a7362986ba22243d06218b93efe46 \ + --hash=sha256:f9fff78ba10d4250bfc07a01bd6254a6d87dc67f9627adece85c0b2ed754fa96 \ + --hash=sha256:fa0c739ad8b1996bd24823950e3cb5152ae91fca1c09cc791190bf1627ffefba # via # google-api-core # grpcio-status -grpcio-status==1.67.0 \ - --hash=sha256:0e79e2e01ba41a6ca6ed9d7a825323c511fe1653a646f8014c7e3c8132527acc \ - --hash=sha256:c3e5a86fa007e9e263cd5f988a8a907484da4caab582874ea2a4a6092734046b +grpcio-status==1.67.1 \ + --hash=sha256:16e6c085950bdacac97c779e6a502ea671232385e6e37f258884d6883392c2bd \ + --hash=sha256:2bf38395e028ceeecfd8866b081f61628114b384da7d51ae064ddc8d766a5d11 # via google-api-core h2==4.1.0 \ --hash=sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d \ @@ -943,9 +959,9 @@ idna==3.10 \ # via # requests # yarl -ipython==8.28.0 \ - --hash=sha256:0d0d15ca1e01faeb868ef56bc7ee5a0de5bd66885735682e8a322ae289a13d1a \ - --hash=sha256:530ef1e7bb693724d3cdc37287c80b07ad9b25986c007a53aa1857272dac3f35 +ipython==8.29.0 \ + --hash=sha256:0188a1bd83267192123ccea7f4a8ed0a78910535dbaa3f37671dca76ebd429c8 \ + --hash=sha256:40b60e15b22591450eef73e40a027cf77bd652e757523eebc5bd7c7c498290eb # via -r requirements/common.in isodate==0.7.2 \ --hash=sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15 \ @@ -1422,9 +1438,9 @@ multidict==6.1.0 \ # via # aiohttp # yarl -mypy-boto3-s3==1.35.42 \ - --hash=sha256:2d040c05d68a161d91c4b72926127034da28ade98d480def6a9569ab7655cddd \ - --hash=sha256:4e47094a43012be18e8ce858d6ec50f6505ba1d5364ca611093d9f736a38e8b2 +mypy-boto3-s3==1.35.46 \ + --hash=sha256:34d19dfba400f5b9bd6b64f09eb8f8eedef60545b410a3753fe99fec0c41ba78 \ + --hash=sha256:f0087a3765d103b2db565cd8065ebc2b0f70f2dd4e92c132f64b8945dd869940 # via -r requirements/common.in mypy-boto3-ses==1.35.3 \ --hash=sha256:3af5bb82f2ec66221039c61d303baad9699258765556d8ebde3cfa1cd97154d8 \ @@ -1458,64 +1474,65 @@ openapi-spec-validator==0.7.1 \ --hash=sha256:3c81825043f24ccbcd2f4b149b11e8231abce5ba84f37065e14ec947d8f4e959 \ --hash=sha256:8577b85a8268685da6f8aa30990b83b7960d4d1117e901d451b5d572605e5ec7 # via openapi-core -orjson==3.10.9 \ - --hash=sha256:01f5fef452b4d7615f2e94153479370a4b59e0c964efb32dd902978f807a45cd \ - --hash=sha256:060e020d85d0ec145bc1b536b1fd9c10a0519c91991ead9724d6f759ebe26b9a \ - --hash=sha256:063ca59d93d93d1387f0c4bb766c6d4f5b0e423fe7c366d0bd4401a56d1669d1 \ - --hash=sha256:0a9fc7a6cf2b229ddc323e136df13b3fb4466c50d84ed600cd0898223dd2fea3 \ - --hash=sha256:0ab6e3ad10e964392f0e838751bcce2ef9c8fa8be7deddffff83088e5791566d \ - --hash=sha256:0bf37bf0ca538065c34efe1803378b2dadd7e05b06610a086c2857f15ee59e12 \ - --hash=sha256:0c4f5e0360b7f0aba91dafe12469108109a0e8973956d4a9865ca262a6881406 \ - --hash=sha256:0e492b93e122264c2dc78700859122631a4715bda88fabf57d9226954cfe7ec5 \ - --hash=sha256:12e2efe81356b8448f1cd130f8d75d3718de583112d71f2e2f8baa81bd835bb9 \ - --hash=sha256:1471c3274b1a4a9b8f4b9ed6effaea9ad885796373797515c44b365b375c256d \ - --hash=sha256:1b3069b7e2f57f3eef2282029b9c2ba21f08a55f1018e483663a3356f046af4c \ - --hash=sha256:1c3a1e845916a3739ab4162bb48dee66e0e727a19faf397176a7db0d9826cc3c \ - --hash=sha256:2314846e1029a2d2b899140f350eaaf3a73281df43ba84ac44d94ca861b5b269 \ - --hash=sha256:2920c8754f1aedc98bd357ec172af18ce48f5f1017a92244c85fe41d16d3c6e0 \ - --hash=sha256:2ea7a98f3295ed8adb6730a5788cc78dafea28300d19932a1d2143457f7db802 \ - --hash=sha256:41d8cac575acd15918903d74cfaabb5dbe57b357b93341332f647d1013928dcc \ - --hash=sha256:43ad5560db54331c007dc38be5ba7706cb72974a29ae8227019d89305d750a6f \ - --hash=sha256:485358fe9892d6bfd88e5885b66bf88496e1842c8f35f61682ff9928b12a6cf0 \ - --hash=sha256:68ef65223baab00f469c8698f771ab3e6ccf6af2a987e77de5b566b4ec651150 \ - --hash=sha256:6f130848205fea90a2cb9fa2b11cafff9a9f31f4efad225800bc8b9e4a702f24 \ - --hash=sha256:6fdf8d32b6d94019dc15163542d345e9ce4c4661f56b318608aa3088a1a3a23b \ - --hash=sha256:71f73439999fe662843da3607cdf6e75b1551c330f487e5801d463d969091c63 \ - --hash=sha256:731e8859fc99b398c286320726906404091141e9223dd5e9e6917f7e32e1cc68 \ - --hash=sha256:74f5a7a7f282d326be71b722b0c350da7af6f5f15b9378da177e0e4a09bd91a3 \ - --hash=sha256:75b061c11f5aab979a95927a76394b4a85e3e4d63d0a2a16b56a4f7c6503afab \ - --hash=sha256:77d277fa138d4bf145e8b24042004891c188c52ac8492724a183f42b0031cf0c \ - --hash=sha256:7ae82992c00b480c3cc7dac6739324554be8c5d8e858a90044928506a3333ef4 \ - --hash=sha256:7d9d83a91168aa48309acba804e393b7d9216b66f15e38f339b9fbb00db8986d \ - --hash=sha256:80e0c013e50cf7198319d8137931684eb9f32daa067e8276d9dbdd4010bb4add \ - --hash=sha256:938b7fcd79cf06fe348fb24b6163fbaa2fdc9fbed8b1f06318f24467f1487e63 \ - --hash=sha256:95361c4197c7ce9afdf56255de6f4e2474c39d16a277cce31d1b99a2520486d8 \ - --hash=sha256:9d989152df8f60a76867354e0e08d896292ab9fb96a7ef89a5b3838de174522c \ - --hash=sha256:a04f912c32463386ba117591c99a3d9e40b3b69bed9c5123d89dff06f0f5a4b0 \ - --hash=sha256:a377186a11b48c55969e34f0aa414c2826a234f212d6f2b312ba512e3cdb2c6f \ - --hash=sha256:a4948961b6bce1e2086b2cf0b56cc454cdab589d40c7f85be71fb5a5556c51d3 \ - --hash=sha256:ae82ca347829ca47431767b079f96bb977f592189250ccdede676339a80c8982 \ - --hash=sha256:b4289b5d1f88fd05dcafdd7a1f3b17bb722e77712b7618f98e86bdda560e0a1a \ - --hash=sha256:b61b08f6397f004570fd6a840f4a58946b63b4c7029408cdedb45fe85c7d17f7 \ - --hash=sha256:bdce39f96149a74fddeb2674c54f1da5e57724d32952eb6df2ac719b66d453cc \ - --hash=sha256:bfba9605e85bfd19b83a21c2c25c2bed2000d5f097f3fa3ad5b5f8a7263a3148 \ - --hash=sha256:c378074e0c46035dc66e57006993233ec66bf8487d501bab41649b4b7289ed4d \ - --hash=sha256:c7fa3ff6a0d9d15a0d0d2254cca16cd919156a18423654ce5574591392fe9914 \ - --hash=sha256:ca54e6f320e33c8a6e471c424ee16576361d905c15d69e134c2906d3fcb31795 \ - --hash=sha256:cc32a9e43c7693011ccde6f8eff8cba75ca0d2a55de11092faa4a716101e67f5 \ - --hash=sha256:d11383701d4b58e795039b662ada46987744293d57bfa2719e7379b8d67bc796 \ - --hash=sha256:d6ae1b1733e4528e45675ed09a732b6ac37d716bce2facaf467f84ce774adecd \ - --hash=sha256:e0014038a17a1fe273da0a5489787677ef5a64566ab383ad6d929e44ed5683f4 \ - --hash=sha256:e1e91b90c0c26bd79593967c1adef421bcff88c9e723d49c93bb7ad8af80bc6b \ - --hash=sha256:e29bbf08d907756c145a3a3a1f7ce2f11f15e3edbd3342842589d6030981b76f \ - --hash=sha256:e403429e2947a059545e305d97e4b0eb90d3bb44b396d6f327d7ae2018391e13 \ - --hash=sha256:e9ff9521b5be0340c8e686bcfe2619777fd7583f71e7b494601cc91ad3919d2e \ - --hash=sha256:f11949024f785ace1a516db32fa6255f6227226b2c988abf66f5aee61d43d8f7 \ - --hash=sha256:f3bd9df47385b8fabb3b2ee1e83f9960b8accc1905be971a1c257f16c32b491e \ - --hash=sha256:f52d993504827503411df2d60e60acf52885561458d6273f99ecd172f31c4352 \ - --hash=sha256:f9a9eb03a29c9b30b6c8bb35e5fa20d96589a76e0042005be59b7c3af10a7e43 \ - --hash=sha256:fd5083906825d7f5d23089425ce5424d783d6294020bcabb8518a3e1f97833e5 \ - --hash=sha256:fe91c2259c4a859356b6db1c6e649b40577492f66d483da8b8af6da0f87c00e3 +orjson==3.10.10 \ + --hash=sha256:019481fa9ea5ff13b5d5d95e6fd5ab25ded0810c80b150c2c7b1cc8660b662a7 \ + --hash=sha256:081b3fc6a86d72efeb67c13d0ea7c030017bd95f9868b1e329a376edc456153b \ + --hash=sha256:0c25908eb86968613216f3db4d3003f1c45d78eb9046b71056ca327ff92bdbd4 \ + --hash=sha256:0dd57eff09894938b4c86d4b871a479260f9e156fa7f12f8cad4b39ea8028bb5 \ + --hash=sha256:1dcbb0ca5fafb2b378b2c74419480ab2486326974826bbf6588f4dc62137570a \ + --hash=sha256:218cb0bc03340144b6328a9ff78f0932e642199ac184dd74b01ad691f42f93ff \ + --hash=sha256:23458d31fa50ec18e0ec4b0b4343730928296b11111df5f547c75913714116b2 \ + --hash=sha256:23776265c5215ec532de6238a52707048401a568f0fa0d938008e92a147fe2c7 \ + --hash=sha256:24ac62336da9bda1bd93c0491eff0613003b48d3cb5d01470842e7b52a40d5b4 \ + --hash=sha256:2787cd9dedc591c989f3facd7e3e86508eafdc9536a26ec277699c0aa63c685b \ + --hash=sha256:37949383c4df7b4337ce82ee35b6d7471e55195efa7dcb45ab8226ceadb0fe3b \ + --hash=sha256:384cd13579a1b4cd689d218e329f459eb9ddc504fa48c5a83ef4889db7fd7a4f \ + --hash=sha256:3b2625cb37b8fb42e2147404e5ff7ef08712099197a9cd38895006d7053e69d6 \ + --hash=sha256:44bffae68c291f94ff5a9b4149fe9d1bdd4cd0ff0fb575bcea8351d48db629a1 \ + --hash=sha256:5a059afddbaa6dd733b5a2d76a90dbc8af790b993b1b5cb97a1176ca713b5df8 \ + --hash=sha256:6514449d2c202a75183f807bc755167713297c69f1db57a89a1ef4a0170ee269 \ + --hash=sha256:65f9886d3bae65be026219c0a5f32dbbe91a9e6272f56d092ab22561ad0ea33b \ + --hash=sha256:672f9874a8a8fb9bb1b771331d31ba27f57702c8106cdbadad8bda5d10bc1019 \ + --hash=sha256:68b65c93617bcafa7f04b74ae8bc2cc214bd5cb45168a953256ff83015c6747d \ + --hash=sha256:6f9b5c59f7e2a1a410f971c5ebc68f1995822837cd10905ee255f96074537ee6 \ + --hash=sha256:730ed5350147db7beb23ddaf072f490329e90a1d059711d364b49fe352ec987b \ + --hash=sha256:75c38f5647e02d423807d252ce4528bf6a95bd776af999cb1fb48867ed01d1f6 \ + --hash=sha256:766f21487a53aee8524b97ca9582d5c6541b03ab6210fbaf10142ae2f3ced2aa \ + --hash=sha256:78bee66a988f1a333dc0b6257503d63553b1957889c17b2c4ed72385cd1b96ae \ + --hash=sha256:7948cfb909353fce2135dcdbe4521a5e7e1159484e0bb024c1722f272488f2b8 \ + --hash=sha256:804b18e2b88022c8905bb79bd2cbe59c0cd014b9328f43da8d3b28441995cda4 \ + --hash=sha256:829700cc18503efc0cf502d630f612884258020d98a317679cd2054af0259568 \ + --hash=sha256:848ea3b55ab5ccc9d7bbd420d69432628b691fba3ca8ae3148c35156cbd282aa \ + --hash=sha256:8564f48f3620861f5ef1e080ce7cd122ee89d7d6dacf25fcae675ff63b4d6e05 \ + --hash=sha256:879e99486c0fbb256266c7c6a67ff84f46035e4f8749ac6317cc83dacd7f993a \ + --hash=sha256:8cc2a654c08755cef90b468ff17c102e2def0edd62898b2486767204a7f5cc9c \ + --hash=sha256:9972572a1d042ec9ee421b6da69f7cc823da5962237563fa548ab17f152f0b9b \ + --hash=sha256:a12f2003695b10817f0fa8b8fca982ed7f5761dcb0d93cff4f2f9f6709903fd7 \ + --hash=sha256:a8f4bf5f1c85bea2170800020d53a8877812892697f9c2de73d576c9307a8a5f \ + --hash=sha256:aaf29ce0bb5d3320824ec3d1508652421000ba466abd63bdd52c64bcce9eb1fa \ + --hash=sha256:b3be81c42f1242cbed03cbb3973501fcaa2675a0af638f8be494eaf37143d999 \ + --hash=sha256:b788a579b113acf1c57e0a68e558be71d5d09aa67f62ca1f68e01117e550a998 \ + --hash=sha256:bca84df16d6b49325a4084fd8b2fe2229cb415e15c46c529f868c3387bb1339d \ + --hash=sha256:c14ce70e8f39bd71f9f80423801b5d10bf93d1dceffdecd04df0f64d2c69bc01 \ + --hash=sha256:c5bf161a32b479034098c5b81f2608f09167ad2fa1c06abd4e527ea6bf4837a9 \ + --hash=sha256:d5ef198bafdef4aa9d49a4165ba53ffdc0a9e1c7b6f76178572ab33118afea25 \ + --hash=sha256:d78e4cacced5781b01d9bc0f0cd8b70b906a0e109825cb41c1b03f9c41e4ce86 \ + --hash=sha256:d9bbd3a4b92256875cb058c3381b782649b9a3c68a4aa9a2fff020c2f9cfc1be \ + --hash=sha256:dbde6d70cd95ab4d11ea8ac5e738e30764e510fc54d777336eec09bb93b8576c \ + --hash=sha256:dbf3c20c6a7db69df58672a0d5815647ecf78c8e62a4d9bd284e8621c1fe5ccb \ + --hash=sha256:dc6993ab1c2ae7dd0711161e303f1db69062955ac2668181bfdf2dd410e65258 \ + --hash=sha256:dddd5516bcc93e723d029c1633ae79c4417477b4f57dad9bfeeb6bc0315e654a \ + --hash=sha256:e0ceb5e0e8c4f010ac787d29ae6299846935044686509e2f0f06ed441c1ca949 \ + --hash=sha256:e2277ec2cea3775640dc81ab5195bb5b2ada2fe0ea6eee4677474edc75ea6785 \ + --hash=sha256:e27b4c6437315df3024f0835887127dac2a0a3ff643500ec27088d2588fa5ae1 \ + --hash=sha256:e3e67b537ac0c835b25b5f7d40d83816abd2d3f4c0b0866ee981a045287a54f3 \ + --hash=sha256:e4d0d9fe174cc7a5bdce2e6c378bcdb4c49b2bf522a8f996aa586020e1b96cee \ + --hash=sha256:e6eb2598df518281ba0cbc30d24c5b06124ccf7e19169e883c14e0831217a0bc \ + --hash=sha256:e8e28406f97fc2ea0c6150f4c1b6e8261453318930b334abc419214c82314f85 \ + --hash=sha256:eb0a42831372ec2b05acc9ee45af77bcaccbd91257345f93780a8e654efc75db \ + --hash=sha256:f0c4f37f8bf3f1075c6cc8dd8a9f843689a4b618628f8812d0a71e6968b95ffd \ + --hash=sha256:f1d647ca8d62afeb774340a343c7fc023efacfd3a39f70c798991063f0c681dd \ + --hash=sha256:ff38c5fb749347768a603be1fb8a31856458af839f31f064c5aa74aca5be9efe # via -r requirements/common.in parse==1.20.2 \ --hash=sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558 \ @@ -1533,9 +1550,9 @@ pexpect==4.9.0 \ --hash=sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523 \ --hash=sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f # via ipython -phonenumberslite==8.13.47 \ - --hash=sha256:9a4d040f4ef9ea5cbbd907f6fe9a52313d46191051e3a9994102c05082a9db67 \ - --hash=sha256:baf770804c056a122c76f0d29d3a85bd3111c511c5350548e1c3355449b824e9 +phonenumberslite==8.13.48 \ + --hash=sha256:9548bc4c3a7c4d67f7945ba0286e9a37c3ee4ea5531f7ea2518d1cbdc857f50f \ + --hash=sha256:db060fd07421306f8fbc4332ca16b8785df0fa70371d3ed632519546c1a11274 # via django-two-factor-auth pika==1.3.2 \ --hash=sha256:0779a7c1fafd805672796085560d290213a465e4f6f76a6fb19e378d8041a14f \ @@ -1653,24 +1670,24 @@ propcache==0.2.0 \ --hash=sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016 \ --hash=sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504 # via yarl -proto-plus==1.24.0 \ - --hash=sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445 \ - --hash=sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12 +proto-plus==1.25.0 \ + --hash=sha256:c91fc4a65074ade8e458e95ef8bac34d4008daa7cce4a12d6707066fca648961 \ + --hash=sha256:fbb17f57f7bd05a68b7707e745e26528b0b3c34e378db91eef93912c54982d91 # via # google-api-core # google-cloud-firestore -protobuf==5.28.2 \ - --hash=sha256:2c69461a7fcc8e24be697624c09a839976d82ae75062b11a0972e41fd2cd9132 \ - --hash=sha256:35cfcb15f213449af7ff6198d6eb5f739c37d7e4f1c09b5d0641babf2cc0c68f \ - --hash=sha256:52235802093bd8a2811abbe8bf0ab9c5f54cca0a751fdd3f6ac2a21438bffece \ - --hash=sha256:59379674ff119717404f7454647913787034f03fe7049cbef1d74a97bb4593f0 \ - --hash=sha256:5e8a95246d581eef20471b5d5ba010d55f66740942b95ba9b872d918c459452f \ - --hash=sha256:87317e9bcda04a32f2ee82089a204d3a2f0d3c8aeed16568c7daf4756e4f1fe0 \ - --hash=sha256:8ddc60bf374785fb7cb12510b267f59067fa10087325b8e1855b898a0d81d276 \ - --hash=sha256:a8b9403fc70764b08d2f593ce44f1d2920c5077bf7d311fefec999f8c40f78b7 \ - --hash=sha256:c0ea0123dac3399a2eeb1a1443d82b7afc9ff40241433296769f7da42d142ec3 \ - --hash=sha256:ca53faf29896c526863366a52a8f4d88e69cd04ec9571ed6082fa117fac3ab36 \ - --hash=sha256:eeea10f3dc0ac7e6b4933d32db20662902b4ab81bf28df12218aa389e9c2102d +protobuf==5.28.3 \ + --hash=sha256:0c4eec6f987338617072592b97943fdbe30d019c56126493111cf24344c1cc24 \ + --hash=sha256:135658402f71bbd49500322c0f736145731b16fc79dc8f367ab544a17eab4535 \ + --hash=sha256:27b246b3723692bf1068d5734ddaf2fccc2cdd6e0c9b47fe099244d80200593b \ + --hash=sha256:3e6101d095dfd119513cde7259aa703d16c6bbdfae2554dfe5cfdbe94e32d548 \ + --hash=sha256:3fa2de6b8b29d12c61911505d893afe7320ce7ccba4df913e2971461fa36d584 \ + --hash=sha256:64badbc49180a5e401f373f9ce7ab1d18b63f7dd4a9cdc43c92b9f0b481cef7b \ + --hash=sha256:70585a70fc2dd4818c51287ceef5bdba6387f88a578c86d47bb34669b5552c36 \ + --hash=sha256:712319fbdddb46f21abb66cd33cb9e491a5763b2febd8f228251add221981135 \ + --hash=sha256:91fba8f445723fcf400fdbe9ca796b19d3b1242cd873907979b9ed71e4afe868 \ + --hash=sha256:a3f6857551e53ce35e60b403b8a27b0295f7d6eb63d10484f12bc6879c715687 \ + --hash=sha256:cee1757663fa32a1ee673434fcf3bf24dd54763c79690201208bafec62f19eed # via # google-api-core # google-cloud-firestore @@ -1932,9 +1949,9 @@ pypng==0.20220715.0 \ --hash=sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c \ --hash=sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1 # via qrcode -python-binary-memcached==0.31.2 \ - --hash=sha256:290f70451e277df6a39aa0eea3cb6ca2eefcf5d601f957cf2ec1d353d7676c03 \ - --hash=sha256:e5b93d54429e835cab7d5b33988649f9748344aa49adaed8eed94b37e714d562 +python-binary-memcached==0.31.3 \ + --hash=sha256:aa7c7e1a8ef27a3e89f5f1823e04c58d891ef824ce8ca26ba683be5460f17c0a \ + --hash=sha256:e173091db6d2d7d244218ad2732edb4b99988de0894cb544d7b94dbedf2ca416 # via # -r requirements/common.in # django-bmemcached @@ -2034,9 +2051,9 @@ qrcode==7.4.2 \ --hash=sha256:581dca7a029bcb2deef5d01068e39093e80ef00b4a61098a2182eac59d01643a \ --hash=sha256:9dd969454827e127dbd93696b20747239e6d540e082937c90f14ac95b30f5845 # via django-two-factor-auth -redis==5.1.1 \ - --hash=sha256:f6c997521fedbae53387307c5d0bf784d9acc28d9f1d058abeac566ec4dbed72 \ - --hash=sha256:f8ea06b7482a668c6475ae202ed8d9bcaa409f6e87fb77ed1043d912afd62e24 +redis==5.2.0 \ + --hash=sha256:0b1087665a771b1ff2e003aa5bdd354f15a70c9e25d5a7dbf9c722c16528a7b0 \ + --hash=sha256:ae174f2bb3b1bf2b09d54bf3e51fbc1469cf6c10aa03e21141f51969801a7897 # via -r requirements/common.in referencing==0.35.1 \ --hash=sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c \ @@ -2170,110 +2187,110 @@ rfc3339-validator==0.1.4 \ --hash=sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b \ --hash=sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa # via openapi-schema-validator -rpds-py==0.20.0 \ - --hash=sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c \ - --hash=sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585 \ - --hash=sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5 \ - --hash=sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6 \ - --hash=sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef \ - --hash=sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2 \ - --hash=sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29 \ - --hash=sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318 \ - --hash=sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b \ - --hash=sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399 \ - --hash=sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739 \ - --hash=sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee \ - --hash=sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174 \ - --hash=sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a \ - --hash=sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344 \ - --hash=sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2 \ - --hash=sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03 \ - --hash=sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5 \ - --hash=sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22 \ - --hash=sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e \ - --hash=sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96 \ - --hash=sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91 \ - --hash=sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752 \ - --hash=sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075 \ - --hash=sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253 \ - --hash=sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee \ - --hash=sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad \ - --hash=sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5 \ - --hash=sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce \ - --hash=sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7 \ - --hash=sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b \ - --hash=sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8 \ - --hash=sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57 \ - --hash=sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3 \ - --hash=sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec \ - --hash=sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209 \ - --hash=sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921 \ - --hash=sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045 \ - --hash=sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074 \ - --hash=sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580 \ - --hash=sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7 \ - --hash=sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5 \ - --hash=sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3 \ - --hash=sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0 \ - --hash=sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24 \ - --hash=sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139 \ - --hash=sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db \ - --hash=sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc \ - --hash=sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789 \ - --hash=sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f \ - --hash=sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2 \ - --hash=sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c \ - --hash=sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232 \ - --hash=sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6 \ - --hash=sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c \ - --hash=sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29 \ - --hash=sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489 \ - --hash=sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94 \ - --hash=sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751 \ - --hash=sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2 \ - --hash=sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda \ - --hash=sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9 \ - --hash=sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51 \ - --hash=sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c \ - --hash=sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8 \ - --hash=sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989 \ - --hash=sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511 \ - --hash=sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1 \ - --hash=sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2 \ - --hash=sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150 \ - --hash=sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c \ - --hash=sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965 \ - --hash=sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f \ - --hash=sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58 \ - --hash=sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b \ - --hash=sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f \ - --hash=sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d \ - --hash=sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821 \ - --hash=sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de \ - --hash=sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121 \ - --hash=sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855 \ - --hash=sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272 \ - --hash=sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60 \ - --hash=sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02 \ - --hash=sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1 \ - --hash=sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140 \ - --hash=sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879 \ - --hash=sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940 \ - --hash=sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364 \ - --hash=sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4 \ - --hash=sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e \ - --hash=sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420 \ - --hash=sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5 \ - --hash=sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24 \ - --hash=sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c \ - --hash=sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf \ - --hash=sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f \ - --hash=sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e \ - --hash=sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab \ - --hash=sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08 \ - --hash=sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92 \ - --hash=sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a \ - --hash=sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8 +rpds-py==0.20.1 \ + --hash=sha256:02a0629ec053fc013808a85178524e3cb63a61dbc35b22499870194a63578fb9 \ + --hash=sha256:07924c1b938798797d60c6308fa8ad3b3f0201802f82e4a2c41bb3fafb44cc28 \ + --hash=sha256:07f59760ef99f31422c49038964b31c4dfcfeb5d2384ebfc71058a7c9adae2d2 \ + --hash=sha256:0a3a1e9ee9728b2c1734f65d6a1d376c6f2f6fdcc13bb007a08cc4b1ff576dc5 \ + --hash=sha256:0a90c373ea2975519b58dece25853dbcb9779b05cc46b4819cb1917e3b3215b6 \ + --hash=sha256:0ad56edabcdb428c2e33bbf24f255fe2b43253b7d13a2cdbf05de955217313e6 \ + --hash=sha256:0b581f47257a9fce535c4567782a8976002d6b8afa2c39ff616edf87cbeff712 \ + --hash=sha256:0f8f741b6292c86059ed175d80eefa80997125b7c478fb8769fd9ac8943a16c0 \ + --hash=sha256:0fc212779bf8411667234b3cdd34d53de6c2b8b8b958e1e12cb473a5f367c338 \ + --hash=sha256:13c56de6518e14b9bf6edde23c4c39dac5b48dcf04160ea7bce8fca8397cdf86 \ + --hash=sha256:142c0a5124d9bd0e2976089484af5c74f47bd3298f2ed651ef54ea728d2ea42c \ + --hash=sha256:14511a539afee6f9ab492b543060c7491c99924314977a55c98bfa2ee29ce78c \ + --hash=sha256:15a842bb369e00295392e7ce192de9dcbf136954614124a667f9f9f17d6a216f \ + --hash=sha256:16d4477bcb9fbbd7b5b0e4a5d9b493e42026c0bf1f06f723a9353f5153e75d30 \ + --hash=sha256:1791ff70bc975b098fe6ecf04356a10e9e2bd7dc21fa7351c1742fdeb9b4966f \ + --hash=sha256:19b73643c802f4eaf13d97f7855d0fb527fbc92ab7013c4ad0e13a6ae0ed23bd \ + --hash=sha256:200a23239781f46149e6a415f1e870c5ef1e712939fe8fa63035cd053ac2638e \ + --hash=sha256:2249280b870e6a42c0d972339e9cc22ee98730a99cd7f2f727549af80dd5a963 \ + --hash=sha256:2b431c777c9653e569986ecf69ff4a5dba281cded16043d348bf9ba505486f36 \ + --hash=sha256:2cc3712a4b0b76a1d45a9302dd2f53ff339614b1c29603a911318f2357b04dd2 \ + --hash=sha256:2fbb0ffc754490aff6dabbf28064be47f0f9ca0b9755976f945214965b3ace7e \ + --hash=sha256:32b922e13d4c0080d03e7b62991ad7f5007d9cd74e239c4b16bc85ae8b70252d \ + --hash=sha256:36785be22066966a27348444b40389f8444671630063edfb1a2eb04318721e17 \ + --hash=sha256:37fe0f12aebb6a0e3e17bb4cd356b1286d2d18d2e93b2d39fe647138458b4bcb \ + --hash=sha256:3aea7eed3e55119635a74bbeb80b35e776bafccb70d97e8ff838816c124539f1 \ + --hash=sha256:3c6afcf2338e7f374e8edc765c79fbcb4061d02b15dd5f8f314a4af2bdc7feb5 \ + --hash=sha256:3ccb8ac2d3c71cda472b75af42818981bdacf48d2e21c36331b50b4f16930163 \ + --hash=sha256:3d089d0b88996df627693639d123c8158cff41c0651f646cd8fd292c7da90eaf \ + --hash=sha256:3dd645e2b0dcb0fd05bf58e2e54c13875847687d0b71941ad2e757e5d89d4356 \ + --hash=sha256:3e310838a5801795207c66c73ea903deda321e6146d6f282e85fa7e3e4854804 \ + --hash=sha256:42cbde7789f5c0bcd6816cb29808e36c01b960fb5d29f11e052215aa85497c93 \ + --hash=sha256:483b29f6f7ffa6af845107d4efe2e3fa8fb2693de8657bc1849f674296ff6a5a \ + --hash=sha256:4888e117dd41b9d34194d9e31631af70d3d526efc363085e3089ab1a62c32ed1 \ + --hash=sha256:49fe9b04b6fa685bd39237d45fad89ba19e9163a1ccaa16611a812e682913496 \ + --hash=sha256:4a5a844f68776a7715ecb30843b453f07ac89bad393431efbf7accca3ef599c1 \ + --hash=sha256:4a916087371afd9648e1962e67403c53f9c49ca47b9680adbeef79da3a7811b0 \ + --hash=sha256:4f676e21db2f8c72ff0936f895271e7a700aa1f8d31b40e4e43442ba94973899 \ + --hash=sha256:518d2ca43c358929bf08f9079b617f1c2ca6e8848f83c1225c88caeac46e6cbc \ + --hash=sha256:5265505b3d61a0f56618c9b941dc54dc334dc6e660f1592d112cd103d914a6db \ + --hash=sha256:55cd1fa4ecfa6d9f14fbd97ac24803e6f73e897c738f771a9fe038f2f11ff07c \ + --hash=sha256:58b1d5dd591973d426cbb2da5e27ba0339209832b2f3315928c9790e13f159e8 \ + --hash=sha256:59240685e7da61fb78f65a9f07f8108e36a83317c53f7b276b4175dc44151684 \ + --hash=sha256:5b48e790e0355865197ad0aca8cde3d8ede347831e1959e158369eb3493d2191 \ + --hash=sha256:5d4eea0761e37485c9b81400437adb11c40e13ef513375bbd6973e34100aeb06 \ + --hash=sha256:648386ddd1e19b4a6abab69139b002bc49ebf065b596119f8f37c38e9ecee8ff \ + --hash=sha256:653647b8838cf83b2e7e6a0364f49af96deec64d2a6578324db58380cff82aca \ + --hash=sha256:6740a3e8d43a32629bb9b009017ea5b9e713b7210ba48ac8d4cb6d99d86c8ee8 \ + --hash=sha256:6889469bfdc1eddf489729b471303739bf04555bb151fe8875931f8564309afc \ + --hash=sha256:68cb0a499f2c4a088fd2f521453e22ed3527154136a855c62e148b7883b99f9a \ + --hash=sha256:6aa97af1558a9bef4025f8f5d8c60d712e0a3b13a2fe875511defc6ee77a1ab7 \ + --hash=sha256:6b73c67850ca7cae0f6c56f71e356d7e9fa25958d3e18a64927c2d930859b8e4 \ + --hash=sha256:6c8e9340ce5a52f95fa7d3b552b35c7e8f3874d74a03a8a69279fd5fca5dc751 \ + --hash=sha256:6ca91093a4a8da4afae7fe6a222c3b53ee4eef433ebfee4d54978a103435159e \ + --hash=sha256:754bbed1a4ca48479e9d4182a561d001bbf81543876cdded6f695ec3d465846b \ + --hash=sha256:762703bdd2b30983c1d9e62b4c88664df4a8a4d5ec0e9253b0231171f18f6d75 \ + --hash=sha256:78f0b6877bfce7a3d1ff150391354a410c55d3cdce386f862926a4958ad5ab7e \ + --hash=sha256:7a07ced2b22f0cf0b55a6a510078174c31b6d8544f3bc00c2bcee52b3d613f74 \ + --hash=sha256:7dca7081e9a0c3b6490a145593f6fe3173a94197f2cb9891183ef75e9d64c425 \ + --hash=sha256:7e21b7031e17c6b0e445f42ccc77f79a97e2687023c5746bfb7a9e45e0921b84 \ + --hash=sha256:7f5179583d7a6cdb981151dd349786cbc318bab54963a192692d945dd3f6435d \ + --hash=sha256:83cba698cfb3c2c5a7c3c6bac12fe6c6a51aae69513726be6411076185a8b24a \ + --hash=sha256:842c19a6ce894493563c3bd00d81d5100e8e57d70209e84d5491940fdb8b9e3a \ + --hash=sha256:84b8382a90539910b53a6307f7c35697bc7e6ffb25d9c1d4e998a13e842a5e83 \ + --hash=sha256:8ba6f89cac95c0900d932c9efb7f0fb6ca47f6687feec41abcb1bd5e2bd45535 \ + --hash=sha256:8bbe951244a838a51289ee53a6bae3a07f26d4e179b96fc7ddd3301caf0518eb \ + --hash=sha256:925d176a549f4832c6f69fa6026071294ab5910e82a0fe6c6228fce17b0706bd \ + --hash=sha256:92b68b79c0da2a980b1c4197e56ac3dd0c8a149b4603747c4378914a68706979 \ + --hash=sha256:93da1d3db08a827eda74356f9f58884adb254e59b6664f64cc04cdff2cc19b0d \ + --hash=sha256:95f3b65d2392e1c5cec27cff08fdc0080270d5a1a4b2ea1d51d5f4a2620ff08d \ + --hash=sha256:9c4cb04a16b0f199a8c9bf807269b2f63b7b5b11425e4a6bd44bd6961d28282c \ + --hash=sha256:a624cc00ef2158e04188df5e3016385b9353638139a06fb77057b3498f794782 \ + --hash=sha256:a649dfd735fff086e8a9d0503a9f0c7d01b7912a333c7ae77e1515c08c146dad \ + --hash=sha256:a94e52537a0e0a85429eda9e49f272ada715506d3b2431f64b8a3e34eb5f3e75 \ + --hash=sha256:aa7ac11e294304e615b43f8c441fee5d40094275ed7311f3420d805fde9b07b4 \ + --hash=sha256:b41b6321805c472f66990c2849e152aff7bc359eb92f781e3f606609eac877ad \ + --hash=sha256:b71b8666eeea69d6363248822078c075bac6ed135faa9216aa85f295ff009b1e \ + --hash=sha256:b9c2fe36d1f758b28121bef29ed1dee9b7a2453e997528e7d1ac99b94892527c \ + --hash=sha256:bb63804105143c7e24cee7db89e37cb3f3941f8e80c4379a0b355c52a52b6780 \ + --hash=sha256:be5ef2f1fc586a7372bfc355986226484e06d1dc4f9402539872c8bb99e34b01 \ + --hash=sha256:c142b88039b92e7e0cb2552e8967077e3179b22359e945574f5e2764c3953dcf \ + --hash=sha256:c14937af98c4cc362a1d4374806204dd51b1e12dded1ae30645c298e5a5c4cb1 \ + --hash=sha256:ca449520e7484534a2a44faf629362cae62b660601432d04c482283c47eaebab \ + --hash=sha256:cd945871335a639275eee904caef90041568ce3b42f402c6959b460d25ae8732 \ + --hash=sha256:d0b937b2a1988f184a3e9e577adaa8aede21ec0b38320d6009e02bd026db04fa \ + --hash=sha256:d126b52e4a473d40232ec2052a8b232270ed1f8c9571aaf33f73a14cc298c24f \ + --hash=sha256:d8761c3c891cc51e90bc9926d6d2f59b27beaf86c74622c8979380a29cc23ac3 \ + --hash=sha256:d9ecb51120de61e4604650666d1f2b68444d46ae18fd492245a08f53ad2b7711 \ + --hash=sha256:da584ff96ec95e97925174eb8237e32f626e7a1a97888cdd27ee2f1f24dd0ad8 \ + --hash=sha256:dbcf360c9e3399b056a238523146ea77eeb2a596ce263b8814c900263e46031a \ + --hash=sha256:dbddc10776ca7ebf2a299c41a4dde8ea0d8e3547bfd731cb87af2e8f5bf8962d \ + --hash=sha256:dc73505153798c6f74854aba69cc75953888cf9866465196889c7cdd351e720c \ + --hash=sha256:e13de156137b7095442b288e72f33503a469aa1980ed856b43c353ac86390519 \ + --hash=sha256:e1791c4aabd117653530dccd24108fa03cc6baf21f58b950d0a73c3b3b29a350 \ + --hash=sha256:e75ba609dba23f2c95b776efb9dd3f0b78a76a151e96f96cc5b6b1b0004de66f \ + --hash=sha256:e79059d67bea28b53d255c1437b25391653263f0e69cd7dec170d778fdbca95e \ + --hash=sha256:ecd27a66740ffd621d20b9a2f2b5ee4129a56e27bfb9458a3bcc2e45794c96cb \ + --hash=sha256:f009c69bc8c53db5dfab72ac760895dc1f2bc1b62ab7408b253c8d1ec52459fc \ + --hash=sha256:f16bc1334853e91ddaaa1217045dd7be166170beec337576818461268a3de67f \ + --hash=sha256:f19169781dddae7478a32301b499b2858bc52fc45a112955e798ee307e294977 \ + --hash=sha256:fa3060d885657abc549b2a0f8e1b79699290e5d83845141717c6c90c2df38311 \ + --hash=sha256:fa41a64ac5b08b292906e248549ab48b69c5428f3987b09689ab2441f267d04d \ + --hash=sha256:fbf15aff64a163db29a91ed0868af181d6f68ec1a3a7d5afcfe4501252840bad \ + --hash=sha256:fe00a9057d100e69b4ae4a094203a708d65b0f345ed546fdef86498bf5390982 # via # jsonschema # referencing @@ -2376,9 +2393,9 @@ stack-data==0.6.3 \ --hash=sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9 \ --hash=sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695 # via ipython -stripe==11.1.1 \ - --hash=sha256:0bbdfe54a09728fc54db6bb099b2f440ffc111d07d9674b0f04bfd0d3c1cbdcf \ - --hash=sha256:e79e02238d0ec7c89a64986af941dcae41e4857489b7cc83497acce9def356e5 +stripe==11.2.0 \ + --hash=sha256:4c53d61d7b596070324bfa5d7215843145fe5466e48973d828aab41ad209b5ce \ + --hash=sha256:dec812eabc95488862be40e6c799acdaf2e1225d686490a793f949fab745fdd0 # via -r requirements/common.in https://github.com/zulip/talon/archive/e3879d82331aa8b5a87e9d41b3ba3693caa24cd2.zip#egg=talon-core==1.6.0+git&subdirectory=talon-core \ --hash=sha256:ecd16ee13fa1d82582cec992c96f1996e9f825873b7ef6f72eb6d1820766f1a8 @@ -2406,9 +2423,9 @@ traitlets==5.14.3 \ # via # ipython # matplotlib-inline -twilio==9.3.4 \ - --hash=sha256:2cae99f0f7aecbd9da02fa59ad8f11b360db4a9281fc3fb3237ad50be21d8a9b \ - --hash=sha256:38a6ab04752f44313dcf736eae45236a901528d3f53dfc21d3afd33539243c7f +twilio==9.3.6 \ + --hash=sha256:c5d7f4cfeb50a7928397b8f819c8f7fb2bb956a1a2cabbda1df1d7a40f9ce1d7 \ + --hash=sha256:d42691f7fe1faaa5ba82942f169bfea4d7f01a0a542a456d82018fb49bd1f5b2 # via django-two-factor-auth typing-extensions==4.12.2 \ --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ @@ -2448,8 +2465,8 @@ urllib3==2.2.3 \ # botocore # requests # sentry-sdk -uwsgi==2.0.27 \ - --hash=sha256:3ee5bfb7e6e9c93478c22aa8183eef35b95a2d5b14cca16172e67f135565c458 +uwsgi==2.0.28 \ + --hash=sha256:79ca1891ef2df14508ab0471ee8c0eb94bd2d51d03f32f90c4bbe557ab1e99d0 # via -r requirements/prod.in virtualenv-clone==0.5.7 \ --hash=sha256:418ee935c36152f8f153c79824bb93eaf6f0f7984bae31d3f48f350b9183501a \ @@ -2463,9 +2480,9 @@ webencodings==0.5.1 \ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 # via html5lib -werkzeug==3.0.4 \ - --hash=sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c \ - --hash=sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306 +werkzeug==3.1.0 \ + --hash=sha256:208a2e31a4a54c8b3d2244f2079ca1d3851629a7a7d546646059c64fb746023a \ + --hash=sha256:6f2a0d38f25ba5a75c36c45b4ae350c7a23b57e3b974e9eb2d6851f2c648c00d # via openapi-core wheel==0.44.0 \ --hash=sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f \ @@ -2531,89 +2548,89 @@ xmlsec==1.3.14 \ --hash=sha256:e732a75fcb6b84872b168f972fbbf3749baf76308635f14015d1d35ed0c5719c \ --hash=sha256:ed4034939d8566ccdcd3b4e4f23c63fd807fb8763ae5668d59a19e11640a8242 # via python3-saml -yarl==1.15.5 \ - --hash=sha256:00bb3a559d7bd006a5302ecd7e409916939106a8cdbe31f4eb5e5b9ffcca57ea \ - --hash=sha256:0327081978fe186c3390dd4f73f95f825d0bb9c74967e22c2a1a87735974d8f5 \ - --hash=sha256:041bafaa82b77fd4ec2826d42a55461ec86d999adf7ed9644eef7e8a9febb366 \ - --hash=sha256:06ec070a2d71415f90dbe9d70af3158e7da97a128519dba2d1581156ee27fb92 \ - --hash=sha256:07a4b53abe85813c538b9cdbb02909ebe3734e3af466a587df516e960d500cc8 \ - --hash=sha256:0a843e692f9d5402b3455653f4607dc521de2385f01c5cad7ba4a87c46e2ea8d \ - --hash=sha256:10bfe0bef4cf5ea0383886beda004071faadedf2647048b9f876664284c5b60d \ - --hash=sha256:18940191ec9a83bbfe63eea61c3e9d12474bb910d5613bce8fa46e84a80b75b2 \ - --hash=sha256:19e2a4b2935f95fad0949f420514c5d862f5f18058fbbfd8854f496a97d9fd87 \ - --hash=sha256:1a6b6e95bc621c11cf9ff21012173337e789f2461ebc3b4e5bf65c74ef69adb8 \ - --hash=sha256:1f5a1ca6eaabfe62718b87eac06d9a47b30cf92ffa065fee9196d3ecd24a3cf1 \ - --hash=sha256:21050b6cd569980fe20ceeab4baeb900d3f7247270475e42bafe117416a5496c \ - --hash=sha256:2597a589859b94d0a5e2f5d30fee95081867926e57cb751f8b44a7dd92da4e79 \ - --hash=sha256:294c742a273f44511f14b03a9e06b66094dcdf4bbb75a5e23fead548fd5310ae \ - --hash=sha256:2eeb9ba53c055740cd282ae9d34eb7970d65e73a46f15adec4b0c1b0f2e55cc2 \ - --hash=sha256:30ca64521f1a96b72886dd9e8652f16eab11891b4572dcfcfc1ad6d6ccb27abd \ - --hash=sha256:325e2beb2cd8654b276e7686a3cd203628dd3fe32d5c616e632bc35a2901fb16 \ - --hash=sha256:34816f1d833433a16c4832562a050b0a60eac53dcb71b2032e6ebff82d74b6a7 \ - --hash=sha256:362da97ad4360e4ef1dd24ccdd3bceb18332da7f40026a42f49b7edd686e31c3 \ - --hash=sha256:3b30f13fac56598474071a4f1ecd66c78fdaf2f8619042d7ca135f72dbb348cf \ - --hash=sha256:44088ec0be82fba118ed29b6b429f80bf295297727adae4c257ac297e01e8bcd \ - --hash=sha256:44359c52af9c383e5107f3b6301446fc8269599721fa42fafb2afb5f31a42dcb \ - --hash=sha256:4ac83b307cc4b8907345b52994055c6c3c2601ceb6fcb94c5ed6a93c6b4e8257 \ - --hash=sha256:5093a453176a4fad4f9c3006f507cf300546190bb3e27944275a37cfd6323a65 \ - --hash=sha256:524b3bb7dff320e305bc979c65eddc0342548c56ea9241502f907853fe53c408 \ - --hash=sha256:5848500b6a01497560969e8c3a7eb1b2570853c74a0ca6f67ebaf6064106c49b \ - --hash=sha256:5882faa2a6e684f65ee44f18c701768749a950cbd5e72db452fc07805f6bdec0 \ - --hash=sha256:5b8af4165e097ff84d9bbb97bb4f4d7f71b9c1c9565a2d0e27d93e5f92dae220 \ - --hash=sha256:5c3ac5bdcc1375c8ee52784adf94edbce37c471dd2100a117cfef56fe8dbc2b4 \ - --hash=sha256:5d6be369488d503c8edc14e2f63d71ab2a607041ad216a8ad444fa18e8dea792 \ - --hash=sha256:5fadcf532fd9f6cbad71485ef8c2462dd9a91d3efc72ca01eb0970792c92552a \ - --hash=sha256:607683991bab8607e5158cd290dd8fdaa613442aeab802fe1c237d3a3eee7358 \ - --hash=sha256:625f31d6650829fba4030b4e7bdb2d69e41510dddfa29a1da27076c199521757 \ - --hash=sha256:63d46606b20f80a6476f1044bab78e1a69c2e0747f174583e2f12fc70bad2170 \ - --hash=sha256:6493da9ba5c551978c679ab04856c2cf8f79c316e8ec8c503460a135705edc3b \ - --hash=sha256:6563394492c96cb57f4dff0c69c63d2b28b5469c59c66f35a1e6451583cd0ab4 \ - --hash=sha256:68d21d0563d82aaf46163eac529adac301b20be3181b8a2811f7bd5615466055 \ - --hash=sha256:68e837b3edfcd037f9706157e7cb8efda832de6248c7d9e893e2638356dfae5d \ - --hash=sha256:6b3d2767bd64c62909ea33525b954ba05c8f9726bfdf2141d175da4e344f19ae \ - --hash=sha256:6e2c674cfe4c03ad7a4d536b1f808221f0d11a360486b4b032d2557c0bd633ad \ - --hash=sha256:70d074d5a96e0954fe6db81ff356f4361397da1cda3f7c127fc0902f671a087e \ - --hash=sha256:71730658be0b5de7c570a9795d7404c577b2313c1db370407092c66f70e04ccb \ - --hash=sha256:73143dd279e641543da52c55652ad7b4c7c5f79e797f124f58f04cc060f14271 \ - --hash=sha256:75d04ba8ed335042328086e643e01165e0c24598216f72da709b375930ae3bdb \ - --hash=sha256:7825506fbee4055265528ec3532a8197ff26fc53d4978917a4c8ddbb4c1667d7 \ - --hash=sha256:7983290ede3aaa2c9620879530849532529b4dcbf5b12a0b6a91163a773eadb9 \ - --hash=sha256:7abd7d15aedb3961a967cc65f8144dbbca42e3626a21c5f4f29919cf43eeafb9 \ - --hash=sha256:8249147ee81c1cf4d1dc6f26ba28a1b9d92751529f83c308ad02164bb93abd0d \ - --hash=sha256:86648c53b10c53db8b967a75fb41e0c89dbec7398f6525e34af2b6c456bb0ac0 \ - --hash=sha256:8669a110f655c9eb22f16fb68a7d4942020aeaa09f1def584a80183e3e89953c \ - --hash=sha256:8b7dd6983c81523f9de0ae6334c3b7a3cb33283936e0525f80c4f713f54a9bb6 \ - --hash=sha256:8fc727f0fb388debc771eaa7091c092bd2e8b6b4741b73354b8efadcf96d6031 \ - --hash=sha256:9162ea117ce8bad8ebc95b7376b4135988acd888d2cf4702f8281e3c11f8b81f \ - --hash=sha256:94189746c5ad62e1014a16298130e696fe593d031d442ef135fb7787b7a1f820 \ - --hash=sha256:94ab1185900f43760d5487c8e49f5f1a66f864e36092f282f1813597479b9dfa \ - --hash=sha256:96ce879799fee124d241ea3b84448378f638e290c49493d00b706f3fd57ec22b \ - --hash=sha256:9aa054d97033beac9cb9b19b7c0b8784b85b12cd17879087ca6bffba57884e02 \ - --hash=sha256:9c2d1109c8d92059314cc34dd8f0a31f74b720dc140744923ed7ca228bf9b491 \ - --hash=sha256:a082dc948045606f62dca0228ab24f13737180b253378d6443f5b2b9ef8beefe \ - --hash=sha256:a7d317fb80bc17ed4b34a9aad8b80cef34bea0993654f3e8566daf323def7ef9 \ - --hash=sha256:b06d8b05d0fafef204d635a4711283ddbf19c7c0facdc61b4b775f6e47e2d4be \ - --hash=sha256:b1217102a455e3ac9ac293081093f21f0183e978c7692171ff669fee5296fa28 \ - --hash=sha256:b6c57972a406ea0f61e3f28f2b3a780fb71fbe1d82d267afe5a2f889a83ee7e7 \ - --hash=sha256:b997a806846c00d1f41d6a251803732837771b2091bead7566f68820e317bfe7 \ - --hash=sha256:bb129f77ddaea2d8e6e00417b8d907448de3407af4eddacca0a515574ad71493 \ - --hash=sha256:bb707859218e8335447b210f41a755e7b1367c33e87add884128bba144694a7f \ - --hash=sha256:c166ad987265bb343be58cdf4fbc4478cc1d81f2246d2be9a15f94393b269faa \ - --hash=sha256:c884dfa56b050f718ea3cbbfd972e29a6f07f63a7449b10d9a20d64f7eec92e2 \ - --hash=sha256:cbf36099a9b407e1456dbf55844743a98603fcba32d2a46fb3a698d926facf1b \ - --hash=sha256:cd529e637cd23204bd82072f6637cff7af2516ad2c132e8f3342cbc84871f7d1 \ - --hash=sha256:d3309ee667f2d9c7ac9ecf44620d6b274bfdd8065b8c5019ff6795dd887b8fed \ - --hash=sha256:d56980374a10c74255fcea6ebcfb0aeca7166d212ee9fd7e823ddef35fb62ad0 \ - --hash=sha256:d7fa4b033e2f267e37aabcc36949fa89f9f1716a723395912147f9cf3fb437c7 \ - --hash=sha256:da48cdff56b01ea4282a6d04b83b07a2088351a4a3ff7aacc1e7e9b6b04b90b9 \ - --hash=sha256:de6917946dc6bc237d4b354e38aa13a232e0c7948fdbdb160edee3862e9d735f \ - --hash=sha256:e27861251d9c094f641d39a8a78dd2371fb9a252ea2f689d1ad353a31d46a0bc \ - --hash=sha256:e652aa9f8dfa808bc5b2da4d1f4e286cf1d640570fdfa72ffc0c1d16ba114651 \ - --hash=sha256:e8aa19c39cb20bfb16f0266df175a6004943122cf20707fbf0cacc21f6468a25 \ - --hash=sha256:ed9c72d5361cfd5af5ccadffa8f8077f4929640e1f938aa0f4b92c5a24996ac5 \ - --hash=sha256:f7de0d4b6b4d8a77e422eb54d765255c0ec6883ee03b8fd537101633948619d7 \ - --hash=sha256:fcfd663dc88465ebe41c7c938bdc91c4b01cda96a0d64bf38fd66c1877323771 \ - --hash=sha256:fd56de8b645421ff09c993fdb0ee9c5a3b50d290a8f55793b500d99b34d0c1ce +yarl==1.17.1 \ + --hash=sha256:06157fb3c58f2736a5e47c8fcbe1afc8b5de6fb28b14d25574af9e62150fcaac \ + --hash=sha256:067a63fcfda82da6b198fa73079b1ca40b7c9b7994995b6ee38acda728b64d47 \ + --hash=sha256:0b1794853124e2f663f0ea54efb0340b457f08d40a1cef78edfa086576179c91 \ + --hash=sha256:0bdff5e0995522706c53078f531fb586f56de9c4c81c243865dd5c66c132c3b5 \ + --hash=sha256:117ed8b3732528a1e41af3aa6d4e08483c2f0f2e3d3d7dca7cf538b3516d93df \ + --hash=sha256:14bc88baa44e1f84164a392827b5defb4fa8e56b93fecac3d15315e7c8e5d8b3 \ + --hash=sha256:1654ec814b18be1af2c857aa9000de7a601400bd4c9ca24629b18486c2e35463 \ + --hash=sha256:16bca6678a83657dd48df84b51bd56a6c6bd401853aef6d09dc2506a78484c7b \ + --hash=sha256:1a3b91c44efa29e6c8ef8a9a2b583347998e2ba52c5d8280dbd5919c02dfc3b5 \ + --hash=sha256:1a52a1ffdd824fb1835272e125385c32fd8b17fbdefeedcb4d543cc23b332d74 \ + --hash=sha256:1ce36ded585f45b1e9bb36d0ae94765c6608b43bd2e7f5f88079f7a85c61a4d3 \ + --hash=sha256:299f11b44d8d3a588234adbe01112126010bd96d9139c3ba7b3badd9829261c3 \ + --hash=sha256:2b24ec55fad43e476905eceaf14f41f6478780b870eda5d08b4d6de9a60b65b4 \ + --hash=sha256:2d374d70fdc36f5863b84e54775452f68639bc862918602d028f89310a034ab0 \ + --hash=sha256:2d9f0606baaec5dd54cb99667fcf85183a7477f3766fbddbe3f385e7fc253299 \ + --hash=sha256:2e7ba4c9377e48fb7b20dedbd473cbcbc13e72e1826917c185157a137dac9df2 \ + --hash=sha256:2f0a6423295a0d282d00e8701fe763eeefba8037e984ad5de44aa349002562ac \ + --hash=sha256:327828786da2006085a4d1feb2594de6f6d26f8af48b81eb1ae950c788d97f61 \ + --hash=sha256:380e6c38ef692b8fd5a0f6d1fa8774d81ebc08cfbd624b1bca62a4d4af2f9931 \ + --hash=sha256:3b74ff4767d3ef47ffe0cd1d89379dc4d828d4873e5528976ced3b44fe5b0a21 \ + --hash=sha256:3e844be8d536afa129366d9af76ed7cb8dfefec99f5f1c9e4f8ae542279a6dc3 \ + --hash=sha256:459e81c2fb920b5f5df744262d1498ec2c8081acdcfe18181da44c50f51312f7 \ + --hash=sha256:46ddf6e0b975cd680eb83318aa1d321cb2bf8d288d50f1754526230fcf59ba96 \ + --hash=sha256:482c122b72e3c5ec98f11457aeb436ae4aecca75de19b3d1de7cf88bc40db82f \ + --hash=sha256:561c87fea99545ef7d692403c110b2f99dced6dff93056d6e04384ad3bc46243 \ + --hash=sha256:578d00c9b7fccfa1745a44f4eddfdc99d723d157dad26764538fbdda37209857 \ + --hash=sha256:58c8e9620eb82a189c6c40cb6b59b4e35b2ee68b1f2afa6597732a2b467d7e8f \ + --hash=sha256:5b29beab10211a746f9846baa39275e80034e065460d99eb51e45c9a9495bcca \ + --hash=sha256:5d1d42556b063d579cae59e37a38c61f4402b47d70c29f0ef15cee1acaa64488 \ + --hash=sha256:5f236cb5999ccd23a0ab1bd219cfe0ee3e1c1b65aaf6dd3320e972f7ec3a39da \ + --hash=sha256:62a91aefff3d11bf60e5956d340eb507a983a7ec802b19072bb989ce120cd948 \ + --hash=sha256:64cc6e97f14cf8a275d79c5002281f3040c12e2e4220623b5759ea7f9868d6a5 \ + --hash=sha256:6f4c9156c4d1eb490fe374fb294deeb7bc7eaccda50e23775b2354b6a6739934 \ + --hash=sha256:7294e38f9aa2e9f05f765b28ffdc5d81378508ce6dadbe93f6d464a8c9594473 \ + --hash=sha256:7615058aabad54416ddac99ade09a5510cf77039a3b903e94e8922f25ed203d7 \ + --hash=sha256:7e48cdb8226644e2fbd0bdb0a0f87906a3db07087f4de77a1b1b1ccfd9e93685 \ + --hash=sha256:7f63d176a81555984e91f2c84c2a574a61cab7111cc907e176f0f01538e9ff6e \ + --hash=sha256:7f6595c852ca544aaeeb32d357e62c9c780eac69dcd34e40cae7b55bc4fb1147 \ + --hash=sha256:7fac95714b09da9278a0b52e492466f773cfe37651cf467a83a1b659be24bf71 \ + --hash=sha256:81713b70bea5c1386dc2f32a8f0dab4148a2928c7495c808c541ee0aae614d67 \ + --hash=sha256:846dd2e1243407133d3195d2d7e4ceefcaa5f5bf7278f0a9bda00967e6326b04 \ + --hash=sha256:84c063af19ef5130084db70ada40ce63a84f6c1ef4d3dbc34e5e8c4febb20822 \ + --hash=sha256:881764d610e3269964fc4bb3c19bb6fce55422828e152b885609ec176b41cf11 \ + --hash=sha256:8994b29c462de9a8fce2d591028b986dbbe1b32f3ad600b2d3e1c482c93abad6 \ + --hash=sha256:8c79e9d7e3d8a32d4824250a9c6401194fb4c2ad9a0cec8f6a96e09a582c2cc0 \ + --hash=sha256:8ee427208c675f1b6e344a1f89376a9613fc30b52646a04ac0c1f6587c7e46ec \ + --hash=sha256:949681f68e0e3c25377462be4b658500e85ca24323d9619fdc41f68d46a1ffda \ + --hash=sha256:9e275792097c9f7e80741c36de3b61917aebecc08a67ae62899b074566ff8556 \ + --hash=sha256:9fb815155aac6bfa8d86184079652c9715c812d506b22cfa369196ef4e99d1b4 \ + --hash=sha256:a2a64e62c7a0edd07c1c917b0586655f3362d2c2d37d474db1a509efb96fea1c \ + --hash=sha256:a7ac5b4984c468ce4f4a553df281450df0a34aefae02e58d77a0847be8d1e11f \ + --hash=sha256:aa46dce75078fceaf7cecac5817422febb4355fbdda440db55206e3bd288cfb8 \ + --hash=sha256:ae3476e934b9d714aa8000d2e4c01eb2590eee10b9d8cd03e7983ad65dfbfcba \ + --hash=sha256:b0341e6d9a0c0e3cdc65857ef518bb05b410dbd70d749a0d33ac0f39e81a4258 \ + --hash=sha256:b40d1bf6e6f74f7c0a567a9e5e778bbd4699d1d3d2c0fe46f4b717eef9e96b95 \ + --hash=sha256:b5c4804e4039f487e942c13381e6c27b4b4e66066d94ef1fae3f6ba8b953f383 \ + --hash=sha256:b5d6a6c9602fd4598fa07e0389e19fe199ae96449008d8304bf5d47cb745462e \ + --hash=sha256:b5f1ac7359e17efe0b6e5fec21de34145caef22b260e978336f325d5c84e6938 \ + --hash=sha256:c0167540094838ee9093ef6cc2c69d0074bbf84a432b4995835e8e5a0d984374 \ + --hash=sha256:c180ac742a083e109c1a18151f4dd8675f32679985a1c750d2ff806796165b55 \ + --hash=sha256:c73df5b6e8fabe2ddb74876fb82d9dd44cbace0ca12e8861ce9155ad3c886139 \ + --hash=sha256:c7e177c619342e407415d4f35dec63d2d134d951e24b5166afcdfd1362828e17 \ + --hash=sha256:cbad927ea8ed814622305d842c93412cb47bd39a496ed0f96bfd42b922b4a217 \ + --hash=sha256:cc353841428d56b683a123a813e6a686e07026d6b1c5757970a877195f880c2d \ + --hash=sha256:cc7c92c1baa629cb03ecb0c3d12564f172218fb1739f54bf5f3881844daadc6d \ + --hash=sha256:cc7d768260f4ba4ea01741c1b5fe3d3a6c70eb91c87f4c8761bbcce5181beafe \ + --hash=sha256:d0eea830b591dbc68e030c86a9569826145df485b2b4554874b07fea1275a199 \ + --hash=sha256:d216e5d9b8749563c7f2c6f7a0831057ec844c68b4c11cb10fc62d4fd373c26d \ + --hash=sha256:d401f07261dc5aa36c2e4efc308548f6ae943bfff20fcadb0a07517a26b196d8 \ + --hash=sha256:d6324274b4e0e2fa1b3eccb25997b1c9ed134ff61d296448ab8269f5ac068c4c \ + --hash=sha256:d8a8b74d843c2638f3864a17d97a4acda58e40d3e44b6303b8cc3d3c44ae2d29 \ + --hash=sha256:d9b6b28a57feb51605d6ae5e61a9044a31742db557a3b851a74c13bc61de5172 \ + --hash=sha256:de599af166970d6a61accde358ec9ded821234cbbc8c6413acfec06056b8e860 \ + --hash=sha256:e594b22688d5747b06e957f1ef822060cb5cb35b493066e33ceac0cf882188b7 \ + --hash=sha256:e5b078134f48552c4d9527db2f7da0b5359abd49393cdf9794017baec7506170 \ + --hash=sha256:eb6dce402734575e1a8cc0bb1509afca508a400a57ce13d306ea2c663bad1138 \ + --hash=sha256:f1790a4b1e8e8e028c391175433b9c8122c39b46e1663228158e61e6f915bf06 \ + --hash=sha256:f5efe0661b9fcd6246f27957f6ae1c0eb29bc60552820f01e970b4996e016004 \ + --hash=sha256:f9cbfbc5faca235fbdf531b93aa0f9f005ec7d267d9d738761a4d42b744ea159 \ + --hash=sha256:fbea1751729afe607d84acfd01efd95e3b31db148a181a441984ce9b3d3469da \ + --hash=sha256:fca4b4307ebe9c3ec77a084da3a9d1999d164693d16492ca2b64594340999988 \ + --hash=sha256:ff5c6771c7e3511a06555afa317879b7db8d640137ba55d6ab0d0c50425cab75 # via aiohttp https://github.com/zulip/python-zulip-api/archive/0.9.0.zip#egg=zulip==0.9.0+git&subdirectory=zulip \ --hash=sha256:71ea94192f1a0de1c45fd177e827b65fe4970c4fc8565991fd066b4de77eaccb @@ -2628,8 +2645,8 @@ zxcvbn==4.4.28 \ # via -r requirements/common.in # The following packages are considered to be unsafe in a requirements file: -https://github.com/zulip/pip/archive/39e8b5ecfcd8d19872f103d680713bc8e9de9d00.zip#egg=pip==20.3.4+git \ - --hash=sha256:2c8ae0783fea3e2b98b422d443b19c443779f0881882f905e0728be7b4043d1b +https://github.com/zulip/pip/archive/50e61dcc78d0da8a041e4fecc566f40b2b0604df.zip#egg=pip==20.3.4+git \ + --hash=sha256:35e47938b7e2a91523359b54a53c62fc2aeed5adaaba24437a33cbd9a4641574 # via # -r requirements/pip.in # zulip-bots diff --git a/scripts/lib/check-database-compatibility b/scripts/lib/check-database-compatibility index 8ed3a97d12..9e9332c429 100755 --- a/scripts/lib/check-database-compatibility +++ b/scripts/lib/check-database-compatibility @@ -62,10 +62,10 @@ if os.path.exists("/etc/init.d/postgresql") and os.path.exists("/etc/zulip/zulip ) sys.exit(1) -if django_pg_version < 12: +if django_pg_version < 13: logging.critical("Unsupported PostgreSQL version: %d", postgresql_version) logging.info( - "Please upgrade to PostgreSQL 12 or newer first.\n" + "Please upgrade to PostgreSQL 13 or newer first.\n" "See https://zulip.readthedocs.io/en/stable/production/" "upgrade.html#upgrading-postgresql" ) diff --git a/scripts/lib/install b/scripts/lib/install index 7066077c3e..a68d5aff9b 100755 --- a/scripts/lib/install +++ b/scripts/lib/install @@ -199,8 +199,8 @@ if [ "$EXTERNAL_HOST" = zulip.example.com ] \ fi case "$POSTGRESQL_VERSION" in - [0-9] | [0-9].* | 1[01] | 1[01].*) - echo "error: PostgreSQL 12 or newer is required." >&2 + [0-9] | [0-9].* | 1[0-2] | 1[0-2].*) + echo "error: PostgreSQL 13 or newer is required." >&2 exit 1 ;; esac diff --git a/scripts/lib/install-node b/scripts/lib/install-node index 8c8b824013..ac746e7882 100755 --- a/scripts/lib/install-node +++ b/scripts/lib/install-node @@ -1,18 +1,18 @@ #!/usr/bin/env bash set -euo pipefail -version=20.18.0 +version=22.11.0 arch="$(uname -m)" case $arch in x86_64) tarball="node-v$version-linux-x64.tar.xz" - sha256=4543670b589593f8fa5f106111fd5139081da42bb165a9239f05195e405f240a + sha256=83bf07dd343002a26211cf1fcd46a9d9534219aad42ee02847816940bf610a72 ;; aarch64) tarball="node-v$version-linux-arm64.tar.xz" - sha256=a9ce85675ba33f00527f6234d90000946c0936fb4fca605f1891bb5f4fe6fb0a + sha256=6031d04b98f59ff0f7cb98566f65b115ecd893d3b7870821171708cdbaf7ae6e ;; esac diff --git a/scripts/setup/reindex-textual-data b/scripts/setup/reindex-textual-data index 3557672268..954a9955ff 100755 --- a/scripts/setup/reindex-textual-data +++ b/scripts/setup/reindex-textual-data @@ -48,12 +48,6 @@ pg_args["connect_timeout"] = "600" conn = psycopg2.connect(connection_factory=None, **pg_args) conn.autocommit = True -pg_server_version = conn.server_version -can_concurrently = pg_server_version >= 110000 # Version 11.0.0 - -if options.concurrently and not can_concurrently: - raise RuntimeError("Only PostgreSQL 11 and above can REINDEX CONCURRENTLY.") - cursor = conn.cursor() cursor.execute( """ diff --git a/static/images/email-gateway-bot.png b/static/images/email-gateway-bot.png deleted file mode 100644 index ce10f48fc2..0000000000 Binary files a/static/images/email-gateway-bot.png and /dev/null differ diff --git a/static/images/integrations/airbyte/001.png b/static/images/integrations/airbyte/001.png new file mode 100644 index 0000000000..f90bf6e885 Binary files /dev/null and b/static/images/integrations/airbyte/001.png differ diff --git a/static/images/integrations/bot_avatars/airbyte.png b/static/images/integrations/bot_avatars/airbyte.png new file mode 100644 index 0000000000..1c563d017d Binary files /dev/null and b/static/images/integrations/bot_avatars/airbyte.png differ diff --git a/static/images/integrations/logos/airbyte.svg b/static/images/integrations/logos/airbyte.svg new file mode 100644 index 0000000000..b81818d577 Binary files /dev/null and b/static/images/integrations/logos/airbyte.svg differ diff --git a/static/images/static_avatars/emailgateway-medium.png b/static/images/static_avatars/emailgateway-medium.png new file mode 100644 index 0000000000..348f0f2ff5 Binary files /dev/null and b/static/images/static_avatars/emailgateway-medium.png differ diff --git a/static/images/static_avatars/emailgateway.png b/static/images/static_avatars/emailgateway.png new file mode 100644 index 0000000000..9e3f840c4c Binary files /dev/null and b/static/images/static_avatars/emailgateway.png differ diff --git a/static/images/static_avatars/notification-bot-medium.png b/static/images/static_avatars/notification-bot-medium.png new file mode 100644 index 0000000000..74edb620e5 Binary files /dev/null and b/static/images/static_avatars/notification-bot-medium.png differ diff --git a/static/images/static_avatars/notification-bot.png b/static/images/static_avatars/notification-bot.png new file mode 100644 index 0000000000..e179b5fc8d Binary files /dev/null and b/static/images/static_avatars/notification-bot.png differ diff --git a/static/images/static_avatars/welcome-bot-medium.png b/static/images/static_avatars/welcome-bot-medium.png new file mode 100644 index 0000000000..df0252864d Binary files /dev/null and b/static/images/static_avatars/welcome-bot-medium.png differ diff --git a/static/images/static_avatars/welcome-bot.png b/static/images/static_avatars/welcome-bot.png new file mode 100644 index 0000000000..dfc7f27c56 Binary files /dev/null and b/static/images/static_avatars/welcome-bot.png differ diff --git a/static/images/welcome-bot.png b/static/images/welcome-bot.png deleted file mode 100644 index 4a65e65d97..0000000000 Binary files a/static/images/welcome-bot.png and /dev/null differ diff --git a/stylelint.config.js b/stylelint.config.js index d922b9cd22..2a677539f0 100644 --- a/stylelint.config.js +++ b/stylelint.config.js @@ -2,6 +2,7 @@ module.exports = { extends: ["stylelint-config-standard"], + plugins: ["stylelint-high-performance-animation"], rules: { // Add some exceptions for recommended rules "at-rule-no-unknown": [true, {ignoreAtRules: ["extend"]}], @@ -37,6 +38,7 @@ module.exports = { // We use hsl instead of rgb "rgb", ], + "plugin/no-low-performance-animation-properties": [true, {ignore: "paint-properties"}], // Zulip CSS should have no dependencies on external resources "function-url-no-scheme-relative": true, diff --git a/templates/zerver/development/dev_tools.html b/templates/zerver/development/dev_tools.html index cd8e00ff73..84381701a5 100644 --- a/templates/zerver/development/dev_tools.html +++ b/templates/zerver/development/dev_tools.html @@ -88,6 +88,8 @@
  • ./manage.py mark_all_messages_unread: Useful for testing reading messages.
  • ./manage.py create_realm: Add a new realm. Useful for testing onboarding.
  • ./manage.py create_user: Add a new user. Useful for testing onboarding.
  • +
  • ./manage.py send_zulip_update_announcements: Send Zulip + update notices drafted in `zerver/lib/zulip_update_announcements.py`.
  • ./manage.py add_mock_conversation: Add test messages, streams, images, emoji, etc. into the dev environment. First edit zilencer/management/commands/add_mock_conversation.py to add the data you're testing. diff --git a/templates/zerver/find_account.html b/templates/zerver/find_account.html index 1ea2aaced5..7e15c9c2fe 100644 --- a/templates/zerver/find_account.html +++ b/templates/zerver/find_account.html @@ -38,12 +38,12 @@ {% else %} -
    {{ form.emails.help_text }}
    +
    {% if form.emails.errors %} diff --git a/templates/zerver/invalid_realm.html b/templates/zerver/invalid_realm.html index eb065631c4..a885cac7c4 100644 --- a/templates/zerver/invalid_realm.html +++ b/templates/zerver/invalid_realm.html @@ -12,14 +12,15 @@

    {{ _('No organization found') }}

    -
    + diff --git a/templates/zerver/realm_redirect.html b/templates/zerver/realm_redirect.html index 9ffedcb22f..0f7dafdd3e 100644 --- a/templates/zerver/realm_redirect.html +++ b/templates/zerver/realm_redirect.html @@ -44,7 +44,9 @@
    - {{ _("Need to get your group started on Zulip?") }} {{ _("Create a new organization.") }} + {% trans org_creation_link="/new/" %} + Create a new organization if you don't have one yet. + {% endtrans %}
    diff --git a/tools/ci/activate-venv b/tools/ci/activate-venv index dcaff5ea72..50e18883cd 100644 --- a/tools/ci/activate-venv +++ b/tools/ci/activate-venv @@ -1,4 +1,6 @@ #!/usr/bin/env bash +ZULIP_PATH="$(dirname "${BASH_SOURCE[0]}")/../.." source /srv/zulip-py3-venv/bin/activate +source "$ZULIP_PATH"/tools/python-warnings.bash echo "Using $VIRTUAL_ENV" diff --git a/tools/ci/setup-backend b/tools/ci/setup-backend index 48777fe336..9e39a56cf9 100755 --- a/tools/ci/setup-backend +++ b/tools/ci/setup-backend @@ -2,6 +2,9 @@ set -e set -x +ZULIP_PATH="$(dirname "${BASH_SOURCE[0]}")/../.." +. "$ZULIP_PATH/tools/python-warnings.bash" + # This is just a thin wrapper around provision. # Provisioning may fail due to many issues but most of the times a network # connection issue is the reason. So we are going to retry entire provisioning diff --git a/tools/convert-help-center-docs-to-mdx b/tools/convert-help-center-docs-to-mdx index 3ed537b084..0986ff6a30 100755 --- a/tools/convert-help-center-docs-to-mdx +++ b/tools/convert-help-center-docs-to-mdx @@ -18,6 +18,21 @@ os.environ["DJANGO_SETTINGS_MODULE"] = "zproject.settings" django.setup() +def replace_image_path(markdown_string: str) -> str: + """ + We will point to the existing image folder till + the cutover. After that, we will copy the images + to src folder for help-beta in order to take + advantage of Astro's image optimization. + See https://chat.zulip.org/#narrow/stream/6-frontend/topic/Handling.20images.20in.20help.20center.20starlight.20migration.2E/near/1915130 + """ + # We do not replace /static/images directly since there are a few + # instances in the documentation where zulip.com links are + # referenced with that blurb as a part of the url. + result = markdown_string.replace("(/static/images/help-beta", "(../../../../static/images/help") + return result.replace('="/static/images/help-beta', '="../../../../static/images/help') + + def escape_curly_braces(markdown_string: str) -> str: """ MDX will treat curly braces as a JS expression, @@ -54,6 +69,7 @@ def insert_frontmatter(markdown_string: str) -> str: def convert_string_to_mdx(markdown_string: str) -> str: result = escape_curly_braces(markdown_string) result = fix_relative_path(result) + result = replace_image_path(result) return insert_frontmatter(result) diff --git a/tools/lib/capitalization.py b/tools/lib/capitalization.py index 5d4394a65d..efc49a0aca 100644 --- a/tools/lib/capitalization.py +++ b/tools/lib/capitalization.py @@ -1,4 +1,5 @@ import re +from io import StringIO from re import Match from bs4 import BeautifulSoup @@ -163,6 +164,8 @@ IGNORED_PHRASES = [ r"does not apply to users who can delete any message", # Used as indicator with names for guest users. r"guest", + # Used as indicator with names for archived streams. + r"archived", # Used in pills for deactivated users. r"deactivated", # This is a reference to a setting/secret and should be lowercase. @@ -182,7 +185,8 @@ IGNORED_PHRASES.sort(key=len, reverse=True) # text using BeautifulSoup and then removes extra whitespaces from # it. This step enables us to add HTML in our regexes directly. COMPILED_IGNORED_PHRASES = [ - re.compile(r" ".join(BeautifulSoup(regex, "lxml").text.split())) for regex in IGNORED_PHRASES + re.compile(r" ".join(BeautifulSoup(StringIO(regex), "lxml").text.split())) + for regex in IGNORED_PHRASES ] SPLIT_BOUNDARY = r"?.!" # Used to split string into sentences. @@ -241,7 +245,7 @@ def get_safe_text(text: str) -> str: This returns text which is rendered by BeautifulSoup and is in the form that can be split easily and has all IGNORED_PHRASES processed. """ - soup = BeautifulSoup(text, "lxml") + soup = BeautifulSoup(StringIO(text), "lxml") text = " ".join(soup.text.split()) # Remove extra whitespaces. for phrase_regex in COMPILED_IGNORED_PHRASES: text = phrase_regex.sub(replace_with_safe_phrase, text) diff --git a/tools/python-warnings.bash b/tools/python-warnings.bash new file mode 100644 index 0000000000..11518edc87 --- /dev/null +++ b/tools/python-warnings.bash @@ -0,0 +1,83 @@ +# shellcheck shell=bash + +export PYTHONWARNINGS=error + +PYTHONWARNINGS+=',ignore::ResourceWarning' + +# https://github.com/python/cpython/pull/96629 +PYTHONWARNINGS+=',default:check_home argument is deprecated and ignored.:DeprecationWarning:distutils.command.install' + +# https://github.com/disqus/django-bitfield/pull/135 +PYTHONWARNINGS+=',default:Attribute s is deprecated and will be removed in Python 3.14; use value instead:DeprecationWarning:__main__' + +# https://github.com/jaysonsantos/python-binary-memcached/pull/257 +PYTHONWARNINGS+=',ignore:urllib.parse.splitport() is deprecated as of 3.8:DeprecationWarning:bmemcached.protocol' + +# https://github.com/boto/botocore/pull/3239 +PYTHONWARNINGS+=',ignore:datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version.:DeprecationWarning:botocore.auth' + +# https://bugs.launchpad.net/beautifulsoup/+bug/2076897 +PYTHONWARNINGS+=',ignore:The '\''strip_cdata'\'' option of HTMLParser() has never done anything and will eventually be removed.:DeprecationWarning:bs4.builder._lxml' + +# https://github.com/fabfuel/circuitbreaker/pull/63 +PYTHONWARNINGS+=',ignore:datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version.:DeprecationWarning:circuitbreaker' + +# This gets triggered due to our do_patch_activate_script +PYTHONWARNINGS+=',default:Attempting to work in a virtualenv.:UserWarning:IPython.core.interactiveshell' + +# https://github.com/SAML-Toolkits/python3-saml/pull/420 +PYTHONWARNINGS+=',ignore:datetime.datetime.utcfromtimestamp() is deprecated and scheduled for removal in a future version.:DeprecationWarning:onelogin.saml2.utils' +PYTHONWARNINGS+=',ignore:datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version.:DeprecationWarning:onelogin.saml2.utils' + +# Probably due to ancient pip +PYTHONWARNINGS+=',default:DEPRECATION::pip._internal.models.link' +PYTHONWARNINGS+=',default:Unimplemented abstract methods:DeprecationWarning:pip._internal.metadata.importlib._dists' +PYTHONWARNINGS+=',default:module '\''sre_constants'\'' is deprecated:DeprecationWarning:pip._vendor.pyparsing' +PYTHONWARNINGS+=',default:Creating a LegacyVersion has been deprecated and will be removed in the next major release:DeprecationWarning:pip._vendor.packaging.version' +PYTHONWARNINGS+=',default:path is deprecated.:DeprecationWarning:pip._vendor.certifi.core' +PYTHONWARNINGS+=',default:ssl.PROTOCOL_TLS is deprecated:DeprecationWarning:pip._vendor.urllib3.util.ssl_' +PYTHONWARNINGS+=',default:Creating a LegacyVersion has been deprecated and will be removed in the next major release:DeprecationWarning:pip._vendor.packaging.specifiers' +PYTHONWARNINGS+=',default:path is deprecated.:DeprecationWarning:pip._vendor.pep517.wrappers' +PYTHONWARNINGS+=',default:The distutils package is deprecated and slated for removal in Python 3.12.:DeprecationWarning:pip._internal.locations' +PYTHONWARNINGS+=',default:The distutils.sysconfig module is deprecated:DeprecationWarning:pip._internal.locations' +PYTHONWARNINGS+=',default:ssl.match_hostname() is deprecated:DeprecationWarning:pip._vendor.urllib3.connection' +PYTHONWARNINGS+=',default:The distutils package is deprecated and slated for removal in Python 3.12.:DeprecationWarning:pip._internal.locations._distutils' +PYTHONWARNINGS+=',default:The distutils.sysconfig module is deprecated:DeprecationWarning:distutils.command.install' +PYTHONWARNINGS+=',default:The distutils package is deprecated and slated for removal in Python 3.12.:DeprecationWarning:pip._internal.cli.cmdoptions' + +# https://github.com/python-openapi/openapi-core/issues/931 +PYTHONWARNINGS+=',ignore::DeprecationWarning:openapi_core.validation.request.validators' + +# pkg_resources deprecation +PYTHONWARNINGS+=',default:pkg_resources is deprecated as an API.:DeprecationWarning' +PYTHONWARNINGS+=',default:Deprecated call to `pkg_resources.declare_namespace(:DeprecationWarning:pkg_resources' + +# https://github.com/seb-m/pyinotify/issues/204 +PYTHONWARNINGS+=',ignore:The asyncore module is deprecated and will be removed in Python 3.12.:DeprecationWarning:pyinotify' + +# Semgrep still supports Python 3.8 +PYTHONWARNINGS+=',ignore:path is deprecated.:DeprecationWarning:semgrep.semgrep_core' + +# Various warnings from setuptools +PYTHONWARNINGS+=',default:bdist_wheel.universal is deprecated:UserWarning' +PYTHONWARNINGS+=',ignore:setup.py install is deprecated.:UserWarning' +PYTHONWARNINGS+=',default:Unknown distribution option:UserWarning' +PYTHONWARNINGS+=',default:setuptools.installer and fetch_build_eggs are deprecated.:UserWarning' +PYTHONWARNINGS+=',default:The '\''wheel'\'' package is no longer the canonical location of the '\''bdist_wheel'\'' command:DeprecationWarning:wheel.bdist_wheel' +PYTHONWARNINGS+=',default:Package '\''integrations.:UserWarning' +PYTHONWARNINGS+=',default:Package '\''zulip.:UserWarning' +PYTHONWARNINGS+=',default:Could not find libsqlite3:UserWarning' + +# https://github.com/scrapy/scrapy/issues/3288 +PYTHONWARNINGS+=',ignore:Passing method to twisted.internet.ssl.CertificateOptions was deprecated in Twisted 17.1.0.:DeprecationWarning:scrapy.core.downloader.contextfactory' + +# https://github.com/scrapy/scrapy/issues/6450 +PYTHONWARNINGS+=',ignore:twisted.web.http.HTTPClient was deprecated in Twisted 24.7.0:DeprecationWarning:scrapy.core.downloader.webclient' + +# https://github.com/adamchainz/time-machine/pull/486 +PYTHONWARNINGS+=',ignore:datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version.:DeprecationWarning:time_machine' + +# https://github.com/zulip/python-zulip-api/pull/833 +PYTHONWARNINGS+=',default:distro.linux_distribution() is deprecated.:DeprecationWarning:zulip' + +export SQLALCHEMY_WARN_20=1 diff --git a/tools/setup/install-shfmt b/tools/setup/install-shfmt index d79b5a3406..9b07c94f46 100755 --- a/tools/setup/install-shfmt +++ b/tools/setup/install-shfmt @@ -1,18 +1,18 @@ #!/usr/bin/env bash set -eu -version=3.9.0 +version=3.10.0 arch="$(uname -m)" case $arch in "x86_64") binary="shfmt_v${version}_linux_amd64" - sha256=d99b06506aee2ac9113daec3049922e70dc8cffb84658e3ae512c6a6cbe101b6 + sha256=1f57a384d59542f8fac5f503da1f3ea44242f46dff969569e80b524d64b71dbc ;; "aarch64") binary="shfmt_v${version}_linux_arm64" - sha256=5e511463068f3d27ae1b087fb597fb9e8ad865be2ac501964a222a834fc1c463 + sha256=9d23013d56640e228732fd2a04a9ede0ab46bc2d764bf22a4a35fb1b14d707a8 ;; esac diff --git a/tools/test-api b/tools/test-api index 73fa6acd1b..b3b2310973 100755 --- a/tools/test-api +++ b/tools/test-api @@ -59,7 +59,8 @@ with test_server_running( # Prepare the admin client email = "iago@zulip.com" # Iago is an admin realm = get_realm("zulip") - user = get_user(email, realm) + iago = get_user(email, realm) + user = iago # Iago needs permission to manage all user groups. admins_group = NamedUserGroup.objects.get( @@ -69,9 +70,10 @@ with test_server_running( realm, "can_manage_all_groups", admins_group, acting_user=None ) - # Required to test can_create_users endpoints. + # Required to test can_create_users and can_change_user_emails endpoints. user.can_create_users = True - user.save(update_fields=["can_create_users"]) + user.can_change_user_emails = True + user.save(update_fields=["can_create_users", "can_change_user_emails"]) api_key = get_api_key(user) site = "http://zulip.zulipdev.com:9981" @@ -85,6 +87,7 @@ with test_server_running( email = "desdemona@zulip.com" # desdemona is an owner realm = get_realm("zulip") user = get_user(email, realm) + api_key = get_api_key(user) site = "http://zulip.zulipdev.com:9981" owner_client = Client( diff --git a/tools/test-js-with-node b/tools/test-js-with-node index 9e38314ffd..a6ab868b34 100755 --- a/tools/test-js-with-node +++ b/tools/test-js-with-node @@ -94,7 +94,7 @@ EXEMPT_FILES = make_set( "web/src/css_variables.js", "web/src/custom_profile_fields_ui.ts", "web/src/debug.ts", - "web/src/demo_organizations_ui.js", + "web/src/demo_organizations_ui.ts", "web/src/deprecated_feature_notice.ts", "web/src/desktop_integration.js", "web/src/desktop_notifications.ts", @@ -199,7 +199,7 @@ EXEMPT_FILES = make_set( "web/src/saved_snippets_ui.ts", "web/src/scheduled_messages.ts", "web/src/scheduled_messages_feed_ui.ts", - "web/src/scheduled_messages_overlay_ui.js", + "web/src/scheduled_messages_overlay_ui.ts", "web/src/scheduled_messages_ui.ts", "web/src/scroll_bar.ts", "web/src/scroll_util.ts", @@ -210,7 +210,7 @@ EXEMPT_FILES = make_set( "web/src/sentry.ts", "web/src/server_events.js", "web/src/settings.js", - "web/src/settings_account.js", + "web/src/settings_account.ts", "web/src/settings_bots.ts", "web/src/settings_components.ts", "web/src/settings_emoji.ts", @@ -231,7 +231,7 @@ EXEMPT_FILES = make_set( "web/src/settings_toggle.js", "web/src/settings_ui.ts", "web/src/settings_user_topics.ts", - "web/src/settings_users.js", + "web/src/settings_users.ts", "web/src/setup.ts", "web/src/sidebar_ui.ts", "web/src/spectators.ts", @@ -243,7 +243,7 @@ EXEMPT_FILES = make_set( "web/src/stream_color_events.ts", "web/src/stream_create.ts", "web/src/stream_create_subscribers.ts", - "web/src/stream_edit.js", + "web/src/stream_edit.ts", "web/src/stream_edit_subscribers.ts", "web/src/stream_edit_toggler.ts", "web/src/stream_list.ts", @@ -292,7 +292,7 @@ EXEMPT_FILES = make_set( "web/src/user_sort.ts", "web/src/user_status.ts", "web/src/user_status_ui.ts", - "web/src/user_topic_popover.js", + "web/src/user_topic_popover.ts", "web/src/user_topics.ts", "web/src/user_topics_ui.ts", "web/src/views_util.ts", diff --git a/version.py b/version.py index c0c47a6a89..8684d70b09 100644 --- a/version.py +++ b/version.py @@ -34,7 +34,7 @@ DESKTOP_WARNING_VERSION = "5.9.3" # new level means in api_docs/changelog.md, as well as "**Changes**" # entries in the endpoint's documentation in `zulip.yaml`. -API_FEATURE_LEVEL = 312 # Last bumped for adding 'realm_export_consent' event type. +API_FEATURE_LEVEL = 319 # Last bumped for message-link class # Bump the minor PROVISION_VERSION to indicate that folks should provision # only when going from an old version of the code to a newer version. Bump @@ -49,4 +49,4 @@ API_FEATURE_LEVEL = 312 # Last bumped for adding 'realm_export_consent' event t # historical commits sharing the same major version, in which case a # minor version bump suffices. -PROVISION_VERSION = (295, 0) # bumped 2024-10-20 to upgrade Python requirements +PROVISION_VERSION = (298, 1) # bumped 2024-11-05 to upgrade shfmt diff --git a/web/babel.config.js b/web/babel.config.js index 845a83c7c1..e0bc155efc 100644 --- a/web/babel.config.js +++ b/web/babel.config.js @@ -14,7 +14,7 @@ module.exports = { [ "@babel/preset-env", { - corejs: "3.37", + corejs: "3.39", include: ["transform-optional-chaining"], shippedProposals: true, useBuiltIns: "usage", diff --git a/web/e2e-tests/message-basics.test.ts b/web/e2e-tests/message-basics.test.ts index 7bcc170670..0102867957 100644 --- a/web/e2e-tests/message-basics.test.ts +++ b/web/e2e-tests/message-basics.test.ts @@ -404,21 +404,21 @@ async function test_stream_search_filters_stream_list(page: Page): Promise async function test_users_search(page: Page): Promise { console.log("Search users using right sidebar"); async function assert_in_list(page: Page, name: string): Promise { - await page.waitForSelector(`#buddy-list-other-users li [data-name="${CSS.escape(name)}"]`, { + await page.waitForSelector(`#buddy-list-other-users li[data-name="${CSS.escape(name)}"]`, { visible: true, }); } async function assert_selected(page: Page, name: string): Promise { await page.waitForSelector( - `#buddy-list-other-users li.highlighted_user [data-name="${CSS.escape(name)}"]`, + `#buddy-list-other-users li.highlighted_user[data-name="${CSS.escape(name)}"]`, {visible: true}, ); } async function assert_not_selected(page: Page, name: string): Promise { await page.waitForSelector( - `#buddy-list-other-users li.highlighted_user [data-name="${CSS.escape(name)}"]`, + `#buddy-list-other-users li.highlighted_user[data-name="${CSS.escape(name)}"]`, {hidden: true}, ); } @@ -429,7 +429,7 @@ async function test_users_search(page: Page): Promise { await assert_in_list(page, "aaron"); // Enter the search box and test selected suggestion navigation - await page.click("#user_filter_icon"); + await page.click(".user-list-filter"); await page.waitForSelector("#buddy-list-other-users .highlighted_user", {visible: true}); await assert_selected(page, "Desdemona"); await assert_not_selected(page, "Cordelia, Lear's daughter"); @@ -452,7 +452,7 @@ async function test_users_search(page: Page): Promise { await arrow(page, "Down"); // Now Iago must be highlighted - await page.waitForSelector('#buddy-list-other-users li.highlighted_user [data-name="Iago"]', { + await page.waitForSelector('#buddy-list-other-users li.highlighted_user[data-name="Iago"]', { visible: true, }); await assert_not_selected(page, "King Hamlet"); diff --git a/web/shared/icons/archive.svg b/web/shared/icons/archive.svg new file mode 100644 index 0000000000..1d77140f04 Binary files /dev/null and b/web/shared/icons/archive.svg differ diff --git a/web/src/activity_ui.ts b/web/src/activity_ui.ts index cd49215a90..7148b8077f 100644 --- a/web/src/activity_ui.ts +++ b/web/src/activity_ui.ts @@ -212,7 +212,7 @@ export function narrow_for_user_id(opts: {user_id: number}): void { assert(narrow_by_email); narrow_by_email(email); assert(user_filter !== undefined); - user_filter.clear_and_hide_search(); + user_filter.clear_search(); } function keydown_enter_key(): void { @@ -274,9 +274,9 @@ export function initiate_search(): void { } } -export function escape_search(): void { +export function clear_search(): void { if (user_filter) { - user_filter.clear_and_hide_search(); + user_filter.clear_search(); } } diff --git a/web/src/add_group_members_pill.ts b/web/src/add_group_members_pill.ts index b6aa065a2e..bb85c00e20 100644 --- a/web/src/add_group_members_pill.ts +++ b/web/src/add_group_members_pill.ts @@ -1,11 +1,16 @@ +import assert from "minimalistic-assert"; + import * as add_subscribers_pill from "./add_subscribers_pill"; import * as input_pill from "./input_pill"; import * as keydown_util from "./keydown_util"; import type {User} from "./people"; import * as stream_pill from "./stream_pill"; import type {CombinedPill, CombinedPillContainer} from "./typeahead_helper"; +import * as user_group_components from "./user_group_components"; import * as user_group_create_members_data from "./user_group_create_members_data"; import * as user_group_pill from "./user_group_pill"; +import * as user_groups from "./user_groups"; +import type {UserGroup} from "./user_groups"; import * as user_pill from "./user_pill"; function get_pill_user_ids(pill_widget: CombinedPillContainer): number[] { @@ -19,6 +24,80 @@ function get_pill_group_ids(pill_widget: CombinedPillContainer): number[] { return group_user_ids; } +export function create_item_from_text( + text: string, + current_items: CombinedPill[], +): CombinedPill | undefined { + const funcs = [ + stream_pill.create_item_from_stream_name, + user_group_pill.create_item_from_group_name, + user_pill.create_item_from_email, + ]; + + const stream_item = stream_pill.create_item_from_stream_name(text, current_items); + if (stream_item) { + return stream_item; + } + + const group_item = user_group_pill.create_item_from_group_name(text, current_items); + if (group_item) { + const subgroup = user_groups.get_user_group_from_id(group_item.group_id); + const current_group_id = user_group_components.active_group_id; + assert(current_group_id !== undefined); + const current_group = user_groups.get_user_group_from_id(current_group_id); + if (user_groups.check_group_can_be_subgroup(subgroup, current_group)) { + return group_item; + } + + return undefined; + } + for (const func of funcs) { + const item = func(text, current_items); + if (item) { + return item; + } + } + return undefined; +} + +export function create({ + $pill_container, + get_potential_members, + get_potential_groups, +}: { + $pill_container: JQuery; + get_potential_members: () => User[]; + get_potential_groups: () => UserGroup[]; +}): CombinedPillContainer { + const pill_widget = input_pill.create({ + $container: $pill_container, + create_item_from_text, + get_text_from_item: add_subscribers_pill.get_text_from_item, + get_display_value_from_item: add_subscribers_pill.get_display_value_from_item, + generate_pill_html: add_subscribers_pill.generate_pill_html, + }); + function get_users(): User[] { + const potential_members = get_potential_members(); + return user_pill.filter_taken_users(potential_members, pill_widget); + } + + function get_user_groups(): UserGroup[] { + const potential_groups = get_potential_groups(); + return user_group_pill.filter_taken_groups(potential_groups, pill_widget); + } + + add_subscribers_pill.set_up_pill_typeahead({ + pill_widget, + $pill_container, + get_users, + get_user_groups, + }); + + add_subscribers_pill.set_up_handlers_for_add_button_state(pill_widget, $pill_container); + + return pill_widget; +} + export function create_without_add_button({ $pill_container, onPillCreateAction, diff --git a/web/src/add_stream_options_popover.ts b/web/src/add_stream_options_popover.ts index e7b4f2c5d8..cb5d4e5ea7 100644 --- a/web/src/add_stream_options_popover.ts +++ b/web/src/add_stream_options_popover.ts @@ -31,6 +31,8 @@ export function initialize(): void { instance.setContent(parse_html(render_left_sidebar_stream_setting_popover())); popover_menus.on_show_prep(instance); + $("#streams_header").addClass("showing-streams-popover"); + // When showing the popover menu, we want the // "Add channels" and the "Filter channels" tooltip // to appear below the "Add channels" icon. @@ -55,6 +57,9 @@ export function initialize(): void { onHidden(instance) { instance.destroy(); popover_menus.popover_instances.stream_settings = null; + + $("#streams_header").removeClass("showing-streams-popover"); + // After the popover menu is closed, we want the // "Add channels" and the "Filter channels" tooltip // to appear at it's original position that is diff --git a/web/src/add_subscribers_pill.ts b/web/src/add_subscribers_pill.ts index 452085007d..46743ab8c6 100644 --- a/web/src/add_subscribers_pill.ts +++ b/web/src/add_subscribers_pill.ts @@ -11,6 +11,7 @@ import * as stream_pill from "./stream_pill"; import type {CombinedPill, CombinedPillContainer} from "./typeahead_helper"; import * as user_group_pill from "./user_group_pill"; import * as user_groups from "./user_groups"; +import type {UserGroup} from "./user_groups"; import * as user_pill from "./user_pill"; export function create_item_from_text( @@ -51,17 +52,28 @@ export function set_up_pill_typeahead({ pill_widget, $pill_container, get_users, + get_user_groups, }: { pill_widget: CombinedPillContainer; $pill_container: JQuery; get_users: () => User[]; + get_user_groups?: () => UserGroup[]; }): void { - const opts = { + const opts: { + user_source: () => User[]; + stream: boolean; + user_group: boolean; + user: boolean; + user_group_source?: () => UserGroup[]; + } = { user_source: get_users, stream: true, user_group: true, user: true, }; + if (get_user_groups !== undefined) { + opts.user_group_source = get_user_groups; + } pill_typeahead.set_up_combined($pill_container.find(".input"), pill_widget, opts); } @@ -88,6 +100,30 @@ export function generate_pill_html(item: CombinedPill): string { return stream_pill.generate_pill_html(item); } +export function set_up_handlers_for_add_button_state( + pill_widget: CombinedPillContainer, + $pill_container: JQuery, +): void { + const $pill_widget_input = $pill_container.find(".input"); + const $pill_widget_button = $pill_container.parent().find(".add-users-button"); + + // Disable the add button first time the pill container is created. + $pill_widget_button.prop("disabled", true); + + // If all the pills are removed, disable the add button. + pill_widget.onPillRemove(() => + $pill_widget_button.prop("disabled", pill_widget.items().length === 0), + ); + // Disable the add button when there is no pending text that can be converted + // into a pill and the number of existing pills is zero. + $pill_widget_input.on("input", () => + $pill_widget_button.prop( + "disabled", + !pill_widget.is_pending() && pill_widget.items().length === 0, + ), + ); +} + export function create({ $pill_container, get_potential_subscribers, @@ -109,23 +145,7 @@ export function create({ set_up_pill_typeahead({pill_widget, $pill_container, get_users}); - const $pill_widget_input = $pill_container.find(".input"); - const $pill_widget_button = $pill_container.parent().find(".add-users-button"); - // Disable the add button first time the pill container is created. - $pill_widget_button.prop("disabled", true); - - // If all the pills are removed, disable the add button. - pill_widget.onPillRemove(() => - $pill_widget_button.prop("disabled", pill_widget.items().length === 0), - ); - // Disable the add button when there is no pending text that can be converted - // into a pill and the number of existing pills is zero. - $pill_widget_input.on("input", () => - $pill_widget_button.prop( - "disabled", - !pill_widget.is_pending() && pill_widget.items().length === 0, - ), - ); + set_up_handlers_for_add_button_state(pill_widget, $pill_container); return pill_widget; } diff --git a/web/src/admin.js b/web/src/admin.js index 6713c2575e..3f74031b5c 100644 --- a/web/src/admin.js +++ b/web/src/admin.js @@ -8,6 +8,7 @@ import * as bot_data from "./bot_data"; import * as demo_organizations_ui from "./demo_organizations_ui"; import {$t, get_language_name, language_list} from "./i18n"; import {page_params} from "./page_params"; +import * as people from "./people"; import {realm_user_settings_defaults} from "./realm_user_settings_defaults"; import * as settings from "./settings"; import * as settings_bots from "./settings_bots"; @@ -21,7 +22,6 @@ import * as settings_sections from "./settings_sections"; import * as settings_toggle from "./settings_toggle"; import * as settings_users from "./settings_users"; import {current_user, realm} from "./state_data"; -import * as user_groups from "./user_groups"; const admin_settings_label = { // Organization profile @@ -110,6 +110,7 @@ export function build_page() { const options = { custom_profile_field_types: realm.custom_profile_field_types, full_name: current_user.full_name, + profile_picture: people.small_avatar_url_for_person(current_user), realm_name: realm.realm_name, realm_org_type: realm.realm_org_type, realm_available_video_chat_providers: realm.realm_available_video_chat_providers, @@ -142,8 +143,6 @@ export function build_page() { language_list, realm_default_language_name: get_language_name(realm.realm_default_language), realm_default_language_code: realm.realm_default_language, - realm_direct_message_initiator_group_id: realm.realm_direct_message_initiator_group, - realm_direct_message_permission_group_id: realm.realm_direct_message_permission_group, realm_waiting_period_threshold: realm.realm_waiting_period_threshold, realm_new_stream_announcements_stream_id: realm.realm_new_stream_announcements_stream_id, realm_signup_announcements_stream_id: realm.realm_signup_announcements_stream_id, @@ -184,12 +183,6 @@ export function build_page() { realm_invite_required: realm.realm_invite_required, can_create_user_groups: settings_data.user_can_create_user_groups(), policy_values: settings_config.common_policy_values, - realm_can_delete_any_message_group: realm.realm_can_delete_any_message_group, - realm_can_delete_own_message_group: realm.realm_can_delete_own_message_group, - realm_can_add_custom_emoji_group: realm.realm_can_add_custom_emoji_group, - realm_can_add_custom_emoji_group_name: user_groups.get_user_group_from_id( - realm.realm_can_add_custom_emoji_group, - ).name, ...settings_org.get_organization_settings_options(), demote_inactive_streams_values: settings_config.demote_inactive_streams_values, web_mark_read_on_scroll_policy_values: @@ -282,10 +275,6 @@ export function build_page() { tippy.default($("#realm_can_access_all_users_group_widget_container")[0], opts); } - - settings_org.check_disable_direct_message_initiator_group_dropdown( - realm.realm_direct_message_permission_group, - ); } export function launch(section, user_settings_tab) { diff --git a/web/src/attachments_ui.ts b/web/src/attachments_ui.ts index a56044c32c..7d0ef31877 100644 --- a/web/src/attachments_ui.ts +++ b/web/src/attachments_ui.ts @@ -92,7 +92,6 @@ function delete_attachments(attachment: string, file_name: string): void { html_heading: $t_html({defaultMessage: "Delete file?"}), html_body, html_submit_button: $t_html({defaultMessage: "Delete"}), - id: "confirm_delete_file_modal", focus_submit_on_open: true, on_click() { dialog_widget.submit_api_request(channel.del, "/json/attachments/" + attachment, {}); diff --git a/web/src/buddy_data.ts b/web/src/buddy_data.ts index f05e075e83..02b2475096 100644 --- a/web/src/buddy_data.ts +++ b/web/src/buddy_data.ts @@ -172,11 +172,13 @@ export type BuddyUserInfo = { href: string; name: string; user_id: number; + profile_picture: string; status_emoji_info: user_status.UserStatusEmojiInfo | undefined; is_current_user: boolean; num_unread: number; user_circle_class: string; status_text: string | undefined; + has_status_text: boolean; user_list_style: { COMPACT: boolean; WITH_STATUS: boolean; @@ -204,10 +206,12 @@ export function info_for(user_id: number): BuddyUserInfo { name: person.full_name, user_id, status_emoji_info, + profile_picture: people.small_avatar_url_for_person(person), is_current_user: people.is_my_user_id(user_id), num_unread: get_num_unread(user_id), user_circle_class, status_text, + has_status_text: Boolean(status_text), user_list_style, should_add_guest_user_indicator: people.should_add_guest_user_indicator(user_id), }; @@ -370,7 +374,7 @@ function filter_user_ids(user_filter_text: string, user_ids: number[]): number[] return user_ids; } - // If a query is present in "Search people", we return matches. + // If a query is present in "Filter users", we return matches. const persons = user_ids.map((user_id) => people.get_by_user_id(user_id)); return [...people.filter_people_by_search_terms(persons, user_filter_text)]; } @@ -379,8 +383,7 @@ function get_filtered_user_id_list( user_filter_text: string, conversation_participants: Set, ): number[] { - // We always want to show conversation participants even if they're inactive. - let base_user_id_list = [...conversation_participants]; + let base_user_id_list = []; if (user_filter_text) { // If there's a filter, select from all users, not just those @@ -418,7 +421,9 @@ function get_filtered_user_id_list( } const user_ids = filter_user_ids(user_filter_text, base_user_id_list); - return user_ids; + // Make sure all the participants are in the list, even if they're inactive. + const user_ids_set = new Set([...user_ids, ...conversation_participants]); + return [...user_ids_set]; } export function get_conversation_participants(): Set { diff --git a/web/src/buddy_list.ts b/web/src/buddy_list.ts index 7cc2b9f240..82378a3974 100644 --- a/web/src/buddy_list.ts +++ b/web/src/buddy_list.ts @@ -77,9 +77,8 @@ type BuddyListRenderData = { pm_ids_set: Set; total_human_subscribers_count: number; other_users_count: number; - total_human_users: number; hide_headers: boolean; - participant_ids_set: Set; + all_participant_ids: Set; }; function get_render_data(): BuddyListRenderData { @@ -87,19 +86,17 @@ function get_render_data(): BuddyListRenderData { const pm_ids_set = narrow_state.pm_ids_set(); const total_human_subscribers_count = get_total_human_subscriber_count(current_sub, pm_ids_set); - const total_human_users = people.get_active_human_count(); - const other_users_count = total_human_users - total_human_subscribers_count; + const other_users_count = people.get_active_human_count() - total_human_subscribers_count; const hide_headers = should_hide_headers(current_sub, pm_ids_set); - const participant_ids_set = buddy_data.get_conversation_participants(); + const all_participant_ids = buddy_data.get_conversation_participants(); return { current_sub, pm_ids_set, total_human_subscribers_count, other_users_count, - total_human_users, hide_headers, - participant_ids_set, + all_participant_ids, }; } @@ -247,9 +244,8 @@ export class BuddyList extends BuddyListConf { ); } } else { - const total_human_users = people.get_active_human_count(); const other_users_count = - total_human_users - total_human_subscribers_count; + people.get_active_human_count() - total_human_subscribers_count; tooltip_text = $t( { defaultMessage: @@ -285,26 +281,38 @@ export class BuddyList extends BuddyListConf { // in already-sorted order. this.all_user_ids = opts.all_user_ids; - this.fill_screen_with_content(); - $("#buddy-list-users-matching-view-container .view-all-subscribers-link").remove(); $("#buddy-list-other-users-container .view-all-users-link").remove(); - if (!buddy_data.get_is_searching_users()) { - this.render_view_user_list_links(); - } - - this.render_section_headers(); - if (this.render_data.hide_headers) { - // Ensure the section isn't collapsed, because we're hiding its header - // so there's no way to collapse or uncollapse the list in this view. - $("#buddy-list-other-users-container").toggleClass("collapsed", false); + if (buddy_data.get_is_searching_users()) { + // Show all sections when searching users + this.set_section_collapse(".buddy-list-section-container", false); } else { - $("#buddy-list-other-users-container").toggleClass( - "collapsed", + this.render_view_user_list_links(); + this.set_section_collapse( + "#buddy-list-participants-container", + this.participants_is_collapsed, + ); + this.set_section_collapse( + "#buddy-list-users-matching-view-container", + this.users_matching_view_is_collapsed, + ); + this.set_section_collapse( + "#buddy-list-other-users-container", this.other_users_is_collapsed, ); - this.update_empty_list_placeholders(); } + + // Ensure the "other" section is visible when headers are collapsed, + // because we're hiding its header so there's no way to collapse or + // uncollapse the list in this view. Ensure we're showing/hiding as + // the user specified otherwise. + this.set_section_collapse( + "#buddy-list-other-users-container", + this.render_data.hide_headers ? false : this.other_users_is_collapsed, + ); + + this.update_empty_list_placeholders(); + this.fill_screen_with_content(); } update_empty_list_placeholders(): void { @@ -315,10 +323,13 @@ export class BuddyList extends BuddyListConf { let matching_view_empty_list_message; let other_users_empty_list_message; + // Only visible when searching, since we usually hide an empty participants section + let participants_empty_list_message; if (buddy_data.get_is_searching_users()) { matching_view_empty_list_message = $t({defaultMessage: "No matching users."}); other_users_empty_list_message = $t({defaultMessage: "No matching users."}); + participants_empty_list_message = $t({defaultMessage: "No matching users."}); } else { if (has_inactive_users_matching_view) { matching_view_empty_list_message = $t({defaultMessage: "No active users."}); @@ -333,36 +344,43 @@ export class BuddyList extends BuddyListConf { } } - $("#buddy-list-users-matching-view").attr( - "data-search-results-empty", - matching_view_empty_list_message, - ); - if ($("#buddy-list-users-matching-view .empty-list-message").length) { - const empty_list_widget_html = render_empty_list_widget_for_list({ - empty_list_message: matching_view_empty_list_message, - }); - $("#buddy-list-users-matching-view").html(empty_list_widget_html); + function add_or_update_empty_list_placeholder(selector: string, message: string): void { + if ( + $(selector).children().length === 0 || + $(`${selector} .empty-list-message`).length + ) { + const empty_list_widget_html = render_empty_list_widget_for_list({ + empty_list_message: message, + }); + $(selector).html(empty_list_widget_html); + } } - $("#buddy-list-other-users").attr( - "data-search-results-empty", + add_or_update_empty_list_placeholder( + "#buddy-list-users-matching-view", + matching_view_empty_list_message, + ); + + add_or_update_empty_list_placeholder( + "#buddy-list-other-users", other_users_empty_list_message, ); - if ($("#buddy-list-other-users .empty-list-message").length) { - const empty_list_widget_html = render_empty_list_widget_for_list({ - empty_list_message: other_users_empty_list_message, - }); - $("#buddy-list-other-users").html(empty_list_widget_html); + + if (participants_empty_list_message) { + add_or_update_empty_list_placeholder( + "#buddy-list-participants", + participants_empty_list_message, + ); } } update_section_header_counts(): void { - const {total_human_subscribers_count, other_users_count, participant_ids_set} = + const {total_human_subscribers_count, other_users_count, all_participant_ids} = this.render_data; const subscriber_section_user_count = - total_human_subscribers_count - this.participant_user_ids.length; + total_human_subscribers_count - all_participant_ids.size; - const formatted_participants_count = get_formatted_sub_count(participant_ids_set.size); + const formatted_participants_count = get_formatted_sub_count(all_participant_ids.size); const formatted_sub_users_count = get_formatted_sub_count(subscriber_section_user_count); const formatted_other_users_count = get_formatted_sub_count(other_users_count); @@ -378,7 +396,7 @@ export class BuddyList extends BuddyListConf { $("#buddy-list-participants-section-heading").attr( "data-user-count", - participant_ids_set.size, + all_participant_ids.size, ); $("#buddy-list-users-matching-view-section-heading").attr( "data-user-count", @@ -391,11 +409,11 @@ export class BuddyList extends BuddyListConf { } render_section_headers(): void { - const {hide_headers, participant_ids_set} = this.render_data; + const {hide_headers, all_participant_ids} = this.render_data; // We only show the participants list if it has members, so even if we're not // changing filters and only updating user counts for the current filter, that // can affect if we show/hide this section. - const show_participants_list = !hide_headers && participant_ids_set.size; + const show_participants_list = !hide_headers && all_participant_ids.size; $("#buddy-list-participants-container").toggleClass("no-display", !show_participants_list); // If we're not changing filters, this just means some users were added or @@ -407,8 +425,7 @@ export class BuddyList extends BuddyListConf { } this.current_filter = narrow_state.filter(); - const {current_sub, total_human_subscribers_count, other_users_count, total_human_users} = - this.render_data; + const {current_sub, total_human_subscribers_count, other_users_count} = this.render_data; $(".buddy-list-subsection-header").empty(); // If we're in the mode of hiding headers, that means we're only showing the "other users" @@ -424,20 +441,13 @@ export class BuddyList extends BuddyListConf { $("#buddy-list-users-matching-view-container").toggleClass("no-display", true); } - // Usually we show the user counts in the headers, but if we're hiding - // those headers then we show the total user count in the main title. - const default_userlist_title = $t({defaultMessage: "USERS"}); if (hide_headers) { - const formatted_count = get_formatted_sub_count(total_human_users); - const userlist_title = `${default_userlist_title} (${formatted_count})`; - $("#userlist-title").text(userlist_title); return; } - $("#userlist-title").text(default_userlist_title); let header_text; if (current_sub) { - if (participant_ids_set.size) { + if (all_participant_ids.size) { header_text = $t({defaultMessage: "Others in this channel"}); } else { header_text = $t({defaultMessage: "In this channel"}); @@ -451,8 +461,7 @@ export class BuddyList extends BuddyListConf { render_section_header({ id: "buddy-list-participants-section-heading", header_text: $t({defaultMessage: "In this conversation"}), - user_count: get_formatted_sub_count(this.participant_user_ids.length), - toggle_class: "toggle-participants", + user_count: get_formatted_sub_count(all_participant_ids.size), is_collapsed: this.participants_is_collapsed, }), ), @@ -464,9 +473,8 @@ export class BuddyList extends BuddyListConf { id: "buddy-list-users-matching-view-section-heading", header_text, user_count: get_formatted_sub_count( - total_human_subscribers_count - this.participant_user_ids.length, + total_human_subscribers_count - all_participant_ids.size, ), - toggle_class: "toggle-users-matching-view", is_collapsed: this.users_matching_view_is_collapsed, }), ), @@ -478,25 +486,28 @@ export class BuddyList extends BuddyListConf { id: "buddy-list-other-users-section-heading", header_text: $t({defaultMessage: "Others"}), user_count: get_formatted_sub_count(other_users_count), - toggle_class: "toggle-other-users", is_collapsed: this.other_users_is_collapsed, }), ), ); } + set_section_collapse(container_selector: string, is_collapsed: boolean): void { + $(container_selector).toggleClass("collapsed", is_collapsed); + $(`${container_selector} .buddy-list-section-toggle`).toggleClass( + "rotate-icon-down", + !is_collapsed, + ); + $(`${container_selector} .buddy-list-section-toggle`).toggleClass( + "rotate-icon-right", + is_collapsed, + ); + } + toggle_participants_section(): void { this.participants_is_collapsed = !this.participants_is_collapsed; - $("#buddy-list-participants-container").toggleClass( - "collapsed", - this.participants_is_collapsed, - ); - $("#buddy-list-participants-container .toggle-participants").toggleClass( - "rotate-icon-down", - !this.participants_is_collapsed, - ); - $("#buddy-list-participants-container .toggle-participants").toggleClass( - "rotate-icon-right", + this.set_section_collapse( + "#buddy-list-participants-container", this.participants_is_collapsed, ); @@ -507,16 +518,8 @@ export class BuddyList extends BuddyListConf { toggle_users_matching_view_section(): void { this.users_matching_view_is_collapsed = !this.users_matching_view_is_collapsed; - $("#buddy-list-users-matching-view-container").toggleClass( - "collapsed", - this.users_matching_view_is_collapsed, - ); - $("#buddy-list-users-matching-view-container .toggle-users-matching-view").toggleClass( - "rotate-icon-down", - !this.users_matching_view_is_collapsed, - ); - $("#buddy-list-users-matching-view-container .toggle-users-matching-view").toggleClass( - "rotate-icon-right", + this.set_section_collapse( + "#buddy-list-users-matching-view-container", this.users_matching_view_is_collapsed, ); @@ -527,16 +530,8 @@ export class BuddyList extends BuddyListConf { toggle_other_users_section(): void { this.other_users_is_collapsed = !this.other_users_is_collapsed; - $("#buddy-list-other-users-container").toggleClass( - "collapsed", - this.other_users_is_collapsed, - ); - $("#buddy-list-other-users-container .toggle-other-users").toggleClass( - "rotate-icon-down", - !this.other_users_is_collapsed, - ); - $("#buddy-list-other-users-container .toggle-other-users").toggleClass( - "rotate-icon-right", + this.set_section_collapse( + "#buddy-list-other-users-container", this.other_users_is_collapsed, ); @@ -566,7 +561,7 @@ export class BuddyList extends BuddyListConf { for (const item of items) { if (buddy_data.user_matches_narrow(item.user_id, pm_ids_set, current_sub?.stream_id)) { - if (this.render_data.participant_ids_set.has(item.user_id)) { + if (this.render_data.all_participant_ids.has(item.user_id)) { participants.push(item); this.participant_user_ids.push(item.user_id); } else { @@ -581,6 +576,10 @@ export class BuddyList extends BuddyListConf { this.$participants_list = $(this.participants_list_selector); if (participants.length) { + // Remove the empty list message before adding users + if ($(`${this.participants_list_selector} .empty-list-message`).length > 0) { + this.$participants_list.empty(); + } const participants_html = this.items_to_html({ items: participants, }); @@ -826,7 +825,7 @@ export class BuddyList extends BuddyListConf { list_user_id, current_sub, pm_ids_set, - this.render_data.participant_ids_set, + this.render_data.all_participant_ids, ) < 0, ); return i === -1 ? user_id_list.length : i; @@ -938,7 +937,7 @@ export class BuddyList extends BuddyListConf { current_sub?.stream_id, ); let user_id_list; - if (this.render_data.participant_ids_set.has(user_id)) { + if (this.render_data.all_participant_ids.has(user_id)) { user_id_list = this.participant_user_ids; } else if (is_subscribed_user) { user_id_list = this.users_matching_view_ids; @@ -963,7 +962,7 @@ export class BuddyList extends BuddyListConf { html, new_user_id, is_subscribed_user, - is_participant_user: this.render_data.participant_ids_set.has(user_id), + is_participant_user: this.render_data.all_participant_ids.has(user_id), }); } diff --git a/web/src/click_handlers.js b/web/src/click_handlers.js index 3530238a7c..5ceb2472e5 100644 --- a/web/src/click_handlers.js +++ b/web/src/click_handlers.js @@ -440,6 +440,9 @@ export function initialize() { if (e.metaKey || e.ctrlKey || e.shiftKey) { return; } + if ($(e.target).parents(".user-profile-picture").length === 1) { + return; + } const $li = $(e.target).parents("li"); @@ -515,8 +518,9 @@ export function initialize() { // BUDDY LIST TOOLTIPS (not displayed on touch devices) $(".buddy-list-section").on("mouseenter", ".selectable_sidebar_block", (e) => { e.stopPropagation(); - const $elem = $(e.currentTarget).closest(".user_sidebar_entry").find(".user-presence-link"); - const user_id_string = $elem.attr("data-user-id"); + const user_id_string = $(e.currentTarget) + .closest(".user_sidebar_entry") + .attr("data-user-id"); const title_data = buddy_data.get_title_data(user_id_string, false); // `target_node` is the `ul` element since it stays in DOM even after updates. @@ -531,28 +535,26 @@ export function initialize() { ); } - do_render_buddy_list_tooltip( - $elem.parent(), - title_data, - get_target_node, - check_reference_removed, - ); + const $elem = $(e.currentTarget) + .closest(".user_sidebar_entry") + .find(".selectable_sidebar_block"); + do_render_buddy_list_tooltip($elem, title_data, get_target_node, check_reference_removed); /* The following implements a little tooltip giving the name for status emoji when hovering them in the right sidebar. This requires special logic, to avoid conflicting with the main tooltip or showing duplicate tooltips. */ - $(".user-presence-link .status-emoji-name").off("mouseenter").off("mouseleave"); - $(".user-presence-link .status-emoji-name").on("mouseenter", () => { - const instance = $elem.parent()[0]._tippy; + $(".user_sidebar_entry .status-emoji-name").off("mouseenter").off("mouseleave"); + $(".user_sidebar_entry .status-emoji-name").on("mouseenter", () => { + const instance = $elem[0]._tippy; if (instance && instance.state.isVisible) { instance.destroy(); } }); - $(".user-presence-link .status-emoji-name").on("mouseleave", () => { + $(".user_sidebar_entry .status-emoji-name").on("mouseleave", () => { do_render_buddy_list_tooltip( - $elem.parent(), + $elem, title_data, get_target_node, check_reference_removed, diff --git a/web/src/common.ts b/web/src/common.ts index 9e80d2b70a..d9b5018a76 100644 --- a/web/src/common.ts +++ b/web/src/common.ts @@ -34,10 +34,14 @@ export function adjust_mac_kbd_tags(kbd_elem_class: string): void { let key_text = $(this).text(); // We use data-mac-key attribute to override the default key in case - // of exceptions. Currently, there are 2 shortcuts (for navigating back - // and forth in browser history) which need `Cmd` instead of the expected - // mapping (`Opt`) for the `Alt` key, so we use this attribute to override - // `Opt` with `Cmd`. + // of exceptions: + // - There are 2 shortcuts (for navigating back and forth in browser + // history) which need "⌘" instead of the expected mapping ("Opt") + // for the "Alt" key, so we use this attribute to override "Opt" + // with "⌘". + // - The "Ctrl" + "[" shortcuts (which match the Vim keybinding behavior + // of mapping to "Esc") need to display "Ctrl" for all users, so we + // use this attribute to override "⌘" with "Ctrl". const replace_key = $(this).attr("data-mac-key") ?? keys_map.get(key_text); if (replace_key !== undefined) { key_text = replace_key; diff --git a/web/src/compose_recipient.ts b/web/src/compose_recipient.ts index 21d46a4e0a..7f5b61e2e1 100644 --- a/web/src/compose_recipient.ts +++ b/web/src/compose_recipient.ts @@ -259,7 +259,7 @@ function get_options_for_recipient_widget(): Option[] { name: $t({defaultMessage: "Direct message"}), }; - if (!user_groups.is_empty_group(realm.realm_direct_message_permission_group)) { + if (!user_groups.is_setting_group_empty(realm.realm_direct_message_permission_group)) { options.unshift(direct_messages_option); } else { options.push(direct_messages_option); diff --git a/web/src/compose_validate.ts b/web/src/compose_validate.ts index 683c40d4cd..098aa2a0e4 100644 --- a/web/src/compose_validate.ts +++ b/web/src/compose_validate.ts @@ -114,7 +114,7 @@ export function needs_subscribe_warning(user_id: number, stream_id: number): boo export function check_dm_permissions_and_get_error_string(user_ids_string: string): string { if (!people.user_can_direct_message(user_ids_string)) { - if (user_groups.is_empty_group(realm.realm_direct_message_permission_group)) { + if (user_groups.is_setting_group_empty(realm.realm_direct_message_permission_group)) { return $t({ defaultMessage: "Direct messages are disabled in this organization.", }); @@ -569,6 +569,10 @@ export function validate_stream_message_mentions(opts: StreamWildcardOptions): b } export function validate_stream_message_address_info(sub: StreamSubscription): boolean { + if (sub.is_archived) { + compose_banner.show_stream_does_not_exist_error(sub.name); + return false; + } if (sub.subscribed) { return true; } diff --git a/web/src/copy_and_paste.ts b/web/src/copy_and_paste.ts index 051403bd5a..14825f8fd2 100644 --- a/web/src/copy_and_paste.ts +++ b/web/src/copy_and_paste.ts @@ -666,24 +666,40 @@ export function try_stream_topic_syntax_text(text: string): string | null { return null; } + // Now we're sure that the URL is a valid stream topic URL. + // But the produced #**stream>topic** syntax could be broken. + const stream = stream_data.get_sub_by_id(stream_topic.stream_id); assert(stream !== undefined); const stream_name = stream.name; if (topic_link_util.will_produce_broken_stream_topic_link(stream_name)) { - return null; + return topic_link_util.get_fallback_markdown_link( + stream_name, + stream_topic.topic_name, + stream_topic.message_id, + ); } if ( stream_topic.topic_name !== undefined && topic_link_util.will_produce_broken_stream_topic_link(stream_topic.topic_name) ) { - return null; + return topic_link_util.get_fallback_markdown_link( + stream_name, + stream_topic.topic_name, + stream_topic.message_id, + ); } let syntax_text = "#**" + stream_name; if (stream_topic.topic_name) { syntax_text += ">" + stream_topic.topic_name; } + + if (stream_topic.message_id !== undefined) { + syntax_text += "@" + stream_topic.message_id; + } + syntax_text += "**"; return syntax_text; } diff --git a/web/src/custom_profile_fields_ui.ts b/web/src/custom_profile_fields_ui.ts index d2295b8ad3..d7f13ff278 100644 --- a/web/src/custom_profile_fields_ui.ts +++ b/web/src/custom_profile_fields_ui.ts @@ -77,23 +77,22 @@ export function append_custom_profile_fields(element_id: string, user_id: number } } +export type PillUpdateField = { + type: number; + field_data: string; + hint: string; + id: number; + name: string; + order: number; + required: boolean; + display_in_profile_summary?: boolean | undefined; +}; + export function initialize_custom_user_type_fields( element_id: string, user_id: number, is_target_element_editable: boolean, - pill_update_handler?: ( - field: { - type: number; - field_data: string; - hint: string; - id: number; - name: string; - order: number; - required: boolean; - display_in_profile_summary?: boolean | undefined; - }, - pills: UserPillWidget, - ) => void, + pill_update_handler?: (field: PillUpdateField, pills: UserPillWidget) => void, ): Map { const field_types = realm.custom_profile_field_types; const user_pills = new Map(); diff --git a/web/src/demo_organizations_ui.js b/web/src/demo_organizations_ui.ts similarity index 83% rename from web/src/demo_organizations_ui.js rename to web/src/demo_organizations_ui.ts index 5bf20ad7eb..5412dcf0c2 100644 --- a/web/src/demo_organizations_ui.js +++ b/web/src/demo_organizations_ui.ts @@ -1,4 +1,5 @@ import $ from "jquery"; +import {z} from "zod"; import render_convert_demo_organization_form from "../templates/settings/convert_demo_organization_form.hbs"; import render_demo_organization_warning from "../templates/settings/demo_organization_warning.hbs"; @@ -11,9 +12,11 @@ import {get_demo_organization_deadline_days_remaining} from "./navbar_alerts"; import * as settings_config from "./settings_config"; import * as settings_data from "./settings_data"; import * as settings_org from "./settings_org"; +import type {RequestOpts} from "./settings_ui"; import {current_user, realm} from "./state_data"; +import type {HTMLSelectOneElement} from "./types"; -export function insert_demo_organization_warning() { +export function insert_demo_organization_warning(): void { const days_remaining = get_demo_organization_deadline_days_remaining(); const rendered_demo_organization_warning = render_demo_organization_warning({ is_demo_organization: realm.demo_organization_scheduled_deletion_date, @@ -23,7 +26,7 @@ export function insert_demo_organization_warning() { $(".organization-box").find(".settings-section").prepend($(rendered_demo_organization_warning)); } -export function handle_demo_organization_conversion() { +export function handle_demo_organization_conversion(): void { $(".convert-demo-organization-button").on("click", () => { if (!current_user.is_owner) { return; @@ -39,7 +42,7 @@ export function handle_demo_organization_conversion() { realm_org_type_values: settings_org.get_org_type_dropdown_options(), }); - function demo_organization_conversion_post_render() { + function demo_organization_conversion_post_render(): void { const $convert_submit_button = $( "#demo-organization-conversion-modal .dialog_submit_button", ); @@ -53,8 +56,10 @@ export function handle_demo_organization_conversion() { } else { // Disable submit button if either form field blank. $("#convert-demo-organization-form").on("input change", () => { - const string_id = $("#new_subdomain").val().trim(); - const org_type = $("#add_organization_type").val(); + const string_id = $("input#new_subdomain").val()!.trim(); + const org_type = $( + "select:not([multiple])#add_organization_type", + ).val()!; $convert_submit_button.prop( "disabled", string_id === "" || @@ -65,15 +70,16 @@ export function handle_demo_organization_conversion() { } } - function submit_subdomain() { + function submit_subdomain(): void { const $string_id = $("#new_subdomain"); const $organization_type = $("#add_organization_type"); const data = { string_id: $string_id.val(), org_type: $organization_type.val(), }; - const opts = { - success_continuation(data) { + const opts: RequestOpts = { + success_continuation(raw_data) { + const data = z.object({realm_url: z.string()}).parse(raw_data); window.location.href = data.realm_url; }, }; diff --git a/web/src/dialog_widget.ts b/web/src/dialog_widget.ts index 2b2afb46da..a1155752af 100644 --- a/web/src/dialog_widget.ts +++ b/web/src/dialog_widget.ts @@ -25,9 +25,9 @@ function current_dialog_widget_selector(): string { } /* - * Look for confirm_dialog in settings_user_groups - * to see an example of how to use this widget. It's - * pretty simple to use! + * Look for dialog_widget or confirm_dialog in various + * 'web/src/' files to see examples of how to use this widget. + * It's pretty simple to use! * * Some things to note: * 1) We create DOM on the fly, and we remove @@ -88,12 +88,11 @@ type RequestOpts = { }; export function hide_dialog_spinner(): void { - $(".dialog_submit_button span").show(); const dialog_widget_selector = current_dialog_widget_selector(); + const $spinner = $(`${dialog_widget_selector} .modal__spinner`); $(`${dialog_widget_selector} .modal__btn`).prop("disabled", false); - const $spinner = $(`${dialog_widget_selector} .modal__spinner`); - loading.destroy_indicator($spinner); + loading.hide_spinner($(".dialog_submit_button"), $spinner); } export function show_dialog_spinner(): void { @@ -102,17 +101,8 @@ export function show_dialog_spinner(): void { $(`${dialog_widget_selector} .modal__btn`).prop("disabled", true); const $spinner = $(`${dialog_widget_selector} .modal__spinner`); - const dialog_submit_button_span_width = $(".dialog_submit_button span").width(); - const dialog_submit_button_span_height = $(".dialog_submit_button span").height(); - // Hide the submit button after computing its height, since submit - // buttons with long text might affect the size of the button. - $(".dialog_submit_button span").hide(); - - loading.make_indicator($spinner, { - width: dialog_submit_button_span_width, - height: dialog_submit_button_span_height, - }); + loading.show_spinner($(".dialog_submit_button"), $spinner); } // Supports a callback to be called once the modal finishes closing. diff --git a/web/src/echo.ts b/web/src/echo.ts index 9d91a7e2ff..695b1712dd 100644 --- a/web/src/echo.ts +++ b/web/src/echo.ts @@ -13,6 +13,7 @@ import * as echo_state from "./echo_state"; import * as local_message from "./local_message"; import * as markdown from "./markdown"; import * as message_events_util from "./message_events_util"; +import * as message_list_data_cache from "./message_list_data_cache"; import * as message_lists from "./message_lists"; import * as message_live_update from "./message_live_update"; import * as message_store from "./message_store"; @@ -557,6 +558,17 @@ export function process_from_server(messages: ServerMessage[]): ServerMessage[] // message content, but in practice, there's no harm to just // doing it unconditionally. msg_list.view.rerender_messages(msgs_to_rerender_or_add_to_narrow); + msg_list.add_messages(msgs_to_rerender_or_add_to_narrow, {}); + } + } + + for (const msg_list_data of message_lists.non_rendered_data()) { + if (!msg_list_data.filter.can_apply_locally()) { + // Ideally we would ask server to if messages matches filter + // but it is not worth doing so for every new message. + message_list_data_cache.remove(msg_list_data.filter); + } else { + msg_list_data.add_messages(msgs_to_rerender_or_add_to_narrow); } } } diff --git a/web/src/fetch_status.ts b/web/src/fetch_status.ts index f60f4b4c4e..4ae7298cc7 100644 --- a/web/src/fetch_status.ts +++ b/web/src/fetch_status.ts @@ -141,4 +141,15 @@ export class FetchStatus { max_id_for_messages(messages), ); } + + copy_status(fetch_status: FetchStatus): void { + this._found_newest = fetch_status.has_found_newest(); + this._found_oldest = fetch_status.has_found_oldest(); + this._expected_max_message_id = fetch_status._expected_max_message_id; + this._history_limited = fetch_status._history_limited; + // We don't want to copy over the loading state of the message list + // data since the same data object is not used for two messages lists + // and hence when the fetch is finished, only the original message list + // data will be updated. + } } diff --git a/web/src/filter.ts b/web/src/filter.ts index 625866cf31..8f145b1ae0 100644 --- a/web/src/filter.ts +++ b/web/src/filter.ts @@ -1772,6 +1772,7 @@ export class Filter { "not-in-home", "in-all", "not-in-all", + "search", ]); for (const term of term_types) { @@ -1782,4 +1783,18 @@ export class Filter { return false; } + + get_stringified_narrow_for_server_query(): string { + return JSON.stringify( + this._terms.map((term) => { + if (term.operator === "channel") { + return { + ...term, + operand: Number.parseInt(term.operand, 10), + }; + } + return term; + }), + ); + } } diff --git a/web/src/group_setting_pill.ts b/web/src/group_setting_pill.ts index ad16fa9eb4..da9492aee4 100644 --- a/web/src/group_setting_pill.ts +++ b/web/src/group_setting_pill.ts @@ -153,7 +153,7 @@ export function set_up_pill_typeahead({ opts: { setting_name: string; setting_type: "realm" | "stream" | "group"; - group: UserGroup | undefined; + group?: UserGroup | undefined; }; }): void { pill_typeahead.set_up_group_setting_typeahead( diff --git a/web/src/hash_util.ts b/web/src/hash_util.ts index d3c6a0ff1a..e4e1b3558d 100644 --- a/web/src/hash_util.ts +++ b/web/src/hash_util.ts @@ -223,14 +223,18 @@ export function validate_channels_settings_hash(hash: string): string { const stream_id = Number.parseInt(section, 10); const sub = sub_store.get(stream_id); // There are a few situations where we can't display stream settings: - // 1. This is a stream that's been archived. (sub=undefined) + // 1. This is a stream that's been archived. (sub.is_archived=true) // 2. The stream ID is invalid. (sub=undefined) // 3. The current user is a guest, and was unsubscribed from the stream // stream in the current session. (In future sessions, the stream will // not be in sub_store). // // In all these cases we redirect the user to 'subscribed' tab. - if (sub === undefined || (page_params.is_guest && !stream_data.is_subscribed(stream_id))) { + if ( + sub === undefined || + sub.is_archived || + (page_params.is_guest && !stream_data.is_subscribed(stream_id)) + ) { return channels_settings_section_url(); } @@ -289,7 +293,7 @@ export function validate_group_settings_hash(hash: string): string { export function decode_stream_topic_from_url( url_str: string, -): {stream_id: number; topic_name?: string} | null { +): {stream_id: number; topic_name?: string; message_id?: string} | null { try { const url = new URL(url_str); if (url.origin !== window.location.origin || !url.hash.startsWith("#narrow")) { @@ -299,9 +303,8 @@ export function decode_stream_topic_from_url( if (terms === undefined) { return null; } - if (terms.length > 2) { + if (terms.length > 3) { // The link should only contain stream and topic, - // near/ links are not transformed. return null; } // This check is important as a malformed url @@ -320,7 +323,13 @@ export function decode_stream_topic_from_url( if (terms[1]?.operator !== "topic") { return null; } - return {stream_id, topic_name: terms[1].operand}; + if (terms.length === 2) { + return {stream_id, topic_name: terms[1].operand}; + } + if (terms[2]?.operator !== "near") { + return null; + } + return {stream_id, topic_name: terms[1].operand, message_id: terms[2].operand}; } catch { return null; } diff --git a/web/src/hotkey.js b/web/src/hotkey.js index 0c6fb3f2ef..735a9bbaf7 100644 --- a/web/src/hotkey.js +++ b/web/src/hotkey.js @@ -306,7 +306,7 @@ export function process_escape_key(e) { if (processing_text()) { if (activity_ui.searching()) { - activity_ui.escape_search(); + activity_ui.clear_search(); return true; } diff --git a/web/src/inbox_ui.ts b/web/src/inbox_ui.ts index e668caa5a3..78405978c3 100644 --- a/web/src/inbox_ui.ts +++ b/web/src/inbox_ui.ts @@ -71,6 +71,7 @@ const direct_message_context_properties: (keyof DirectMessageContext)[] = [ type StreamContext = { is_stream: boolean; + is_archived: boolean; invite_only: boolean; is_web_public: boolean; stream_name: string; @@ -372,6 +373,7 @@ function format_stream(stream_id: number): StreamContext { return { is_stream: true, + is_archived: stream_info.is_archived, invite_only: stream_info.invite_only, is_web_public: stream_info.is_web_public, stream_name: stream_info.name, diff --git a/web/src/integration_url_modal.ts b/web/src/integration_url_modal.ts index 61f285fd01..5e6c25b3a6 100644 --- a/web/src/integration_url_modal.ts +++ b/web/src/integration_url_modal.ts @@ -1,7 +1,10 @@ import ClipboardJS from "clipboard"; import $ from "jquery"; import type * as tippy from "tippy.js"; +import {z} from "zod"; +import render_generate_integration_url_config_checkbox_modal from "../templates/settings/generate_integration_url_config_checkbox_modal.hbs"; +import render_generate_integration_url_config_text_modal from "../templates/settings/generate_integration_url_config_text_modal.hbs"; import render_generate_integration_url_modal from "../templates/settings/generate_integration_url_modal.hbs"; import render_integration_events from "../templates/settings/integration_events.hbs"; @@ -14,6 +17,20 @@ import {realm} from "./state_data"; import * as stream_data from "./stream_data"; import * as util from "./util"; +type ConfigOption = { + key: string; + label: string; + validator: string; +}; + +const config_option_schema = z.object({ + key: z.string(), + label: z.string(), + validator: z.string(), +}); + +const config_options_schema = z.array(config_option_schema); + export function show_generate_integration_url_modal(api_key: string): void { const default_url_message = $t_html({defaultMessage: "Integration URL will appear here."}); const streams = stream_data.subscribed_subs(); @@ -42,6 +59,7 @@ export function show_generate_integration_url_modal(api_key: string): void { const $integration_url = $("#generate-integration-url-modal .integration-url"); const $dialog_submit_button = $("#generate-integration-url-modal .dialog_submit_button"); const $show_integration_events = $("#show-integration-events"); + const $config_container = $("#integration-url-config-options-container"); $dialog_submit_button.prop("disabled", true); $("#integration-url-stream_widget").prop("disabled", true); @@ -57,6 +75,40 @@ export function show_generate_integration_url_modal(api_key: string): void { ); }); + function render_config(config: ConfigOption[]): void { + const validated_config = config_options_schema.parse(config); + $config_container.empty(); + + for (const option of validated_config) { + let $config_element: JQuery; + + if (option.validator === "check_bool") { + const config_html = render_generate_integration_url_config_checkbox_modal({ + key: option.key, + label: option.label, + }); + $config_element = $(config_html); + $config_element + .find(`#integration-url-${option.key}-checkbox`) + .on("change", () => { + update_url(); + }); + } else if (option.validator === "check_string") { + const config_html = render_generate_integration_url_config_text_modal({ + key: option.key, + label: option.label, + }); + $config_element = $(config_html); + $config_element.find(`#integration-url-${option.key}-text`).on("change", () => { + update_url(); + }); + } else { + continue; + } + $config_container.append($config_element); + } + } + $override_topic.on("change", function () { const checked = this.checked; $topic_input.parent().toggleClass("hide", !checked); @@ -109,6 +161,7 @@ export function show_generate_integration_url_modal(api_key: string): void { (bot) => bot.name === selected_integration, ); const all_event_types = selected_integration_data?.all_event_types; + const config = selected_integration_data?.config_options; if (all_event_types !== null) { $("#integration-events-parameter").removeClass("hide"); @@ -136,8 +189,27 @@ export function show_generate_integration_url_modal(api_key: string): void { params.set("topic", topic_name); } } + const selected_events = set_events_param(params); + if (config) { + for (const option of config) { + let $input_element; + if (option.validator === "check_bool") { + $input_element = $(`#integration-url-${option.key}-checkbox`); + if ($input_element.prop("checked")) { + params.set(option.key, "true"); + } + } else if (option.validator === "check_string") { + $input_element = $(`#integration-url-${option.key}-text`); + const value = $input_element.val(); + if (value) { + params.set(option.key, value.toString()); + } + } + } + } + const realm_url = realm.realm_url; const base_url = `${realm_url}/api/v1/external/`; $integration_url.text(`${base_url}${selected_integration}?${params.toString()}`); @@ -181,6 +253,15 @@ export function show_generate_integration_url_modal(api_key: string): void { integration_input_dropdown_widget.render(); $(".integration-url-name-wrapper").trigger("input"); + const selected_integration = integration_input_dropdown_widget.value(); + const selected_integration_data = realm.realm_incoming_webhook_bots.find( + (bot) => bot.name === selected_integration, + ); + + if (selected_integration_data?.config_options) { + render_config(selected_integration_data.config_options); + } + dropdown.hide(); event.preventDefault(); event.stopPropagation(); @@ -265,6 +346,7 @@ export function show_generate_integration_url_modal(api_key: string): void { $topic_input.parent().addClass("hide"); stream_input_dropdown_widget.render(direct_messages_option.unique_id); + $config_container.empty(); } } diff --git a/web/src/invite.ts b/web/src/invite.ts index 1b5d966c0f..85dd98dda3 100644 --- a/web/src/invite.ts +++ b/web/src/invite.ts @@ -24,6 +24,7 @@ import * as input_pill from "./input_pill"; import * as invite_stream_picker_pill from "./invite_stream_picker_pill"; import {page_params} from "./page_params"; import * as peer_data from "./peer_data"; +import * as settings_components from "./settings_components"; import * as settings_config from "./settings_config"; import * as settings_data from "./settings_data"; import {current_user, realm} from "./state_data"; @@ -254,58 +255,35 @@ function generate_multiuse_invite(): void { }); } -function valid_to(time_valid: number): string { - if (!time_valid) { +function valid_to(): string { + const $expires_in = $("select:not([multiple])#expires_in"); + const time_input_value = $expires_in.val()!; + + if (time_input_value === "null") { return $t({defaultMessage: "Never expires"}); } + let time_in_minutes: number; + if (time_input_value === "custom") { + if (!util.validate_custom_time_input(custom_expiration_time_input)) { + return $t({defaultMessage: "Invalid custom time"}); + } + time_in_minutes = util.get_custom_time_in_minutes( + custom_expiration_time_unit, + custom_expiration_time_input, + ); + } else { + time_in_minutes = Number.parseFloat(time_input_value); + } + // The below is a duplicate of timerender.get_full_datetime, with a different base string. - const valid_to = add(new Date(), {minutes: time_valid}); + const valid_to = add(new Date(), {minutes: time_in_minutes}); const date = timerender.get_localized_date_or_time_for_format(valid_to, "dayofyear_year"); const time = timerender.get_localized_date_or_time_for_format(valid_to, "time"); return $t({defaultMessage: "Expires on {date} at {time}"}, {date, time}); } -function set_custom_expires_on_text(): void { - if (util.validate_custom_time_input(custom_expiration_time_input)) { - $("#custom_expires_on").text( - valid_to( - util.get_custom_time_in_minutes( - custom_expiration_time_unit, - custom_expiration_time_input, - ), - ), - ); - } else { - $("#custom_expires_on").text($t({defaultMessage: "Invalid custom time"})); - } -} - -function set_expires_on_text(): void { - const $expires_in = $("select:not([multiple])#expires_in"); - if ($expires_in.val() === "custom") { - $("#expires_on").hide(); - set_custom_expires_on_text(); - } else { - $("#expires_on").show(); - $("#expires_on").text(valid_to(Number.parseFloat($expires_in.val()!))); - } -} - -function set_custom_time_inputs_visibility(): void { - const $expires_in = $("select:not([multiple])#expires_in"); - if ($expires_in.val() === "custom") { - $("#custom-expiration-time-input").val(custom_expiration_time_input); - $("select:not([multiple])#custom-expiration-time-unit").val( - custom_expiration_time_unit, - ); - $("#custom-invite-expiration-time").show(); - } else { - $("#custom-invite-expiration-time").hide(); - } -} - function set_streams_to_join_list_visibility(): void { const realm_has_default_streams = stream_data.get_default_stream_ids().length !== 0; const hide_streams_list = @@ -385,8 +363,13 @@ function open_invite_user_modal(e: JQuery.ClickEvent): void const user_has_email_set = !settings_data.user_email_not_configured(); - set_custom_time_inputs_visibility(); - set_expires_on_text(); + settings_components.set_custom_time_inputs_visibility( + $expires_in, + custom_expiration_time_unit, + custom_expiration_time_input, + ); + const valid_to_text = valid_to(); + settings_components.set_time_input_formatted_text($expires_in, valid_to_text); if (settings_data.user_can_subscribe_other_users()) { set_streams_to_join_list_visibility(); @@ -439,8 +422,13 @@ function open_invite_user_modal(e: JQuery.ClickEvent): void pills.onTextInputHook(toggle_invite_submit_button); $expires_in.on("change", () => { - set_custom_time_inputs_visibility(); - set_expires_on_text(); + settings_components.set_custom_time_inputs_visibility( + $expires_in, + custom_expiration_time_unit, + custom_expiration_time_input, + ); + const valid_to_text = valid_to(); + settings_components.set_time_input_formatted_text($expires_in, valid_to_text); toggle_invite_submit_button(); }); @@ -451,14 +439,15 @@ function open_invite_user_modal(e: JQuery.ClickEvent): void } }); - $(".custom-expiration-time").on("change", () => { + $("#custom-expiration-time-input, #custom-expiration-time-unit").on("change", () => { custom_expiration_time_input = util.check_time_input( $("input#custom-expiration-time-input").val()!, ); custom_expiration_time_unit = $( "select:not([multiple])#custom-expiration-time-unit", ).val()!; - set_custom_expires_on_text(); + const valid_to_text = valid_to(); + settings_components.set_time_input_formatted_text($expires_in, valid_to_text); toggle_invite_submit_button(); }); diff --git a/web/src/loading.ts b/web/src/loading.ts index 1d74935121..cf9173cf01 100644 --- a/web/src/loading.ts +++ b/web/src/loading.ts @@ -91,3 +91,26 @@ export function show_button_spinner($elt: JQuery, using_dark_theme: boolean): vo } $elt.css("display", "inline-block"); } + +export function show_spinner($button_element: JQuery, $spinner: JQuery): void { + const span_width = $button_element.find(".submit-button-text").width(); + const span_height = $button_element.find(".submit-button-text").height(); + + // Hide the submit button after computing its height, since submit + // buttons with long text might affect the size of the button. + $button_element.find(".submit-button-text").hide(); + + // Create the loading indicator + make_indicator($spinner, { + width: span_width, + height: span_height, + }); +} + +export function hide_spinner($button_element: JQuery, $spinner: JQuery): void { + // Show the span + $button_element.find(".submit-button-text").show(); + + // Destroy the loading indicator + destroy_indicator($spinner); +} diff --git a/web/src/message_edit.ts b/web/src/message_edit.ts index 5f2637755a..ed7e6107af 100644 --- a/web/src/message_edit.ts +++ b/web/src/message_edit.ts @@ -102,13 +102,17 @@ export function is_topic_editable(message: Message, edit_limit_seconds_buffer = return false; } + if (message.type === "stream" && stream_data.is_stream_archived(message.stream_id)) { + return false; + } + if (!settings_data.user_can_move_messages_to_another_topic()) { return false; } // Organization admins and moderators can edit message topics indefinitely, - // irrespective of the topic editing deadline, if edit_topic_policy allows - // them to do so. + // irrespective of the topic editing deadline, if they are in the + // can_move_messages_between_topics_group. if (current_user.is_admin || current_user.is_moderator) { return true; } @@ -206,6 +210,10 @@ export function is_message_sent_by_my_bot(message: Message): boolean { } export function get_deletability(message: Message): boolean { + if (message.type === "stream" && stream_data.is_stream_archived(message.stream_id)) { + return false; + } + if (settings_data.user_can_delete_any_message()) { return true; } @@ -243,6 +251,10 @@ export function is_stream_editable(message: Message, edit_limit_seconds_buffer = return false; } + if (message.type === "stream" && stream_data.is_stream_archived(message.stream_id)) { + return false; + } + if (!settings_data.user_can_move_messages_between_streams()) { return false; } diff --git a/web/src/message_events.js b/web/src/message_events.js index 4433787310..2c7b7e9e2d 100644 --- a/web/src/message_events.js +++ b/web/src/message_events.js @@ -39,6 +39,70 @@ import * as unread from "./unread"; import * as unread_ui from "./unread_ui"; import * as util from "./util"; +function filter_has_term_type(filter, term_type) { + return ( + filter !== undefined && + (filter.sorted_term_types().includes(term_type) || + filter.sorted_term_types().includes(`not-${term_type}`)) + ); +} + +export function discard_cached_lists_with_term_type(term_type) { + // Discards cached MessageList and MessageListData which have + // `term_type` and `not-term_type`. + assert(!term_type.includes("not-")); + + // We loop over rendered message lists and cached message data separately since + // they are separately maintained and can have different items. + for (const msg_list of message_lists.all_rendered_message_lists()) { + // We never want to discard the current message list. + if (msg_list === message_lists.current) { + continue; + } + + const filter = msg_list.data.filter; + if (filter_has_term_type(filter, term_type)) { + message_lists.delete_message_list(msg_list); + message_list_data_cache.remove(filter); + } + } + + for (const msg_list_data of message_lists.non_rendered_data()) { + const filter = msg_list_data.filter; + if (filter_has_term_type(filter, term_type)) { + message_list_data_cache.remove(filter); + } + } +} + +export function update_current_view_for_topic_visibility() { + // If we have rendered message list / cached data based on topic + // visibility policy, we need to rerender it to reflect the changes. It + // is easier to just load the narrow from scratch, instead of asking server + // for relevant messages in the updated topic. + const filter = message_lists.current?.data.filter; + if (filter_has_term_type(filter, "is-followed")) { + // Use `set_timeout to call after we update the topic + // visibility policy locally. + // Calling this outside `user_topics_ui` to avoid circular imports. + const msg_list_id = message_lists.current.id; + setTimeout(() => { + if (message_lists.current.id !== msg_list_id) { + // Check if the message list is still the same. + return; + } + + message_view.show(filter.terms(), { + then_select_id: message_lists.current.selected_id(), + trigger: "topic visibility policy change", + force_rerender: true, + }); + }, 0); + return true; + } + return false; +} + export function update_views_filtered_on_message_property( message_ids, property_term_type, @@ -47,8 +111,11 @@ export function update_views_filtered_on_message_property( // NOTE: Call this function after updating the message property locally. assert(!property_term_type.includes("not-")); - // List of narrow terms whose msg list doesn't get updated elsewhere but - // can be applied locally. + // List of narrow terms where the message list doesn't get + // automatically updated elsewhere when the property changes, but + // we can apply locally if we have the message. + // + // is:followed is handled via update_current_view_for_topic_visibility. const supported_term_types = [ "has-image", "has-link", @@ -58,8 +125,6 @@ export function update_views_filtered_on_message_property( "is-unread", "is-mentioned", "is-alerted", - // TODO: Implement support for these terms. - // "is-followed", ]; if (message_ids.length === 0 || !supported_term_types.includes(property_term_type)) { diff --git a/web/src/message_list.ts b/web/src/message_list.ts index ab60d4d519..6b62a7a545 100644 --- a/web/src/message_list.ts +++ b/web/src/message_list.ts @@ -432,7 +432,7 @@ export class MessageList { const is_web_public = sub?.is_web_public; const can_toggle_subscription = sub !== undefined && stream_data.can_toggle_subscription(sub); - if (sub === undefined) { + if (sub === undefined || sub.is_archived) { deactivated = true; } else if (!subscribed && !this.last_message_historical) { just_unsubscribed = true; diff --git a/web/src/message_list_view.ts b/web/src/message_list_view.ts index ee62d2b3bf..c72e469e20 100644 --- a/web/src/message_list_view.ts +++ b/web/src/message_list_view.ts @@ -99,6 +99,7 @@ export type MessageGroup = { stream_name?: string; stream_privacy_icon_color: string; stream_url: string; + is_archived: boolean; subscribed?: boolean; topic: string; topic_is_resolved: boolean; @@ -106,6 +107,7 @@ export type MessageGroup = { topic_url: string | undefined; user_can_resolve_topic: boolean; visibility_policy: number | false; + always_display_date: boolean; } | { is_stream: false; @@ -114,6 +116,7 @@ export type MessageGroup = { is_private: true; pm_with_url: string; recipient_users: RecipientRowUser[]; + always_display_date: boolean; } ); @@ -425,6 +428,14 @@ function maybe_restore_focus_to_message_edit_form(): void { }, 0); } +function is_search_view(): boolean { + const current_filter = narrow_state.filter(); + if (current_filter && !current_filter.supports_collapsing_recipients()) { + return true; + } + return false; +} + type SubscriptionMarkers = { bookend_top: boolean; stream_name: string; @@ -443,6 +454,9 @@ function populate_group_from_message( const message_group_id = _.uniqueId("message_group_"); const date = get_group_display_date(message, year_changed); + // Each searched message is a self-contained result, + // so we always display date in the recipient bar for those messages. + const always_display_date = is_search_view(); if (is_stream) { assert(message.type === "stream"); // stream messages have string display_recipient @@ -455,6 +469,7 @@ function populate_group_from_message( const topic = message.topic; const match_topic = util.get_match_topic(message); const stream_url = hash_util.by_stream_url(message.stream_id); + const is_archived = stream_data.is_stream_archived(message.stream_id); const topic_url = hash_util.by_stream_topic_url(message.stream_id, message.topic); const sub = sub_store.get(message.stream_id); @@ -495,12 +510,14 @@ function populate_group_from_message( is_web_public, match_topic, stream_url, + is_archived, topic_url, stream_id, is_subscribed, topic_is_resolved, visibility_policy, all_visibility_policies, + always_display_date, }; } // Private message group @@ -520,6 +537,7 @@ function populate_group_from_message( pm_with_url: message.pm_with_url, recipient_users: get_users_for_recipient_row(message), display_reply_to_for_tooltip: message_store.get_pm_full_names(user_ids), + always_display_date, }; } diff --git a/web/src/message_lists.ts b/web/src/message_lists.ts index 6e06ea5bd5..a8c8f8c69e 100644 --- a/web/src/message_lists.ts +++ b/web/src/message_lists.ts @@ -15,7 +15,7 @@ export function set_current(msg_list: MessageList | undefined): void { current = msg_list; } -function delete_message_list(message_list: MessageList): void { +export function delete_message_list(message_list: MessageList): void { message_list.view.$list.remove(); rendered_message_lists.delete(message_list.id); message_list.data.set_rendered_message_list_id(undefined); diff --git a/web/src/message_scroll.js b/web/src/message_scroll.js index 50267e9183..8057faaf98 100644 --- a/web/src/message_scroll.js +++ b/web/src/message_scroll.js @@ -96,7 +96,7 @@ export function scroll_finished() { // enter the screen and become read. Calling // unread_ops.process_visible will update necessary // data structures and DOM elements. - setTimeout(unread_ops.process_visible, 0); + unread_ops.process_visible(); } else { message_scroll_state.set_update_selection_on_next_scroll(true); } @@ -157,12 +157,24 @@ export function initialize() { if (event.mark_read && event.previously_selected_id !== -1) { // Mark messages between old pointer and new pointer as read - let messages; if (event.id < event.previously_selected_id) { - messages = event.msg_list.message_range(event.id, event.previously_selected_id); - } else { - messages = event.msg_list.message_range(event.previously_selected_id, event.id); + // We don't mark messages as read when the pointer moves up. + return; } + + const messages = event.msg_list.message_range(event.previously_selected_id, event.id); + // If the user just arrived at the message `event.id`, we don't mark it as read + // unless it is the last message in the list. + // We only mark messages as read when the pointer moves past the message. + // This is likely the last message in the list. So, we loop through the messages + // in reverse order to find the message. + for (let i = messages.length - 1; i >= 0; i -= 1) { + if (messages[i].id === event.id && event.id !== event.msg_list.last()?.id) { + delete messages[i]; + break; + } + } + if (event.msg_list.can_mark_messages_read()) { unread_ops.notify_server_messages_read(messages, {from: "pointer"}); } else if ( diff --git a/web/src/message_view.ts b/web/src/message_view.ts index 09b0b0b97d..406eef2af9 100644 --- a/web/src/message_view.ts +++ b/web/src/message_view.ts @@ -502,7 +502,6 @@ export function show(raw_terms: NarrowTerm[], show_opts: ShowMessageViewOpts): v ); if (adjusted_terms === null) { - blueslip.error("adjusted_terms impossibly null"); return; } diff --git a/web/src/narrow_state.ts b/web/src/narrow_state.ts index 8d595e7ddf..5158602731 100644 --- a/web/src/narrow_state.ts +++ b/web/src/narrow_state.ts @@ -197,12 +197,11 @@ export function pm_emails_string( export function get_first_unread_info( current_filter: Filter | undefined = filter(), ): {flavor: "cannot_compute" | "not_found"} | {flavor: "found"; msg_id: number} { + const cannot_compute_response: {flavor: "cannot_compute"} = {flavor: "cannot_compute"}; if (current_filter === undefined) { // we don't yet support the all-messages view blueslip.error("unexpected call to get_first_unread_info"); - return { - flavor: "cannot_compute", - }; + return cannot_compute_response; } if (!current_filter.can_apply_locally()) { @@ -210,18 +209,14 @@ export function get_first_unread_info( // that the client isn't privy to, we need to wait for the // server to give us a definitive list of messages before // deciding where we'll move the selection. - return { - flavor: "cannot_compute", - }; + return cannot_compute_response; } const unread_ids = _possible_unread_message_ids(current_filter); if (unread_ids === undefined) { // _possible_unread_message_ids() only works for certain narrows - return { - flavor: "cannot_compute", - }; + return cannot_compute_response; } const msg_id = current_filter.first_valid_id_from(unread_ids); diff --git a/web/src/onboarding_steps.ts b/web/src/onboarding_steps.ts index 715e3301ec..6548f03a0f 100644 --- a/web/src/onboarding_steps.ts +++ b/web/src/onboarding_steps.ts @@ -15,7 +15,7 @@ export function post_onboarding_step_as_read(onboarding_step_name: string): void data: {onboarding_step: onboarding_step_name}, error(err) { if (err.readyState !== 0) { - blueslip.error("Failed to fetch onboarding steps", { + blueslip.error(`Failed to mark ${onboarding_step_name} as read.`, { readyState: err.readyState, status: err.status, body: err.responseText, diff --git a/web/src/peer_data.ts b/web/src/peer_data.ts index 1801cc05b4..20b1fd8093 100644 --- a/web/src/peer_data.ts +++ b/web/src/peer_data.ts @@ -43,7 +43,7 @@ export function potential_subscribers(stream_id: number): User[] { stream. This may include some bots. We currently use it for typeahead in - stream_edit.js. + stream_edit.ts. This may be a superset of the actual subscribers that you can change in some cases diff --git a/web/src/people.ts b/web/src/people.ts index 851b5a774a..f388c13852 100644 --- a/web/src/people.ts +++ b/web/src/people.ts @@ -17,7 +17,7 @@ import * as settings_data from "./settings_data"; import type {StateData, profile_datum_schema, user_schema} from "./state_data"; import {current_user, realm} from "./state_data"; import * as timerender from "./timerender"; -import {is_user_in_group} from "./user_groups"; +import {is_user_in_setting_group} from "./user_groups"; import {user_settings} from "./user_settings"; import * as util from "./util"; @@ -767,9 +767,8 @@ export function should_add_guest_user_indicator(user_id: number): boolean { } export function user_can_initiate_direct_message_thread(recipient_ids_string: string): boolean { - const direct_message_initiator_group_id = realm.realm_direct_message_initiator_group; const recipient_ids = user_ids_string_to_ids_array(recipient_ids_string); - if (is_user_in_group(direct_message_initiator_group_id, my_user_id)) { + if (is_user_in_setting_group(realm.realm_direct_message_initiator_group, my_user_id)) { return true; } for (const recipient of recipient_ids) { @@ -781,9 +780,8 @@ export function user_can_initiate_direct_message_thread(recipient_ids_string: st } export function user_can_direct_message(recipient_ids_string: string): boolean { - const direct_message_permission_group_id = realm.realm_direct_message_permission_group; const recipient_ids = user_ids_string_to_ids_array(recipient_ids_string); - if (is_user_in_group(direct_message_permission_group_id, my_user_id)) { + if (is_user_in_setting_group(realm.realm_direct_message_permission_group, my_user_id)) { return true; } @@ -792,7 +790,7 @@ export function user_can_direct_message(recipient_ids_string: string): boolean { if (is_valid_bot_user(recipient_id) || recipient_id === my_user_id) { continue; } - if (is_user_in_group(direct_message_permission_group_id, recipient_id)) { + if (is_user_in_setting_group(realm.realm_direct_message_permission_group, recipient_id)) { return true; } other_human_recipients_exist = true; @@ -811,27 +809,28 @@ export function small_avatar_url_for_person(person: User): string { } if (person.avatar_url === null) { - return gravatar_url_for_email(person.email); + person.avatar_url = gravatar_url_for_email(person.email); + return person.avatar_url; } return `/avatar/${person.user_id}`; } -function medium_gravatar_url_for_email(email: string): string { - const hash = md5(email.toLowerCase()); - const avatar_url = "https://secure.gravatar.com/avatar/" + hash + "?d=identicon"; - const url = new URL(avatar_url, window.location.origin); - url.search += (url.search ? "&" : "") + "s=500"; - return url.href; -} - export function medium_avatar_url_for_person(person: User): string { /* Unlike the small avatar URL case, we don't generally have a * medium avatar URL included in person objects. So only have the * gravatar and server endpoints here. */ if (person.avatar_url === null) { - return medium_gravatar_url_for_email(person.email); + person.avatar_url = gravatar_url_for_email(person.email); + } + + if (person.avatar_url !== undefined) { + const url = new URL(person.avatar_url, window.location.origin); + if (url.origin === "https://secure.gravatar.com") { + url.search += (url.search ? "&" : "") + "s=500"; + return url.href; + } } // We need to attach a version to the URL as a cache-breaker so that the browser diff --git a/web/src/pill_typeahead.ts b/web/src/pill_typeahead.ts index ee0eb356ec..5b49d83b56 100644 --- a/web/src/pill_typeahead.ts +++ b/web/src/pill_typeahead.ts @@ -135,7 +135,7 @@ export function set_up_group_setting_typeahead( opts: { setting_name: string; setting_type: "realm" | "stream" | "group"; - group: UserGroup | undefined; + group?: UserGroup | undefined; }, ): void { const bootstrap_typeahead_input: TypeaheadInputElement = { @@ -222,6 +222,7 @@ export function set_up_combined( user_group?: boolean; stream?: boolean; user_source?: () => User[]; + user_group_source?: () => UserGroup[]; exclude_bots?: boolean; update_func?: () => void; }, @@ -252,7 +253,14 @@ export function set_up_combined( } if (include_user_groups) { - source = [...source, ...user_group_pill.typeahead_source(pills)]; + if (opts.user_group_source !== undefined) { + const groups: UserGroupPillData[] = opts + .user_group_source() + .map((user_group) => ({type: "user_group", ...user_group})); + source = [...source, ...groups]; + } else { + source = [...source, ...user_group_pill.typeahead_source(pills)]; + } } if (include_users) { diff --git a/web/src/pm_list.ts b/web/src/pm_list.ts index 3dc1508917..71d4dc0e27 100644 --- a/web/src/pm_list.ts +++ b/web/src/pm_list.ts @@ -258,4 +258,12 @@ export function initialize(): void { clear_search(); }); + + $(".direct-messages-container").on("mouseenter", () => { + $("#direct-messages-section-header").addClass("hover-over-dm-section"); + }); + + $(".direct-messages-container").on("mouseleave", () => { + $("#direct-messages-section-header").removeClass("hover-over-dm-section"); + }); } diff --git a/web/src/popover_menus_data.ts b/web/src/popover_menus_data.ts index d33e22737a..5774796cab 100644 --- a/web/src/popover_menus_data.ts +++ b/web/src/popover_menus_data.ts @@ -206,7 +206,10 @@ export function get_actions_popover_content_context(message_id: number): ActionP // `media_breakpoints.sm_min`, we need to include the reaction button in the // popover if it is not displayed. const should_display_add_reaction_option = - !message.is_me_message && !is_add_reaction_icon_visible() && not_spectator; + !message.is_me_message && + !is_add_reaction_icon_visible() && + not_spectator && + !(stream_id && stream_data.is_stream_archived(stream_id)); return { message_id: message.id, diff --git a/web/src/portico/team.ts b/web/src/portico/team.ts index 55e6e512cd..61795f29ab 100644 --- a/web/src/portico/team.ts +++ b/web/src/portico/team.ts @@ -74,9 +74,7 @@ export type Contributor = { email?: string | undefined; github_username?: string | undefined; name?: string | undefined; -} & { - [K in RepositoryName]?: number; -}; +} & Partial>; type ContributorData = { avatar: string; email?: string | undefined; diff --git a/web/src/resize.ts b/web/src/resize.ts index 52ddfabf02..53b12e694a 100644 --- a/web/src/resize.ts +++ b/web/src/resize.ts @@ -33,8 +33,7 @@ function get_new_heights(): { const usable_height = viewport_height - Number.parseInt($("#right-sidebar").css("paddingTop"), 10) - - ($("#userlist-header").outerHeight(true) ?? 0) - - ($("#user_search_section:not(.notdisplayed)").outerHeight(true) ?? 0); + ($("#userlist-header").outerHeight(true) ?? 0); const buddy_list_wrapper_max_height = Math.max(80, usable_height); diff --git a/web/src/scheduled_messages.ts b/web/src/scheduled_messages.ts index 8c7c9f1bcd..4af844552e 100644 --- a/web/src/scheduled_messages.ts +++ b/web/src/scheduled_messages.ts @@ -14,7 +14,7 @@ type TimeKey = | "tomorrow_four_pm" | "monday_nine_am"; -type SendOption = {[key in TimeKey]?: {text: string; stamp: number}}; +type SendOption = Partial>; export const MINIMUM_SCHEDULED_MESSAGE_DELAY_SECONDS = 5 * 60; diff --git a/web/src/scheduled_messages_overlay_ui.js b/web/src/scheduled_messages_overlay_ui.ts similarity index 58% rename from web/src/scheduled_messages_overlay_ui.js rename to web/src/scheduled_messages_overlay_ui.ts index 26edb6574b..fc16ce67d2 100644 --- a/web/src/scheduled_messages_overlay_ui.js +++ b/web/src/scheduled_messages_overlay_ui.ts @@ -1,4 +1,5 @@ import $ from "jquery"; +import assert from "minimalistic-assert"; import render_scheduled_message from "../templates/scheduled_message.hbs"; import render_scheduled_messages_overlay from "../templates/scheduled_messages_overlay.hbs"; @@ -8,12 +9,30 @@ import * as messages_overlay_ui from "./messages_overlay_ui"; import * as overlays from "./overlays"; import * as people from "./people"; import * as scheduled_messages from "./scheduled_messages"; +import type {ScheduledMessage} from "./scheduled_messages"; import * as scheduled_messages_ui from "./scheduled_messages_ui"; import * as stream_color from "./stream_color"; import * as stream_data from "./stream_data"; import * as sub_store from "./sub_store"; import * as timerender from "./timerender"; +type ScheduledMessageRenderContext = ScheduledMessage & + ( + | { + is_stream: true; + formatted_send_at_time: string; + recipient_bar_color: string; + stream_id: number; + stream_name: string; + stream_privacy_icon_color: string; + } + | { + is_stream: false; + formatted_send_at_time: string; + recipients: string; + } + ); + export const keyboard_handling_context = { get_items_ids() { const scheduled_messages_ids = []; @@ -24,11 +43,11 @@ export const keyboard_handling_context = { return scheduled_messages_ids; }, on_enter() { - const focused_element_id = Number.parseInt( - messages_overlay_ui.get_focused_element_id(this), - 10, - ); - scheduled_messages_ui.edit_scheduled_message(focused_element_id); + const focused_element_id = messages_overlay_ui.get_focused_element_id(this); + if (focused_element_id === undefined) { + return; + } + scheduled_messages_ui.edit_scheduled_message(Number.parseInt(focused_element_id, 10)); overlays.close_overlay("scheduled"); }, on_delete() { @@ -40,7 +59,7 @@ export const keyboard_handling_context = { messages_overlay_ui.focus_on_sibling_element(this); // We need to have a super responsive UI feedback here, so we remove the row from the DOM manually $focused_row.remove(); - scheduled_messages.delete_scheduled_message(focused_element_id); + scheduled_messages.delete_scheduled_message(Number.parseInt(focused_element_id, 10)); }, items_container_selector: "scheduled-messages-container", items_list_selector: "scheduled-messages-list", @@ -49,44 +68,61 @@ export const keyboard_handling_context = { id_attribute_name: "data-scheduled-message-id", }; -function sort_scheduled_messages(scheduled_messages) { +function sort_scheduled_messages( + scheduled_messages: Map, +): ScheduledMessage[] { const sorted_messages = [...scheduled_messages.values()].sort( (msg1, msg2) => msg1.scheduled_delivery_timestamp - msg2.scheduled_delivery_timestamp, ); return sorted_messages; } -export function handle_keyboard_events(event_key) { +export function handle_keyboard_events(event_key: string): void { messages_overlay_ui.modals_handle_events(event_key, keyboard_handling_context); } -function format(scheduled_messages) { +function format( + scheduled_messages: Map, +): ScheduledMessageRenderContext[] { const formatted_msgs = []; const sorted_messages = sort_scheduled_messages(scheduled_messages); + for (const msg of sorted_messages) { - const msg_render_context = {...msg}; - if (msg.type === "stream") { - msg_render_context.is_stream = true; - msg_render_context.stream_id = msg.to; - msg_render_context.stream_name = sub_store.maybe_get_stream_name( - msg_render_context.stream_id, - ); - const color = stream_data.get_color(msg_render_context.stream_id); - msg_render_context.recipient_bar_color = stream_color.get_recipient_bar_color(color); - msg_render_context.stream_privacy_icon_color = - stream_color.get_stream_privacy_icon_color(color); - } else { - msg_render_context.is_stream = false; - msg_render_context.recipients = people.get_recipients(msg.to.join(",")); - } + let msg_render_context; const time = new Date(msg.scheduled_delivery_timestamp * 1000); - msg_render_context.formatted_send_at_time = timerender.get_full_datetime(time, "time"); + const formatted_send_at_time = timerender.get_full_datetime(time, "time"); + if (msg.type === "stream") { + const stream_id = msg.to; + const stream_name = sub_store.maybe_get_stream_name(stream_id); + const color = stream_data.get_color(stream_id); + const recipient_bar_color = stream_color.get_recipient_bar_color(color); + const stream_privacy_icon_color = stream_color.get_stream_privacy_icon_color(color); + + assert(stream_name !== undefined); + msg_render_context = { + ...msg, + is_stream: true as const, + stream_id, + stream_name, + recipient_bar_color, + stream_privacy_icon_color, + formatted_send_at_time, + }; + } else { + const recipients = people.get_recipients(msg.to.join(",")); + msg_render_context = { + ...msg, + is_stream: false as const, + recipients, + formatted_send_at_time, + }; + } formatted_msgs.push(msg_render_context); } return formatted_msgs; } -export function launch() { +export function launch(): void { $("#scheduled_messages_overlay_container").html(render_scheduled_messages_overlay()); overlays.open_overlay({ name: "scheduled", @@ -103,10 +139,13 @@ export function launch() { $messages_list.append($(rendered_list)); const first_element_id = keyboard_handling_context.get_items_ids()[0]; - messages_overlay_ui.set_initial_element(first_element_id, keyboard_handling_context); + if (first_element_id === undefined) { + return; + } + messages_overlay_ui.set_initial_element(String(first_element_id), keyboard_handling_context); } -export function rerender() { +export function rerender(): void { if (!overlays.scheduled_messages_open()) { return; } @@ -118,7 +157,7 @@ export function rerender() { $messages_list.append($(rendered_list)); } -export function remove_scheduled_message_id(scheduled_msg_id) { +export function remove_scheduled_message_id(scheduled_msg_id: number): void { if (overlays.scheduled_messages_open()) { $( `#scheduled_messages_overlay .scheduled-message-row[data-scheduled-message-id=${scheduled_msg_id}]`, @@ -126,12 +165,12 @@ export function remove_scheduled_message_id(scheduled_msg_id) { } } -export function initialize() { +export function initialize(): void { $("body").on("click", ".scheduled-message-row .restore-overlay-message", (e) => { - let scheduled_msg_id = $(e.currentTarget) - .closest(".scheduled-message-row") - .attr("data-scheduled-message-id"); - scheduled_msg_id = Number.parseInt(scheduled_msg_id, 10); + const scheduled_msg_id = Number.parseInt( + $(e.currentTarget).closest(".scheduled-message-row").attr("data-scheduled-message-id")!, + 10, + ); scheduled_messages_ui.edit_scheduled_message(scheduled_msg_id); overlays.close_overlay("scheduled"); e.stopPropagation(); @@ -142,13 +181,15 @@ export function initialize() { const scheduled_msg_id = $(e.currentTarget) .closest(".scheduled-message-row") .attr("data-scheduled-message-id"); - scheduled_messages.delete_scheduled_message(scheduled_msg_id); + assert(scheduled_msg_id !== undefined); + + scheduled_messages.delete_scheduled_message(Number.parseInt(scheduled_msg_id, 10)); e.stopPropagation(); e.preventDefault(); }); - $("body").on("focus", ".scheduled-message-info-box", (e) => { - messages_overlay_ui.activate_element(e.target, keyboard_handling_context); + $("body").on("focus", ".scheduled-message-info-box", function (this: HTMLElement) { + messages_overlay_ui.activate_element(this, keyboard_handling_context); }); } diff --git a/web/src/search.ts b/web/src/search.ts index 5b5085d24c..c83bb46826 100644 --- a/web/src/search.ts +++ b/web/src/search.ts @@ -13,6 +13,7 @@ import * as search_pill from "./search_pill"; import type {SearchPillWidget} from "./search_pill"; import * as search_suggestion from "./search_suggestion"; import type {NarrowTerm} from "./state_data"; +import * as util from "./util"; // Exported for unit testing export let is_using_input_method = false; @@ -24,8 +25,12 @@ let on_narrow_search: OnNarrowSearch; function set_search_bar_text(text: string): void { $("#search_query").text(text); - // After setting the text, move the cursor to the end of the line. - window.getSelection()!.modify("move", "forward", "line"); + const current_selection = window.getSelection()!; + if (current_selection.anchorNode?.isSameNode(util.the($("#search_query")))) { + // After setting the text, move the cursor to the end of the line if + // the cursor is in the search bar. + current_selection.modify("move", "forward", "line"); + } } function get_search_bar_text(): string { diff --git a/web/src/server_events_dispatch.js b/web/src/server_events_dispatch.js index a45c015768..7f99f99a1d 100644 --- a/web/src/server_events_dispatch.js +++ b/web/src/server_events_dispatch.js @@ -28,6 +28,7 @@ import * as message_edit from "./message_edit"; import * as message_events from "./message_events"; import * as message_lists from "./message_lists"; import * as message_live_update from "./message_live_update"; +import * as message_view_header from "./message_view_header"; import * as muted_users_ui from "./muted_users_ui"; import * as narrow_state from "./narrow_state"; import * as narrow_title from "./narrow_title"; @@ -89,6 +90,7 @@ import * as user_group_edit from "./user_group_edit"; import * as user_groups from "./user_groups"; import {user_settings} from "./user_settings"; import * as user_status from "./user_status"; +import * as user_topics from "./user_topics"; import * as user_topics_ui from "./user_topics_ui"; export function dispatch_normal_event(event) { @@ -207,12 +209,17 @@ export function dispatch_normal_event(event) { const realm_settings = { allow_edit_history: noop, allow_message_editing: noop, - edit_topic_policy: noop, avatar_changes_disabled: settings_account.update_avatar_change_display, bot_creation_policy: settings_bots.update_bot_permissions_ui, + can_add_custom_emoji_group: noop, + can_create_groups: noop, + can_create_private_channel_group: noop, + can_create_public_channel_group: noop, can_delete_any_message_group: noop, can_delete_own_message_group: noop, + can_manage_all_groups: noop, can_move_messages_between_channels_group: noop, + can_move_messages_between_topics_group: noop, create_multiuse_invite_group: noop, invite_to_stream_policy: noop, default_code_block_language: noop, @@ -315,14 +322,12 @@ export function dispatch_normal_event(event) { key === "direct_message_initiator_group" || key === "direct_message_permission_group" ) { - settings_org.check_disable_direct_message_initiator_group_dropdown( - realm.realm_direct_message_permission_group, - ); + settings_org.check_disable_direct_message_initiator_group_widget(); compose_closed_ui.update_buttons_for_private(); compose_recipient.check_posting_policy_for_compose_box(); } - if (key === "edit_topic_policy") { + if (key === "can_move_messages_between_topics_group") { message_live_update.rerender_messages_view(); } @@ -603,6 +608,7 @@ export function dispatch_normal_event(event) { ); stream_data.delete_sub(stream.stream_id); stream_settings_ui.remove_stream(stream.stream_id); + message_view_header.maybe_rerender_title_area_for_stream(stream); if (was_subscribed) { stream_list.remove_sidebar_row(stream.stream_id); if (stream.stream_id === compose_state.selected_recipient_id) { @@ -631,6 +637,7 @@ export function dispatch_normal_event(event) { message_lists.current.update_trailing_bookend(true); } } + message_live_update.rerender_messages_view(); stream_list.update_subscribe_to_more_streams_link(); break; default: @@ -743,6 +750,9 @@ export function dispatch_normal_event(event) { break; } + // TODO/typescript: Move privacy_setting_name_schema and PrivacySettingName + // here from `settings_account` when this file is converted to typescript, + // and use them instead of `privacy_settings`. const privacy_settings = [ "send_stream_typing_notifications", "send_private_typing_notifications", @@ -1008,8 +1018,23 @@ export function dispatch_normal_event(event) { } break; - case "user_topic": - user_topics_ui.handle_topic_updates(event); + case "user_topic": { + const previous_topic_visibility = user_topics.get_topic_visibility_policy( + event.stream_id, + event.topic_name, + ); + user_topics_ui.handle_topic_updates( + event, + message_events.update_current_view_for_topic_visibility(), + ); + // Discard cached message lists if `event` topic was / is followed. + if ( + event.visibility_policy === user_topics.all_visibility_policies.FOLLOWED || + previous_topic_visibility === user_topics.all_visibility_policies.FOLLOWED + ) { + message_events.discard_cached_lists_with_term_type("is-followed"); + } break; + } } } diff --git a/web/src/settings.js b/web/src/settings.js index e45c4cc16e..de0275eee2 100644 --- a/web/src/settings.js +++ b/web/src/settings.js @@ -93,6 +93,9 @@ export function build_page() { const rendered_settings_tab = render_settings_tab({ full_name: people.my_full_name(), + profile_picture: people.small_avatar_url_for_person( + people.get_by_user_id(people.my_current_user_id()), + ), date_joined_text: get_parsed_date_of_joining(), current_user, page_params, diff --git a/web/src/settings_account.js b/web/src/settings_account.ts similarity index 76% rename from web/src/settings_account.js rename to web/src/settings_account.ts index f600f241a0..a06d0f8eae 100644 --- a/web/src/settings_account.js +++ b/web/src/settings_account.ts @@ -1,4 +1,6 @@ import $ from "jquery"; +import assert from "minimalistic-assert"; +import {z} from "zod"; import render_change_email_modal from "../templates/change_email_modal.hbs"; import render_demo_organization_add_email_modal from "../templates/demo_organization_add_email_modal.hbs"; @@ -7,11 +9,11 @@ import render_settings_api_key_modal from "../templates/settings/api_key_modal.h import render_settings_dev_env_email_access from "../templates/settings/dev_env_email_access.hbs"; import * as avatar from "./avatar"; -import * as blueslip from "./blueslip"; import * as channel from "./channel"; import * as common from "./common"; import {csrf_token} from "./csrf"; import * as custom_profile_fields_ui from "./custom_profile_fields_ui"; +import type {PillUpdateField} from "./custom_profile_fields_ui"; import * as dialog_widget from "./dialog_widget"; import {$t_html} from "./i18n"; import * as keydown_util from "./keydown_util"; @@ -29,13 +31,17 @@ import * as ui_report from "./ui_report"; import * as ui_util from "./ui_util"; import * as user_deactivation_ui from "./user_deactivation_ui"; import * as user_pill from "./user_pill"; +import type {UserPillWidget} from "./user_pill"; import * as user_profile from "./user_profile"; import {user_settings} from "./user_settings"; +import * as util from "./util"; -let password_quality; // Loaded asynchronously +let password_quality: + | ((password: string, $bar: JQuery | undefined, $password_field: JQuery) => boolean) + | undefined; // Loaded asynchronously let user_avatar_widget_created = false; -export function update_email(new_email) { +export function update_email(new_email: string): void { const $email_input = $("#change_email_button"); if ($email_input) { @@ -43,7 +49,7 @@ export function update_email(new_email) { } } -export function update_full_name(new_full_name) { +export function update_full_name(new_full_name: string): void { // Arguably, this should work more like how the `update_email` // flow works, where we update the name in the modal on open, // rather than updating it here, but this works. @@ -53,7 +59,7 @@ export function update_full_name(new_full_name) { } } -export function update_name_change_display() { +export function update_name_change_display(): void { if ($("#user_details_section").length === 0) { return; } @@ -67,7 +73,7 @@ export function update_name_change_display() { } } -export function update_email_change_display() { +export function update_email_change_display(): void { if ($("#user_details_section").length === 0) { return; } @@ -81,24 +87,27 @@ export function update_email_change_display() { } } -function display_avatar_upload_complete() { +function display_avatar_upload_complete(): void { $("#user-avatar-upload-widget .upload-spinner-background").css({visibility: "hidden"}); $("#user-avatar-upload-widget .image-upload-text").show(); $("#user-avatar-upload-widget .image-delete-button").show(); } -function display_avatar_upload_started() { +function display_avatar_upload_started(): void { $("#user-avatar-source").hide(); $("#user-avatar-upload-widget .upload-spinner-background").css({visibility: "visible"}); $("#user-avatar-upload-widget .image-upload-text").hide(); $("#user-avatar-upload-widget .image-delete-button").hide(); } -function upload_avatar($file_input) { +function upload_avatar($file_input: JQuery): void { const form_data = new FormData(); + assert(csrf_token !== undefined); form_data.append("csrfmiddlewaretoken", csrf_token); - for (const [i, file] of Array.prototype.entries.call($file_input[0].files)) { + const files = util.the($file_input).files; + assert(files !== null); + for (const [i, file] of [...files].entries()) { form_data.append("file-" + i, file); } display_avatar_upload_started(); @@ -119,16 +128,17 @@ function upload_avatar($file_input) { if (current_user.avatar_source === "G") { $("#user-avatar-source").show(); } - if (xhr.responseJSON?.msg) { + const parsed = z.object({msg: z.string()}).safeParse(xhr.responseJSON); + if (parsed.success) { const $error = $("#user-avatar-upload-widget .image_file_input_error"); - $error.text(xhr.responseJSON.msg); + $error.text(parsed.data.msg); $error.show(); } }, }); } -export function update_avatar_change_display() { +export function update_avatar_change_display(): void { if ($("#user-avatar-upload-widget").length === 0) { return; } @@ -137,7 +147,7 @@ export function update_avatar_change_display() { $("#user-avatar-upload-widget .image_upload_button").addClass("hide"); $("#user-avatar-upload-widget .image-disabled").removeClass("hide"); } else { - if (user_avatar_widget_created === false) { + if (!user_avatar_widget_created) { avatar.build_user_avatar_widget(upload_avatar); user_avatar_widget_created = true; } @@ -146,7 +156,7 @@ export function update_avatar_change_display() { } } -export function update_account_settings_display() { +export function update_account_settings_display(): void { if ($("#user_details_section").length === 0) { return; } @@ -156,7 +166,7 @@ export function update_account_settings_display() { update_avatar_change_display(); } -export function maybe_update_deactivate_account_button() { +export function maybe_update_deactivate_account_button(): void { if (!current_user.is_owner) { return; } @@ -173,7 +183,7 @@ export function maybe_update_deactivate_account_button() { } } -export function update_send_read_receipts_tooltip() { +export function update_send_read_receipts_tooltip(): void { if (realm.realm_enable_read_receipts) { $("#send_read_receipts_label .settings-info-icon").hide(); } else { @@ -181,49 +191,51 @@ export function update_send_read_receipts_tooltip() { } } -function settings_change_error(message_html, xhr) { +function settings_change_error(message_html: string, xhr?: JQuery.jqXHR): void { ui_report.error(message_html, xhr, $("#account-settings-status").expectOne()); } -function update_custom_profile_field(field, method) { - let field_id; +function update_custom_profile_field( + field: CustomProfileFieldData, + method: channel.AjaxRequestHandler, +): void { + let data; if (method === channel.del) { - field_id = field; + data = JSON.stringify([field.id]); } else { - field_id = field.id; + data = JSON.stringify([field]); } const $spinner_element = $( - `.custom_user_field[data-field-id="${CSS.escape(field_id)}"] .custom-field-status`, + `.custom_user_field[data-field-id="${CSS.escape(field.id.toString())}"] .custom-field-status`, ).expectOne(); - settings_ui.do_settings_change( - method, - "/json/users/me/profile_data", - {data: JSON.stringify([field])}, - $spinner_element, - ); + settings_ui.do_settings_change(method, "/json/users/me/profile_data", {data}, $spinner_element); } -function update_user_custom_profile_fields(fields, method) { - if (method === undefined) { - blueslip.error("Undefined method in update_user_custom_profile_fields"); - } +type CustomProfileFieldData = { + id: number; + value?: number[] | string; +}; +function update_user_custom_profile_fields( + fields: CustomProfileFieldData[], + method: channel.AjaxRequestHandler, +): void { for (const field of fields) { update_custom_profile_field(field, method); } } -function update_user_type_field(field, pills) { +function update_user_type_field(field: PillUpdateField, pills: UserPillWidget): void { const user_ids = user_pill.get_user_ids(pills); if (user_ids.length < 1) { - update_user_custom_profile_fields([field.id], channel.del); + update_user_custom_profile_fields([{id: field.id}], channel.del); } else { update_user_custom_profile_fields([{id: field.id, value: user_ids}], channel.patch); } } -export function add_custom_profile_fields_to_settings() { +export function add_custom_profile_fields_to_settings(): void { if (!overlays.settings_open()) { return; } @@ -231,7 +243,9 @@ export function add_custom_profile_fields_to_settings() { const element_id = "#profile-settings .custom-profile-fields-form"; $(element_id).empty(); - const pill_update_handler = (field, pills) => update_user_type_field(field, pills); + const pill_update_handler = (field: PillUpdateField, pills: UserPillWidget): void => { + update_user_type_field(field, pills); + }; custom_profile_fields_ui.append_custom_profile_fields(element_id, people.my_current_user_id()); custom_profile_fields_ui.initialize_custom_user_type_fields( @@ -244,14 +258,25 @@ export function add_custom_profile_fields_to_settings() { custom_profile_fields_ui.initialize_custom_pronouns_type_fields(element_id); } -export function hide_confirm_email_banner() { +export function hide_confirm_email_banner(): void { if (!overlays.settings_open()) { return; } $("#account-settings-status").hide(); } -export function update_privacy_settings_box(property) { +// TODO/typescript: Move these to server_events_dispatch when it's converted to typescript. +export const privacy_setting_name_schema = z.enum([ + "send_stream_typing_notifications", + "send_private_typing_notifications", + "send_read_receipts", + "presence_enabled", + "email_address_visibility", + "allow_private_data_export", +]); +export type PrivacySettingName = z.infer; + +export function update_privacy_settings_box(property: PrivacySettingName): void { if (!overlays.settings_open()) { return; } @@ -261,19 +286,24 @@ export function update_privacy_settings_box(property) { settings_components.set_input_element_value($input_elem, user_settings[property]); } -export function set_up() { +export function set_up( + load_password_quality: () => Promise< + (password: string, $bar: JQuery | undefined, $password_field: JQuery) => boolean + >, +): void { // Add custom profile fields elements to user account settings. add_custom_profile_fields_to_settings(); $("#account-settings-status").hide(); - const setup_api_key_modal = () => { - function request_api_key(data) { + const setup_api_key_modal = (): void => { + function request_api_key(data: {password?: string}): void { channel.post({ url: "/json/fetch_api_key", data, success(data) { $("#get_api_key_password").val(""); - $("#api_key_value").text(data.api_key); + const api_key = z.object({api_key: z.string()}).parse(data).api_key; + $("#api_key_value").text(api_key); // The display property on the error bar is set to important // so instead of making display: none !important we just // remove it. @@ -303,14 +333,15 @@ export function set_up() { {tippy_tooltips: true}, ); - function do_get_api_key() { + function do_get_api_key(): void { $("#api_key_status").hide(); - const data = {}; - data.password = $("#get_api_key_password").val(); + const data = { + password: $("input#get_api_key_password").val()!, + }; request_api_key(data); } - if (realm.realm_password_auth_enabled === false) { + if (!realm.realm_password_auth_enabled) { // Skip the password prompt step, since the user doesn't have one. request_api_key({}); } else { @@ -339,11 +370,13 @@ export function set_up() { url: "/api/v1/users/me/api_key/regenerate", headers: {Authorization: authorization_header}, success(data) { - $("#api_key_value").text(data.api_key); + const api_key = z.object({api_key: z.string()}).parse(data).api_key; + $("#api_key_value").text(api_key); }, error(xhr) { - if (xhr.responseJSON?.msg) { - $("#user_api_key_error").text(xhr.responseJSON.msg).show(); + const parsed = z.object({msg: z.string()}).safeParse(xhr.responseJSON); + if (parsed.success) { + $("#user_api_key_error").text(parsed.data.msg).show(); } }, }); @@ -383,7 +416,7 @@ export function set_up() { e.stopPropagation(); }); - function clear_password_change() { + function clear_password_change(): void { // Clear the password boxes so that passwords don't linger in the DOM // for an XSS attacker to find. common.reset_password_toggle_icons( @@ -398,7 +431,7 @@ export function set_up() { password_quality?.("", $("#pw_strength .bar"), $("#new_password")); } - function change_password_post_render() { + function change_password_post_render(): void { $("#change_password_modal") .find("[data-micromodal-close]") .on("click", () => { @@ -417,11 +450,11 @@ export function set_up() { clear_password_change(); } - $("#change_password").on("click", async (e) => { + $("#change_password").on("click", (e) => { e.preventDefault(); e.stopPropagation(); - function validate_input() { + function validate_input(): boolean { const old_password = $("#old_password").val(); const new_password = $("#new_password").val(); @@ -463,26 +496,29 @@ export function set_up() { }, }); - if (realm.realm_password_auth_enabled !== false) { + if (realm.realm_password_auth_enabled) { // zxcvbn.js is pretty big, and is only needed on password // change, so load it asynchronously. - password_quality = (await import("./password_quality")).password_quality; - $("#pw_strength .bar").removeClass("hide"); + void (async () => { + password_quality = await load_password_quality(); + $("#pw_strength .bar").removeClass("hide"); - $("#new_password").on("input", () => { - const $field = $("#new_password"); - password_quality($field.val(), $("#pw_strength .bar"), $field); - }); + $("#new_password").on("input", () => { + const $field = $("input#new_password"); + assert(password_quality !== undefined); + password_quality($field.val()!, $("#pw_strength .bar"), $field); + }); + })(); } }); - function do_change_password() { + function do_change_password(): void { const $change_password_error = $("#change_password_modal").find("#dialog_error"); $change_password_error.hide(); const data = { old_password: $("#old_password").val(), - new_password: $("#new_password").val(), + new_password: $("input#new_password").val()!, }; const $new_pw_field = $("#new_password"); @@ -512,7 +548,6 @@ export function set_up() { channel.set_password_change_in_progress(false); }, $error_msg_element: $change_password_error, - failure_msg_html: null, }; settings_ui.do_settings_change( channel.patch, @@ -527,9 +562,9 @@ export function set_up() { $("#full_name").on("change", (e) => { e.preventDefault(); e.stopPropagation(); - const data = {}; - - data.full_name = $("#full_name").val(); + const data = { + full_name: $("#full_name").val(), + }; settings_ui.do_settings_change( channel.patch, @@ -539,10 +574,11 @@ export function set_up() { ); }); - function do_change_email() { + function do_change_email(): void { const $change_email_error = $("#change_email_modal").find("#dialog_error"); - const data = {}; - data.email = $("#change_email_form").find("input[name='email']").val(); + const data = { + email: $("#change_email_form").find("input[name='email']").val(), + }; const opts = { success_continuation() { @@ -588,20 +624,21 @@ export function set_up() { form_id: "change_email_form", on_click: do_change_email, on_shown() { - ui_util.place_caret_at_end($("#change_email_form input")[0]); + ui_util.place_caret_at_end(util.the($("#change_email_form input"))); }, update_submit_disabled_state_on_change: true, }); } }); - function do_demo_organization_add_email(e) { + function do_demo_organization_add_email(e: JQuery.ClickEvent): void { e.preventDefault(); e.stopPropagation(); const $change_email_error = $("#demo_organization_add_email_modal").find("#dialog_error"); - const data = {}; - data.email = $("#demo_organization_add_email").val(); - data.full_name = $("#demo_organization_update_full_name").val(); + const data = { + email: $("input#demo_organization_add_email").val(), + full_name: $("#demo_organization_update_full_name").val(), + }; const opts = { success_continuation() { @@ -638,10 +675,12 @@ export function set_up() { e.preventDefault(); e.stopPropagation(); - function demo_organization_add_email_post_render() { + function demo_organization_add_email_post_render(): void { // Disable submit button if either input is an empty string. - const $add_email_element = $("#demo_organization_add_email"); - const $add_name_element = $("#demo_organization_update_full_name"); + const $add_email_element = $("input#demo_organization_add_email"); + const $add_name_element = $( + "input#demo_organization_update_full_name", + ); const $demo_organization_submit_button = $( "#demo_organization_add_email_modal .dialog_submit_button", @@ -651,7 +690,8 @@ export function set_up() { $("#demo_organization_add_email_form input").on("input", () => { $demo_organization_submit_button.prop( "disabled", - $add_email_element.val().trim() === "" || $add_name_element.val().trim() === "", + $add_email_element.val()!.trim() === "" || + $add_name_element.val()!.trim() === "", ); }); } @@ -673,33 +713,38 @@ export function set_up() { form_id: "demo_organization_add_email_form", on_click: do_demo_organization_add_email, on_shown() { - ui_util.place_caret_at_end($("#demo_organization_add_email_form input")[0]); + ui_util.place_caret_at_end(util.the($("input#demo_organization_add_email"))); }, post_render: demo_organization_add_email_post_render, }); } }); - $("#profile-settings").on("click", ".custom_user_field .remove_date", (e) => { - e.preventDefault(); - e.stopPropagation(); - const $field = $(e.target).closest(".custom_user_field").expectOne(); - const field_id = Number.parseInt($field.attr("data-field-id"), 10); - update_user_custom_profile_fields([field_id], channel.del); - }); + $("#profile-settings").on( + "click", + ".custom_user_field .remove_date", + function (this: HTMLElement, e) { + e.preventDefault(); + e.stopPropagation(); + const $field = $(this).closest(".custom_user_field").expectOne(); + const field_id = Number.parseInt($field.attr("data-field-id")!, 10); + update_user_custom_profile_fields([{id: field_id}], channel.del); + }, + ); - $("#profile-settings").on("change", ".custom_user_field_value", function (e) { - const fields = []; - const value = $(this).val(); + $("#profile-settings").on("change", ".custom_user_field_value", function (this: HTMLElement) { + const fields: CustomProfileFieldData[] = []; + const value = $(this).val()!; + assert(typeof value === "string"); const field_id = Number.parseInt( - $(e.target).closest(".custom_user_field").attr("data-field-id"), + $(this).closest(".custom_user_field").attr("data-field-id")!, 10, ); if (value) { fields.push({id: field_id, value}); update_user_custom_profile_fields(fields, channel.patch); } else { - fields.push(field_id); + fields.push({id: field_id}); update_user_custom_profile_fields(fields, channel.del); } }); @@ -714,7 +759,7 @@ export function set_up() { e.preventDefault(); e.stopPropagation(); - function handle_confirm() { + function handle_confirm(): void { channel.del({ url: "/json/users/me", success() { @@ -738,9 +783,15 @@ export function set_up() { )}`, }, ); - let rendered_error_msg; - if (xhr.responseJSON?.code === "CANNOT_DEACTIVATE_LAST_USER") { - if (xhr.responseJSON.is_last_owner) { + let rendered_error_msg = ""; + const parsed = z + .object({ + code: z.literal("CANNOT_DEACTIVATE_LAST_USER"), + is_last_owner: z.boolean(), + }) + .safeParse(xhr.responseJSON); + if (parsed.success) { + if (parsed.data.is_last_owner) { rendered_error_msg = error_last_owner; } else { rendered_error_msg = error_last_user; @@ -782,7 +833,7 @@ export function set_up() { $("#user_timezone").val(user_settings.timezone); - $("#user_timezone").on("change", function (e) { + $("select#user_timezone").on("change", function (e) { e.preventDefault(); e.stopPropagation(); @@ -796,13 +847,13 @@ export function set_up() { ); }); - $("#privacy_settings_box").on("change", "input", (e) => { + $("#privacy_settings_box").on("change", "input", function (this: HTMLInputElement, e) { e.preventDefault(); e.stopPropagation(); - const $input_elem = $(e.currentTarget); - const setting_name = $input_elem.attr("name"); - const checked = $input_elem.prop("checked"); + const $input_elem = $(this); + const setting_name = $input_elem.attr("name")!; + const checked = util.the($input_elem).checked; const data = {[setting_name]: checked}; settings_ui.do_settings_change( @@ -815,7 +866,7 @@ export function set_up() { $("#user_email_address_visibility").val(user_settings.email_address_visibility); - $("#user_email_address_visibility").on("change", function (e) { + $("select#user_email_address_visibility").on("change", function (e) { e.preventDefault(); e.stopPropagation(); diff --git a/web/src/settings_bots.ts b/web/src/settings_bots.ts index d598b1eafe..795d36f5b7 100644 --- a/web/src/settings_bots.ts +++ b/web/src/settings_bots.ts @@ -105,7 +105,12 @@ export function encode_zuliprc_as_url(zuliprc: string): string { return "data:application/octet-stream;charset=utf-8," + encodeURIComponent(zuliprc); } -export function generate_zuliprc_content(bot: bot_data.Bot): string { +export function generate_zuliprc_content(bot: { + bot_type?: number; + user_id: number; + email: string; + api_key: string; +}): string { let token; // For outgoing webhooks, include the token in the zuliprc. // It's needed for authenticating to the Botserver. diff --git a/web/src/settings_components.ts b/web/src/settings_components.ts index e9145e1ff5..e44a7b354a 100644 --- a/web/src/settings_components.ts +++ b/web/src/settings_components.ts @@ -27,7 +27,7 @@ import * as scroll_util from "./scroll_util"; import * as settings_config from "./settings_config"; import * as settings_data from "./settings_data"; import type {CustomProfileField, GroupSettingValue} from "./state_data"; -import {current_user, realm, realm_schema} from "./state_data"; +import {current_user, group_setting_value_schema, realm, realm_schema} from "./state_data"; import * as stream_data from "./stream_data"; import type {StreamSubscription} from "./sub_store"; import {stream_subscription_schema} from "./sub_store"; @@ -238,7 +238,6 @@ export const simple_dropdown_realm_settings_schema = realm_schema.pick({ realm_invite_to_stream_policy: true, realm_invite_to_realm_policy: true, realm_wildcard_mention_policy: true, - realm_edit_topic_policy: true, realm_org_type: true, }); export type SimpleDropdownRealmSettings = z.infer; @@ -477,20 +476,9 @@ const dropdown_widget_map = new Map([ ["realm_signup_announcements_stream_id", null], ["realm_zulip_update_announcements_stream_id", null], ["realm_default_code_block_language", null], - ["realm_create_multiuse_invite_group", null], ["can_remove_subscribers_group", null], ["realm_can_access_all_users_group", null], - ["realm_can_add_custom_emoji_group", null], - ["realm_can_create_groups", null], - ["realm_can_create_public_channel_group", null], - ["realm_can_create_private_channel_group", null], ["realm_can_create_web_public_channel_group", null], - ["realm_can_delete_any_message_group", null], - ["realm_can_delete_own_message_group", null], - ["realm_can_manage_all_groups", null], - ["realm_can_move_messages_between_channels_group", null], - ["realm_direct_message_initiator_group", null], - ["realm_direct_message_permission_group", null], ]); export function get_widget_for_dropdown_list_settings( @@ -801,21 +789,27 @@ export function check_realm_settings_property_changed(elem: HTMLElement): boolea case "realm_signup_announcements_stream_id": case "realm_zulip_update_announcements_stream_id": case "realm_default_code_block_language": - case "realm_create_multiuse_invite_group": case "realm_can_access_all_users_group": + case "realm_can_create_web_public_channel_group": + proposed_val = get_dropdown_list_widget_setting_value($elem); + break; case "realm_can_add_custom_emoji_group": case "realm_can_create_groups": case "realm_can_create_public_channel_group": case "realm_can_create_private_channel_group": - case "realm_can_create_web_public_channel_group": case "realm_can_delete_any_message_group": case "realm_can_delete_own_message_group": case "realm_can_manage_all_groups": case "realm_can_move_messages_between_channels_group": + case "realm_can_move_messages_between_topics_group": + case "realm_create_multiuse_invite_group": case "realm_direct_message_initiator_group": - case "realm_direct_message_permission_group": - proposed_val = get_dropdown_list_widget_setting_value($elem); + case "realm_direct_message_permission_group": { + const pill_widget = get_group_setting_widget(property_name); + assert(pill_widget !== null); + proposed_val = get_group_setting_widget_value(pill_widget); break; + } case "realm_message_content_edit_limit_seconds": case "realm_message_content_delete_limit_seconds": case "realm_move_messages_between_streams_limit_seconds": @@ -844,7 +838,7 @@ export function check_realm_settings_property_changed(elem: HTMLElement): boolea blueslip.error("Element refers to unknown property", {property_name}); } } - return current_val !== proposed_val; + return !_.isEqual(current_val, proposed_val); } export function check_stream_settings_property_changed( @@ -1043,7 +1037,8 @@ export function populate_data_for_realm_settings_request( continue; } - const realm_group_settings_using_new_api_format = new Set([ + const realm_group_settings = new Set([ + "can_access_all_users_group", "can_add_custom_emoji_group", "can_create_groups", "can_create_private_channel_group", @@ -1053,10 +1048,12 @@ export function populate_data_for_realm_settings_request( "can_delete_any_message_group", "can_delete_own_message_group", "can_move_messages_between_channels_group", + "can_move_messages_between_topics_group", + "create_multiuse_invite_group", "direct_message_initiator_group", "direct_message_permission_group", ]); - if (realm_group_settings_using_new_api_format.has(property_name)) { + if (realm_group_settings.has(property_name)) { const old_value = get_realm_settings_property_value( // eslint-disable-next-line @typescript-eslint/consistent-type-assertions ("realm_" + property_name) as RealmSettingProperty, @@ -1206,14 +1203,17 @@ export function save_discard_realm_settings_widget_status_handler($subsection: J export function save_discard_stream_settings_widget_status_handler( $subsection: JQuery, - sub: StreamSubscription, + sub: StreamSubscription | undefined, ): void { $subsection.find(".subsection-failed-status p").hide(); $subsection.find(".save-button").show(); const properties_elements = get_subsection_property_elements($subsection); - const show_change_process_button = properties_elements.some((elem) => - check_stream_settings_property_changed(elem, sub), - ); + let show_change_process_button = false; + if (sub !== undefined) { + show_change_process_button = properties_elements.some((elem) => + check_stream_settings_property_changed(elem, sub), + ); + } const $save_btn_controls = $subsection.find(".subsection-header .save-button-controls"); const button_state = show_change_process_button ? "unsaved" : "discarded"; @@ -1225,6 +1225,7 @@ export function save_discard_stream_settings_widget_status_handler( // making it private unless they subscribe. if ( button_state === "unsaved" && + sub && !sub.invite_only && !sub.subscribed && switching_to_private(properties_elements) @@ -1351,6 +1352,40 @@ function should_disable_save_button_for_time_limit_settings( return disable_save_btn; } +function should_disable_save_button_for_group_settings(settings: string[]): boolean { + for (const setting_name of settings) { + let group_setting_config; + if (setting_name.startsWith("realm_")) { + const setting_name_without_prefix = /^realm_(.*)$/.exec(setting_name)![1]!; + group_setting_config = group_permission_settings.get_group_permission_setting_config( + setting_name_without_prefix, + "realm", + ); + } else { + // We do not have any stream settings using the new UI currently, + // so we know that this block will be called for group setting only. + group_setting_config = group_permission_settings.get_group_permission_setting_config( + setting_name, + "group", + ); + } + assert(group_setting_config !== undefined); + if (group_setting_config.allow_nobody_group) { + continue; + } + + const pill_widget = get_group_setting_widget(setting_name); + assert(pill_widget !== null); + + const setting_value = get_group_setting_widget_value(pill_widget); + const nobody_group = user_groups.get_user_group_from_name("role:nobody")!; + if (setting_value === nobody_group.id) { + return true; + } + } + return false; +} + function enable_or_disable_save_button($subsection_elem: JQuery): void { const time_limit_settings = [...$subsection_elem.find(".time-limit-setting")]; @@ -1380,6 +1415,15 @@ function enable_or_disable_save_button($subsection_elem: JQuery): void { } } + if (!disable_save_btn) { + const group_settings = [...$subsection_elem.find(".pill-container")].map((elem) => + extract_property_name($(elem)), + ); + if (group_settings.length) { + disable_save_btn = should_disable_save_button_for_group_settings(group_settings); + } + } + $subsection_elem.find(".subsection-changes-save button").prop("disabled", disable_save_btn); } @@ -1424,6 +1468,18 @@ export const group_setting_widget_map = new Map; + +export function create_realm_group_setting_widget({ + $pill_container, + setting_name, + pill_update_callback, +}: { + $pill_container: JQuery; + setting_name: RealmGroupSettingName; + pill_update_callback?: () => void; +}): void { + const pill_widget = group_setting_pill.create_pills($pill_container, setting_name, "realm"); + const opts: { + setting_name: string; + setting_type: "realm"; + } = { + setting_name, + setting_type: "realm", + }; + group_setting_pill.set_up_pill_typeahead({pill_widget, $pill_container, opts}); + + group_setting_widget_map.set("realm_" + setting_name, pill_widget); + + set_group_setting_widget_value( + pill_widget, + group_setting_value_schema.parse( + realm[realm_schema.keyof().parse("realm_" + setting_name)], + ), + ); + + const $save_discard_widget_container = $(`#id_realm_${CSS.escape(setting_name)}`).closest( + ".settings-subsection-parent", + ); + pill_widget.onPillCreate(() => { + if (pill_update_callback !== undefined) { + pill_update_callback(); + } + save_discard_realm_settings_widget_status_handler($save_discard_widget_container); + }); + pill_widget.onPillRemove(() => { + if (pill_update_callback !== undefined) { + pill_update_callback(); + } + save_discard_realm_settings_widget_status_handler($save_discard_widget_container); + }); +} + +export function set_time_input_formatted_text( + $time_select_elem: JQuery, + formatted_text: string, +): void { + if ($time_select_elem.val() === "custom") { + $time_select_elem.parent().find(".time-input-formatted-description").hide(); + $time_select_elem + .parent() + .find(".custom-time-input-formatted-description") + .text(formatted_text); + } else { + $time_select_elem.parent().find(".time-input-formatted-description").show(); + $time_select_elem.parent().find(".time-input-formatted-description").text(formatted_text); + } +} + +export function set_custom_time_inputs_visibility( + $time_select_elem: JQuery, + time_unit: string, + time_value: number, +): void { + if ($time_select_elem.val() === "custom") { + $time_select_elem.parent().find(".custom-time-input-value").val(time_value); + $time_select_elem.parent().find(".custom-time-input-unit").val(time_unit); + $time_select_elem.parent().find(".custom-time-input-container").show(); + } else { + $time_select_elem.parent().find(".custom-time-input-container").hide(); + } +} diff --git a/web/src/settings_config.ts b/web/src/settings_config.ts index bdfb6bcd97..a11dc95867 100644 --- a/web/src/settings_config.ts +++ b/web/src/settings_config.ts @@ -68,7 +68,20 @@ export const web_channel_default_view_values = { }, }; -export const user_list_style_values = { +export const user_list_style_values: { + compact: { + code: number; + description: string; + }; + with_status: { + code: number; + description: string; + }; + with_avatar?: { + code: number; + description: string; + }; +} = { compact: { code: 1, description: $t({defaultMessage: "Compact"}), @@ -77,13 +90,15 @@ export const user_list_style_values = { code: 2, description: $t({defaultMessage: "Show status text"}), }, - // The `with_avatar` design in still in discussion. - // with_avatar: { - // code: 3, - // description: $t({defaultMessage: "Show status text and avatar"}), - // }, }; +if (page_params.development_environment) { + user_list_style_values.with_avatar = { + code: 3, + description: $t({defaultMessage: "Show avatar"}), + }; +} + export const web_animate_image_previews_values = { always: { code: "always", @@ -130,12 +145,13 @@ export const web_home_view_values = { }; type ColorScheme = "automatic" | "dark" | "light"; -export type ColorSchemeValues = { - [key in ColorScheme]: { +export type ColorSchemeValues = Record< + ColorScheme, + { code: number; description: string; - }; -}; + } +>; export const color_scheme_values = { automatic: { @@ -311,43 +327,6 @@ export const wildcard_mention_policy_values = { }, }; -export const common_message_policy_values = { - by_everyone: { - order: 1, - code: 5, - description: $t({defaultMessage: "Admins, moderators, members and guests"}), - }, - by_members: { - order: 2, - code: 1, - description: $t({defaultMessage: "Admins, moderators and members"}), - }, - by_full_members: { - order: 3, - code: 3, - description: $t({defaultMessage: "Admins, moderators and full members"}), - }, - by_moderators_only: { - order: 4, - code: 4, - description: $t({defaultMessage: "Admins and moderators"}), - }, - by_admins_only: { - order: 5, - code: 2, - description: $t({defaultMessage: "Admins only"}), - }, -}; - -export const edit_topic_policy_values = { - ...common_message_policy_values, - nobody: { - order: 6, - code: 6, - description: $t({defaultMessage: "Nobody"}), - }, -}; - export const time_limit_dropdown_values = [ { text: $t({defaultMessage: "Any time"}), @@ -668,15 +647,18 @@ export const general_notifications_table_labels = { "email", "all_mentions", ], - stream: { - is_muted: $t({defaultMessage: "Mute channel"}), - desktop_notifications: $t({defaultMessage: "Visual desktop notifications"}), - audible_notifications: $t({defaultMessage: "Audible desktop notifications"}), - push_notifications: $t({defaultMessage: "Mobile notifications"}), - email_notifications: $t({defaultMessage: "Email notifications"}), - pin_to_top: $t({defaultMessage: "Pin channel to top of left sidebar"}), - wildcard_mentions_notify: $t({defaultMessage: "Notifications for @all/@everyone mentions"}), - }, + stream: [ + ["is_muted", $t({defaultMessage: "Mute channel"})], + ["desktop_notifications", $t({defaultMessage: "Visual desktop notifications"})], + ["audible_notifications", $t({defaultMessage: "Audible desktop notifications"})], + ["push_notifications", $t({defaultMessage: "Mobile notifications"})], + ["email_notifications", $t({defaultMessage: "Email notifications"})], + ["pin_to_top", $t({defaultMessage: "Pin channel to top of left sidebar"})], + [ + "wildcard_mentions_notify", + $t({defaultMessage: "Notifications for @all/@everyone mentions"}), + ], + ] as const, }; export const stream_specific_notification_settings: (keyof StreamSpecificNotificationSettings)[] = [ diff --git a/web/src/settings_data.ts b/web/src/settings_data.ts index 11701e4a1d..bdca2ff94d 100644 --- a/web/src/settings_data.ts +++ b/web/src/settings_data.ts @@ -71,18 +71,7 @@ function user_has_permission(policy_value: number): boolean { return true; } - if (page_params.is_spectator) { - return false; - } - - /* At present, by_everyone is not present in common_policy_values, - * but we include a check for it here, so that code using - * common_message_policy_values or other supersets can use this function. */ - if (policy_value === settings_config.common_message_policy_values.by_everyone.code) { - return true; - } - - if (current_user.is_guest) { + if (page_params.is_spectator || current_user.is_guest) { return false; } @@ -262,7 +251,11 @@ export function user_can_add_custom_emoji(): boolean { } export function user_can_move_messages_to_another_topic(): boolean { - return user_has_permission(realm.realm_edit_topic_policy); + return user_has_permission_for_group_setting( + realm.realm_can_move_messages_between_topics_group, + "can_move_messages_between_topics_group", + "realm", + ); } export function user_can_delete_any_message(): boolean { diff --git a/web/src/settings_emoji.ts b/web/src/settings_emoji.ts index 9237603744..4fab99a473 100644 --- a/web/src/settings_emoji.ts +++ b/web/src/settings_emoji.ts @@ -6,7 +6,6 @@ import render_confirm_deactivate_custom_emoji from "../templates/confirm_dialog/ import emoji_settings_warning_modal from "../templates/confirm_dialog/confirm_emoji_settings_warning.hbs"; import render_add_emoji from "../templates/settings/add_emoji.hbs"; import render_admin_emoji_list from "../templates/settings/admin_emoji_list.hbs"; -import render_settings_emoji_settings_tip from "../templates/settings/emoji_settings_tip.hbs"; import * as channel from "./channel"; import * as confirm_dialog from "./confirm_dialog"; @@ -19,10 +18,9 @@ import * as loading from "./loading"; import * as people from "./people"; import * as scroll_util from "./scroll_util"; import * as settings_data from "./settings_data"; -import {current_user, realm} from "./state_data"; +import {current_user} from "./state_data"; import * as ui_report from "./ui_report"; import * as upload_widget from "./upload_widget"; -import * as user_groups from "./user_groups"; import * as util from "./util"; const meta = { @@ -44,12 +42,6 @@ function can_delete_emoji(emoji: ServerEmoji): boolean { } export function update_custom_emoji_ui(): void { - const rendered_tip = render_settings_emoji_settings_tip({ - realm_can_add_custom_emoji_group_name: user_groups.get_user_group_from_id( - realm.realm_can_add_custom_emoji_group, - ).name, - }); - $("#emoji-settings").find(".emoji-settings-tip-container").html(rendered_tip); if (!settings_data.user_can_add_custom_emoji()) { $(".add-emoji-text").hide(); $("#add-custom-emoji-button").hide(); @@ -58,11 +50,7 @@ export function update_custom_emoji_ui(): void { } else { $(".add-emoji-text").show(); $("#add-custom-emoji-button").show(); - if (current_user.is_admin) { - $("#emoji-settings .emoji-settings-tip-container").show(); - } else { - $("#emoji-settings .emoji-settings-tip-container").hide(); - } + $("#emoji-settings .emoji-settings-tip-container").hide(); $(".org-settings-list li[data-section='emoji-settings'] .locked").hide(); } diff --git a/web/src/settings_linkifiers.ts b/web/src/settings_linkifiers.ts index e733d1d0e6..74e1a8831b 100644 --- a/web/src/settings_linkifiers.ts +++ b/web/src/settings_linkifiers.ts @@ -21,10 +21,6 @@ import * as util from "./util"; type RealmLinkifiers = typeof realm.realm_linkifiers; -const configure_linkifier_api_response_schema = z.object({ - id: z.number(), -}); - const meta = { loaded: false, }; @@ -258,21 +254,14 @@ export function build_page(): void { $linkifier_status.hide(); $pattern_status.hide(); $template_status.hide(); - const linkifier: {[key in string]?: string} & {id?: number} = {}; - - for (const obj of $(this).serializeArray()) { - linkifier[obj.name] = obj.value; - } void channel.post({ url: "/json/realm/filters", data: $(this).serialize(), - success(raw_data) { - const data = configure_linkifier_api_response_schema.parse(raw_data); + success() { $("#linkifier_pattern").val(""); $("#linkifier_template").val(""); $add_linkifier_button.prop("disabled", false); - linkifier.id = data.id; ui_report.success( $t_html({defaultMessage: "Custom linkifier added!"}), $linkifier_status, diff --git a/web/src/settings_org.ts b/web/src/settings_org.ts index 12783a69b8..2bdf2b4a6a 100644 --- a/web/src/settings_org.ts +++ b/web/src/settings_org.ts @@ -20,7 +20,9 @@ import * as realm_logo from "./realm_logo"; import {realm_user_settings_defaults} from "./realm_user_settings_defaults"; import { type MessageMoveTimeLimitSetting, + type RealmGroupSettingName, type SettingOptionValueWithKey, + realm_group_setting_name_schema, realm_setting_property_schema, realm_user_settings_default_properties_schema, simple_dropdown_realm_settings_schema, @@ -105,12 +107,63 @@ export function maybe_disable_widgets(): void { .addClass("control-label-disabled"); } +export function enable_or_disable_group_permission_settings(): void { + if (current_user.is_owner) { + const $permission_pill_container_elements = $("#organization-permissions").find( + ".pill-container", + ); + $permission_pill_container_elements.find(".input").prop("contenteditable", true); + $permission_pill_container_elements + .closest(".input-group") + .removeClass("group_setting_disabled"); + settings_components.enable_opening_typeahead_on_clicking_label( + $("#organization-permissions"), + ); + return; + } + + if (current_user.is_admin) { + const $permission_pill_container_elements = $("#organization-permissions").find( + ".pill-container", + ); + $permission_pill_container_elements.find(".input").prop("contenteditable", true); + $permission_pill_container_elements + .closest(".input-group") + .removeClass("group_setting_disabled"); + settings_components.enable_opening_typeahead_on_clicking_label( + $("#organization-permissions"), + ); + + // Admins are not allowed to update organization joining and group + // related settings. + const owner_editable_settings = [ + "realm_create_multiuse_invite_group", + "realm_can_create_groups", + "realm_can_manage_all_groups", + ]; + for (const setting_name of owner_editable_settings) { + const $permission_pill_container = $(`#id_${CSS.escape(setting_name)}`); + $permission_pill_container.find(".input").prop("contenteditable", false); + $permission_pill_container.closest(".input-group").addClass("group_setting_disabled"); + settings_components.disable_opening_typeahead_on_clicking_label( + $permission_pill_container.closest(".input-group"), + ); + } + return; + } + + const $permission_pill_container_elements = $("#organization-permissions").find( + ".pill-container", + ); + $permission_pill_container_elements.find(".input").prop("contenteditable", false); + $permission_pill_container_elements.closest(".input-group").addClass("group_setting_disabled"); + settings_components.disable_opening_typeahead_on_clicking_label($("#organization-permissions")); +} + type OrganizationSettingsOptions = { common_policy_values: SettingOptionValueWithKey[]; wildcard_mention_policy_values: SettingOptionValueWithKey[]; - common_message_policy_values: SettingOptionValueWithKey[]; invite_to_realm_policy_values: SettingOptionValueWithKey[]; - edit_topic_policy_values: SettingOptionValueWithKey[]; }; export function get_organization_settings_options(): OrganizationSettingsOptions { @@ -121,15 +174,9 @@ export function get_organization_settings_options(): OrganizationSettingsOptions wildcard_mention_policy_values: settings_components.get_sorted_options_list( settings_config.wildcard_mention_policy_values, ), - common_message_policy_values: settings_components.get_sorted_options_list( - settings_config.common_message_policy_values, - ), invite_to_realm_policy_values: settings_components.get_sorted_options_list( settings_config.email_invite_to_realm_policy_values, ), - edit_topic_policy_values: settings_components.get_sorted_options_list( - settings_config.edit_topic_policy_values, - ), }; } @@ -226,106 +273,8 @@ function set_msg_edit_limit_dropdown(): void { settings_components.set_time_limit_setting("realm_message_content_edit_limit_seconds"); } -function message_move_limit_setting_enabled( - related_setting_name: - | "realm_edit_topic_policy" - | "realm_can_move_messages_between_channels_group", -): boolean { - if (related_setting_name === "realm_edit_topic_policy") { - const setting_value_string = $( - `select:not(multiple)#id_${CSS.escape(related_setting_name)}`, - ).val(); - assert(setting_value_string !== undefined); - const setting_value = Number.parseInt(setting_value_string, 10); - const settings_options = settings_config.edit_topic_policy_values; - - if ( - setting_value === settings_options.by_admins_only.code || - setting_value === settings_options.by_moderators_only.code || - setting_value === settings_options.nobody.code - ) { - return false; - } - - return true; - } - const user_group_id = settings_components.get_dropdown_list_widget_setting_value( - $(`#id_${related_setting_name}`), - ); - assert(typeof user_group_id === "number"); - const user_group_name = user_groups.get_user_group_from_id(user_group_id).name; - if ( - user_group_name === "role:administrators" || - user_group_name === "role:moderators" || - user_group_name === "role:nobody" - ) { - return false; - } - - return true; -} - -function enable_or_disable_related_message_move_time_limit_setting( - setting_name: MessageMoveTimeLimitSetting, - disable_setting: boolean, -): void { - const $setting_elem = $(`#id_${CSS.escape(setting_name)}`); - const $custom_input_elem = $setting_elem.parent().find(".time-limit-custom-input"); - - settings_ui.disable_sub_setting_onchange(disable_setting, $setting_elem.attr("id")!, true); - settings_ui.disable_sub_setting_onchange(disable_setting, $custom_input_elem.attr("id")!, true); -} - function set_msg_move_limit_setting(property_name: MessageMoveTimeLimitSetting): void { settings_components.set_time_limit_setting(property_name); - - let disable_setting; - if (property_name === "realm_move_messages_within_stream_limit_seconds") { - disable_setting = message_move_limit_setting_enabled("realm_edit_topic_policy"); - } else { - disable_setting = message_move_limit_setting_enabled( - "realm_can_move_messages_between_channels_group", - ); - } - enable_or_disable_related_message_move_time_limit_setting(property_name, disable_setting); -} - -function message_delete_limit_setting_enabled(): boolean { - // This function is used to check whether the time-limit setting - // should be enabled. The setting is disabled when every user - // who is allowed to delete their own messages is also allowed - // to delete any message in the organization. - const realm_can_delete_own_message_group_id = - settings_components.get_dropdown_list_widget_setting_value( - $("#id_realm_can_delete_own_message_group"), - ); - const realm_can_delete_any_message_group_id = - settings_components.get_dropdown_list_widget_setting_value( - $("#id_realm_can_delete_any_message_group"), - ); - assert(typeof realm_can_delete_any_message_group_id === "number"); - const can_delete_any_message_subgroups = user_groups.get_recursive_subgroups( - user_groups.get_user_group_from_id(realm_can_delete_any_message_group_id), - ); - assert(can_delete_any_message_subgroups !== undefined); - can_delete_any_message_subgroups.add(realm_can_delete_any_message_group_id); - assert(typeof realm_can_delete_own_message_group_id === "number"); - return !can_delete_any_message_subgroups.has(realm_can_delete_own_message_group_id); -} - -function check_disable_message_delete_limit_setting_dropdown(): void { - settings_ui.disable_sub_setting_onchange( - message_delete_limit_setting_enabled(), - "id_realm_message_content_delete_limit_seconds", - true, - ); - if ($("#id_realm_message_content_delete_limit_minutes").length) { - settings_ui.disable_sub_setting_onchange( - message_delete_limit_setting_enabled(), - "id_realm_message_content_delete_limit_minutes", - true, - ); - } } function set_msg_delete_limit_dropdown(): void { @@ -409,11 +358,35 @@ function set_create_web_public_stream_dropdown_visibility(): void { ); } -export function check_disable_direct_message_initiator_group_dropdown(current_value: number): void { - if (user_groups.is_empty_group(current_value)) { - $("#realm_direct_message_initiator_group_widget").prop("disabled", true); - } else { - $("#realm_direct_message_initiator_group_widget").prop("disabled", false); +export function check_disable_direct_message_initiator_group_widget(): void { + const direct_message_permission_group_widget = settings_components.get_group_setting_widget( + "realm_direct_message_permission_group", + ); + if (direct_message_permission_group_widget === null) { + // direct_message_permission_group_widget can be null if + // the settings overlay is not opened yet. + return; + } + assert(direct_message_permission_group_widget !== null); + const direct_message_permission_value = settings_components.get_group_setting_widget_value( + direct_message_permission_group_widget, + ); + if (user_groups.is_setting_group_empty(direct_message_permission_value)) { + $("#id_realm_direct_message_initiator_group").find(".input").prop("contenteditable", false); + $("#id_realm_direct_message_initiator_group") + .closest(".input-group") + .addClass("group_setting_disabled"); + settings_components.disable_opening_typeahead_on_clicking_label( + $("#id_realm_direct_message_initiator_group").closest(".input-group"), + ); + } else if (current_user.is_admin) { + $("#id_realm_direct_message_initiator_group").find(".input").prop("contenteditable", true); + $("#id_realm_direct_message_initiator_group") + .closest(".input-group") + .removeClass("group_setting_disabled"); + settings_components.enable_opening_typeahead_on_clicking_label( + $("#id_realm_direct_message_initiator_group").closest(".input-group"), + ); } } @@ -497,15 +470,6 @@ function update_dependent_subsettings(property_name: string): void { case "realm_allow_message_editing": update_message_edit_sub_settings(realm.realm_allow_message_editing); break; - case "realm_can_delete_any_message_group": - check_disable_message_delete_limit_setting_dropdown(); - break; - case "realm_can_delete_own_message_group": - check_disable_message_delete_limit_setting_dropdown(); - break; - case "realm_can_move_messages_between_channels_group": - set_msg_move_limit_setting("realm_move_messages_between_streams_limit_seconds"); - break; case "realm_org_join_restrictions": set_org_join_restrictions_dropdown(); break; @@ -527,9 +491,7 @@ function update_dependent_subsettings(property_name: string): void { set_create_web_public_stream_dropdown_visibility(); break; case "realm_direct_message_permission_group": - check_disable_direct_message_initiator_group_dropdown( - realm.realm_direct_message_permission_group, - ); + check_disable_direct_message_initiator_group_widget(); break; } } @@ -551,25 +513,34 @@ export function discard_realm_property_element_changes(elem: HTMLElement): void case "realm_signup_announcements_stream_id": case "realm_zulip_update_announcements_stream_id": case "realm_default_code_block_language": - case "realm_create_multiuse_invite_group": - case "realm_direct_message_initiator_group": - case "realm_direct_message_permission_group": - case "realm_can_add_custom_emoji_group": case "realm_can_access_all_users_group": - case "realm_can_create_groups": - case "realm_can_create_public_channel_group": - case "realm_can_create_private_channel_group": case "realm_can_create_web_public_channel_group": - case "realm_can_delete_any_message_group": - case "realm_can_delete_own_message_group": - case "realm_can_manage_all_groups": - case "realm_can_move_messages_between_channels_group": assert(typeof property_value === "string" || typeof property_value === "number"); settings_components.set_dropdown_list_widget_setting_value( property_name, property_value, ); break; + case "realm_can_add_custom_emoji_group": + case "realm_can_create_groups": + case "realm_can_create_public_channel_group": + case "realm_can_create_private_channel_group": + case "realm_can_delete_any_message_group": + case "realm_can_delete_own_message_group": + case "realm_can_manage_all_groups": + case "realm_can_move_messages_between_channels_group": + case "realm_can_move_messages_between_topics_group": + case "realm_create_multiuse_invite_group": + case "realm_direct_message_initiator_group": + case "realm_direct_message_permission_group": { + const pill_widget = settings_components.get_group_setting_widget(property_name); + assert(pill_widget !== null); + settings_components.set_group_setting_widget_value( + pill_widget, + group_setting_value_schema.parse(property_value), + ); + break; + } case "realm_default_language": assert(typeof property_value === "string"); $("#org-notifications .language_selection_widget .language_selection_button span").attr( @@ -830,7 +801,7 @@ export function save_organization_settings( data: Record, $save_button: JQuery, patch_url: string, - success_continuation: (() => void) | undefined, + success_continuation: (() => void) | undefined = undefined, ): void { const $subsection_parent = $save_button.closest(".settings-subsection-parent"); const $save_btn_container = $subsection_parent.find(".save-button-controls"); @@ -863,7 +834,6 @@ function set_up_dropdown_widget( setting_name: keyof Realm, setting_options: () => dropdown_widget.Option[], setting_type: string, - custom_dropdown_widget_callback?: (current_value: string | number | undefined) => void, ): void { const $save_discard_widget_container = $(`#id_${CSS.escape(setting_name)}`).closest( ".settings-subsection-parent", @@ -892,9 +862,6 @@ function set_up_dropdown_widget( settings_components.save_discard_realm_settings_widget_status_handler( $save_discard_widget_container, ); - if (custom_dropdown_widget_callback !== undefined) { - custom_dropdown_widget_callback(this_widget.current_value); - } }, default_id: z.union([z.string(), z.number()]).parse(realm[setting_name]), unique_id_type, @@ -912,49 +879,22 @@ function set_up_dropdown_widget( } export function set_up_dropdown_widget_for_realm_group_settings(): void { - const realm_group_permission_settings = Object.keys( + const realm_group_permission_settings = Object.entries( realm.server_supported_permission_settings.realm, ); - for (const setting_name of realm_group_permission_settings) { + for (const [setting_name, setting_config] of realm_group_permission_settings) { + if (!setting_config.require_system_group) { + // For settings that do not require system groups, + // we use pills UI. + continue; + } const get_setting_options = (): UserGroupForDropdownListWidget[] => user_groups.get_realm_user_groups_for_dropdown_list_widget(setting_name, "realm"); - let dropdown_list_item_click_callback: - | ((current_value: string | number | undefined) => void) - | undefined; - switch (setting_name) { - case "direct_message_permission_group": { - dropdown_list_item_click_callback = ( - current_value: string | number | undefined, - ): void => { - assert(typeof current_value === "number"); - check_disable_direct_message_initiator_group_dropdown(current_value); - }; - - break; - } - case "can_delete_any_message_group": - case "can_delete_own_message_group": { - dropdown_list_item_click_callback = - check_disable_message_delete_limit_setting_dropdown; - - break; - } - case "can_move_messages_between_channels_group": { - dropdown_list_item_click_callback = () => { - set_msg_move_limit_setting("realm_move_messages_between_streams_limit_seconds"); - }; - - break; - } - // No default - } - set_up_dropdown_widget( realm_schema.keyof().parse("realm_" + setting_name), get_setting_options, "group", - dropdown_list_item_click_callback, ); } } @@ -1114,6 +1054,33 @@ export function register_save_discard_widget_handlers( ); } +function initialize_group_setting_widgets(): void { + const realm_group_permission_settings = Object.entries( + realm.server_supported_permission_settings.realm, + ); + for (const [setting_name, setting_config] of realm_group_permission_settings) { + if (setting_config.require_system_group) { + continue; + } + + const opts: { + $pill_container: JQuery; + setting_name: RealmGroupSettingName; + pill_update_callback?: () => void; + } = { + $pill_container: $(`#id_realm_${CSS.escape(setting_name)}`), + setting_name: realm_group_setting_name_schema.parse(setting_name), + }; + if (setting_name === "direct_message_permission_group") { + opts.pill_update_callback = check_disable_direct_message_initiator_group_widget; + } + settings_components.create_realm_group_setting_widget(opts); + } + + enable_or_disable_group_permission_settings(); + check_disable_direct_message_initiator_group_widget(); +} + export function build_page(): void { meta.loaded = true; @@ -1124,6 +1091,8 @@ export function build_page(): void { // Populate realm domains populate_realm_domains_label(realm.realm_domains); + initialize_group_setting_widgets(); + // Populate authentication methods table populate_auth_methods(settings_components.realm_authentication_methods_to_boolean_dict()); @@ -1240,30 +1209,6 @@ export function build_page(): void { update_message_edit_sub_settings(this.checked); }); - $("#org-moving-msgs").on( - "change", - ".move-message-policy-setting", - function (this: HTMLElement) { - const $policy_dropdown_elem = $(this); - const property_name = z - .enum(["realm_edit_topic_policy", "realm_can_move_messages_between_channels_group"]) - .parse(settings_components.extract_property_name($policy_dropdown_elem)); - const disable_time_limit_setting = message_move_limit_setting_enabled(property_name); - - let time_limit_setting_name: MessageMoveTimeLimitSetting; - if (property_name === "realm_edit_topic_policy") { - time_limit_setting_name = "realm_move_messages_within_stream_limit_seconds"; - } else { - time_limit_setting_name = "realm_move_messages_between_streams_limit_seconds"; - } - - enable_or_disable_related_message_move_time_limit_setting( - time_limit_setting_name, - disable_time_limit_setting, - ); - }, - ); - $("#id_realm_org_join_restrictions").on("click", (e) => { // This prevents the disappearance of modal when there are // no allowed domains otherwise it gets closed due to @@ -1346,8 +1291,6 @@ export function build_page(): void { }); } - check_disable_message_delete_limit_setting_dropdown(); - realm_icon.build_realm_icon_widget(upload_realm_logo_or_icon); if (realm.zulip_plan_is_not_limited) { realm_logo.build_realm_logo_widget(upload_realm_logo_or_icon, false); diff --git a/web/src/settings_sections.js b/web/src/settings_sections.js index f35a4327cd..f847269964 100644 --- a/web/src/settings_sections.js +++ b/web/src/settings_sections.js @@ -46,9 +46,9 @@ export function get_group(section) { } } -export function initialize() { +export function initialize(load_password_quality) { // personal - load_func_dict.set("your-account", settings_account.set_up); + load_func_dict.set("your-account", () => settings_account.set_up(load_password_quality)); load_func_dict.set("preferences", () => { settings_preferences.set_up(settings_preferences.user_settings_panel); }); diff --git a/web/src/settings_users.js b/web/src/settings_users.ts similarity index 57% rename from web/src/settings_users.js rename to web/src/settings_users.ts index 2d964370cc..55c8f5a602 100644 --- a/web/src/settings_users.js +++ b/web/src/settings_users.ts @@ -1,4 +1,6 @@ import $ from "jquery"; +import assert from "minimalistic-assert"; +import type * as tippy from "tippy.js"; import render_admin_user_list from "../templates/settings/admin_user_list.hbs"; @@ -8,9 +10,11 @@ import * as channel from "./channel"; import * as dialog_widget from "./dialog_widget"; import * as dropdown_widget from "./dropdown_widget"; import {$t} from "./i18n"; +import type {ListWidget as ListWidgetType} from "./list_widget"; import * as ListWidget from "./list_widget"; import * as loading from "./loading"; import * as people from "./people"; +import type {User} from "./people"; import * as presence from "./presence"; import * as scroll_util from "./scroll_util"; import * as settings_bots from "./settings_bots"; @@ -22,6 +26,7 @@ import * as timerender from "./timerender"; import * as user_deactivation_ui from "./user_deactivation_ui"; import * as user_profile from "./user_profile"; import * as user_sort from "./user_sort"; +import * as util from "./util"; export const active_user_list_dropdown_widget_name = "active_user_list_select_user_role"; export const deactivated_user_list_dropdown_widget_name = "deactivated_user_list_select_user_role"; @@ -29,54 +34,74 @@ export const deactivated_user_list_dropdown_widget_name = "deactivated_user_list let should_redraw_active_users_list = false; let should_redraw_deactivated_users_list = false; -const section = { - active: { - dropdown_widget_name: active_user_list_dropdown_widget_name, - filters: { - text_search: "", - // 0 role_code signifies All roles for our filter. - role_code: 0, - }, - }, - deactivated: { - dropdown_widget_name: deactivated_user_list_dropdown_widget_name, - filters: { - text_search: "", - // 0 role_code signifies All roles for our filter. - role_code: 0, - }, - }, - bots: {}, +type UserSettingsSection = { + dropdown_widget_name: string; + filters: { + text_search: string; + role_code: number; + }; + handle_events: () => void; + create_table: (active_users: number[]) => void; + list_widget: ListWidgetType | undefined; }; -function sort_bot_email(a, b) { - function email(bot) { - return (bot.display_email || "").toLowerCase(); +const active_section: UserSettingsSection = { + dropdown_widget_name: active_user_list_dropdown_widget_name, + filters: { + text_search: "", + // 0 role_code signifies All roles for our filter. + role_code: 0, + }, + handle_events: active_handle_events, + create_table: active_create_table, + list_widget: undefined, +}; + +const deactivated_section: UserSettingsSection = { + dropdown_widget_name: deactivated_user_list_dropdown_widget_name, + filters: { + text_search: "", + // 0 role_code signifies All roles for our filter. + role_code: 0, + }, + handle_events: deactivated_handle_events, + create_table: deactivated_create_table, + list_widget: undefined, +}; + +const bots_section = { + handle_events: bots_handle_events, + create_table: bots_create_table, +}; + +function sort_bot_email(a: BotInfo, b: BotInfo): number { + function email(bot: BotInfo): string { + return (bot.display_email ?? "").toLowerCase(); } - return user_sort.compare_a_b(email(a), email(b)); + return util.compare_a_b(email(a), email(b)); } -function sort_bot_owner(a, b) { - function owner_name(bot) { +function sort_bot_owner(a: BotInfo, b: BotInfo): number { + function owner_name(bot: BotInfo): string { return (bot.bot_owner_full_name || "").toLowerCase(); } - return user_sort.compare_a_b(owner_name(a), owner_name(b)); + return util.compare_a_b(owner_name(a), owner_name(b)); } -function sort_last_active(a, b) { - return user_sort.compare_a_b( - presence.last_active_date(a.user_id) || 0, - presence.last_active_date(b.user_id) || 0, +function sort_last_active(a: User, b: User): number { + return util.compare_a_b( + presence.last_active_date(a.user_id) ?? 0, + presence.last_active_date(b.user_id) ?? 0, ); } -function get_user_info_row(user_id) { - return $(`tr.user_row[data-user-id='${CSS.escape(user_id)}']`); +function get_user_info_row(user_id: number): JQuery { + return $(`tr.user_row[data-user-id='${CSS.escape(user_id.toString())}']`); } -export function allow_sorting_deactivated_users_list_by_email() { +export function allow_sorting_deactivated_users_list_by_email(): boolean { const deactivated_users = people.get_non_active_realm_users(); const deactivated_humans_with_visible_email = deactivated_users.filter( (user) => !user.is_bot && user.delivery_email, @@ -85,7 +110,7 @@ export function allow_sorting_deactivated_users_list_by_email() { return deactivated_humans_with_visible_email.length !== 0; } -export function update_view_on_deactivate(user_id) { +export function update_view_on_deactivate(user_id: number): void { const $row = get_user_info_row(user_id); if ($row.length === 0) { return; @@ -104,7 +129,7 @@ export function update_view_on_deactivate(user_id) { should_redraw_deactivated_users_list = true; } -export function update_view_on_reactivate(user_id) { +export function update_view_on_reactivate(user_id: number): void { const $row = get_user_info_row(user_id); if ($row.length === 0) { return; @@ -122,35 +147,54 @@ export function update_view_on_reactivate(user_id) { should_redraw_deactivated_users_list = true; } -function failed_listing_users() { +function failed_listing_users(): void { loading.destroy_indicator($("#subs_page_loading_indicator")); const user_id = people.my_current_user_id(); blueslip.error("Error while listing users for user_id", {user_id}); } -function add_value_to_filters(section, key, value) { - section.filters[key] = value; +function add_value_to_filters( + section: UserSettingsSection, + key: "role_code" | "text_search", + value: number | string, +): void { + if (key === "role_code") { + assert(typeof value === "number"); + section.filters[key] = value; + } else { + assert(typeof value === "string"); + section.filters[key] = value; + } // This hard_redraw will rerun the relevant predicate function // and in turn apply the new filters. + assert(section.list_widget !== undefined); section.list_widget.hard_redraw(); } -function role_selected_handler(event, dropdown, widget) { +function role_selected_handler( + this: HTMLElement, + event: JQuery.ClickEvent, + dropdown: tippy.Instance, + widget: dropdown_widget.DropdownWidget, +): void { event.preventDefault(); event.stopPropagation(); - const role_code = Number($(event.currentTarget).attr("data-unique-id")); - if (widget.widget_name === section.active.dropdown_widget_name) { - add_value_to_filters(section.active, "role_code", role_code); - } else if (widget.widget_name === section.deactivated.dropdown_widget_name) { - add_value_to_filters(section.deactivated, "role_code", role_code); + const role_code = Number($(this).attr("data-unique-id")); + if (widget.widget_name === active_section.dropdown_widget_name) { + add_value_to_filters(active_section, "role_code", role_code); + } else if (widget.widget_name === deactivated_section.dropdown_widget_name) { + add_value_to_filters(deactivated_section, "role_code", role_code); } dropdown.hide(); widget.render(); } -function get_roles() { +function get_roles(): { + unique_id: number; + name: string; +}[] { return [ {unique_id: 0, name: $t({defaultMessage: "All roles"})}, ...Object.values(settings_config.user_role_values) @@ -162,7 +206,10 @@ function get_roles() { ]; } -function create_role_filter_dropdown($events_container, section) { +function create_role_filter_dropdown( + $events_container: JQuery, + section: UserSettingsSection, +): void { new dropdown_widget.DropdownWidget({ widget_name: section.dropdown_widget_name, unique_id_type: dropdown_widget.DataTypes.NUMBER, @@ -176,7 +223,7 @@ function create_role_filter_dropdown($events_container, section) { }).setup(); } -function populate_users() { +function populate_users(): void { const active_user_ids = people.get_realm_active_human_user_ids(); const deactivated_user_ids = people.get_non_active_human_ids(); @@ -184,19 +231,19 @@ function populate_users() { failed_listing_users(); } - section.active.create_table(active_user_ids); - section.deactivated.create_table(deactivated_user_ids); - create_role_filter_dropdown($("#admin-user-list"), section.active); - create_role_filter_dropdown($("#admin-deactivated-users-list"), section.deactivated); + active_section.create_table(active_user_ids); + deactivated_section.create_table(deactivated_user_ids); + create_role_filter_dropdown($("#admin-user-list"), active_section); + create_role_filter_dropdown($("#admin-deactivated-users-list"), deactivated_section); } -function reset_scrollbar($sel) { +function reset_scrollbar($sel: JQuery): () => void { return function () { scroll_util.reset_scrollbar($sel); }; } -function bot_owner_full_name(owner_id) { +function bot_owner_full_name(owner_id: number | null): string | undefined { if (!owner_id) { return undefined; } @@ -209,53 +256,73 @@ function bot_owner_full_name(owner_id) { return bot_owner.full_name; } -function bot_info(bot_user_id) { - const bot_user = people.maybe_get_user_by_id(bot_user_id); +type BotInfo = { + is_bot: boolean; + role: number; + is_active: boolean; + user_id: number; + full_name: string; + user_role_text: string | undefined; + img_src: string; + bot_type: string | undefined; + bot_owner_full_name: string; + no_owner: boolean; + is_current_user: boolean; + can_modify: boolean; + cannot_deactivate: boolean; + cannot_edit: boolean; + display_email: string; +} & ( + | { + bot_owner_id: number; + is_bot_owner_active: boolean; + owner_img_src: string; + } + | { + bot_owner_id: null; + } +); - if (!bot_user) { - return undefined; - } +function bot_info(bot_user_id: number): BotInfo { + const bot_user = people.get_by_user_id(bot_user_id); + assert(bot_user.is_bot); const owner_id = bot_user.bot_owner_id; + const owner_full_name = bot_owner_full_name(owner_id); - const info = {}; - - info.is_bot = true; - info.role = bot_user.role; - info.is_active = people.is_person_active(bot_user.user_id); - info.user_id = bot_user.user_id; - info.full_name = bot_user.full_name; - info.bot_owner_id = owner_id; - info.user_role_text = people.get_user_type(bot_user_id); - info.img_src = people.small_avatar_url_for_person(bot_user); - - // Convert bot type id to string for viewing to the users. - info.bot_type = settings_data.bot_type_id_to_string(bot_user.bot_type); - - info.bot_owner_full_name = bot_owner_full_name(owner_id); - - if (!info.bot_owner_full_name) { - info.no_owner = true; - info.bot_owner_full_name = $t({defaultMessage: "No owner"}); - } - - info.is_current_user = false; - info.can_modify = current_user.is_admin; - info.cannot_deactivate = bot_user.is_system_bot; - info.cannot_edit = bot_user.is_system_bot; - - // It's always safe to show the real email addresses for bot users - info.display_email = bot_user.email; - - if (owner_id) { - info.is_bot_owner_active = people.is_person_active(owner_id); - info.owner_img_src = people.small_avatar_url_for_person(people.get_by_user_id(owner_id)); - } - - return info; + return { + is_bot: true, + role: bot_user.role, + is_active: people.is_person_active(bot_user.user_id), + user_id: bot_user.user_id, + full_name: bot_user.full_name, + user_role_text: people.get_user_type(bot_user_id), + img_src: people.small_avatar_url_for_person(bot_user), + // Convert bot type id to string for viewing to the users. + bot_type: settings_data.bot_type_id_to_string(bot_user.bot_type), + bot_owner_full_name: owner_full_name ?? $t({defaultMessage: "No owner"}), + no_owner: !owner_full_name, + is_current_user: false, + can_modify: current_user.is_admin, + cannot_deactivate: bot_user.is_system_bot ?? false, + cannot_edit: bot_user.is_system_bot ?? false, + // It's always safe to show the real email addresses for bot users + display_email: bot_user.email, + ...(owner_id + ? { + bot_owner_id: owner_id, + is_bot_owner_active: people.is_person_active(owner_id), + owner_img_src: people.small_avatar_url_for_person( + people.get_by_user_id(owner_id), + ), + } + : { + bot_owner_id: null, + }), + }; } -function get_last_active(user) { +function get_last_active(user: User): string { const last_active_date = presence.last_active_date(user.user_id); if (!last_active_date) { @@ -264,39 +331,48 @@ function get_last_active(user) { return timerender.render_now(last_active_date).time_str; } -function human_info(person) { - const info = {}; - - info.is_bot = false; - info.user_role_text = people.get_user_type(person.user_id); - info.is_active = people.is_person_active(person.user_id); - info.user_id = person.user_id; - info.full_name = person.full_name; - info.bot_owner_id = person.bot_owner_id; - - info.can_modify = current_user.is_admin; - info.is_current_user = people.is_my_user_id(person.user_id); - info.cannot_deactivate = - person.is_owner && (!current_user.is_owner || people.is_current_user_only_owner()); - info.display_email = person.delivery_email; - info.img_src = people.small_avatar_url_for_person(person); - - // TODO: This is not shown in deactivated users table and it is - // controlled by `display_last_active_column` We might just want - // to show this for deactivated users, too, even though it might - // usually just be undefined. - info.last_active_date = get_last_active(person); - - return info; +function human_info(person: User): { + is_bot: false; + user_role_text: string | undefined; + is_active: boolean; + user_id: number; + full_name: string; + bot_owner_id: number | null; + can_modify: boolean; + is_current_user: boolean; + cannot_deactivate: boolean; + display_email: string | null; + img_src: string; + last_active_date: string; +} { + return { + is_bot: false, + user_role_text: people.get_user_type(person.user_id), + is_active: people.is_person_active(person.user_id), + user_id: person.user_id, + full_name: person.full_name, + bot_owner_id: person.is_bot ? person.bot_owner_id : null, + can_modify: current_user.is_admin, + is_current_user: people.is_my_user_id(person.user_id), + cannot_deactivate: + person.is_owner && (!current_user.is_owner || people.is_current_user_only_owner()), + display_email: person.delivery_email, + img_src: people.small_avatar_url_for_person(person), + // TODO: This is not shown in deactivated users table and it is + // controlled by `display_last_active_column` We might just want + // to show this for deactivated users, too, even though it might + // usually just be undefined. + last_active_date: get_last_active(person), + }; } -function set_text_search_value($table, value) { +function set_text_search_value($table: JQuery, value: string): void { $table.closest(".user-settings-section").find(".search").val(value); } -let bot_list_widget; +let bot_list_widget: ListWidgetType; -section.bots.create_table = () => { +function bots_create_table(): void { loading.make_indicator($("#admin_page_bots_loading_indicator"), { text: $t({defaultMessage: "Loading…"}), }); @@ -308,13 +384,10 @@ section.bots.create_table = () => { name: "admin_bot_list", get_item: bot_info, modifier_html: render_admin_user_list, - html_selector: (item) => $(`tr[data-user-id='${CSS.escape(item.user_id)}']`), + html_selector: (item) => $(`tr[data-user-id='${CSS.escape(item.user_id.toString())}']`), filter: { $element: $bots_table.closest(".settings-section").find(".search"), predicate(item, value) { - if (!item) { - return false; - } return ( item.full_name.toLowerCase().includes(value) || item.display_email.toLowerCase().includes(value) @@ -335,21 +408,22 @@ section.bots.create_table = () => { loading.destroy_indicator($("#admin_page_bots_loading_indicator")); $bots_table.show(); -}; +} -section.active.create_table = (active_users) => { +function active_create_table(active_users: number[]): void { const $users_table = $("#admin_users_table"); - section.active.list_widget = ListWidget.create($users_table, active_users, { + active_section.list_widget = ListWidget.create($users_table, active_users, { name: "users_table_list", get_item: people.get_by_user_id, modifier_html(item) { - const info = human_info(item); - info.display_last_active_column = true; - return render_admin_user_list(info); + return render_admin_user_list({ + ...human_info(item), + display_last_active_column: true, + }); }, filter: { predicate(person) { - return people.predicate_for_user_settings_filters(person, section.active.filters); + return people.predicate_for_user_settings_filters(person, active_section.filters); }, onupdate: reset_scrollbar($users_table), }, @@ -365,29 +439,30 @@ section.active.create_table = (active_users) => { $simplebar_container: $("#admin-active-users-list .progressive-table-wrapper"), }); - set_text_search_value($users_table, section.active.filters.text_search); + set_text_search_value($users_table, active_section.filters.text_search); loading.destroy_indicator($("#admin_page_users_loading_indicator")); $("#admin_users_table").show(); -}; +} -section.deactivated.create_table = (deactivated_users) => { +function deactivated_create_table(deactivated_users: number[]): void { const $deactivated_users_table = $("#admin_deactivated_users_table"); - section.deactivated.list_widget = ListWidget.create( + deactivated_section.list_widget = ListWidget.create( $deactivated_users_table, deactivated_users, { name: "deactivated_users_table_list", get_item: people.get_by_user_id, modifier_html(item) { - const info = human_info(item); - info.display_last_active_column = false; - return render_admin_user_list(info); + return render_admin_user_list({ + ...human_info(item), + display_last_active_column: false, + }); }, filter: { predicate(person) { return people.predicate_for_user_settings_filters( person, - section.deactivated.filters, + deactivated_section.filters, ); }, onupdate: reset_scrollbar($deactivated_users_table), @@ -404,12 +479,12 @@ section.deactivated.create_table = (deactivated_users) => { }, ); - set_text_search_value($deactivated_users_table, section.deactivated.filters.text_search); + set_text_search_value($deactivated_users_table, deactivated_section.filters.text_search); loading.destroy_indicator($("#admin_page_deactivated_users_loading_indicator")); $("#admin_deactivated_users_table").show(); -}; +} -export function update_bot_data(bot_user_id) { +export function update_bot_data(bot_user_id: number): void { if (!bot_list_widget) { return; } @@ -417,7 +492,10 @@ export function update_bot_data(bot_user_id) { bot_list_widget.render_item(bot_info(bot_user_id)); } -export function update_user_data(user_id, new_data) { +export function update_user_data( + user_id: number, + new_data: {full_name?: string; role?: number}, +): void { const $user_row = get_user_info_row(user_id); if ($user_row.length === 0) { @@ -430,11 +508,14 @@ export function update_user_data(user_id, new_data) { } if (new_data.role !== undefined) { - $user_row.find(".user_role").text(people.get_user_type(user_id)); + const user_type = people.get_user_type(user_id); + if (user_type) { + $user_row.find(".user_role").text(user_type); + } } } -export function redraw_bots_list() { +export function redraw_bots_list(): void { if (!bot_list_widget) { return; } @@ -446,7 +527,7 @@ export function redraw_bots_list() { bot_list_widget.replace_list_data(bot_user_ids); } -function redraw_users_list(user_section, user_list) { +function redraw_users_list(user_section: UserSettingsSection, user_list: number[]): void { if (!user_section.list_widget) { return; } @@ -454,25 +535,25 @@ function redraw_users_list(user_section, user_list) { user_section.list_widget.replace_list_data(user_list); } -export function redraw_deactivated_users_list() { +export function redraw_deactivated_users_list(): void { if (!should_redraw_deactivated_users_list) { return; } const deactivated_user_ids = people.get_non_active_human_ids(); - redraw_users_list(section.deactivated, deactivated_user_ids); + redraw_users_list(deactivated_section, deactivated_user_ids); should_redraw_deactivated_users_list = false; } -export function redraw_active_users_list() { +export function redraw_active_users_list(): void { if (!should_redraw_active_users_list) { return; } const active_user_ids = people.get_realm_active_human_user_ids(); - redraw_users_list(section.active, active_user_ids); + redraw_users_list(active_section, active_user_ids); should_redraw_active_users_list = false; } -function start_data_load() { +function start_data_load(): void { loading.make_indicator($("#admin_page_users_loading_indicator"), { text: $t({defaultMessage: "Loading…"}), }); @@ -485,7 +566,7 @@ function start_data_load() { populate_users(); } -function handle_deactivation($tbody) { +function handle_deactivation($tbody: JQuery): void { $tbody.on("click", ".deactivate", (e) => { // This click event must not get propagated to parent container otherwise the modal // will not show up because of a call to `close_active` in `settings.js`. @@ -500,7 +581,7 @@ function handle_deactivation($tbody) { url = "/json/users/me"; } - function handle_confirm() { + function handle_confirm(): void { let data = {}; if ($(".send_email").is(":checked")) { data = { @@ -508,30 +589,31 @@ function handle_deactivation($tbody) { }; } - const opts = {}; if (user_id === current_user.user_id) { - opts.success_continuation = () => { - window.location.href = "/login/"; - }; + dialog_widget.submit_api_request(channel.del, url, data, { + success_continuation() { + window.location.href = "/login/"; + }, + }); + } else { + dialog_widget.submit_api_request(channel.del, url, data); } - - dialog_widget.submit_api_request(channel.del, url, data, opts); } user_deactivation_ui.confirm_deactivation(user_id, handle_confirm, true); }); } -function handle_bot_deactivation($tbody) { +function handle_bot_deactivation($tbody: JQuery): void { $tbody.on("click", ".deactivate", (e) => { e.preventDefault(); e.stopPropagation(); const $button_elem = $(e.target); const $row = $button_elem.closest(".user_row"); - const bot_id = Number.parseInt($row.attr("data-user-id"), 10); + const bot_id = Number.parseInt($row.attr("data-user-id")!, 10); - function handle_confirm() { + function handle_confirm(): void { const url = "/json/bots/" + encodeURIComponent(bot_id); dialog_widget.submit_api_request(channel.del, url, {}); } @@ -540,7 +622,7 @@ function handle_bot_deactivation($tbody) { }); } -function handle_reactivation($tbody) { +function handle_reactivation($tbody: JQuery): void { $tbody.on("click", ".reactivate", (e) => { e.preventDefault(); e.stopPropagation(); @@ -548,9 +630,9 @@ function handle_reactivation($tbody) { // Go up the tree until we find the user row, then grab the email element const $button_elem = $(e.target); const $row = $button_elem.closest(".user_row"); - const user_id = Number.parseInt($row.attr("data-user-id"), 10); + const user_id = Number.parseInt($row.attr("data-user-id")!, 10); - function handle_confirm() { + function handle_confirm(): void { const url = "/json/users/" + encodeURIComponent(user_id) + "/reactivate"; dialog_widget.submit_api_request(channel.post, url, {}); } @@ -559,12 +641,12 @@ function handle_reactivation($tbody) { }); } -function handle_edit_form($tbody) { - $tbody.on("click", ".open-user-form", (e) => { +function handle_edit_form($tbody: JQuery): void { + $tbody.on("click", ".open-user-form", function (this: HTMLElement, e) { e.stopPropagation(); e.preventDefault(); - const user_id = Number.parseInt($(e.currentTarget).attr("data-user-id"), 10); + const user_id = Number.parseInt($(this).attr("data-user-id")!, 10); if (people.is_my_user_id(user_id)) { browser_history.go_to_location("#settings/profile"); return; @@ -575,55 +657,55 @@ function handle_edit_form($tbody) { }); } -function handle_filter_change($tbody, section) { +function handle_filter_change($tbody: JQuery, section: UserSettingsSection): void { // This duplicates the built-in search filter live-update logic in // ListWidget for the input.list_widget_filter event type, but we // can't use that, because we're also filtering on Role with our // custom predicate. $tbody .closest(".user-settings-section") - .find(".search") - .on("input.list_widget_filter", function () { + .find(".search") + .on("input.list_widget_filter", function (this: HTMLInputElement) { add_value_to_filters(section, "text_search", this.value.toLocaleLowerCase()); }); } -section.active.handle_events = () => { +function active_handle_events(): void { const $tbody = $("#admin_users_table").expectOne(); - handle_filter_change($tbody, section.active); + handle_filter_change($tbody, active_section); handle_deactivation($tbody); handle_reactivation($tbody); handle_edit_form($tbody); -}; +} -section.deactivated.handle_events = () => { +function deactivated_handle_events(): void { const $tbody = $("#admin_deactivated_users_table").expectOne(); - handle_filter_change($tbody, section.deactivated); + handle_filter_change($tbody, deactivated_section); handle_deactivation($tbody); handle_reactivation($tbody); handle_edit_form($tbody); -}; +} -section.bots.handle_events = () => { +function bots_handle_events(): void { const $tbody = $("#admin_bots_table").expectOne(); handle_bot_deactivation($tbody); handle_reactivation($tbody); handle_edit_form($tbody); -}; +} -export function set_up_humans() { +export function set_up_humans(): void { start_data_load(); - section.active.handle_events(); - section.deactivated.handle_events(); + active_section.handle_events(); + deactivated_section.handle_events(); setting_invites.set_up(); } -export function set_up_bots() { - section.bots.handle_events(); - section.bots.create_table(); +export function set_up_bots(): void { + bots_section.handle_events(); + bots_section.create_table(); $("#admin-bot-list .add-a-new-bot").on("click", (e) => { e.preventDefault(); diff --git a/web/src/state_data.ts b/web/src/state_data.ts index 50a3557bcf..8e70f9f7d7 100644 --- a/web/src/state_data.ts +++ b/web/src/state_data.ts @@ -287,16 +287,17 @@ export const realm_schema = z.object({ realm_bot_creation_policy: z.number(), realm_bot_domain: z.string(), realm_can_access_all_users_group: z.number(), - realm_can_add_custom_emoji_group: z.number(), - realm_can_create_groups: z.number(), - realm_can_create_public_channel_group: z.number(), - realm_can_create_private_channel_group: z.number(), + realm_can_add_custom_emoji_group: group_setting_value_schema, + realm_can_create_groups: group_setting_value_schema, + realm_can_create_public_channel_group: group_setting_value_schema, + realm_can_create_private_channel_group: group_setting_value_schema, realm_can_create_web_public_channel_group: z.number(), - realm_can_delete_any_message_group: z.number(), - realm_can_delete_own_message_group: z.number(), - realm_can_manage_all_groups: z.number(), - realm_can_move_messages_between_channels_group: z.number(), - realm_create_multiuse_invite_group: z.number(), + realm_can_delete_any_message_group: group_setting_value_schema, + realm_can_delete_own_message_group: group_setting_value_schema, + realm_can_manage_all_groups: group_setting_value_schema, + realm_can_move_messages_between_channels_group: group_setting_value_schema, + realm_can_move_messages_between_topics_group: group_setting_value_schema, + realm_create_multiuse_invite_group: group_setting_value_schema, realm_date_created: z.number(), realm_default_code_block_language: z.string(), realm_default_external_accounts: z.record( @@ -312,8 +313,8 @@ export const realm_schema = z.object({ realm_description: z.string(), realm_digest_emails_enabled: z.boolean(), realm_digest_weekday: z.number(), - realm_direct_message_initiator_group: z.number(), - realm_direct_message_permission_group: z.number(), + realm_direct_message_initiator_group: group_setting_value_schema, + realm_direct_message_permission_group: group_setting_value_schema, realm_disallow_disposable_email_addresses: z.boolean(), realm_domains: z.array( z.object({ @@ -321,7 +322,6 @@ export const realm_schema = z.object({ allow_subdomains: z.boolean(), }), ), - realm_edit_topic_policy: z.number(), realm_email_auth_enabled: z.boolean(), realm_email_changes_disabled: z.boolean(), realm_emails_restricted_to_domains: z.boolean(), @@ -342,7 +342,15 @@ export const realm_schema = z.object({ display_name: z.string(), name: z.string(), all_event_types: z.nullable(z.array(z.string())), - // We currently ignore the `config` field in these objects. + config_options: z + .array( + z.object({ + key: z.string(), + label: z.string(), + validator: z.string(), + }), + ) + .optional(), }), ), realm_inline_image_preview: z.boolean(), diff --git a/web/src/stream_create.ts b/web/src/stream_create.ts index 2f58be4d7d..89a26bd3fb 100644 --- a/web/src/stream_create.ts +++ b/web/src/stream_create.ts @@ -3,9 +3,11 @@ import assert from "minimalistic-assert"; import {z} from "zod"; import render_subscription_invites_warning_modal from "../templates/confirm_dialog/confirm_subscription_invites_warning.hbs"; +import render_change_stream_info_modal from "../templates/stream_settings/change_stream_info_modal.hbs"; import * as channel from "./channel"; import * as confirm_dialog from "./confirm_dialog"; +import * as dialog_widget from "./dialog_widget"; import {$t, $t_html} from "./i18n"; import * as keydown_util from "./keydown_util"; import * as loading from "./loading"; @@ -16,6 +18,7 @@ import {current_user, realm} from "./state_data"; import * as stream_create_subscribers from "./stream_create_subscribers"; import * as stream_data from "./stream_data"; import * as stream_settings_components from "./stream_settings_components"; +import * as stream_settings_data from "./stream_settings_data"; import * as stream_ui_updates from "./stream_ui_updates"; import type {HTMLSelectOneElement} from "./types"; import * as ui_report from "./ui_report"; @@ -58,6 +61,12 @@ export function should_show_first_stream_created_modal(): boolean { return onboarding_steps.ONE_TIME_NOTICES_TO_DISPLAY.has("first_stream_created_banner"); } +export function maybe_update_error_message(): void { + if ($("#stream_name_error").is(":visible") && $("#archived_stream_rename").is(":visible")) { + $("#create_stream_name").trigger("input"); + } +} + class StreamSubscriptionError { report_no_subs_to_stream(): void { $("#stream_subscription_error").text( @@ -83,15 +92,16 @@ class StreamSubscriptionError { const stream_subscription_error = new StreamSubscriptionError(); class StreamNameError { - report_already_exists(): void { - $("#stream_name_error").text( - $t({defaultMessage: "A channel with this name already exists."}), - ); + report_already_exists(error?: string): void { + const error_message = + error ?? $t({defaultMessage: "A channel with this name already exists."}); + $("#stream_name_error").text(error_message); $("#stream_name_error").show(); } clear_errors(): void { $("#stream_name_error").hide(); + $("#archived_stream_rename").hide(); } report_empty_stream(): void { @@ -103,6 +113,12 @@ class StreamNameError { $("#create_stream_name").trigger("focus").trigger("select"); } + rename_archived_stream(stream_id: number): void { + $("#archived_stream_rename").text($t({defaultMessage: "Rename archived channel"})); + $("#archived_stream_rename").attr("data-stream-id", stream_id); + $("#archived_stream_rename").show(); + } + pre_validate(stream_name: string): void { // Don't worry about empty strings...we just want to call this // to warn users early before they start doing too much work @@ -111,8 +127,16 @@ class StreamNameError { // out it already exists, and I was just too lazy to look at // the public streams that I'm not subscribed to yet. Once I // realize the stream already exists, I may want to cancel.) - if (stream_name && stream_data.get_sub(stream_name)) { - this.report_already_exists(); + const stream = stream_data.get_sub(stream_name); + if (stream_name && stream) { + let error; + if (stream.is_archived) { + error = $t({defaultMessage: "An archived channel with this name already exists."}); + if (stream_settings_data.get_sub_for_settings(stream).can_change_name_description) { + this.rename_archived_stream(stream.stream_id); + } + } + this.report_already_exists(error); return; } @@ -126,8 +150,13 @@ class StreamNameError { return false; } - if (stream_data.get_sub(stream_name)) { - this.report_already_exists(); + const stream = stream_data.get_sub(stream_name); + if (stream) { + let error; + if (stream.is_archived) { + error = $t({defaultMessage: "An archived channel with this name already exists."}); + } + this.report_already_exists(error); this.select(); return false; } @@ -380,13 +409,19 @@ function create_stream(): void { // "Error creating channel"? stream_name_error.report_already_exists(); stream_name_error.select(); - } + const message = $t_html({ + defaultMessage: + "Error creating channel: A channel with this name already exists.", + }); - ui_report.error( - $t_html({defaultMessage: "Error creating channel"}), - xhr, - $(".stream_create_info"), - ); + ui_report.error(message, undefined, $(".stream_create_info")); + } else { + ui_report.error( + $t_html({defaultMessage: "Error creating channel"}), + xhr, + $(".stream_create_info"), + ); + } loading.destroy_indicator($("#stream_creating_indicator")); }, }); @@ -548,3 +583,56 @@ export function set_up_handlers(): void { assert(stream_settings_components.new_stream_can_remove_subscribers_group_widget !== null); stream_settings_components.new_stream_can_remove_subscribers_group_widget.setup(); } + +export function initialize(): void { + $("#channels_overlay_container").on("click", "#archived_stream_rename", (e) => { + e.preventDefault(); + e.stopPropagation(); + const stream_id = Number.parseInt($("#archived_stream_rename").attr("data-stream-id")!, 10); + const stream = stream_data.get_sub_by_id(stream_id); + + assert(stream !== undefined); + + const template_data = { + stream_name: stream.name, + stream_description: stream.description, + max_stream_name_length: realm.max_stream_name_length, + max_stream_description_length: realm.max_stream_description_length, + }; + const change_stream_info_modal = render_change_stream_info_modal(template_data); + dialog_widget.launch({ + html_heading: $t_html( + {defaultMessage: "Edit #{stream_name} (archived)"}, + {stream_name: stream.name}, + ), + html_body: change_stream_info_modal, + id: "change_stream_info_modal", + loading_spinner: true, + on_click: save_stream_info, + post_render() { + $("#change_stream_info_modal .dialog_submit_button") + .addClass("save-button") + .attr("data-stream-id", stream_id); + }, + update_submit_disabled_state_on_change: true, + }); + }); + + function save_stream_info(): void { + const stream_id = Number.parseInt($("#archived_stream_rename").attr("data-stream-id")!, 10); + const sub = stream_data.get_sub_by_id(stream_id); + const url = `/json/streams/${sub?.stream_id}`; + const data: {new_name?: string; description?: string} = {}; + const new_name = $("#change_stream_name").val()!.trim(); + const new_description = $("#change_stream_description").val()!.trim(); + + if (new_name !== sub?.name) { + data.new_name = new_name; + } + if (new_description !== sub?.description) { + data.description = new_description; + } + + dialog_widget.submit_api_request(channel.patch, url, data); + } +} diff --git a/web/src/stream_data.ts b/web/src/stream_data.ts index f6cc3255d0..682ae78e0e 100644 --- a/web/src/stream_data.ts +++ b/web/src/stream_data.ts @@ -108,11 +108,6 @@ class BinaryDict { this.trues.delete(k); this.falses.set(k, v); } - - delete(k: number): void { - this.trues.delete(k); - this.falses.delete(k); - } } // The stream_info variable maps stream ids to stream properties objects @@ -143,6 +138,10 @@ export function rename_sub(sub: StreamSubscription, new_name: string): void { } export function subscribe_myself(sub: StreamSubscription): void { + if (sub.is_archived) { + blueslip.warn("Can't subscribe to an archived stream."); + return; + } const user_id = people.my_current_user_id(); peer_data.add_subscriber(sub.stream_id, user_id); sub.subscribed = true; @@ -297,12 +296,13 @@ export function slug_to_stream_id(slug: string): number | undefined { } export function delete_sub(stream_id: number): void { - if (!stream_info.get(stream_id)) { + const sub = get_sub_by_id(stream_id); + if (sub === undefined || !stream_info.get(stream_id)) { blueslip.warn("Failed to archive stream " + stream_id.toString()); return; } - sub_store.delete_sub(stream_id); - stream_info.delete(stream_id); + sub.is_archived = true; + stream_info.set_false(stream_id, sub); } export function get_non_default_stream_names(): {name: string; unique_id: number}[] { @@ -378,7 +378,7 @@ export function get_invite_stream_data(): StreamSubscription[] { const streams = []; const all_subs = get_unsorted_subs(); for (const sub of all_subs) { - if (can_subscribe_others(sub)) { + if (!sub.is_archived && can_subscribe_others(sub)) { streams.push(sub); } } @@ -481,11 +481,9 @@ export function can_toggle_subscription(sub: StreamSubscription): boolean { // Spectators cannot subscribe to any streams. // // Note that the correctness of this logic relies on the fact that - // one cannot be subscribed to a deactivated stream, and - // deactivated streams are automatically made private during the - // archive stream process. + // one cannot be subscribed to a deactivated stream. return ( - (sub.subscribed || (!current_user.is_guest && !sub.invite_only)) && + (sub.subscribed || (!current_user.is_guest && !(sub.invite_only || sub.is_archived))) && !page_params.is_spectator ); } @@ -570,6 +568,10 @@ export function can_unsubscribe_others(sub: StreamSubscription): boolean { } export function can_post_messages_in_stream(stream: StreamSubscription): boolean { + if (stream.is_archived) { + return false; + } + if (page_params.is_spectator) { return false; } @@ -631,6 +633,11 @@ export function get_stream_privacy_policy(stream_id: number): string { return settings_config.stream_privacy_policy_values.private_with_public_history.code; } +export function is_stream_archived(stream_id: number): boolean { + const sub = sub_store.get(stream_id); + return sub ? sub.is_archived : false; +} + export function is_web_public(stream_id: number): boolean { const sub = sub_store.get(stream_id); return sub ? sub.is_web_public : false; diff --git a/web/src/stream_edit.js b/web/src/stream_edit.ts similarity index 64% rename from web/src/stream_edit.js rename to web/src/stream_edit.ts index 2072c91145..8fcd591bd9 100644 --- a/web/src/stream_edit.js +++ b/web/src/stream_edit.ts @@ -1,5 +1,7 @@ import ClipboardJS from "clipboard"; import $ from "jquery"; +import assert from "minimalistic-assert"; +import {z} from "zod"; import render_settings_deactivation_stream_modal from "../templates/confirm_dialog/confirm_deactivate_stream.hbs"; import render_inline_decorated_stream_name from "../templates/inline_decorated_stream_name.hbs"; @@ -31,16 +33,43 @@ import * as stream_data from "./stream_data"; import * as stream_edit_subscribers from "./stream_edit_subscribers"; import * as stream_edit_toggler from "./stream_edit_toggler"; import * as stream_settings_api from "./stream_settings_api"; +import type {SubData} from "./stream_settings_api"; import * as stream_settings_components from "./stream_settings_components"; import * as stream_settings_containers from "./stream_settings_containers"; import * as stream_settings_data from "./stream_settings_data"; +import type {SettingsSubscription} from "./stream_settings_data"; +import { + stream_properties_schema, + stream_specific_notification_settings_schema, +} from "./stream_types"; import * as stream_ui_updates from "./stream_ui_updates"; import * as sub_store from "./sub_store"; +import type {StreamSubscription} from "./sub_store"; import * as ui_report from "./ui_report"; import * as user_groups from "./user_groups"; import {user_settings} from "./user_settings"; +import * as util from "./util"; -export function setup_subscriptions_tab_hash(tab_key_value) { +type StreamSetting = { + name: z.output; + label: string; + disabled_realm_setting: boolean; + is_disabled: boolean; + has_global_notification_setting: boolean; + is_checked: boolean; +}; + +const settings_labels_schema = stream_properties_schema.omit({color: true}).keyof(); + +const realm_labels_schema = z.enum([ + "push_notifications", + "enable_online_push_notifications", + "message_content_in_email_notifications", +]); + +const notification_labels_schema = stream_specific_notification_settings_schema.keyof(); + +export function setup_subscriptions_tab_hash(tab_key_value: string): void { if ($("#subscription_overlay .right").hasClass("show")) { return; } @@ -63,7 +92,7 @@ export function setup_subscriptions_tab_hash(tab_key_value) { } } -export function get_display_text_for_realm_message_retention_setting() { +export function get_display_text_for_realm_message_retention_setting(): string { const realm_message_retention_days = realm.realm_message_retention_days; if (realm_message_retention_days === settings_config.retain_message_forever) { return $t({defaultMessage: "(forever)"}); @@ -74,29 +103,21 @@ export function get_display_text_for_realm_message_retention_setting() { ); } -function get_stream_id(target) { +function get_stream_id(target: HTMLElement): number { const $row = $(target).closest( ".stream-row, .stream_settings_header, .subscription_settings, .save-button", ); - return Number.parseInt($row.attr("data-stream-id"), 10); + return Number.parseInt($row.attr("data-stream-id")!, 10); } -function get_sub_for_target(target) { +function get_sub_for_target(target: HTMLElement): StreamSubscription { const stream_id = get_stream_id(target); - if (!stream_id) { - blueslip.error("Cannot find stream id for target"); - return undefined; - } - const sub = sub_store.get(stream_id); - if (!sub) { - blueslip.error("get_sub_for_target() failed id lookup", {stream_id}); - return undefined; - } + assert(sub !== undefined); return sub; } -export function open_edit_panel_for_row(stream_row) { +export function open_edit_panel_for_row(stream_row: HTMLElement): void { const sub = get_sub_for_target(stream_row); $(".stream-row.active").removeClass("active"); @@ -105,21 +126,26 @@ export function open_edit_panel_for_row(stream_row) { setup_stream_settings(stream_row); } -export function empty_right_panel() { +export function empty_right_panel(): void { $(".stream-row.active").removeClass("active"); $("#subscription_overlay .right").removeClass("show"); stream_settings_components.show_subs_pane.nothing_selected(); } -export function open_edit_panel_empty() { - const tab_key = stream_settings_components.get_active_data().$tabs.first().attr("data-tab-key"); +export function open_edit_panel_empty(): void { + const tab_key = stream_settings_components + .get_active_data() + .$tabs.first() + .attr("data-tab-key")!; empty_right_panel(); setup_subscriptions_tab_hash(tab_key); } -export function update_stream_name(sub, new_name) { +export function update_stream_name(sub: StreamSubscription, new_name: string): void { const $edit_container = stream_settings_containers.get_edit_container(sub); - $edit_container.find(".email-address").text(sub.email_address); + if (sub.email_address !== undefined) { + $edit_container.find(".email-address").text(sub.email_address); + } $edit_container.find(".sub-stream-name").text(new_name); const active_data = stream_settings_components.get_active_data(); @@ -128,7 +154,7 @@ export function update_stream_name(sub, new_name) { } } -export function update_stream_description(sub) { +export function update_stream_description(sub: StreamSubscription): void { const $edit_container = stream_settings_containers.get_edit_container(sub); $edit_container.find("input.description").val(sub.description); const html = render_stream_description({ @@ -137,7 +163,7 @@ export function update_stream_description(sub) { $edit_container.find(".stream-description").html(html); } -function show_subscription_settings(sub) { +function show_subscription_settings(sub: SettingsSubscription): void { const $edit_container = stream_settings_containers.get_edit_container(sub); const $colorpicker = $edit_container.find(".colorpicker"); @@ -167,49 +193,48 @@ function show_subscription_settings(sub) { }); } -function has_global_notification_setting(setting_label) { - if (setting_label.includes("_notifications")) { - return true; - } else if (setting_label.includes("_notify")) { - return true; - } - return false; +function is_notification_setting(setting_label: string): boolean { + return ( + notification_labels_schema.safeParse(setting_label).success || setting_label === "is_muted" + ); } -function is_notification_setting(setting_label) { - return has_global_notification_setting(setting_label) || setting_label === "is_muted"; -} - -export function stream_settings(sub) { +export function stream_settings(sub: StreamSubscription): StreamSetting[] { const settings_labels = settings_config.general_notifications_table_labels.stream; const check_realm_setting = settings_config.all_notifications(user_settings).disabled_notification_settings; - const settings = Object.keys(settings_labels).map((setting) => { - const ret = { - name: setting, - label: settings_labels[setting], - disabled_realm_setting: check_realm_setting[setting], - is_disabled: check_realm_setting[setting], - has_global_notification_setting: has_global_notification_setting(setting), - }; - if (has_global_notification_setting(setting)) { + return settings_labels.map(([setting, label]) => { + const parsed_realm_setting = realm_labels_schema.safeParse(setting); + const realm_setting = parsed_realm_setting.success + ? check_realm_setting[parsed_realm_setting.data] + : false; + const notification_setting = notification_labels_schema.safeParse(setting); + + let is_checked; + if (notification_setting.success) { // This block ensures we correctly display to users the // current state of stream-level notification settings // with a value of `null`, which inherit the user's global // notification settings for streams. - ret.is_checked = - stream_data.receives_notifications(sub.stream_id, setting) && - !check_realm_setting[setting]; - return ret; + is_checked = + stream_data.receives_notifications(sub.stream_id, notification_setting.data) && + !realm_setting; + } else { + is_checked = Boolean(sub[setting]) && !realm_setting; } - ret.is_checked = sub[setting] && !check_realm_setting[setting]; - return ret; + return { + name: setting, + label, + disabled_realm_setting: realm_setting, + is_disabled: realm_setting, + has_global_notification_setting: notification_setting.success, + is_checked, + }; }); - return settings; } -function setup_dropdown(sub, slim_sub) { +function setup_dropdown(sub: StreamSubscription, slim_sub: StreamSubscription): void { const can_remove_subscribers_group_widget = new dropdown_widget.DropdownWidget({ widget_name: "can_remove_subscribers_group", get_options: () => @@ -242,17 +267,18 @@ function setup_dropdown(sub, slim_sub) { can_remove_subscribers_group_widget.setup(); } -export function show_settings_for(node) { +export function show_settings_for(node: HTMLElement): void { // Hide any tooltips or popovers before we rerender / change // currently displayed stream settings. popovers.hide_all(); const stream_id = get_stream_id(node); const slim_sub = sub_store.get(stream_id); + assert(slim_sub !== undefined); stream_data.clean_up_description(slim_sub); const sub = stream_settings_data.get_sub_for_settings(slim_sub); const all_settings = stream_settings(sub); - const other_settings = []; + const other_settings: StreamSetting[] = []; const notification_settings = all_settings.filter((setting) => { if (is_notification_setting(setting.name)) { return true; @@ -304,12 +330,12 @@ export function show_settings_for(node) { ); } -export function setup_stream_settings(node) { +export function setup_stream_settings(node: HTMLElement): void { stream_edit_toggler.setup_toggler(); show_settings_for(node); } -export function update_muting_rendering(sub) { +export function update_muting_rendering(sub: StreamSubscription): void { const $edit_container = stream_settings_containers.get_edit_container(sub); const $is_muted_checkbox = $edit_container.find("#sub_is_muted_setting .sub_setting_control"); @@ -317,45 +343,48 @@ export function update_muting_rendering(sub) { $edit_container.find(".mute-note").toggleClass("hide-mute-note", !sub.is_muted); } -function stream_notification_reset(e) { - const sub = get_sub_for_target(e.target); - const data = [{stream_id: sub.stream_id, property: "is_muted", value: false}]; +function stream_notification_reset(elem: HTMLElement): void { + const sub = get_sub_for_target(elem); + const data: SubData = [{stream_id: sub.stream_id, property: "is_muted", value: false}]; for (const [per_stream_setting_name, global_setting_name] of Object.entries( settings_config.generalize_stream_notification_setting, )) { data.push({ stream_id: sub.stream_id, - property: per_stream_setting_name, + property: settings_labels_schema.parse(per_stream_setting_name), value: user_settings[global_setting_name], }); } stream_settings_api.bulk_set_stream_property( data, - $(e.target).closest(".subsection-parent").find(".alert-notification"), + $(elem).closest(".subsection-parent").find(".alert-notification"), ); } -function stream_setting_changed(e) { - const sub = get_sub_for_target(e.target); - const $status_element = $(e.target).closest(".subsection-parent").find(".alert-notification"); - const setting = e.target.name; - if (!sub) { - blueslip.error("undefined sub in stream_setting_changed()"); - return; - } - if (has_global_notification_setting(setting) && sub[setting] === null) { +function stream_setting_changed(elem: HTMLInputElement): void { + const sub = get_sub_for_target(elem); + const $status_element = $(elem).closest(".subsection-parent").find(".alert-notification"); + const setting = settings_labels_schema.parse(elem.name); + const notification_setting = notification_labels_schema.safeParse(setting); + if (notification_setting.success && sub[setting] === null) { sub[setting] = - user_settings[settings_config.generalize_stream_notification_setting[setting]]; + user_settings[ + settings_config.generalize_stream_notification_setting[notification_setting.data] + ]; } stream_settings_api.set_stream_property( sub, - {property: setting, value: e.target.checked}, + {property: setting, value: elem.checked}, $status_element, ); } -export function archive_stream(stream_id, $alert_element, $stream_row) { +export function archive_stream( + stream_id: number, + $alert_element: JQuery, + $stream_row: JQuery, +): void { channel.del({ url: "/json/streams/" + stream_id, error(xhr) { @@ -367,7 +396,7 @@ export function archive_stream(stream_id, $alert_element, $stream_row) { }); } -export function get_stream_email_address(flags, address) { +export function get_stream_email_address(flags: string[], address: string): string { const clean_address = address .replace(".show-sender", "") .replace(".include-footer", "") @@ -379,7 +408,7 @@ export function get_stream_email_address(flags, address) { return clean_address.replace("@", flag_string + "@"); } -function show_stream_email_address_modal(address) { +function show_stream_email_address_modal(address: string): void { const copy_email_address_modal_html = render_copy_email_address_modal({ email_address: address, tags: [ @@ -413,29 +442,32 @@ function show_stream_email_address_modal(address) { html_submit_button: $t_html({defaultMessage: "Copy address"}), html_exit_button: $t_html({defaultMessage: "Close"}), help_link: "/help/message-a-channel-by-email#configuration-options", - on_click() {}, + on_click() { + // This is handled by the ClipboardJS object below. + }, close_on_submit: false, }); $("#show-sender").prop("checked", true); - const clipboard = new ClipboardJS("#copy_email_address_modal .dialog_submit_button", { + const submit_button = util.the($("#copy_email_address_modal .dialog_submit_button")); + const clipboard = new ClipboardJS(submit_button, { text() { return address; }, }); // Show a tippy tooltip when the stream email address copied - clipboard.on("success", (e) => { - show_copied_confirmation(e.trigger); + clipboard.on("success", () => { + show_copied_confirmation(submit_button); }); $("#copy_email_address_modal .tag-checkbox").on("change", () => { const $checked_checkboxes = $(".copy-email-modal").find("input:checked"); - const flags = []; + const flags: string[] = []; $($checked_checkboxes).each(function () { - flags.push($(this).attr("id")); + flags.push($(this).attr("id")!); }); address = get_stream_email_address(flags, address); @@ -444,7 +476,7 @@ function show_stream_email_address_modal(address) { }); } -export function initialize() { +export function initialize(): void { $("#main_div").on("click", ".stream_sub_unsub_button", (e) => { e.preventDefault(); e.stopPropagation(); @@ -457,35 +489,40 @@ export function initialize() { stream_settings_components.sub_or_unsub(sub); }); - $("#channels_overlay_container").on("click", "#open_stream_info_modal", (e) => { - e.preventDefault(); - e.stopPropagation(); - const stream_id = get_stream_id(e.target); - const stream = sub_store.get(stream_id); - const template_data = { - stream_name: stream.name, - stream_description: stream.description, - max_stream_name_length: realm.max_stream_name_length, - max_stream_description_length: realm.max_stream_description_length, - }; - const change_stream_info_modal = render_change_stream_info_modal(template_data); - dialog_widget.launch({ - html_heading: $t_html( - {defaultMessage: "Edit #{channel_name}"}, - {channel_name: stream.name}, - ), - html_body: change_stream_info_modal, - id: "change_stream_info_modal", - loading_spinner: true, - on_click: save_stream_info, - post_render() { - $("#change_stream_info_modal .dialog_submit_button") - .addClass("save-button") - .attr("data-stream-id", stream_id); - }, - update_submit_disabled_state_on_change: true, - }); - }); + $("#channels_overlay_container").on( + "click", + "#open_stream_info_modal", + function (this: HTMLElement, e) { + e.preventDefault(); + e.stopPropagation(); + const stream_id = get_stream_id(this); + const stream = sub_store.get(stream_id); + assert(stream !== undefined); + const template_data = { + stream_name: stream.name, + stream_description: stream.description, + max_stream_name_length: realm.max_stream_name_length, + max_stream_description_length: realm.max_stream_description_length, + }; + const change_stream_info_modal = render_change_stream_info_modal(template_data); + dialog_widget.launch({ + html_heading: $t_html( + {defaultMessage: "Edit #{channel_name}"}, + {channel_name: stream.name}, + ), + html_body: change_stream_info_modal, + id: "change_stream_info_modal", + loading_spinner: true, + on_click: save_stream_info, + post_render() { + $("#change_stream_info_modal .dialog_submit_button") + .addClass("save-button") + .attr("data-stream-id", stream_id); + }, + update_submit_disabled_state_on_change: true, + }); + }, + ); $("#channels_overlay_container").on("keypress", "#change_stream_description", (e) => { // Stream descriptions cannot be multiline, so disable enter key @@ -513,26 +550,30 @@ export function initialize() { event.stopPropagation(); const $target = $(event.target).parents(".main-view-banner"); - const stream_id = Number.parseInt($target.attr("data-stream-id"), 10); + const stream_id = Number.parseInt($target.attr("data-stream-id")!, 10); // Makes sure we take the correct stream_row. const $stream_row = $( `#channels_overlay_container div.stream-row[data-stream-id='${CSS.escape( - stream_id, + stream_id.toString(), )}']`, ); const sub = sub_store.get(stream_id); + assert(sub !== undefined); stream_settings_components.sub_or_unsub(sub, $stream_row); $("#stream_permission_settings .stream-permissions-warning-banner").empty(); }, ); - function save_stream_info(e) { - const sub = get_sub_for_target(e.currentTarget); - + function save_stream_info(): void { + const sub = get_sub_for_target( + util.the($("#change_stream_info_modal .dialog_submit_button")), + ); const url = `/json/streams/${sub.stream_id}`; - const data = {}; - const new_name = $("#change_stream_name").val().trim(); - const new_description = $("#change_stream_description").val().trim(); + const data: {new_name?: string; description?: string} = {}; + const new_name = $("input#change_stream_name").val()!.trim(); + const new_description = $("textarea#change_stream_description") + .val()! + .trim(); if (new_name !== sub.name) { data.new_name = new_name; @@ -544,92 +585,89 @@ export function initialize() { dialog_widget.submit_api_request(channel.patch, url, data); } - $("#channels_overlay_container").on("click", ".copy_email_button", (e) => { - e.preventDefault(); - e.stopPropagation(); + $("#channels_overlay_container").on( + "click", + ".copy_email_button", + function (this: HTMLElement, e) { + e.preventDefault(); + e.stopPropagation(); - const stream_id = get_stream_id(e.target); + const stream_id = get_stream_id(this); - channel.get({ - url: "/json/streams/" + stream_id + "/email_address", - success(data) { - const address = data.email; - show_stream_email_address_modal(address); - }, - error(xhr) { - ui_report.error( - $t_html({defaultMessage: "Failed"}), - xhr, - $(".stream_email_address_error"), - ); - }, - }); - }); + channel.get({ + url: "/json/streams/" + stream_id + "/email_address", + success(data) { + const address = z.object({email: z.string()}).parse(data).email; + show_stream_email_address_modal(address); + }, + error(xhr) { + ui_report.error( + $t_html({defaultMessage: "Failed"}), + xhr, + $(".stream_email_address_error"), + ); + }, + }); + }, + ); $("#channels_overlay_container").on( "click", ".subsection-parent .reset-stream-notifications-button", - stream_notification_reset, + function on_click(this: HTMLElement) { + stream_notification_reset(this); + }, ); - $("#channels_overlay_container").on( + $("input#channels_overlay_container").on( "change", ".sub_setting_checkbox .sub_setting_control", - stream_setting_changed, + function on_change(this: HTMLInputElement) { + stream_setting_changed(this); + }, ); // This handler isn't part of the normal edit interface; it's the convenient // checkmark in the subscriber list. - $("#channels_overlay_container").on("click", ".sub_unsub_button", (e) => { - if ($(e.currentTarget).hasClass("disabled")) { - // We do not allow users to subscribe themselves to private streams. - return; - } - - const sub = get_sub_for_target(e.target); - // Makes sure we take the correct stream_row. - const $stream_row = $( - `#channels_overlay_container div.stream-row[data-stream-id='${CSS.escape( - sub.stream_id, - )}']`, - ); - stream_settings_components.sub_or_unsub(sub, $stream_row); - - if (!sub.subscribed) { - open_edit_panel_for_row($stream_row); - } - stream_ui_updates.update_regular_sub_settings(sub); - - e.preventDefault(); - e.stopPropagation(); - }); - - $("#channels_overlay_container").on("click", ".deactivate", (e) => { - e.preventDefault(); - e.stopPropagation(); - - const stream_id = get_stream_id(e.target); - if (!stream_id) { - ui_report.client_error( - $t_html({defaultMessage: "Invalid channel ID"}), - $(".stream_change_property_info"), - ); - return; - } - - function do_archive_stream() { - const stream_id = Number($(".dialog_submit_button").attr("data-stream-id")); - if (!stream_id) { - ui_report.client_error( - $t_html({defaultMessage: "Invalid channel ID"}), - $(".stream_change_property_info"), - ); + $("#channels_overlay_container").on( + "click", + ".sub_unsub_button", + function (this: HTMLElement, e) { + if ($(this).hasClass("disabled")) { + // We do not allow users to subscribe themselves to private streams. return; } + + const sub = get_sub_for_target(this); + // Makes sure we take the correct stream_row. + const $stream_row = $( + `#channels_overlay_container div.stream-row[data-stream-id='${CSS.escape( + sub.stream_id.toString(), + )}']`, + ); + stream_settings_components.sub_or_unsub(sub, $stream_row); + + if (!sub.subscribed) { + open_edit_panel_for_row(util.the($stream_row)); + } + stream_ui_updates.update_regular_sub_settings(sub); + + e.preventDefault(); + e.stopPropagation(); + }, + ); + + $("#channels_overlay_container").on("click", ".deactivate", function (this: HTMLElement, e) { + e.preventDefault(); + e.stopPropagation(); + + function do_archive_stream(): void { + const stream_id = Number($(".dialog_submit_button").attr("data-stream-id")); const $row = $(".stream-row.active"); archive_stream(stream_id, $(".stream_change_property_info"), $row); } + const stream_id = get_stream_id(this); const stream = sub_store.get(stream_id); const stream_name_with_privacy_symbol_html = render_inline_decorated_stream_name({stream}); @@ -667,54 +705,66 @@ export function initialize() { $(".dialog_submit_button").attr("data-stream-id", stream_id); }); - $("#channels_overlay_container").on("click", ".stream-row", function (e) { - if ($(e.target).closest(".check, .subscription_settings").length === 0) { + $("#channels_overlay_container").on("click", ".stream-row", function (this: HTMLElement) { + if ($(this).closest(".check, .subscription_settings").length === 0) { open_edit_panel_for_row(this); } }); - $("#channels_overlay_container").on("change", ".stream_message_retention_setting", (e) => { - const message_retention_setting_dropdown_value = e.target.value; - settings_components.change_element_block_display_property( - "stream_message_retention_custom_input", - message_retention_setting_dropdown_value === "custom_period", - ); - }); + $("#channels_overlay_container").on( + "change", + "select.stream_message_retention_setting", + function (this: HTMLSelectElement) { + const message_retention_setting_dropdown_value = this.value; + settings_components.change_element_block_display_property( + "stream_message_retention_custom_input", + message_retention_setting_dropdown_value === "custom_period", + ); + }, + ); - $("#channels_overlay_container").on("change input", "input, select, textarea", (e) => { - e.preventDefault(); - e.stopPropagation(); + $("#channels_overlay_container").on( + "change input", + "input, select, textarea", + function (this: HTMLElement, e): boolean { + e.preventDefault(); + e.stopPropagation(); - if ($(e.target).hasClass("no-input-change-detection")) { - // This is to prevent input changes detection in elements - // within a subsection whose changes should not affect the - // visibility of the discard button - return false; - } + if ($(this).hasClass("no-input-change-detection")) { + // This is to prevent input changes detection in elements + // within a subsection whose changes should not affect the + // visibility of the discard button + return false; + } - const stream_id = get_stream_id(e.target); - const sub = sub_store.get(stream_id); - const $subsection = $(e.target).closest(".settings-subsection-parent"); - settings_components.save_discard_stream_settings_widget_status_handler($subsection, sub); - if (sub && $subsection.attr("id") === "stream_permission_settings") { - stream_ui_updates.update_default_stream_and_stream_privacy_state($subsection); - } - return true; - }); + const stream_id = get_stream_id(this); + const sub = sub_store.get(stream_id); + const $subsection = $(this).closest(".settings-subsection-parent"); + settings_components.save_discard_stream_settings_widget_status_handler( + $subsection, + sub, + ); + if (sub && $subsection.attr("id") === "stream_permission_settings") { + stream_ui_updates.update_default_stream_and_stream_privacy_state($subsection); + } + return true; + }, + ); $("#channels_overlay_container").on( "click", ".subsection-header .subsection-changes-save button", - (e) => { + function (this: HTMLElement, e) { e.preventDefault(); e.stopPropagation(); - const $save_button = $(e.currentTarget); + const $save_button = $(this); const $subsection_elem = $save_button.closest(".settings-subsection-parent"); const stream_id = Number( $save_button.closest(".subscription_settings.show").attr("data-stream-id"), ); const sub = sub_store.get(stream_id); + assert(sub !== undefined); const data = settings_components.populate_data_for_stream_settings_request( $subsection_elem, sub, @@ -730,7 +780,7 @@ export function initialize() { } dialog_widget.launch({ html_heading: $t_html({defaultMessage: "Confirm changing access permissions"}), - html_body: render_confirm_stream_privacy_change_modal, + html_body: render_confirm_stream_privacy_change_modal(), id: "confirm_stream_privacy_change", html_submit_button: $t_html({defaultMessage: "Confirm"}), on_click() { @@ -744,16 +794,17 @@ export function initialize() { $("#channels_overlay_container").on( "click", ".subsection-header .subsection-changes-discard button", - (e) => { + function (e) { e.preventDefault(); e.stopPropagation(); const stream_id = Number( - $(e.target).closest(".subscription_settings.show").attr("data-stream-id"), + $(this).closest(".subscription_settings.show").attr("data-stream-id"), ); const sub = sub_store.get(stream_id); + assert(sub !== undefined); - const $subsection = $(e.target).closest(".settings-subsection-parent"); + const $subsection = $(this).closest(".settings-subsection-parent"); settings_org.discard_stream_settings_subsection_changes($subsection, sub); if ($subsection.attr("id") === "stream_permission_settings") { stream_ui_updates.update_default_stream_and_stream_privacy_state($subsection); diff --git a/web/src/stream_list.ts b/web/src/stream_list.ts index a4edfa063c..e1fd2ac8a5 100644 --- a/web/src/stream_list.ts +++ b/web/src/stream_list.ts @@ -227,7 +227,8 @@ export function create_initial_sidebar_rows(): void { // This code is slightly opaque, but it ends up building // up list items and attaching them to the "sub" data // structures that are kept in stream_data.js. - const subs = stream_data.subscribed_subs(); + let subs = stream_data.subscribed_subs(); + subs = subs.filter((sub) => !sub.is_archived); for (const sub of subs) { create_sidebar_row(sub); @@ -445,6 +446,7 @@ export function set_in_home_view(stream_id: number, in_home: boolean): void { function build_stream_sidebar_li(sub: StreamSubscription): JQuery { const name = sub.name; const is_muted = stream_data.is_muted(sub.stream_id); + const can_post_messages = stream_data.can_post_messages_in_stream(sub); const args = { name, id: sub.stream_id, @@ -455,6 +457,7 @@ function build_stream_sidebar_li(sub: StreamSubscription): JQuery { color: sub.color, pin_to_top: sub.pin_to_top, hide_unread_count: settings_data.should_mask_unread_count(is_muted), + can_post_messages, }; const $list_item = $(render_stream_sidebar_row(args)); return $list_item; @@ -687,7 +690,7 @@ export function get_sidebar_stream_topic_info(filter: Filter): { return result; } - if (!stream_data.is_subscribed(stream_id)) { + if (!stream_data.is_subscribed(stream_id) || stream_data.is_stream_archived(stream_id)) { return result; } @@ -846,6 +849,14 @@ export function set_event_handlers({ e.stopPropagation(); const stream_id = stream_id_for_elt($(e.target).parents("li")); + const current_narrow_stream_id = narrow_state.stream_id(); + const current_topic = narrow_state.topic(); + + if (current_narrow_stream_id === stream_id && current_topic) { + const channel_feed_url = hash_util.by_stream_url(stream_id); + browser_history.go_to_location(channel_feed_url); + return; + } if ( user_settings.web_channel_default_view === diff --git a/web/src/stream_list_sort.ts b/web/src/stream_list_sort.ts index 50c2e7f1d1..2b3bad124a 100644 --- a/web/src/stream_list_sort.ts +++ b/web/src/stream_list_sort.ts @@ -110,6 +110,9 @@ export function sort_groups(stream_ids: number[], search_term: string): StreamLi const sub = sub_store.get(stream_id); assert(sub); const pinned = sub.pin_to_top; + if (sub.is_archived) { + continue; + } if (pinned) { if (!sub.is_muted) { pinned_streams.push(stream_id); diff --git a/web/src/stream_pill.ts b/web/src/stream_pill.ts index 25f33d36d6..be14111380 100644 --- a/web/src/stream_pill.ts +++ b/web/src/stream_pill.ts @@ -134,7 +134,10 @@ export function typeahead_source( const potential_streams = invite_streams ? stream_data.get_invite_stream_data() : stream_data.get_unsorted_subs(); - return filter_taken_streams(potential_streams, pill_widget).map((stream) => ({ + + const active_streams = potential_streams.filter((sub) => !sub.is_archived); + + return filter_taken_streams(active_streams, pill_widget).map((stream) => ({ ...stream, type: "stream", })); diff --git a/web/src/stream_popover.js b/web/src/stream_popover.js index 7623a16976..a1c2127a5b 100644 --- a/web/src/stream_popover.js +++ b/web/src/stream_popover.js @@ -355,7 +355,7 @@ export async function build_move_topic_to_stream_popover( // When the modal is opened for moving the whole topic from left sidebar, // we do not have any message object and so we disable the stream input // based on the can_move_messages_between_channels_group setting and topic - // input based on edit_topic_policy. In other cases, message object is + // input based on can_move_messages_between_topics_group. In other cases, message object is // available and thus we check the time-based permissions as well in the // below if block to enable or disable the stream and topic input. let disable_stream_input = !settings_data.user_can_move_messages_between_streams(); @@ -491,6 +491,16 @@ export async function build_move_topic_to_stream_popover( current_stream_id, old_topic_name, (message_id) => { + if (message_id === undefined) { + // There are no messages in the given topic, so we show an error banner + // and return, preventing any attempts to move a non-existent topic. + dialog_widget.hide_dialog_spinner(); + ui_report.client_error( + $t_html({defaultMessage: "There are no messages to move."}), + $("#move_topic_modal #dialog_error"), + ); + return; + } message_edit.move_topic_containing_message_to_stream( message_id, select_stream_id, @@ -547,6 +557,9 @@ export async function build_move_topic_to_stream_popover( dropdown.hide(); event.preventDefault(); event.stopPropagation(); + + // Move focus to the topic input after a new stream is selected. + $("#move_topic_form .move_messages_edit_topic").trigger("focus"); } function move_topic_post_render() { diff --git a/web/src/stream_settings_api.ts b/web/src/stream_settings_api.ts index e81c53fb77..30fe753b94 100644 --- a/web/src/stream_settings_api.ts +++ b/web/src/stream_settings_api.ts @@ -5,16 +5,15 @@ import * as settings_ui from "./settings_ui"; import type {StreamProperties, StreamSubscription} from "./sub_store"; import * as sub_store from "./sub_store"; -export function bulk_set_stream_property( - sub_data: { - [Property in keyof StreamProperties]: { - stream_id: number; - property: Property; - value: StreamProperties[Property]; - }; - }[keyof StreamProperties][], - $status_element?: JQuery, -): void { +export type SubData = { + [Property in keyof StreamProperties]: { + stream_id: number; + property: Property; + value: StreamProperties[Property]; + }; +}[keyof StreamProperties][]; + +export function bulk_set_stream_property(sub_data: SubData, $status_element?: JQuery): void { const url = "/json/users/me/subscriptions/properties"; const data = {subscription_data: JSON.stringify(sub_data)}; if (!$status_element) { diff --git a/web/src/stream_settings_components.ts b/web/src/stream_settings_components.ts index 2e54db013c..a936255f35 100644 --- a/web/src/stream_settings_components.ts +++ b/web/src/stream_settings_components.ts @@ -280,7 +280,10 @@ export function unsubscribe_from_private_stream(sub: StreamSubscription): void { }); } -export function sub_or_unsub(sub: StreamSubscription, $stream_row: JQuery): void { +export function sub_or_unsub( + sub: StreamSubscription, + $stream_row: JQuery | undefined = undefined, +): void { if (sub.subscribed) { // TODO: This next line should allow guests to access web-public streams. if (sub.invite_only || current_user.is_guest) { diff --git a/web/src/stream_settings_data.ts b/web/src/stream_settings_data.ts index 660bf63a86..d7cb664a8a 100644 --- a/web/src/stream_settings_data.ts +++ b/web/src/stream_settings_data.ts @@ -63,7 +63,7 @@ function get_subs_for_settings(subs: StreamSubscription[]): SettingsSubscription // delegating, so that we can more efficiently compute subscriber counts // (in bulk). If that plan appears to have been aborted, feel free to // inline this. - return subs.map((sub) => get_sub_for_settings(sub)); + return subs.filter((sub) => !sub.is_archived).map((sub) => get_sub_for_settings(sub)); } export function get_updated_unsorted_subs(): SettingsSubscription[] { diff --git a/web/src/stream_settings_ui.js b/web/src/stream_settings_ui.js index 7ee0f08f44..c2c6a25c48 100644 --- a/web/src/stream_settings_ui.js +++ b/web/src/stream_settings_ui.js @@ -2,6 +2,7 @@ import $ from "jquery"; import _ from "lodash"; import render_stream_creation_confirmation_banner from "../templates/modal_banner/stream_creation_confirmation_banner.hbs"; +import render_stream_info_banner from "../templates/modal_banner/stream_info_banner.hbs"; import render_browse_streams_list from "../templates/stream_settings/browse_streams_list.hbs"; import render_browse_streams_list_item from "../templates/stream_settings/browse_streams_list_item.hbs"; import render_stream_settings from "../templates/stream_settings/stream_settings.hbs"; @@ -124,6 +125,9 @@ export function update_stream_name(sub, new_name) { // Update navbar if needed message_view_header.maybe_rerender_title_area_for_stream(sub); + + // Update the create stream error if needed + stream_create.maybe_update_error_message(); } export function update_stream_description(sub, description, rendered_description) { @@ -702,6 +706,18 @@ export function setup_page(callback) { throttled_redraw_left_panel(); }); + const context = { + banner_type: compose_banner.INFO, + classname: "stream_info", + hide_close_button: true, + button_text: $t({defaultMessage: "Learn more"}), + button_link: "/help/introduction-to-channels", + }; + + $("#channels_overlay_container .nothing-selected .stream-info-banner").html( + render_stream_info_banner(context), + ); + // When hitting Enter in the stream creation box, we open the // "create stream" UI with the stream name prepopulated. This // is only useful if the user has permission to create @@ -818,14 +834,16 @@ export function launch(section, left_side_tab, right_side_tab) { }, }); change_state(section, left_side_tab, right_side_tab); + setTimeout(() => { + if (!stream_settings_components.get_active_data().id) { + if (section === "new") { + $("#create_stream_name").trigger("focus"); + } else { + $("#search_stream_name").trigger("focus"); + } + } + }, 0); }); - if (!stream_settings_components.get_active_data().id) { - if (section === "new") { - $("#create_stream_name").trigger("focus"); - } else { - $("#search_stream_name").trigger("focus"); - } - } } export function switch_rows(event) { diff --git a/web/src/stream_types.ts b/web/src/stream_types.ts index cb6b37455f..8340a5b3cb 100644 --- a/web/src/stream_types.ts +++ b/web/src/stream_types.ts @@ -16,6 +16,7 @@ export const stream_schema = z.object({ history_public_to_subscribers: z.boolean(), invite_only: z.boolean(), is_announcement_only: z.boolean(), + is_archived: z.boolean(), is_web_public: z.boolean(), message_retention_days: z.number().nullable(), name: z.string(), diff --git a/web/src/sub_store.ts b/web/src/sub_store.ts index 4a7d388ef4..9479f702a7 100644 --- a/web/src/sub_store.ts +++ b/web/src/sub_store.ts @@ -60,10 +60,6 @@ export function clear(): void { subs_by_stream_id.clear(); } -export function delete_sub(stream_id: number): void { - subs_by_stream_id.delete(stream_id); -} - export function add_hydrated_sub(stream_id: number, sub: StreamSubscription): void { // The only code that should call this directly is // in stream_data.js. Grep there to find callers. diff --git a/web/src/tippyjs.ts b/web/src/tippyjs.ts index d2c6f1d5b0..8079251829 100644 --- a/web/src/tippyjs.ts +++ b/web/src/tippyjs.ts @@ -698,6 +698,7 @@ export function initialize(): void { tippy.delegate("body", { target: "#userlist-header-search", + delay: LONG_HOVER_DELAY, placement: "top", appendTo: () => document.body, onShow(instance) { diff --git a/web/src/topic_link_util.ts b/web/src/topic_link_util.ts index f56c914622..80960ced2c 100644 --- a/web/src/topic_link_util.ts +++ b/web/src/topic_link_util.ts @@ -4,7 +4,7 @@ import * as internal_url from "../shared/src/internal_url"; import * as stream_data from "./stream_data"; -const invalid_stream_topic_regex = /[*>`]|(\$\$)/g; +const invalid_stream_topic_regex = /[`>*&]|(\$\$)/g; export function will_produce_broken_stream_topic_link(word: string): boolean { return invalid_stream_topic_regex.test(word); @@ -19,11 +19,13 @@ function get_stream_name_from_topic_link_syntax(syntax: string): string { export function escape_invalid_stream_topic_characters(text: string): string { switch (text) { case "`": - return "`"; + return "`"; case ">": return ">"; case "*": return "*"; + case "&": + return "&"; case "$$": return "$$"; default: @@ -35,13 +37,25 @@ export function html_escape_markdown_syntax_characters(text: string): string { return text.replaceAll(invalid_stream_topic_regex, escape_invalid_stream_topic_characters); } -export function get_fallback_markdown_link(stream_name: string, topic_name?: string): string { +export function get_fallback_markdown_link( + stream_name: string, + topic_name?: string, + message_id?: string, +): string { const stream = stream_data.get_sub(stream_name); const stream_id = stream?.stream_id; assert(stream_id !== undefined); const escape = html_escape_markdown_syntax_characters; if (topic_name !== undefined) { - return `[#${escape(stream_name)}>${escape(topic_name)}](${internal_url.by_stream_topic_url(stream_id, topic_name, () => stream_name)})`; + const stream_topic_url = internal_url.by_stream_topic_url( + stream_id, + topic_name, + () => stream_name, + ); + if (message_id !== undefined) { + return `[#${escape(stream_name)} > ${escape(topic_name)} @ 💬](${stream_topic_url}/near/${message_id})`; + } + return `[#${escape(stream_name)} > ${escape(topic_name)}](${stream_topic_url})`; } return `[#${escape(stream_name)}](${internal_url.by_stream_url(stream_id, () => stream_name)})`; } diff --git a/web/src/ui_init.js b/web/src/ui_init.js index 3a001a8b26..30bb445a3e 100644 --- a/web/src/ui_init.js +++ b/web/src/ui_init.js @@ -117,6 +117,7 @@ import * as starred_messages from "./starred_messages"; import * as starred_messages_ui from "./starred_messages_ui"; import {current_user, realm, set_current_user, set_realm, state_data_schema} from "./state_data"; import * as stream_card_popover from "./stream_card_popover"; +import * as stream_create from "./stream_create"; import * as stream_data from "./stream_data"; import * as stream_edit from "./stream_edit"; import * as stream_edit_subscribers from "./stream_edit_subscribers"; @@ -533,6 +534,7 @@ export function initialize_everything(state_data) { on_send_message_success: compose.send_message_success, send_message: transmit.send_message, }); + stream_create.initialize(); stream_edit.initialize(); user_group_edit.initialize(); stream_edit_subscribers.initialize(); @@ -628,7 +630,11 @@ export function initialize_everything(state_data) { settings_notifications.initialize(); settings_realm_user_settings_defaults.initialize(); settings_panel_menu.initialize(); - settings_sections.initialize(); + settings_sections.initialize( + // zxcvbn.js is pretty big, and is only needed on password + // change, so load it asynchronously. + async () => (await import("./password_quality")).password_quality, + ); settings_toggle.initialize(); about_zulip.initialize(); diff --git a/web/src/unread_ops.ts b/web/src/unread_ops.ts index 966df26424..98fb049fe3 100644 --- a/web/src/unread_ops.ts +++ b/web/src/unread_ops.ts @@ -251,17 +251,9 @@ function process_newly_read_message( recent_view_ui.update_topic_unread_count(message); } -export function mark_as_unread_from_here( - message_id: number, - include_anchor = true, - messages_marked_unread_till_now = 0, - num_after = INITIAL_BATCH_SIZE - 1, - narrow?: string, -): void { +export function mark_as_unread_from_here(message_id: number): void { assert(message_lists.current !== undefined); - if (narrow === undefined) { - narrow = JSON.stringify(message_lists.current.data.filter.terms()); - } + const narrow = message_lists.current.data.filter.get_stringified_narrow_for_server_query(); message_lists.current.prevent_reading(); // If we have already fully fetched the current view, we can @@ -271,17 +263,16 @@ export function mark_as_unread_from_here( if (message_lists.current.data.fetch_status.has_found_newest()) { message_ids_to_update = message_lists.current .all_messages() - .filter( - (msg) => - (include_anchor && msg.id >= message_id) || - (!include_anchor && msg.id > message_id), - ) + .filter((msg) => msg.id >= message_id) .map((msg) => msg.id); } if (message_ids_to_update !== undefined && message_ids_to_update.length < 200) { do_mark_unread_by_ids(message_ids_to_update); } else { + const include_anchor = true; + const messages_marked_unread_till_now = 0; + const num_after = INITIAL_BATCH_SIZE - 1; do_mark_unread_by_narrow( message_id, include_anchor, diff --git a/web/src/user_card_popover.js b/web/src/user_card_popover.js index b00ecfb5be..01f6630a37 100644 --- a/web/src/user_card_popover.js +++ b/web/src/user_card_popover.js @@ -458,7 +458,7 @@ function toggle_user_card_popover_for_message( export function unsaved_message_user_mention_event_handler(e) { e.stopPropagation(); - const id_string = $(e.target).attr("data-user-id"); + const id_string = $(e.currentTarget).attr("data-user-id"); // Do not open popover for @all mention if (id_string === "*") { return; @@ -526,7 +526,7 @@ function get_user_card_popover_for_message_items() { // Functions related to the user card popover in the user sidebar. function toggle_sidebar_user_card_popover($target) { - const user_id = elem_to_user_id($target.find("a")); + const user_id = elem_to_user_id($target); const user = people.get_by_user_id(user_id); // Hiding popovers may mutate current_user_sidebar_user_id. @@ -783,6 +783,13 @@ function register_click_handlers() { toggle_sidebar_user_card_popover($target); }); + $(".buddy-list-section").on("click", ".user-profile-picture", (e) => { + e.stopPropagation(); + const $target = $(e.currentTarget).closest("li"); + + toggle_sidebar_user_card_popover($target); + }); + $("body").on("click", ".sidebar-popover-mute-user", (e) => { const user_id = elem_to_user_id($(e.target).parents("ul")); hide_all_user_card_popovers(); diff --git a/web/src/user_events.js b/web/src/user_events.js index 7cd30fd3d4..1d0f9cee85 100644 --- a/web/src/user_events.js +++ b/web/src/user_events.js @@ -87,6 +87,7 @@ export const update_person = function update(person) { if (people.is_my_user_id(person.user_id) && current_user.is_owner !== person_obj.is_owner) { current_user.is_owner = person_obj.is_owner; settings_org.maybe_disable_widgets(); + settings_org.enable_or_disable_group_permission_settings(); settings.update_lock_icon_in_sidebar(); } @@ -94,6 +95,7 @@ export const update_person = function update(person) { current_user.is_admin = person_obj.is_admin; settings_linkifiers.maybe_disable_widgets(); settings_org.maybe_disable_widgets(); + settings_org.enable_or_disable_group_permission_settings(); settings_profile_fields.maybe_disable_widgets(); settings_streams.maybe_disable_widgets(); settings_realm_user_settings_defaults.maybe_disable_widgets(); diff --git a/web/src/user_group_components.ts b/web/src/user_group_components.ts index d3a774c76a..9ac44ef421 100644 --- a/web/src/user_group_components.ts +++ b/web/src/user_group_components.ts @@ -5,6 +5,7 @@ import * as people from "./people"; import type {User} from "./people"; import type {UserGroup} from "./user_groups"; import * as user_sort from "./user_sort"; +import * as util from "./util"; export let active_group_id: number | undefined; @@ -77,7 +78,7 @@ export function sort_group_member_email(a: User | UserGroup, b: User | UserGroup return 1; } - return user_sort.compare_a_b(a.name.toLowerCase(), b.name.toLowerCase()); + return util.compare_a_b(a.name.toLowerCase(), b.name.toLowerCase()); } export function sort_group_member_name(a: User | UserGroup, b: User | UserGroup): number { @@ -95,7 +96,7 @@ export function sort_group_member_name(a: User | UserGroup, b: User | UserGroup) b_name = b.name; } - return user_sort.compare_a_b(a_name.toLowerCase(), b_name.toLowerCase()); + return util.compare_a_b(a_name.toLowerCase(), b_name.toLowerCase()); } export function build_group_member_matcher(query: string): (member: User | UserGroup) => boolean { diff --git a/web/src/user_group_create.ts b/web/src/user_group_create.ts index 5741cc23b9..06755447d2 100644 --- a/web/src/user_group_create.ts +++ b/web/src/user_group_create.ts @@ -262,31 +262,26 @@ export function set_up_handlers(): void { can_add_members_group_widget = settings_components.create_group_setting_widget({ $pill_container: $container.find(".can-add-members-group-container .pill-container"), setting_name: "can_add_members_group", - setting_type: "group", }); const $pill_container = $container.find(".can-manage-group-container .pill-container"); can_manage_group_widget = settings_components.create_group_setting_widget({ $pill_container, setting_name: "can_manage_group", - setting_type: "group", }); can_join_group_widget = settings_components.create_group_setting_widget({ $pill_container: $container.find(".can-join-group-container .pill-container"), setting_name: "can_join_group", - setting_type: "group", }); can_leave_group_widget = settings_components.create_group_setting_widget({ $pill_container: $container.find(".can-leave-group-container .pill-container"), setting_name: "can_leave_group", - setting_type: "group", }); can_mention_group_widget = settings_components.create_group_setting_widget({ $pill_container: $container.find(".can-mention-group-container .pill-container"), setting_name: "can_mention_group", - setting_type: "group", }); } diff --git a/web/src/user_group_edit.js b/web/src/user_group_edit.js index ba9cc7aaf0..adcd642ab8 100644 --- a/web/src/user_group_edit.js +++ b/web/src/user_group_edit.js @@ -1,6 +1,7 @@ import $ from "jquery"; import render_confirm_delete_user from "../templates/confirm_dialog/confirm_delete_user.hbs"; +import render_group_info_banner from "../templates/modal_banner/user_group_info_banner.hbs"; import render_browse_user_groups_list_item from "../templates/user_group_settings/browse_user_groups_list_item.hbs"; import render_cannot_deactivate_group_banner from "../templates/user_group_settings/cannot_deactivate_group_banner.hbs"; import render_change_user_group_info_modal from "../templates/user_group_settings/change_user_group_info_modal.hbs"; @@ -11,6 +12,7 @@ import * as blueslip from "./blueslip"; import * as browser_history from "./browser_history"; import * as channel from "./channel"; import * as components from "./components"; +import * as compose_banner from "./compose_banner"; import * as confirm_dialog from "./confirm_dialog"; import * as dialog_widget from "./dialog_widget"; import * as hash_util from "./hash_util"; @@ -88,7 +90,9 @@ function update_add_members_elements(group) { if (settings_data.can_add_members_to_user_group(group.id)) { $input_element.prop("contenteditable", true); - $button_element.prop("disabled", false); + if (user_group_edit_members.pill_widget.items().length !== 0) { + $button_element.prop("disabled", false); + } $button_element.css("pointer-events", ""); $add_members_container[0]._tippy?.destroy(); $add_members_container.removeClass("add_members_disabled"); @@ -154,35 +158,30 @@ function show_general_settings(group) { settings_components.create_group_setting_widget({ $pill_container: $edit_container.find(".can-add-members-group-container .pill-container"), setting_name: "can_add_members_group", - setting_type: "group", group, }); settings_components.create_group_setting_widget({ $pill_container: $edit_container.find(".can-manage-group-container .pill-container"), setting_name: "can_manage_group", - setting_type: "group", group, }); settings_components.create_group_setting_widget({ $pill_container: $edit_container.find(".can-join-group-container .pill-container"), setting_name: "can_join_group", - setting_type: "group", group, }); settings_components.create_group_setting_widget({ $pill_container: $edit_container.find(".can-leave-group-container .pill-container"), setting_name: "can_leave_group", - setting_type: "group", group, }); settings_components.create_group_setting_widget({ $pill_container: $edit_container.find(".can-mention-group-container .pill-container"), setting_name: "can_mention_group", - setting_type: "group", group, }); update_general_panel_ui(group); @@ -869,6 +868,18 @@ export function setup_page(callback) { ); $groups_overlay_container.html(groups_overlay_html); + const context = { + banner_type: compose_banner.INFO, + classname: "group_info", + hide_close_button: true, + button_text: $t({defaultMessage: "Learn more"}), + button_link: "/help/user-groups", + }; + + $("#groups_overlay_container .nothing-selected .group-info-banner").html( + render_group_info_banner(context), + ); + // Initially as the overlay is build with empty right panel, // active_group_id is undefined. user_group_components.reset_active_group_id(); diff --git a/web/src/user_group_edit_members.ts b/web/src/user_group_edit_members.ts index 9a6b12b0c0..b447113285 100644 --- a/web/src/user_group_edit_members.ts +++ b/web/src/user_group_edit_members.ts @@ -9,7 +9,6 @@ import render_user_group_membership_request_result from "../templates/user_group import render_user_group_subgroup_entry from "../templates/user_group_settings/user_group_subgroup_entry.hbs"; import * as add_group_members_pill from "./add_group_members_pill"; -import * as add_subscribers_pill from "./add_subscribers_pill"; import * as blueslip from "./blueslip"; import * as channel from "./channel"; import * as confirm_dialog from "./confirm_dialog"; @@ -44,6 +43,10 @@ function get_potential_members(): User[] { return people.filter_all_users(is_potential_member); } +function get_potential_subgroups(): UserGroup[] { + return user_groups.get_potential_subgroups(current_group_id); +} + function get_user_group_members(group: UserGroup): (User | UserGroup)[] { const member_ids = [...group.members]; const member_users = people.get_users_from_ids(member_ids); @@ -138,9 +141,10 @@ export function enable_member_management({ // current_group_id and pill_widget are module-level variables current_group_id = group_id; - pill_widget = add_subscribers_pill.create({ + pill_widget = add_group_members_pill.create({ $pill_container, - get_potential_subscribers: get_potential_members, + get_potential_members, + get_potential_groups: get_potential_subgroups, }); $pill_container.find(".input").on("input", () => { diff --git a/web/src/user_group_popover.ts b/web/src/user_group_popover.ts index bfa0113ff0..9580d0ec83 100644 --- a/web/src/user_group_popover.ts +++ b/web/src/user_group_popover.ts @@ -14,6 +14,7 @@ import * as popover_menus from "./popover_menus"; import * as rows from "./rows"; import {current_user} from "./state_data"; import * as ui_util from "./ui_util"; +import * as user_group_components from "./user_group_components"; import * as user_groups from "./user_groups"; import * as util from "./util"; @@ -95,13 +96,15 @@ export function toggle_user_group_info_popover( const args = { group_name: user_groups.get_display_group_name(group.name), group_description: group.description, - members: sort_group_members( - fetch_group_members([...user_groups.get_recursive_group_members(group)]), - ), + members: sort_group_members(fetch_group_members([...group.members])), + subgroups: user_groups + .get_direct_subgroups_of_group(group) + .sort(user_group_components.sort_group_member_name), group_edit_url: hash_util.group_edit_url(group, "general"), is_guest: current_user.is_guest, is_system_group: group.is_system_group, deactivated: group.deactivated, + members_count: user_groups.get_recursive_group_members(group).size, }; instance.setContent(ui_util.parse_html(render_user_group_info_popover(args))); }, diff --git a/web/src/user_groups.ts b/web/src/user_groups.ts index 2172384c00..d6dc0216ca 100644 --- a/web/src/user_groups.ts +++ b/web/src/user_groups.ts @@ -14,9 +14,9 @@ import type {UserGroupUpdateEvent} from "./types"; type UserGroupRaw = z.infer; -// The members field is a number array which we convert -// to a Set in the initialize function. export const user_group_schema = raw_user_group_schema.extend({ + // These are delivered via the API as lists, but converted to sets + // during initialization for more convenient manipulation. members: z.set(z.number()), direct_subgroup_ids: z.set(z.number()), }); @@ -266,6 +266,24 @@ export function is_empty_group(user_group_id: number): boolean { return true; } +export function is_setting_group_empty(setting_group: GroupSettingValue): boolean { + if (typeof setting_group === "number") { + return is_empty_group(setting_group); + } + + if (setting_group.direct_members.length > 0) { + return false; + } + + for (const subgroup_id of setting_group.direct_subgroups) { + if (!is_empty_group(subgroup_id)) { + return false; + } + } + + return true; +} + export function get_user_groups_of_user(user_id: number): UserGroup[] { const user_groups_realm = get_realm_user_groups(); const groups_of_user = user_groups_realm.filter((group) => @@ -311,6 +329,52 @@ export function get_recursive_group_members(target_user_group: UserGroup): Set + check_group_can_be_subgroup(user_group, target_user_group), + ); +} + +export function get_direct_subgroups_of_group(target_user_group: UserGroup): UserGroup[] { + const direct_subgroups = []; + const subgroup_ids = target_user_group.direct_subgroup_ids; + for (const subgroup_id of subgroup_ids) { + const subgroup = user_group_by_id_dict.get(subgroup_id); + assert(subgroup !== undefined); + direct_subgroups.push(subgroup); + } + return direct_subgroups; +} + export function is_user_in_group( user_group_id: number, user_id: number, diff --git a/web/src/user_profile.ts b/web/src/user_profile.ts index 71d071a40b..4e0342f552 100644 --- a/web/src/user_profile.ts +++ b/web/src/user_profile.ts @@ -88,21 +88,14 @@ const EMBEDDED_BOT_TYPE = "4"; export function show_button_spinner($button: JQuery): void { const $spinner = $button.find(".modal__spinner"); - const dialog_submit_button_span_width = $button.find("span").width(); - const dialog_submit_button_span_height = $button.find("span").height(); $button.prop("disabled", true); - $button.find("span").hide(); - loading.make_indicator($spinner, { - width: dialog_submit_button_span_width, - height: dialog_submit_button_span_height, - }); + loading.show_spinner($button, $spinner); } export function hide_button_spinner($button: JQuery): void { const $spinner = $button.find(".modal__spinner"); $button.prop("disabled", false); - $button.find("span").show(); - loading.destroy_indicator($spinner); + loading.hide_spinner($button, $spinner); } function compare_by_name( @@ -920,7 +913,7 @@ function get_human_profile_data(fields_user_pills: Map("input.user-list-filter").expectOne(); _reset_items: () => void; _update_list: () => void; @@ -25,12 +24,11 @@ export class UserSearch { $("#clear_search_people_button").on("click", () => { this.clear_search(); }); - $("#userlist-header-search").on("click", () => { - this.toggle_filter_displayed(); - }); this.$input.on("input", () => { - buddy_data.set_is_searching_users(this.$input.val() !== ""); + const input_is_empty = this.$input.val() === ""; + buddy_data.set_is_searching_users(!input_is_empty); + $("#clear_search_people_button").toggleClass("hidden", input_is_empty); opts.update_list(); }); this.$input.on("focus", (e) => { @@ -52,54 +50,17 @@ export class UserSearch { return this.$input.is(":focus"); } - empty(): boolean { - return this.text() === ""; - } - // This clears search input but doesn't close // the search widget unless it was already empty. clear_search(): void { buddy_data.set_is_searching_users(false); - - if (this.empty()) { - this.close_widget(); - return; - } + $("#clear_search_people_button").toggleClass("hidden", true); this.$input.val(""); this.$input.trigger("blur"); this._reset_items(); } - // This always clears and closes search. - clear_and_hide_search(): void { - this.clear_search(); - this._update_list(); - this.close_widget(); - } - - hide_widget(): void { - this.$widget.addClass("notdisplayed"); - resize.resize_sidebars(); - } - - show_widget(): void { - // Hide all the popovers. - popovers.hide_all(); - this.$widget.removeClass("notdisplayed"); - resize.resize_sidebars(); - } - - widget_shown(): boolean { - return this.$widget.hasClass("notdisplayed"); - } - - close_widget(): void { - this.$input.trigger("blur"); - this.hide_widget(); - this._reset_items(); - } - expand_column(): void { const $column = this.$input.closest(".app-main [class^='column-']"); if (!$column.hasClass("expanded")) { @@ -114,21 +75,12 @@ export class UserSearch { initiate_search(): void { this.expand_column(); - this.show_widget(); // Needs to be called when input is visible after fix_invite_user_button_flicker. setTimeout(() => { this.$input.trigger("focus"); }, 0); } - toggle_filter_displayed(): void { - if (this.widget_shown()) { - this.initiate_search(); - } else { - this.clear_and_hide_search(); - } - } - on_focus(e: JQuery.FocusEvent): void { this._on_focus(); e.stopPropagation(); diff --git a/web/src/user_sort.ts b/web/src/user_sort.ts index 750b62ddb6..624d8bac12 100644 --- a/web/src/user_sort.ts +++ b/web/src/user_sort.ts @@ -1,13 +1,5 @@ import type {User} from "./people"; - -export function compare_a_b(a: number | string, b: number | string): number { - if (a > b) { - return 1; - } else if (a === b) { - return 0; - } - return -1; -} +import {compare_a_b} from "./util"; export function sort_email(a: User, b: User): number { const email_a = a.delivery_email; @@ -29,7 +21,7 @@ export function sort_email(a: User, b: User): number { return compare_a_b(email_a.toLowerCase(), email_b.toLowerCase()); } -export function sort_role(a: User, b: User): number { +export function sort_role(a: T, b: T): number { return compare_a_b(a.role, b.role); } diff --git a/web/src/user_topic_popover.js b/web/src/user_topic_popover.ts similarity index 75% rename from web/src/user_topic_popover.js rename to web/src/user_topic_popover.ts index bd608834f1..80a1155fe3 100644 --- a/web/src/user_topic_popover.js +++ b/web/src/user_topic_popover.ts @@ -1,4 +1,5 @@ import $ from "jquery"; +import assert from "minimalistic-assert"; import render_change_visibility_policy_popover from "../templates/popovers/change_visibility_policy_popover.hbs"; @@ -8,7 +9,7 @@ import {parse_html} from "./ui_util"; import * as user_topics from "./user_topics"; import * as util from "./util"; -export function initialize() { +export function initialize(): void { popover_menus.register_popover_menu(".change_visibility_policy", { theme: "popover-menu", placement: "bottom", @@ -28,22 +29,32 @@ export function initialize() { popover_menus.popover_instances.change_visibility_policy = instance; popover_menus.on_show_prep(instance); const $elt = $(instance.reference).closest(".change_visibility_policy").expectOne(); - const stream_id = $elt.attr("data-stream-id"); - const topic_name = $elt.attr("data-topic-name"); + const stream_id_str = $elt.attr("data-stream-id"); $elt.addClass("visibility-policy-popover-visible"); + assert(stream_id_str !== undefined); + + const stream_id = Number.parseInt(stream_id_str, 10); + const topic_name = $elt.attr("data-topic-name")!; - instance.context = - popover_menus_data.get_change_visibility_policy_popover_content_context( - Number.parseInt(stream_id, 10), - topic_name, - ); instance.setContent( - parse_html(render_change_visibility_policy_popover(instance.context)), + parse_html( + render_change_visibility_policy_popover( + popover_menus_data.get_change_visibility_policy_popover_content_context( + stream_id, + topic_name, + ), + ), + ), ); }, onMount(instance) { const $popper = $(instance.popper); - const {stream_id, topic_name} = instance.context; + const $elt = $(instance.reference).closest(".change_visibility_policy").expectOne(); + const stream_id_str = $elt.attr("data-stream-id"); + assert(stream_id_str !== undefined); + + const stream_id = Number.parseInt(stream_id_str, 10); + const topic_name = $elt.attr("data-topic-name")!; if (!stream_id) { popover_menus.hide_current_popover_if_visible(instance); @@ -54,11 +65,11 @@ export function initialize() { const start_time = Date.now(); const visibility_policy = Number.parseInt( - $(e.currentTarget).attr("data-visibility-policy"), + $(e.currentTarget).attr("data-visibility-policy")!, 10, ); - const success_cb = () => { + const success_cb = (): void => { setTimeout( () => { popover_menus.hide_current_popover_if_visible(instance); @@ -67,7 +78,8 @@ export function initialize() { ); }; - const error_cb = () => { + const error_cb = (): void => { + assert(stream_id !== undefined); const prev_visibility_policy = user_topics.get_topic_visibility_policy( stream_id, topic_name, @@ -82,7 +94,7 @@ export function initialize() { util.get_remaining_time(start_time, 500), ); }; - + assert(stream_id !== undefined); user_topics.set_user_topic_visibility_policy( stream_id, topic_name, @@ -101,7 +113,7 @@ export function initialize() { .expectOne() .removeClass("visibility-policy-popover-visible"); instance.destroy(); - popover_menus.popover_instances.change_visibility_policy = undefined; + popover_menus.popover_instances.change_visibility_policy = null; // If the reference is in recent view / inbox, we would ideally restore focus // to the reference icon here but we don't do that because there are a lot of diff --git a/web/src/user_topics_ui.ts b/web/src/user_topics_ui.ts index 7b7cf7990e..02db6e10cc 100644 --- a/web/src/user_topics_ui.ts +++ b/web/src/user_topics_ui.ts @@ -33,7 +33,10 @@ function should_add_topic_update_delay(visibility_policy: number): boolean | und return is_topic_muted && is_relevant_popover_open && !is_inbox_view && !is_topic_narrow; } -export function handle_topic_updates(user_topic_event: ServerUserTopic): void { +export function handle_topic_updates( + user_topic_event: ServerUserTopic, + refreshed_current_narrow = false, +): void { // Update the UI after changes in topic visibility policies. user_topics.set_user_topic(user_topic_event); @@ -41,11 +44,14 @@ export function handle_topic_updates(user_topic_event: ServerUserTopic): void { () => { stream_list.update_streams_sidebar(); unread_ui.update_unread_counts(); - message_lists.current?.update_muting_and_rerender(); recent_view_ui.update_topic_visibility_policy( user_topic_event.stream_id, user_topic_event.topic_name, ); + + if (!refreshed_current_narrow) { + message_lists.current?.update_muting_and_rerender(); + } }, should_add_topic_update_delay(user_topic_event.visibility_policy) ? 500 : 0, ); diff --git a/web/src/util.ts b/web/src/util.ts index c1ee1c7f6f..eaacb89808 100644 --- a/web/src/util.ts +++ b/web/src/util.ts @@ -440,15 +440,17 @@ export function get_remaining_time(start_time: number, duration: number): number export function get_custom_time_in_minutes(time_unit: string, time_input: number): number { switch (time_unit) { + case "minutes": + return time_input; case "hours": return time_input * 60; case "days": return time_input * 24 * 60; case "weeks": return time_input * 7 * 24 * 60; - default: - return time_input; } + blueslip.error(`Unexpected custom time unit: ${time_unit}`); + return time_input; } export function check_time_input(input_value: string, keep_number_as_float = false): number { @@ -486,3 +488,12 @@ export function the(items: T[] | JQuery): T { } return items[0]!; } + +export function compare_a_b(a: T, b: T): number { + if (a > b) { + return 1; + } else if (a === b) { + return 0; + } + return -1; +} diff --git a/web/styles/alerts.css b/web/styles/alerts.css index ca3df6ae90..2bec56e098 100644 --- a/web/styles/alerts.css +++ b/web/styles/alerts.css @@ -214,7 +214,6 @@ $alert-error-red: hsl(0deg 80% 40%); /* animation section */ @keyframes fadeIn { 0% { - display: block; opacity: 0; transform: translateY(-100px); } @@ -232,7 +231,6 @@ $alert-error-red: hsl(0deg 80% 40%); } 100% { - display: none; opacity: 0; transform: translateY(-100px); } diff --git a/web/styles/app_components.css b/web/styles/app_components.css index fb020742ad..782d9d916b 100644 --- a/web/styles/app_components.css +++ b/web/styles/app_components.css @@ -178,6 +178,9 @@ input::placeholder { &:hover { background-color: var(--color-background-zulip-button-hover); + /* Reset styles on a.button instances. */ + text-decoration: none; + color: inherit; } &:focus { @@ -216,7 +219,6 @@ input::placeholder { &:active { border-color: hsl(156deg 30% 40%); color: hsl(156deg 44% 43%); - background-color: hsl(154deg 33% 96%); } } @@ -232,7 +234,6 @@ input::placeholder { &:active { color: hsl(35deg 82% 40%); border-color: hsl(35deg 55% 70%); - background-color: hsl(33deg 48% 96%); } } @@ -248,7 +249,6 @@ input::placeholder { &:active { color: hsl(357deg 55% 63%); border-color: hsl(2deg 46% 68%); - background-color: hsl(7deg 82% 98%); } } @@ -362,7 +362,8 @@ div.overlay { opacity: 0; visibility: hidden; - transition: all 0.2s ease-in; + transition: none 0.2s ease-in; + transition-property: opacity, visibility; overflow: hidden; .overlay-content { @@ -442,8 +443,8 @@ input.settings_text_input { .grey-box { margin: 0; padding: 5px 10px; - background-color: hsl(0deg 0% 98%); - border: 1px solid hsl(0deg 0% 87%); + background-color: var(--color-background-modal-bar); + border: 1px solid var(--color-border-modal-bar); border-radius: 4px; list-style-type: none; @@ -625,7 +626,7 @@ input.settings_text_input { .unread_mention_info:not(:empty) { margin-right: 5px; margin-left: 2px; - opacity: 0.7; + opacity: 0.5; } /* Implement the web app's default-hidden convention for alert @@ -669,7 +670,8 @@ input.settings_text_input { .animated-purple-button { color: hsl(0deg 0% 100%); - transition: all 80ms linear; + transition: none 80ms linear; + transition-property: background-color, box-shadow; box-shadow: none; /* This color just passes WCAG AA */ background-color: hsl(240deg 96% 68%); @@ -716,10 +718,11 @@ input.settings_text_input { .color-animated-button-text { color: hsl(0deg 0% 100%); - transition: all 0.2s ease; + transition: color 0.2s ease; } - transition: all 0.2s ease; + transition: none 0.2s ease; + transition-property: background-color, color; } .zulip-icon, @@ -1001,7 +1004,8 @@ input.settings_text_input { padding-top: 4px; padding-bottom: 8px; text-align: center; - border-bottom: 1px solid hsl(0deg 0% 87%); + background-color: var(--color-background-modal-bar); + border-bottom: 1px solid var(--color-border-modal-bar); & h1 { margin: 0; @@ -1177,7 +1181,7 @@ input.settings_text_input { color: hsl(0deg 0% 33%); border: 1px solid hsl(0deg 0% 80%); transition: - border linear 0.2s, + border-color linear 0.2s, box-shadow linear 0.2s; box-shadow: inset 0 1px 1px hsl(0deg 0% 0% / 7.5%); border-radius: 4px; diff --git a/web/styles/app_variables.css b/web/styles/app_variables.css index 18499cc048..b3b5a36d1e 100644 --- a/web/styles/app_variables.css +++ b/web/styles/app_variables.css @@ -276,6 +276,7 @@ the smaller line-height. */ --line-height-sidebar-row-prominent: 1.7142em; /* 24px / 14px em */ --line-height-sidebar-row: 1.5714em; /* 22px / 14px em */ + --line-height-sidebar-row-with-avatars: 1.2857em; /* 18px / 14px em */ /* Right sidebar */ --right-sidebar-padding-right: 8px; @@ -283,6 +284,8 @@ space created by the padding-left on .right-sidebar, which separates the right sidebar from the message area. */ --right-sidebar-padding-left: 2px; + --right-sidebar-avatar-width: 2em; + --right-sidebar-avatar-height: var(--right-sidebar-avatar-width); /* Tippy popover related values */ --navbar-popover-menu-min-width: 230px; @@ -585,7 +588,10 @@ --color-unread-marker: hsl(217deg 64% 59%); --color-masked-unread-marker: hsl(0deg 0% 80%); --color-failed-message-send-icon: hsl(3.88deg 98.84% 66.27%); - --color-background-modal: hsl(0deg 0% 98%); + --color-background-modal: #ededed; + --color-background-modal-bar: #f5f5f5; + --color-border-modal: color-mix(in srgb, #8c8c8c 25%, transparent); + --color-border-modal-bar: color-mix(in srgb, #c2c2c2 80%, transparent); --color-background-invitee-emails-pill-container: hsl(0deg 0% 100%); --color-unmuted-or-followed-topic-list-item: hsl(0deg 0% 20%); --color-topic-indent-border: hsl(0deg 0% 0% / 19%); @@ -608,16 +614,30 @@ --color-background-active-popover-menu: hsl(220deg 12% 5% / 7%); --color-border-popover-menu: hsl(0deg 0% 0% / 40%); --color-border-personal-menu-avatar: hsl(0deg 0% 0% / 10%); - --color-background-unread-counter: hsl(105deg 2% 50%); + --color-background-unread-counter-prominent: hsl(240deg 10% 50% / 70%); + --color-background-unread-counter-normal: hsl(240deg 10% 50% / 25%); + --color-background-unread-counter-quiet: transparent; + --color-unread-counter-prominent: hsl(0deg 0% 100%); + --color-unread-counter-normal: hsl(0deg 0% 0% / 90%); + --color-unread-counter-quiet: hsl(240deg 15% 50%); + /* Legacy unread-counter color value. */ + --color-background-unread-counter: var( + --color-background-unread-counter-prominent + ); + --color-unread-counter-muted: hsl(240deg 10% 50% / 35%); --color-border-add-subscription-button-focus: hsl(0deg 0% 20%); - /* There's no alpha channel here, but this keeps - the variable names in line. */ - --color-background-unread-counter-no-alpha: var( - --color-background-unread-counter - ); - --color-background-unread-counter-dot: var( - --color-background-unread-counter + /* When unreads are hovered on the condensed + views, they should not have an alpha. + The first color corresponds to + --color-background-unread-counter-prominent. + The second color aligns with light mode's + --color-background. */ + --color-background-unread-counter-no-alpha: color-mix( + in srgb, + hsl(240deg 10% 50%) 70%, + hsl(0deg 0% 94%) ); + --color-background-unread-counter-dot: hsl(240deg 30% 40%); --color-border-unread-counter: var(--color-background-unread-counter); --color-border-unread-counter-popover-menu: inherit; --color-background-tab-picker-container: hsl(0deg 0% 0% / 7%); @@ -668,6 +688,20 @@ --color-compose-send-control-button-focus-shadow: var( --color-compose-send-button-focus-shadow ); + --color-compose-recipient-box-text-color: inherit; + --color-compose-recipient-box-background-color: hsl(0deg 0% 100%); + /* Because of how the background color is assigned on + recipient-row elements, we need here to mix down the + border color from the compose text area, + --color-message-content-container-border, + with the compose box's background color, + --color-compose-box-background. */ + --color-compose-recipient-box-border-color: color-mix( + in srgb, + hsl(0deg 0% 0%) 10%, + hsl(232deg 20% 92%) + ); + --color-compose-recipient-box-has-focus: hsl(0deg 0% 57%); --color-compose-collapsed-reply-button-area-background: hsl(0deg 0% 100%); --color-compose-collapsed-reply-button-area-background-interactive: var( --color-compose-collapsed-reply-button-area-background @@ -713,6 +747,7 @@ /* Text colors */ --color-text-default: hsl(0deg 0% 20%); --color-text-message-default: hsl(0deg 0% 15%); + --color-text-message-header-archived: hsl(0deg 0% 50%); --color-text-message-view-header: hsl(0deg 0% 20% / 100%); --color-text-message-header: hsl(0deg 0% 15%); /* Light and dark mode both use the same hover color on @@ -761,6 +796,7 @@ --color-kbd-background: hsl(0deg 0% 98%); --color-kbd-border: hsl(0deg 0% 80%); --color-kbd-text: hsl(0deg 0% 20%); + --color-kbd-enter-sends: hsl(0deg 0% 40%); /* Markdown colors */ --color-background-rendered-markdown-thead: hsl(0deg 0% 93%); @@ -804,6 +840,9 @@ --color-left-sidebar-navigation-icon: hsl(240deg 30% 40%); --color-left-sidebar-heads-up-icon: hsl(240deg 30% 40%); --color-left-sidebar-heads-up-icon-hover: hsl(240deg 100% 15%); + --color-left-sidebar-dm-partners-icon: var( + --color-left-sidebar-navigation-icon + ); --background-color-left-sidebar-heads-up-icon-hover: hsl( 240deg 100% 50% / 7% ); @@ -900,6 +939,9 @@ 4deg 75% 53% / 15% ); --color-background-user-pill: hsla(0deg 0% 100% / 85%); + --color-background-compose-direct-recipient-pill-container: hsl( + 0deg 0% 100% + ); /* Inbox view constants - Values from Figma design */ --height-inbox-search: 26px; @@ -1094,7 +1136,10 @@ --color-navbar-bottom-border: hsl(0deg 0% 0% / 60%); --color-unread-marker: hsl(217deg 64% 59%); --color-masked-unread-marker: hsl(0deg 0% 30%); - --color-background-modal: hsl(212deg 28% 18%); + --color-background-modal: #242424; + --color-background-modal-bar: #333; + --color-border-modal: color-mix(in srgb, #fff 8%, transparent); + --color-border-modal-bar: color-mix(in srgb, #fff 5%, transparent); --color-background-invitee-emails-pill-container: hsl(0deg 0% 0% / 20%); --color-unmuted-or-followed-topic-list-item: hsl(236deg 33% 90%); --color-recipient-bar-controls-spinner: hsl(0deg 0% 100%); @@ -1114,7 +1159,6 @@ --color-background-active-popover-menu: hsl(220deg 12% 95% / 7%); --color-border-popover-menu: hsl(0deg 0% 0%); --color-border-personal-menu-avatar: hsl(0deg 0% 100% / 20%); - --color-background-unread-counter: hsl(105deg 2% 50% / 50%); /* When unreads are hovered on the condensed views, they should not have an alpha. @@ -1124,10 +1168,21 @@ an rgb() value by PostCSS Preset Env. */ --color-background-unread-counter-no-alpha: color-mix( in srgb, - hsl(105deg 2% 50%) 50%, + hsl(240deg 10% 50%) 35%, hsl(0deg 0% 11%) ); - --color-background-unread-counter-dot: hsl(105deg 2% 50% / 90%); + --color-background-unread-counter-dot: hsl(240deg 35% 68%); + --color-background-unread-counter-prominent: hsl(240deg 18.37% 34.42%); + --color-background-unread-counter-normal: hsl(240deg 10% 50% / 35%); + --color-background-unread-counter-quiet: transparent; + --color-unread-counter-prominent: hsl(0deg 0% 100%); + --color-unread-counter-normal: hsl(0deg 0% 100% / 85%); + --color-unread-counter-quiet: hsl(240deg 15% 60%); + /* Legacy unread-counter color value. */ + --color-background-unread-counter: var( + --color-background-unread-counter-prominent + ); + --color-unread-counter-muted: hsl(240deg 10% 50% / 35%); --color-border-unread-counter: hsl(105deg 2% 50%); --color-border-unread-counter-popover-menu: var( --color-border-unread-counter @@ -1181,6 +1236,10 @@ --color-compose-send-control-button-focus-shadow: var( --color-compose-send-button-focus-shadow ); + --color-compose-recipient-box-text-color: inherit; + --color-compose-recipient-box-background-color: hsl(0deg 0% 0% / 20%); + --color-compose-recipient-box-border-color: hsl(0deg 0% 0% / 80%); + --color-compose-recipient-box-has-focus: hsl(0deg 0% 100% / 27%); --color-compose-collapsed-reply-button-area-background: hsl( 0deg 0% 0% / 20% ); @@ -1276,6 +1335,7 @@ --color-kbd-background: hsl(211deg 29% 14%); --color-kbd-border: hsl(211deg 29% 14%); --color-kbd-text: hsl(236deg 33% 90%); + --color-kbd-enter-sends: hsl(236deg 33% 90%); /* Markdown colors */ --color-background-rendered-markdown-thead: hsl(0deg 0% 0% / 50%); @@ -1325,6 +1385,9 @@ --color-left-sidebar-navigation-icon: hsl(240deg 35% 68%); --color-left-sidebar-heads-up-icon: hsl(240deg 35% 68%); --color-left-sidebar-heads-up-icon-hover: hsl(240deg 100% 90%); + --color-left-sidebar-dm-partners-icon: var( + --color-left-sidebar-navigation-icon + ); --background-color-left-sidebar-heads-up-icon-hover: hsl( 240deg 100% 75% / 20% ); @@ -1421,6 +1484,9 @@ --color-close-deactivated-user-pill: hsl(7deg 100% 74%); --color-background-exit-hover-deactivated-user-pill: hsl(0deg 0% 100% / 7%); --color-background-user-pill: hsl(0deg 0% 0% / 40%); + --color-background-compose-direct-recipient-pill-container: hsl( + 0deg 0% 0% / 20% + ); /* Inbox view */ --color-background-inbox: var(--color-background); diff --git a/web/styles/components.css b/web/styles/components.css index 5bb03435ea..5973919471 100644 --- a/web/styles/components.css +++ b/web/styles/components.css @@ -79,20 +79,20 @@ a.no-underline:hover { } &.simplebar-vertical { - transition: width 0.2s ease 1s; + transition: width 0.2s ease 1s; /* stylelint-disable-line plugin/no-low-performance-animation-properties */ &.simplebar-hover { width: 15px; - transition: width 0.2s ease; + transition: width 0.2s ease; /* stylelint-disable-line plugin/no-low-performance-animation-properties */ } } &.simplebar-horizontal { - transition: height 0.2s ease 1s; + transition: height 0.2s ease 1s; /* stylelint-disable-line plugin/no-low-performance-animation-properties */ &.simplebar-hover { height: 15px; - transition: height 0.2s ease; + transition: height 0.2s ease; /* stylelint-disable-line plugin/no-low-performance-animation-properties */ } } } diff --git a/web/styles/compose.css b/web/styles/compose.css index a28a76699b..f4da4fad59 100644 --- a/web/styles/compose.css +++ b/web/styles/compose.css @@ -240,7 +240,8 @@ color: var(--color-compose-chevron-arrow); text-decoration: none; cursor: default; - transition: all 0.2s ease-in-out; + transition: none 0.2s ease-in-out; + transition-property: background, color; &.narrow_to_compose_recipients { background: var( @@ -320,7 +321,7 @@ grid-template-areas: "message-content composebox-buttons"; border-radius: 4px; border: 1px solid var(--color-message-content-container-border); - transition: border 0.2s ease; + transition: border-color 0.2s ease; &:has(.new_message_textarea:focus) { border-color: var(--color-message-content-container-border-focus); @@ -839,7 +840,7 @@ width: 0; /* The progress updates seem to come every second or so, so this is the smoothest it can probably get. */ - transition: width 1s ease-in-out; + transition: width 1s ease-in-out; /* stylelint-disable-line plugin/no-low-performance-animation-properties */ background: hsl(204deg 63% 85%); top: 0; bottom: 0; @@ -903,16 +904,16 @@ textarea.new_message_textarea { grid-template-columns: minmax(0, 1fr) auto; align-items: stretch; flex: 1 1 0; - border: 1px solid hsl(0deg 0% 0% / 20%); - border-radius: 3px; - transition: border 0.2s ease; - background: hsl(0deg 0% 100%); + border: 1px solid var(--color-compose-recipient-box-border-color); + border-radius: 4px; + transition: border-color 0.2s ease; + background: var(--color-compose-recipient-box-background-color); /* Give the recipient box, a `
    `, the correct styles when focus is in the #stream_message_recipient_topic `` */ &:focus-within { - border: 1px solid hsl(0deg 0% 67%); + border-color: var(--color-compose-recipient-box-has-focus); } #stream_message_recipient_topic, @@ -972,6 +973,11 @@ textarea.new_message_textarea { #compose_select_recipient_widget { width: auto; outline: none; + /* We override the component-level colors to + ensure concord with the topic box. */ + color: var(--color-compose-recipient-box-text-color); + background-color: var(--color-compose-recipient-box-background-color); + border-color: var(--color-compose-recipient-box-border-color); &.dropdown-widget-button { padding: 0 6px; @@ -991,7 +997,7 @@ textarea.new_message_textarea { background-color: var(--color-compose-send-button-background); &:active { - transition: all 80ms; + transition: transform 80ms; transform: scale(0.96); } @@ -1145,9 +1151,8 @@ textarea.new_message_textarea { .unread_count { margin: 1px 0 0 6px; - border: 0.5px solid var(--color-border-unread-counter); - background-color: unset; - color: inherit; + background-color: var(--color-background-unread-counter-quiet); + color: var(--color-unread-counter-quiet); } } @@ -1426,6 +1431,14 @@ textarea.new_message_textarea { justify-content: flex-start; height: var(--compose-recipient-box-min-height); + &:focus-visible { + outline: 0; + + #compose_select_recipient_widget { + border-color: var(--color-compose-recipient-box-has-focus); + } + } + .dropdown_widget_value { flex-grow: 1; text-overflow: ellipsis; @@ -1461,7 +1474,7 @@ textarea.new_message_textarea { font-weight: 450; &:active { - transition: all 80ms; + transition: transform 80ms; transform: scale(0.96); } diff --git a/web/styles/dark_theme.css b/web/styles/dark_theme.css index 0a60bbf7fc..cce37efb8b 100644 --- a/web/styles/dark_theme.css +++ b/web/styles/dark_theme.css @@ -43,18 +43,6 @@ } } - #subscription_overlay #stream-creation .settings-sticky-footer, - #groups_overlay #user-group-creation .settings-sticky-footer { - box-shadow: inset 0 1px 0 hsl(0deg 0% 0% / 20%); - } - - #user_enter_sends_label, - #realm_enter_sends_label { - & kbd { - color: var(--color-kbd-text); - } - } - #message-formatting, #keyboard-shortcuts { & kbd { @@ -293,11 +281,6 @@ } } - .stream_name_search_section, - .group_name_search_section { - border-color: hsl(0deg 0% 0% / 20%); - } - #stream-actions-menu-popover .sp-container { background-color: transparent; @@ -359,11 +342,10 @@ select, .pill-container, .user-status-content-wrapper, - #custom-expiration-time-input, + .custom-time-input-value, #organization-permissions .dropdown-widget-button, #organization-settings .dropdown-widget-button, - #stream-advanced-configurations .dropdown-widget-button, - #compose_recipient_box { + #stream-advanced-configurations .dropdown-widget-button { background-color: hsl(0deg 0% 0% / 20%); border-color: hsl(0deg 0% 0% / 60%); color: inherit; @@ -393,10 +375,6 @@ border-color: hsl(3deg 73% 74%); } - #compose-direct-recipient .pill-container { - background-color: hsl(0deg 0% 0% / 20%); - } - #searchbox { /* Light theme shows hover mostly through box-shadow, and dark theme shows it mostly through changing color @@ -477,7 +455,6 @@ #message-edit-history-overlay-container { .flex.overlay-content > .overlay-container { box-shadow: 0 0 30px hsl(213deg 31% 0%); - background-color: var(--color-background); } } @@ -572,21 +549,13 @@ background-color: hsl(228deg 11% 17%); } - & .drafts-container .drafts-header, - .subscriptions-container .subscriptions-header, - .user-groups-container .user-groups-header, - .overlay-messages-header, + & .overlay-messages-header, .grey-box, .white-box, .stream-email, #generate-integration-url-modal .integration-url, - #settings_page .settings-header, #settings_page .sidebar li.active, - #settings_page .sidebar-wrapper .tab-container, - .table-striped tbody tr:nth-child(odd) th, - #subscription_overlay #stream-creation .settings-sticky-footer, - #groups_overlay #user-group-creation .settings-sticky-footer { - border-color: hsl(0deg 0% 0% / 20%); + .table-striped tbody tr:nth-child(odd) th { background-color: hsl(0deg 0% 0% / 20%); } @@ -599,21 +568,11 @@ ); } - .user-groups-container .right .display-type, - .subscriptions-container .right .display-type, - .stream-row, - .group-row, - .subscriptions-container .left .list-toggler-container, - .user-groups-container .left .list-toggler-container, - .subscriptions-container .left, - .user-groups-container .left, .subscriber-list-box, .subscriber-list-box .subscriber_list_container .subscriber-list td, .member-list-box, .member-list-box .member_list_container .member-list td, #subscription_overlay .settings-radio-input-parent, - #settings_page .sidebar, - #settings_page .sidebar .sidebar-item, #recent_view_table table td { border-color: hsl(0deg 0% 0% / 20%); } @@ -766,9 +725,7 @@ } #feedback_container { - background-color: hsl(212deg 25% 15%); border-color: hsl(0deg 0% 0% / 50%); - color: inherit; & a:hover { color: hsl(0deg 0% 100%); diff --git a/web/styles/image_upload_widget.css b/web/styles/image_upload_widget.css index 3a75d37826..7fb9bb3091 100644 --- a/web/styles/image_upload_widget.css +++ b/web/styles/image_upload_widget.css @@ -5,8 +5,6 @@ box-shadow: 0 0 10px hsl(0deg 0% 0% / 10%); overflow: hidden; - transition: all 0.3s ease; - .image-block { background-size: contain; height: 100%; @@ -141,11 +139,14 @@ } .user-avatar-section, -.realm-logo-section, .realm-icon-section { margin: 20px 0; } +.realm-logo-section { + margin: 0 0 20px; +} + /* CSS related to settings page user avatar upload widget only */ #user-avatar-upload-widget { .image-block { @@ -186,25 +187,26 @@ } #realm-day-logo-upload-widget { - background-color: hsl(0deg 100% 100%); + /* Match to light-theme --color-background-navbar. */ + background-color: hsl(0deg 0% 97%); + padding: 5px; } #realm-night-logo-upload-widget { - background-color: hsl(212deg 28% 18%); -} - -.realm-logo-block { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - padding: 0 20px; + /* Match to dark-theme --color-background-navbar. */ + background-color: hsl(0deg 0% 13%); + padding: 5px; } .realm-logo-group { display: flex; - justify-content: space-around; flex-wrap: wrap; + gap: 20px; + + .image_upload_button { + top: 0; + left: 0; + } } /* CSS related to upload widget's preview image */ diff --git a/web/styles/inbox.css b/web/styles/inbox.css index e3862c968d..e10ec81aaa 100644 --- a/web/styles/inbox.css +++ b/web/styles/inbox.css @@ -19,10 +19,12 @@ } .unread_count { - opacity: 0.7; + opacity: 1; + outline: 0 solid var(--color-background-unread-counter); + transition: outline-width 0.1s ease; &:hover { - opacity: 1; + outline-width: 1.5px; } } @@ -79,7 +81,8 @@ font-size: 16px; color: var(--color-icons-inbox); opacity: 0.4; - transition: all 140ms; + transition: none 140ms; + transition-property: background-color, opacity, transform; padding: 5px; margin: 0; /* = -Width of the button. */ @@ -478,6 +481,10 @@ } } } + + .inbox-header-stream-archived { + color: var(--color-text-message-header-archived); + } } #inbox-view { diff --git a/web/styles/input_pill.css b/web/styles/input_pill.css index ecfb66ba11..3c808f8c9b 100644 --- a/web/styles/input_pill.css +++ b/web/styles/input_pill.css @@ -180,8 +180,10 @@ } #compose-direct-recipient .pill-container { - border: 1px solid hsl(0deg 0% 0% / 20%); - background-color: hsl(0deg 0% 100%); + border: 1px solid var(--color-compose-recipient-box-border-color); + background-color: var( + --color-background-compose-direct-recipient-pill-container + ); .input:first-child:empty::before { content: attr(data-no-recipients-text); @@ -192,6 +194,10 @@ content: attr(data-some-recipients-text); opacity: 0.5; } + + &:has(.input:focus) { + border-color: var(--color-compose-recipient-box-has-focus); + } } #invitee_emails_container .pill-container { diff --git a/web/styles/left_sidebar.css b/web/styles/left_sidebar.css index f8ee22fc60..ceaa75c93e 100644 --- a/web/styles/left_sidebar.css +++ b/web/styles/left_sidebar.css @@ -25,6 +25,10 @@ text-overflow: ellipsis; } +#left-sidebar .unread_count { + user-select: none; +} + .sidebar-topic-check, .topic-markers-and-unreads { cursor: pointer; @@ -67,7 +71,7 @@ /* Masked unreads display as flex when revealed. */ align-items: center; justify-content: center; - color: var(--color-masked-unread-marker); + color: var(--color-unread-counter-muted); width: var(--left-sidebar-single-digit-unread-width); } @@ -81,11 +85,10 @@ margin: 3px 0; .clear_search_button { - grid-area: row-content; - justify-self: self-end; + grid-area: clear-button; /* Override app-component positioning. */ position: static; - padding-right: 4px; + padding: 0; } } @@ -259,6 +262,7 @@ } } + &.hover-over-dm-section, &.zoom-in, &:hover { #compose-new-direct-message, @@ -287,7 +291,7 @@ } .zulip-icon { - opacity: 0.7; + color: var(--color-left-sidebar-dm-partners-icon); } } @@ -492,7 +496,7 @@ ul.filters { } .zulip-icon-follow { - opacity: 0.6; + opacity: 0.5; &:hover { opacity: 1; @@ -687,10 +691,6 @@ li.active-sub-filter { .left-sidebar-navigation-label-container { .left-sidebar-navigation-label { - /* Again, for the sake of low-resolution screens, - we'll let the actual label take 1 as a line-height - value, and allow grid to handle the alignment. */ - line-height: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -715,9 +715,8 @@ li.active-sub-filter { .top_left_drafts .unread_count, .top_left_scheduled_messages .unread_count, .condensed-views-popover-menu .unread_count { - background-color: unset; - color: inherit; - border: 0.5px solid var(--color-border-unread-counter); + background-color: var(--color-background-unread-counter-quiet); + color: var(--color-unread-counter-quiet); } /* Don't show unread counts on views... */ @@ -772,6 +771,13 @@ li.top_left_scheduled_messages { .left-sidebar-navigation-label { grid-area: row-content; padding-right: var(--left-sidebar-before-unread-count-padding); + + @media screen and (resolution <= 1x) { + /* For the sake of low-resolution screens, + we'll let the actual label take 1 as a line-height + value, and allow grid to handle the alignment. */ + line-height: 1; + } } .unread_count { @@ -785,6 +791,30 @@ li.top_left_scheduled_messages { } } +/* Low-attention unreads have no bounding box, + so their counters should be aligned on the + same baseline as the navigation label. */ +.top_left_starred_messages, +.top_left_drafts, +.top_left_scheduled_messages { + .left-sidebar-navigation-label-container { + align-items: baseline; + } + + .left-sidebar-navigation-label { + @media screen and (resolution <= 1x) { + /* Owing to the baseline alignment in this + area, we don't need the low-res line-height + adjustment. */ + line-height: inherit; + } + } + + .filter-icon { + align-self: center; + } +} + .top_left_starred_messages { &.hide_starred_message_count { .masked_unread_count { @@ -1028,14 +1058,6 @@ li.top_left_scheduled_messages { } } -.condensed-views-popover-menu { - .unread_count { - margin: 1px 0 0 6px; - border-color: var(--color-border-unread-counter-popover-menu); - width: max-content; - } -} - .subscription_block { grid-template-columns: var(--left-sidebar-toggle-width-offset) var( @@ -1097,11 +1119,11 @@ li.top_left_scheduled_messages { .topic_search_section { grid-template-columns: - var(--left-sidebar-toggle-width-offset) 0 0 minmax(0, 1fr) minmax( - 0, - max-content - ) - 30px 0; + var(--left-sidebar-toggle-width-offset) 0 0 + [filter-box-start] minmax(0, 1fr) + minmax(0, max-content) + [clear-button-start] var(--left-sidebar-vdots-width) + [clear-button-end filter-box-end] 0; } .topic-box .zero_count { @@ -1156,7 +1178,7 @@ li.top_left_scheduled_messages { } .topic-list-filter { - grid-area: row-content; + grid-area: filter-box; } #filter-topic-input:placeholder-shown + #clear_search_topic_button { @@ -1556,16 +1578,6 @@ li.topic-list-item { background-color: var(--color-background); border-radius: 4px; - &:hover { - background-color: var(--color-background-opaque-hover-narrow-filter); - box-shadow: inset 0 0 0 1px var(--color-shadow-sidebar-row-hover); - - .left-sidebar-title, - .sidebar-heading-icon { - opacity: var(--opacity-sidebar-heading-hover); - } - } - &.showing-stream-search-section { /* Open up the stream-search rows. The 10px row maintains space with the streams list @@ -1610,10 +1622,6 @@ li.topic-list-item { } } - &:hover #filter_streams_tooltip { - display: flex; - } - #add_streams_tooltip { grid-row: 1 / 1; margin: 2px 0; @@ -1632,8 +1640,20 @@ li.topic-list-item { } } - &:hover #streams_inline_icon { - display: flex; + &:hover, + &.showing-streams-popover { + background-color: var(--color-background-opaque-hover-narrow-filter); + box-shadow: inset 0 0 0 1px var(--color-shadow-sidebar-row-hover); + + .left-sidebar-title, + .sidebar-heading-icon { + opacity: var(--opacity-sidebar-heading-hover); + } + + #filter_streams_tooltip, + #streams_inline_icon { + display: flex; + } } .stream_search_section { diff --git a/web/styles/lightbox.css b/web/styles/lightbox.css index e8f078bd23..446a43b3ca 100644 --- a/web/styles/lightbox.css +++ b/web/styles/lightbox.css @@ -59,7 +59,7 @@ opacity: 0; pointer-events: none; cursor: pointer; - transition: all 0.2s ease; + transition: opacity 0.2s ease; } &.show .exit { @@ -183,7 +183,7 @@ cursor: pointer; opacity: 0.5; - transition: all 0.3s ease; + transition: opacity 0.3s ease; &:hover { opacity: 1; diff --git a/web/styles/message_header.css b/web/styles/message_header.css index 0c9686a108..84d832d957 100644 --- a/web/styles/message_header.css +++ b/web/styles/message_header.css @@ -210,6 +210,15 @@ } } } + + .message-header-stream-name { + overflow: hidden; + text-overflow: ellipsis; + } + + .message-header-stream-archived { + color: var(--color-text-message-header-archived); + } } .recipient_bar_controls { diff --git a/web/styles/message_row.css b/web/styles/message_row.css index 4332696f45..dc74d9dbcc 100644 --- a/web/styles/message_row.css +++ b/web/styles/message_row.css @@ -428,7 +428,8 @@ .message_control_button { opacity: 0; visibility: hidden; - transition: all 0.2s ease; + transition: none 0.2s ease; + transition-property: opacity, visibility; width: var(--message-box-icon-width); height: var(--message-box-icon-height); text-align: center; @@ -527,14 +528,14 @@ .unread_marker { margin-left: var(--unread-marker-left); opacity: 0; - transition: all 0.3s ease-out; + transition: opacity 0.3s ease-out; &.slow_fade { - transition: all 2s ease-out; + transition: opacity 2s ease-out; } &.fast_fade { - transition: all 0.3s ease-out; + transition: opacity 0.3s ease-out; } &.date_unread_marker { @@ -562,7 +563,7 @@ } .unread .unread_marker { - transition: all 0.3s ease-out; + transition: opacity 0.3s ease-out; opacity: 1; } @@ -657,7 +658,7 @@ .edit-content-container { border-radius: 4px; border: 1px solid var(--color-message-content-container-border); - transition: border 0.2s ease; + transition: border-color 0.2s ease; &:has(.message_edit_content:focus) { border-color: var(--color-message-content-container-border-focus); diff --git a/web/styles/message_view_header.css b/web/styles/message_view_header.css index d677bc1b99..8401e5b28c 100644 --- a/web/styles/message_view_header.css +++ b/web/styles/message_view_header.css @@ -66,6 +66,12 @@ text-overflow: ellipsis; } + .message-header-archived { + color: var(--color-text-message-header-archived); + cursor: default; + padding-left: 5px; + } + .narrow_description { /* Flexbox's baseline alignment is responsible for matching the description's baseline to the title. diff --git a/web/styles/modal.css b/web/styles/modal.css index 65626713d8..f2a8043542 100644 --- a/web/styles/modal.css +++ b/web/styles/modal.css @@ -406,7 +406,7 @@ border: 1px solid hsl(0deg 0% 80%); box-shadow: inset 0 1px 1px hsl(0deg 0% 0% / 7.5%); transition: - border linear 0.2s, + border-color linear 0.2s, box-shadow linear 0.2s; margin-bottom: 10px; width: 206px; diff --git a/web/styles/popovers.css b/web/styles/popovers.css index cf884abedf..3fc646e95e 100644 --- a/web/styles/popovers.css +++ b/web/styles/popovers.css @@ -346,6 +346,15 @@ ul.popover-group-menu-member-list { display: flex; align-items: center; padding: 0 10px; + + .zulip-icon-triple-users { + color: var(--color-icon-purple); + } + + .zulip-icon { + padding-left: 3px; + padding-right: 3px; + } } .popover-group-menu-member-name { @@ -753,7 +762,7 @@ ul.popover-group-menu-member-list { opacity: 0; pointer-events: none; - transition: all 0.3s ease; + transition: opacity 0.3s ease; &.fade.in { opacity: 1; @@ -1177,12 +1186,16 @@ ul.popover-group-menu-member-list { flex-flow: row nowrap; align-items: flex-start; gap: 5px; + /* 3px at 15px/1em, 10px at 15px/1em */ padding: 0.2em 0.6666em; /* 15px at 15px/1em */ font-size: 1em; /* 16px at 15px/1em */ line-height: 1.0667em; - min-height: 26px; + /* 26px at 16px/1em - this height was carried forward + despite the information density change in 15px > 16px, + so we calculate its height to the 16px em in use. */ + min-height: 1.625em; .popover-menu-icon { /* 16px at 15px/1em */ @@ -1463,3 +1476,35 @@ ul.popover-menu-list { border: 1px solid var(--color-border-popover-hotkey-hint); } } + +.condensed-views-popover-menu { + .popover-menu-link:has(.label-and-unread-wrapper) { + align-items: center; + + .popover-menu-icon { + margin-top: 1px; + } + + .label-and-unread-wrapper { + /* Occupy the maximum width of the + parent flex container. */ + flex: 1 0 max-content; + display: flex; + gap: 5px; + align-items: baseline; + } + + .popover-menu-label { + margin-top: 0; + } + + .unread_count { + margin: 0 0 0 6px; + border-color: var(--color-border-unread-counter-popover-menu); + width: max-content; + height: auto; + line-height: 1.2445em; + align-self: baseline; + } + } +} diff --git a/web/styles/portico/activity.css b/web/styles/portico/activity.css index 933a7e28fb..b64ea94cec 100644 --- a/web/styles/portico/activity.css +++ b/web/styles/portico/activity.css @@ -145,7 +145,7 @@ tr.admin td:first-child { color: hsl(0deg 0% 33%); vertical-align: text-bottom; transition: - border linear 0.2s, + border-color linear 0.2s, box-shadow linear 0.2s; box-shadow: inset 0 1px 1px hsla(0deg 0% 0% / 7.5%); @@ -372,7 +372,7 @@ tr.admin td:first-child { border: 1px solid hsl(0deg 0% 80%); box-shadow: inset 0 1px 1px hsl(0deg 0% 0% / 7.5%); transition: - border linear 0.2s, + border-color linear 0.2s, box-shadow linear 0.2s; color: hsl(0deg 0% 33%); diff --git a/web/styles/portico/billing.css b/web/styles/portico/billing.css index d42cb41253..e51e501ee5 100644 --- a/web/styles/portico/billing.css +++ b/web/styles/portico/billing.css @@ -59,7 +59,8 @@ width: 120px; height: 70px; background-color: hsl(0deg 0% 94%); - transition: all 0.2s ease; + transition: none 0.2s ease; + transition-property: background-color, border-color; display: inline-block; text-align: center; cursor: pointer; diff --git a/web/styles/portico/comparison_table.css b/web/styles/portico/comparison_table.css index de76a5bb94..8e56c71f33 100644 --- a/web/styles/portico/comparison_table.css +++ b/web/styles/portico/comparison_table.css @@ -95,7 +95,7 @@ color: inherit; text-decoration: none; border-bottom: 1px solid hsl(0deg 0% 100% / 50%) !important; - transition: border 0.4s ease-out; + transition: border 0.4s ease-out; /* stylelint-disable-line plugin/no-low-performance-animation-properties */ &:hover { border-bottom: 2px solid hsl(0deg 0% 100%) !important; @@ -130,7 +130,7 @@ align-items: center; border-radius: 3px; cursor: pointer; - transition: all 120ms ease-out; + transition: background-color 120ms ease-out; } .comparison-tab:hover { @@ -139,7 +139,6 @@ } .comparison-tab:active { - transition: all 120ms ease-out; background-color: hsl(0deg 0% 100% / 70%); } @@ -178,7 +177,7 @@ } .comparison-table tr { - transition: all 200ms ease-out; + transition: background-color 200ms ease-out; } .comparison-table tbody tr:hover { @@ -199,7 +198,6 @@ font-weight: 700; color: hsl(223deg 40% 30% / 100%); background: hsl(209deg 41% 94%); - transition: all 200ms ease-out; } .comparison-table th .icon { @@ -228,11 +226,6 @@ letter-spacing: 0.1ch; } - .comparison-table th.sticky { - padding: 8px 2px; - border-radius: 0; - } - .comparison-table td { padding: 8px; border-top: 1px solid hsl(209deg 40% 40% / 30%); @@ -345,8 +338,7 @@ } @media (width <= 730px) { - .comparison-table th, - .comparison-table th.sticky { + .comparison-table th { font-size: 14px; line-height: 18px; box-sizing: border-box; @@ -378,8 +370,7 @@ } @media (width <= 500px) { - .comparison-table th, - .comparison-table th.sticky { + .comparison-table th { font-size: 13px; line-height: 14px; } @@ -544,19 +535,10 @@ .comparison-table th.comparison-table-feature { padding-top: 28px; - /* Don't apply transitions to the - padding. This prevents the "Features" - label from sliding into place when - switching over to the All view. */ - transition: none; } .comparison-table td.stuck { padding-top: 24px; - /* Don't apply transitions to the - padding. This makes the stuck state - look more precise. */ - transition: none; } @media (width <= 730px) { diff --git a/web/styles/portico/email_log.css b/web/styles/portico/email_log.css index 7258f76f67..a0c8bb0b9e 100644 --- a/web/styles/portico/email_log.css +++ b/web/styles/portico/email_log.css @@ -111,7 +111,7 @@ border: 1px solid hsl(0deg 0% 80%); box-shadow: inset 0 1px 1px hsl(0deg 0% 0% / 7.5%); transition: - border linear 0.2s, + border-color linear 0.2s, box-shadow linear 0.2s; margin-bottom: 10px; width: 206px; diff --git a/web/styles/portico/footer.css b/web/styles/portico/footer.css index 0efc433503..83443a1a38 100644 --- a/web/styles/portico/footer.css +++ b/web/styles/portico/footer.css @@ -77,7 +77,6 @@ line-height: 20px; color: var(--color-links); border-bottom: 1px solid var(--color-footer-background); - transition: border 0.4s ease-out; } & a, @@ -172,7 +171,7 @@ mask-position: center; mask-repeat: no-repeat; mask-image: var(--footer-social-icon); - transition: all 150ms ease-out; + transition: background-color 150ms ease-out; &:hover { background-color: hsl(238.6deg 84.31% 90%); diff --git a/web/styles/portico/hello.css b/web/styles/portico/hello.css index 7dc0ab38ff..2f15b76a9d 100644 --- a/web/styles/portico/hello.css +++ b/web/styles/portico/hello.css @@ -165,7 +165,8 @@ ul { "pnum" on, "lnum" on; color: hsl(0deg 0% 100%); - transition: all 0.8s ease-out; + transition: none 0.8s ease-out; + transition-property: background, transform; cursor: pointer; } @@ -197,7 +198,7 @@ ul { } .client-logos-div { - transition: all 0.7s ease-out; + transition: opacity 0.7s ease-out; background-position: center; } @@ -264,7 +265,7 @@ ul { line-height: 130%; /* 33.8px */ position: relative; opacity: 0.7; - transition: all 0.1s; + transition: opacity 0.1s; } .screen-2__tabs ul li label:hover { @@ -282,7 +283,7 @@ ul { border-radius: 5px; background: hsl(0deg 0% 100%); opacity: 0.3; - transition: all 0.1s; + transition: opacity 0.1s; } .screen-2__tabs ul li label:hover::before { @@ -418,7 +419,8 @@ ul { } .quote__text { - transition: all 0.5s ease-out; + transition: none 0.5s ease-out; + transition-property: background, box-shadow; font-family: var(--font-ss3); font-style: normal; font-weight: 400; @@ -448,7 +450,7 @@ ul { bottom: -37px; left: 0; mask-image: var(--quote-tail-mask); - transition: all 0.5s ease-out; + transition: background 0.5s ease-out; background: linear-gradient( 0deg, hsl(0deg 0% 100% / 49%) 0%, @@ -525,11 +527,11 @@ ul { .quote__source a { border-bottom: 1px solid hsl(0deg 0% 100% / 50%); - transition: border 0.4s ease-out; + transition: border-bottom-color 0.4s ease-out; } .quote__source a:hover { - border-bottom: 1px solid hsl(0deg 0% 100%); + border-bottom-color: hsl(0deg 0% 100%); transition: none; } @@ -622,12 +624,12 @@ ul { .screen-4__desc a { font-weight: 550; border-bottom: 1px solid hsl(0deg 0% 100% / 50%); - transition: border 0.4s ease-out; + transition: border-bottom-color 0.4s ease-out; } .screen-2__desc a:hover, .screen-4__desc a:hover { - border-bottom: 1px solid hsl(0deg 0% 100%); + border-bottom-color: hsl(0deg 0% 100%); transition: none; } @@ -669,7 +671,7 @@ ul { border: 2px solid hsl(0deg 0% 100% / 26%); background: hsl(0deg 0% 100% / 7%); min-height: 80px; - transition: all 0.2s; + transition: background 0.2s; padding: 12px 16px 14px; .card__text { diff --git a/web/styles/portico/integrations.css b/web/styles/portico/integrations.css index 67c044604d..16dde09952 100644 --- a/web/styles/portico/integrations.css +++ b/web/styles/portico/integrations.css @@ -122,12 +122,10 @@ $category-text: hsl(219deg 23% 33%); display: block; color: hsl(0deg 0% 33%); margin-bottom: 10px; - transition: - border linear 0.2s, - box-shadow linear 0.2s; + transition: border-color linear 0.2s; &:focus { - border: 1px solid $border-green; + border-color: $border-green; outline: 0; } @@ -191,7 +189,8 @@ $category-text: hsl(219deg 23% 33%); padding: 6px 10px 3px; margin: 3px 0; border-radius: 5px; - transition: all 0.3s ease; + transition: none 0.3s ease; + transition-property: background-color, color; color: $category-text; &.selected, @@ -261,7 +260,8 @@ $category-text: hsl(219deg 23% 33%); border-left: 1px solid $light-blue-border; border-right: 1px solid $light-blue-border; border-bottom: 1px solid $light-blue-border; - transition: all 0.3s ease; + transition: none 0.3s ease; + transition-property: background-color, color; font-size: 0.9em; &.selected, @@ -317,10 +317,11 @@ $category-text: hsl(219deg 23% 33%); border: 1px solid $light-blue-border; color: $category-text; text-align: center; - transition: all 0.3s ease; + transition: none 0.3s ease; + transition-property: border-color; &:hover { - border: 1px solid $border-green; + border-color: $border-green; } &.legacy { @@ -507,7 +508,8 @@ $category-text: hsl(219deg 23% 33%); width: 165px; text-align: center; border-radius: 5px; - transition: all 0.3s ease; + transition: none 0.3s ease; + transition-property: background-color, color; background-color: $light-blue-border; color: $category-text; diff --git a/web/styles/portico/integrations_dev_panel.css b/web/styles/portico/integrations_dev_panel.css index ddff1aa909..4a82a9a51a 100644 --- a/web/styles/portico/integrations_dev_panel.css +++ b/web/styles/portico/integrations_dev_panel.css @@ -24,7 +24,7 @@ box-shadow: inset 0 1px 1px hsl(0deg 0% 0% / 7.5%); transition: - border linear 0.2s, + border-color linear 0.2s, box-shadow linear 0.2s; &:focus { @@ -68,7 +68,7 @@ border: 1px solid hsl(0deg 0% 80%); box-shadow: inset 0 1px 1px hsl(0deg 0% 0% / 7.5%); transition: - border linear 0.2s, + border-color linear 0.2s, box-shadow linear 0.2s; margin-bottom: 10px; diff --git a/web/styles/portico/landing_page.css b/web/styles/portico/landing_page.css index f33cf53571..9b07b4d117 100644 --- a/web/styles/portico/landing_page.css +++ b/web/styles/portico/landing_page.css @@ -317,7 +317,7 @@ button { box-shadow: 0 3px 10px hsl(0deg 0% 0% / 20%); - transition: all 0.2s ease; + transition: box-shadow 0.2s ease; &:hover { box-shadow: 0 3px 10px hsl(0deg 0% 100% / 20%); @@ -681,8 +681,6 @@ button { .message-feed { height: calc(100% - 20px); margin-top: 20px; - - transition: all 0.1s ease; } } @@ -910,7 +908,7 @@ button { width: 155px; height: 220px; padding: 0 5px; - transition: all 0.3s ease; + transition: border-color 0.3s ease; margin: 10px 5px; color: hsl(219deg 23% 33%); border-radius: 5px; @@ -918,7 +916,7 @@ button { } .portico-landing.hello .integrations .integration-icons .group:hover { - border: 1px solid hsl(170deg 47% 53%); + border-color: hsl(170deg 47% 53%); } .portico-landing.hello .integrations .integration-logo { @@ -1079,7 +1077,7 @@ button { font-size: 3em; text-align: center; - transition: all 0.2s ease; + transition: background 0.2s ease; } .portico-landing.apps .other-apps .apps .icon:hover { @@ -1582,7 +1580,7 @@ button { font-size: 0.9em; - transition: all 0.3s ease; + transition: background-color 0.3s ease; } .for-education-pricing-model .pricing-container .price-box:focus { @@ -2534,10 +2532,6 @@ button { text-align: left; background-color: hsl(0deg 0% 100%); - transition-property: all; - transition-duration: 0.3s; - transition-timing-function: ease; - .bottom { height: 275px; diff --git a/web/styles/portico/navbar.css b/web/styles/portico/navbar.css index ef94e1f49b..27830de18c 100644 --- a/web/styles/portico/navbar.css +++ b/web/styles/portico/navbar.css @@ -194,7 +194,8 @@ details summary::-webkit-details-marker { opacity: 0; height: 0; width: 100%; - transition: all 0.05s; + transition: none 0.05s; + transition-property: height, opacity; /* stylelint-disable-line plugin/no-low-performance-animation-properties */ } .top-menu-tab-input-unselect:not(:checked) + .top-menu-submenu-backdrop { @@ -238,7 +239,8 @@ details summary::-webkit-details-marker { gap: 16px; opacity: 0; visibility: hidden; - transition: all 0.2s; + transition: none 0.2s; + transition-property: opacity, visibility; } #case-studies-submenu { @@ -382,7 +384,8 @@ details summary::-webkit-details-marker { "lnum" on; color: hsl(0deg 0% 100% / 80%); - transition: all 0.2; + transition: none 0.2; + transition-property: backdrop-filter, background, bottom, height; /* stylelint-disable-line plugin/no-low-performance-animation-properties */ overscroll-behavior: contain; } @@ -504,7 +507,8 @@ details summary::-webkit-details-marker { position: sticky; top: 0; z-index: 1; - transition: all 0.3s; + transition: none 0.3s; + transition-property: background, backdrop-filter; height: 60px; overflow: hidden; display: flex; @@ -574,7 +578,7 @@ details summary::-webkit-details-marker { background-position: right; transition: background, - letter-spacing 0.2s; + letter-spacing 0.2s; /* stylelint-disable-line plugin/no-low-performance-animation-properties */ } .top-menu-mobile-summary:active::after { diff --git a/web/styles/portico/portico.css b/web/styles/portico/portico.css index 062d8b1e82..fa981e8406 100644 --- a/web/styles/portico/portico.css +++ b/web/styles/portico/portico.css @@ -533,7 +533,7 @@ input.text-error { font-size: 0.6em; margin-left: 5px; opacity: 0.5; - transition: all 0.2s ease; + transition: opacity 0.2s ease; } &:hover .realm-name i.fa { @@ -684,14 +684,14 @@ input.text-error { padding: 10px; border: 1px solid hsl(0deg 0% 93%); border-radius: 4px; - transition: all 0.3s ease; + transition: border-color 0.3s ease; & a { color: inherit; } &:hover { - border: 1px solid hsl(0deg 0% 73%); + border-color: hsl(0deg 0% 73%); } .avatar { @@ -837,7 +837,8 @@ input.new-organization-button { padding: 2px 7px 1px 8px; font-weight: 600; font-size: 16px; - transition: all 0.2s ease-in; + transition: none 0.2s ease-in; + transition-property: background-color, color; border-radius: 4px; &:hover { @@ -1103,7 +1104,8 @@ input.new-organization-button { right: calc(100% - 40px); fill: hsl(0deg 0% 100%); z-index: 2; - transition: all 0.3s ease; + transition: none 0.3s ease; + transition-property: right, top; /* stylelint-disable-line plugin/no-low-performance-animation-properties */ cursor: pointer; } @@ -1117,7 +1119,7 @@ input.new-organization-button { pointer-events: none; overflow: hidden; transform: translateX(0); - transition: all 0.3s ease; + transition: transform 0.3s ease; } .sidebar.show { diff --git a/web/styles/portico/portico_signin.css b/web/styles/portico/portico_signin.css index 2ebec58c3e..2b48fb8532 100644 --- a/web/styles/portico/portico_signin.css +++ b/web/styles/portico/portico_signin.css @@ -76,10 +76,6 @@ html { color: hsl(0deg 0% 67%); } -.find-account-page-container #find_account i { - font-size: 0.8em; -} - .app-main { .login-page-header { font-size: 1.5em; @@ -134,6 +130,10 @@ html { font-weight: 400; } + .find-account-form-tip { + font-size: 0.8em; + } + &.goto-account-page-container { width: 500px; font-weight: 400; @@ -209,7 +209,7 @@ html { transition: color 0.3s ease, - border 0.3s ease; + border-color 0.3s ease; &:hover { color: hsl(156deg 62% 61%); @@ -268,19 +268,10 @@ html { } } -.find-account-page-container h3, -.forgot-password-container h3 { - margin-top: 0; - - font-weight: 300; - font-size: 2em; -} - .forgot-password-container { width: 503px; - & h3, - form { + & form { margin-bottom: 0; } } @@ -428,7 +419,8 @@ html { border: none; border-radius: 4px; - transition: all 0.3s ease; + transition: none 0.3s ease; + transition-property: background-color, outline; &:hover { background-color: hsl(213deg 33% 31%); @@ -547,7 +539,7 @@ html { background-color: hsl(0deg 0% 100%); color: hsl(0deg 0% 33%); - transition: border 0.3s ease; + transition: border-color 0.3s ease; &:focus:invalid { box-shadow: none; @@ -555,7 +547,7 @@ html { } &:focus { - border: 1px solid hsl(0deg 0% 53%); + border-color: hsl(0deg 0% 53%); outline: 0; } } @@ -571,7 +563,8 @@ html { margin-top: 1px; - transition: all 0.3s ease; + transition: none 0.3s ease; + transition-property: color, font-size, font-weight, transform; /* stylelint-disable-line plugin/no-low-performance-animation-properties */ } &.moving-label { diff --git a/web/styles/portico/pricing_plans.css b/web/styles/portico/pricing_plans.css index 516a8a6d74..145f397a5e 100644 --- a/web/styles/portico/pricing_plans.css +++ b/web/styles/portico/pricing_plans.css @@ -84,7 +84,7 @@ color: inherit; text-decoration: none; border-bottom: 1px solid hsl(0deg 0% 100% / 50%); - transition: border 0.4s ease-out; + transition: border 0.4s ease-out; /* stylelint-disable-line plugin/no-low-performance-animation-properties */ &:hover { text-decoration: none; diff --git a/web/styles/progress_bar.css b/web/styles/progress_bar.css index 5073daf031..fa174b0047 100644 --- a/web/styles/progress_bar.css +++ b/web/styles/progress_bar.css @@ -31,7 +31,7 @@ background-repeat: repeat-x; box-shadow: inset 0 -1px 2px hsl(0deg 0% 0% / 15%); box-sizing: border-box; - transition: width 0.6s ease; + transition: width 0.6s ease; /* stylelint-disable-line plugin/no-low-performance-animation-properties */ } .progress.active .bar { diff --git a/web/styles/reactions.css b/web/styles/reactions.css index 55b02cb072..c0cc0a834d 100644 --- a/web/styles/reactions.css +++ b/web/styles/reactions.css @@ -6,6 +6,12 @@ margin-bottom: var(--message-box-markdown-aligned-vertical-space); } + .message_reaction_container { + &.disabled { + cursor: not-allowed; + } + } + .message_reaction { display: flex; /* Set a pixel and half padding to maintain @@ -22,7 +28,7 @@ box-shadow: inset 0 0 5px 0 var(--color-message-reaction-shadow-inner); transition: transform 100ms linear, - font-weight 100ms linear; + font-weight 100ms linear; /* stylelint-disable-line plugin/no-low-performance-animation-properties */ &.reacted { color: var(--color-message-reaction-text-reacted); @@ -39,6 +45,10 @@ box-shadow: none; } + &.disabled { + pointer-events: none; + } + &:hover { background-color: var(--color-message-reaction-background-hover); } diff --git a/web/styles/recent_view.css b/web/styles/recent_view.css index 1e929cdc75..b96b66eeb7 100644 --- a/web/styles/recent_view.css +++ b/web/styles/recent_view.css @@ -45,7 +45,6 @@ align-items: center; .recent-view-table-link, - .recent-view-table-unread-count, & > .zulip-icon { outline: 0; } @@ -191,7 +190,13 @@ margin-right: 1px; margin-left: 1px; align-self: center; - background-color: hsl(105deg 2% 50%); + opacity: 1; + outline: 0 solid var(--color-background-unread-counter); + transition: outline-width 0.1s ease; + + &:hover { + outline-width: 1.5px; + } } .unread_mention_info:not(:empty) { diff --git a/web/styles/rendered_markdown.css b/web/styles/rendered_markdown.css index 91b26f4ca4..7c6bd5f5dc 100644 --- a/web/styles/rendered_markdown.css +++ b/web/styles/rendered_markdown.css @@ -316,18 +316,20 @@ overflow: hidden; border-top: hsl(0deg 0% 50%) 0 solid; transition: + /* stylelint-disable-next-line plugin/no-low-performance-animation-properties */ height 0.4s ease-in-out, border-top 0.4s step-end, - padding 0.4s step-end; + padding 0.4s step-end; /* stylelint-disable-line plugin/no-low-performance-animation-properties */ padding: 0; height: 0; &.spoiler-content-open { border-top: hsl(0deg 0% 50%) 1px solid; transition: + /* stylelint-disable-next-line plugin/no-low-performance-animation-properties */ height 0.4s ease-in-out, border-top 0.4s step-start, - padding 0.4s step-start; + padding 0.4s step-start; /* stylelint-disable-line plugin/no-low-performance-animation-properties */ padding: 5px; height: auto; } @@ -349,7 +351,7 @@ float: right; width: 15px; cursor: pointer; - transition: 0.4s ease; + transition: transform 0.4s ease; transform: rotate(45deg); &::before, @@ -360,7 +362,7 @@ width: 12px; height: 3px; background-color: hsl(0deg 0% 83%); - transition: 0.4s ease; + transition: transform 0.4s ease; } &::after { @@ -917,7 +919,7 @@ &::-webkit-scrollbar-thumb { background-color: hsl(0deg 0% 0% / 30%); border-radius: 20px; - transition: all 0.2s ease; + transition: background-color 0.2s ease; } &::-webkit-scrollbar-thumb:hover { diff --git a/web/styles/right_sidebar.css b/web/styles/right_sidebar.css index 0468a153ac..bb78bafe2b 100644 --- a/web/styles/right_sidebar.css +++ b/web/styles/right_sidebar.css @@ -51,7 +51,7 @@ $user_status_emoji_width: 24px; } .buddy-list-user-link { - margin-left: 15px; + margin-left: 5px; } } @@ -116,7 +116,7 @@ $user_status_emoji_width: 24px; } .buddy-list-section { - margin-bottom: 0; + margin: 0; overflow-x: hidden; list-style-position: inside; /* Draw the bullets inside our box */ line-height: var(--line-height-sidebar-row); @@ -143,6 +143,10 @@ $user_status_emoji_width: 24px; &.highlighted_user { background-color: var(--color-buddy-list-highlighted-user); box-shadow: inset 0 0 0 1px var(--color-shadow-sidebar-row-hover); + + .user_circle { + outline: 1px solid var(--color-buddy-list-highlighted-user); + } } } @@ -161,6 +165,15 @@ $user_status_emoji_width: 24px; } } + .user_sidebar_entry.with_avatar .user_circle { + display: inline-block; + position: absolute; + width: 0.4em; + height: 0.4em; + top: 1.6em; + left: 1.6em; + } + .empty-list-message { font-style: italic; color: var(--color-text-empty-list-message); @@ -241,6 +254,25 @@ $user_status_emoji_width: 24px; align-items: baseline; } +.user_sidebar_entry.with_avatar { + grid-template: "row-content" var(--line-height-sidebar-row-with-avatars) "row-content" auto / minmax( + 0, + 1fr + ); + + .selectable_sidebar_block { + margin: 2px; + } + + .unread_count:not(.hide) { + margin-right: 2px; + } + + &.with_status .unread_count { + align-self: baseline; + } +} + .user-presence-link { grid-area: row-content; @@ -251,6 +283,23 @@ $user_status_emoji_width: 24px; } } +.information-settings .profile-with-avatar, +.user_sidebar_entry.with_avatar .selectable_sidebar_block { + line-height: var(--line-height-sidebar-row-with-avatars); + display: grid; + grid-template: + "avatar row-content markers-and-controls" var( + --right-sidebar-avatar-width + ) + "avatar row-content ." auto / auto minmax(0, 1fr) minmax(0, auto); + justify-content: flex-start; + align-items: center; +} + +.information-settings .profile-with-avatar { + margin: 5px 0; +} + .my_user_status { opacity: 0.5; white-space: nowrap; @@ -363,38 +412,59 @@ $user_status_emoji_width: 24px; grid-template-rows: var(--line-height-sidebar-row-prominent); grid-template-columns: minmax(0, 1fr) auto; align-items: center; - margin-right: var(--width-simplebar-scroll-hover); + margin-bottom: 10px; #userlist-header-search { display: grid; grid-template-rows: var(--line-height-sidebar-row-prominent); - grid-template-columns: minmax(0, 1fr) 20px; + grid-template-columns: minmax(0, 1fr) 30px; align-items: center; - } - #userlist-title { - margin: 0; - } + & .user-list-filter { + grid-area: 1 / 1 / 2 / 3; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + /* Prevent text from colliding with #clear_search_button */ + padding-right: 28px; + height: var(--line-height-sidebar-row-prominent); + box-sizing: border-box; + } - #user_filter_icon { - opacity: 0.5; - justify-self: center; + #clear_search_people_button { + grid-area: 1 / 2 / 2 / 3; + padding: 0; + background: none; + color: var(--color-text-clear-search-button); + display: grid; - &:hover { - opacity: 1; - cursor: pointer; + &:hover { + color: var(--color-text-clear-search-button-hover); + } + + &:focus, + &:focus-visible, + &:active { + box-shadow: none; + outline: none; + } + + .zulip-icon-close { + align-self: center; + } } } - /* hovering over the userlist-header creates the same highlight effect as hovering over the user_filter_icon */ - &:hover > #user_filter_icon { - opacity: 1; - cursor: pointer; - } - #buddy-list-menu-icon { + color: var(--color-vdots-visible); justify-content: center; display: grid; + width: 25px; + margin-left: 5px; + + &:hover { + color: var(--color-vdots-hover); + } } } @@ -408,29 +478,3 @@ $user_status_emoji_width: 24px; from the legacy value. */ margin-top: calc(25px - (var(--legacy-body-line-height-unitless) * 1em)); } - -#user_search_section { - display: grid; - grid-template-columns: minmax(0, 1fr) 28px; - grid-template-rows: var(--line-height-sidebar-row-prominent); - white-space: nowrap; - margin-bottom: 10px; - - & .user-list-filter { - grid-area: 1 / 1 / 2 / 3; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - /* Prevent text from colliding with .clear_search_button */ - padding-right: 28px; - /* Push back against inherited styles; let CSS Grid be in - charge of the height. */ - height: auto; - } - - .clear_search_button { - grid-area: 1 / 2 / 2 / 3; - position: static; - padding: 0; - } -} diff --git a/web/styles/settings.css b/web/styles/settings.css index b659e7938c..b8c500229c 100644 --- a/web/styles/settings.css +++ b/web/styles/settings.css @@ -510,7 +510,7 @@ input[type="checkbox"] { font-size: 0.9333em; font-style: normal; font-weight: 500; - color: hsl(0deg 0% 40%); + color: var(--color-kbd-enter-sends); position: relative; bottom: 1px; margin: 0 2px; @@ -814,6 +814,22 @@ input[type="checkbox"] { } } +.org-permissions-form .pill-container { + /* 319px + 2 * (2px padding) + 2 * (1px border) = 325px, which is the total + width of dropdown widget buttons */ + min-width: 319px; + background-color: hsl(0deg 0% 100%); + + .input { + flex-grow: 1; + + &:first-child:empty::before { + opacity: 0.5; + content: attr(data-placeholder); + } + } +} + .progressive-table-wrapper { position: relative; max-height: calc(95vh - 220px); @@ -868,7 +884,7 @@ input[type="checkbox"] { position: relative; margin-left: 5px; color: hsl(0deg 0% 67%); - transition: all 0.3s ease; + transition: color 0.3s ease; &:hover { color: hsl(0deg 0% 27%); @@ -1274,7 +1290,7 @@ $option_title_width: 180px; } & h5 { - font-size: 1.2em; + font-size: 1em; font-weight: normal; line-height: 1.2; margin: 10px 0; @@ -1290,7 +1306,8 @@ $option_title_width: 180px; box-sizing: border-box; height: $settings_header_height; padding: 6px; - border-bottom: 1px solid hsl(0deg 0% 87%); + background-color: var(--color-background-modal-bar); + border-bottom: 1px solid var(--color-border-modal-bar); @media (width >= $md_min) { .tab-switcher { @@ -1322,7 +1339,7 @@ $option_title_width: 180px; .sidebar { height: calc(100% - $settings_header_height); overflow-y: auto; - border-right: 1px solid hsl(0deg 0% 93%); + border-right: 1px solid var(--color-border-modal); .header { height: auto; @@ -1332,8 +1349,8 @@ $option_title_width: 180px; text-align: center; text-transform: uppercase; - background-color: hsl(180deg 6% 93%); - border-bottom: 1px solid hsl(0deg 0% 87%); + background-color: var(--color-background-modal-bar); + border-bottom: 1px solid var(--color-border-modal-bar); } .sidebar-item { @@ -1351,15 +1368,16 @@ $option_title_width: 180px; transition: background-color 0.2s ease, border-bottom 0.2s ease; - border-bottom: 1px solid hsl(0deg 0% 93%); + border-bottom: 1px solid var(--color-border-modal); &:last-of-type .text { border-bottom: none; } &.active { - background-color: hsl(0deg 0% 93%); - border-bottom: 1px solid transparent; + /* TODO: Check with Vlad about highlight + colors such as this. */ + background-color: hsl(0deg 0% 98%); } .sidebar-item-icon { @@ -1462,7 +1480,8 @@ $option_title_width: 180px; width: 100%; height: $settings_header_height; box-sizing: border-box; - border-bottom: 1px solid hsl(0deg 0% 87%); + border-bottom: 1px solid var(--color-border-modal-bar); + background-color: var(--color-background-modal-bar); & h1 .section { font-weight: 400; @@ -1494,7 +1513,7 @@ $option_title_width: 180px; color: hsl(0deg 0% 33%); border: 1px solid hsl(0deg 0% 80%); transition: - border linear 0.2s, + border-color linear 0.2s, box-shadow linear 0.2s; &:focus { @@ -1709,7 +1728,7 @@ $option_title_width: 180px; box-shadow: inset 0 1px 1px hsl(0deg 0% 0% / 7.5%); transition: - border linear 0.2s, + border-color linear 0.2s, box-shadow linear 0.2s; &:focus { @@ -2025,7 +2044,7 @@ $option_title_width: 180px; border: 1px solid hsl(0deg 0% 80%); box-shadow: inset 0 1px 1px hsl(0deg 0% 0% / 7.5%); transition: - border linear 0.2s, + border-color linear 0.2s, box-shadow linear 0.2s; margin-bottom: 10px; diff --git a/web/styles/subscriptions.css b/web/styles/subscriptions.css index 521bb049c6..cfa53783b5 100644 --- a/web/styles/subscriptions.css +++ b/web/styles/subscriptions.css @@ -286,7 +286,8 @@ h4.user_group_setting_subsection_title { cursor: pointer; - transition: all 0.3s ease; + transition: none 0.3s ease; + transition-property: opacity, transform; } .user-groups-container .user-groups-header.slide-left .fa-chevron-left, @@ -319,7 +320,8 @@ h4.user_group_setting_subsection_title { text-align: center; text-transform: uppercase; font-weight: 700; - border-bottom: 1px solid hsl(0deg 0% 87%); + background-color: var(--color-background-modal-bar); + border-bottom: 1px solid var(--color-border-modal-bar); .fa-chevron-left { display: none; @@ -328,7 +330,6 @@ h4.user_group_setting_subsection_title { .user-groups-title, .subscriptions-title { display: inline-block; - transition: all 0.3s ease; transform: translate(-13px, 0); } @@ -364,9 +365,9 @@ h4.user_group_setting_subsection_title { .left .no-streams-to-show, .left .no-groups-to-show, - .right .nothing-selected { + .right .nothing-selected .create-stream-button-container, + .right .nothing-selected .create-group-button-container { display: block; - margin-top: calc(45vh - 75px); text-align: center; font-size: 1em; margin-left: 2em; @@ -377,13 +378,35 @@ h4.user_group_setting_subsection_title { } } + .left .no-streams-to-show, + .left .no-groups-to-show { + margin-top: calc(45vh - 75px); + } + + .right .nothing-selected { + padding: 5px 5px 0; + + .stream-info-banner a, + .group-info-banner a { + color: inherit; + } + + .create-stream-button-container { + margin-top: calc(45vh - 128px); + } + + .create-group-button-container { + margin-top: calc(45vh - 134px); + } + } + .left { - border-right: 1px solid hsl(0deg 0% 87%); + border-right: 1px solid var(--color-border-modal-bar); .list-toggler-container { align-items: center; padding: 6px 8px; - border-bottom: 1px solid hsl(0deg 0% 87%); + border-bottom: 1px solid var(--color-border-modal-bar); display: flex; justify-content: space-between; @@ -397,19 +420,18 @@ h4.user_group_setting_subsection_title { .right { width: calc(50% + 1px); - .nothing-selected { - & button { - padding: 6px 10px 8px; - display: block; - margin: 0 auto 10px; - } + .nothing-selected .create_stream_button, + .nothing-selected .create_user_group_button { + padding: 6px 10px 8px; + display: block; + margin: 0 auto 10px; } .display-type { padding: 2px; text-align: center; font-weight: 600; - border-bottom: 1px solid hsl(0deg 0% 87%); + border-bottom: 1px solid var(--color-border-modal-bar); & a { color: inherit; @@ -496,7 +518,7 @@ h4.user_group_setting_subsection_title { justify-content: center; margin-bottom: 0; height: auto; - border-bottom: 1px solid hsl(0deg 0% 87%); + border-bottom: 1px solid var(--color-border-modal-bar); } .user-groups-list, @@ -537,7 +559,7 @@ h4.user_group_setting_subsection_title { .stream-row, .group-row { padding: 15px 10px 11px; - border-bottom: 1px solid hsl(0deg 0% 93%); + border-bottom: 1px solid var(--color-border-modal-bar); cursor: pointer; display: flex; @@ -695,28 +717,34 @@ h4.user_group_setting_subsection_title { } } -.group_setting_disabled { - cursor: not-allowed; - /* This ensures that we do not see the not allowed cursor in the - extra space of a div */ - width: fit-content; - - /* This specific rules are needed to override the default settings - of these elements */ - .pill-container, - .group-setting-label { +.org-permissions-form, +.group-permissions { + .group_setting_disabled { cursor: not-allowed; - } + /* This ensures that we do not see the not allowed cursor in the + extra space of a div */ + width: fit-content; - .pill-container { - .pill { - .exit { - display: none; - } + /* This specific rules are needed to override the default settings + of these elements */ + .pill-container, + .group-setting-label { + cursor: not-allowed; } - .input { - pointer-events: none; + .pill-container { + background-color: hsl(0deg 0% 93%); + opacity: 0.7; + + .pill { + .exit { + display: none; + } + } + + .input { + pointer-events: none; + } } } } @@ -763,9 +791,8 @@ h4.user_group_setting_subsection_title { width: calc(100% - 27px); padding: 9px 15px 15px; text-align: right; - background-color: hsl(0deg 0% 96%); - border-top: 1px solid hsl(0deg 0% 87%); - box-shadow: inset 0 1px 0 hsl(0deg 0% 100%); + background-color: var(--color-background-modal-bar); + border-top: 1px solid var(--color-border-modal-bar); } @media (width > $md_min) { @@ -857,6 +884,14 @@ h4.user_group_setting_subsection_title { margin-right: 3px; text-decoration: none; } + + .deactivate { + height: 100%; + + .icon-container { + display: flex; + } + } } } @@ -1229,7 +1264,7 @@ div.settings-radio-input-parent { background-color: var(--color-background-modal); border-top: none; - transition: all 0.3s ease; + transition: left 0.3s ease; /* stylelint-disable-line plugin/no-low-performance-animation-properties */ z-index: 10; &.show { diff --git a/web/styles/user_circles.css b/web/styles/user_circles.css index aaa20d9590..d06c10362b 100644 --- a/web/styles/user_circles.css +++ b/web/styles/user_circles.css @@ -6,6 +6,10 @@ border: 1px solid; } +.user_sidebar_entry.with_avatar .user_circle { + outline: 1px solid var(--color-background); +} + .user_circle_green { background-color: var(--color-user-circle-active); border-color: var(--color-user-circle-active); @@ -15,13 +19,13 @@ border-color: var(--color-user-circle-idle); background: linear-gradient( to bottom, - hsl(0deg 0% 100% / 0%) 50%, + var(--color-background) 50%, var(--color-user-circle-idle) 50% ); } .user_circle_empty { - background-color: transparent; + background-color: var(--color-background); border-color: hsl(0deg 0% 50%); } diff --git a/web/styles/widgets.css b/web/styles/widgets.css index 3c579cceba..00e1d2298d 100644 --- a/web/styles/widgets.css +++ b/web/styles/widgets.css @@ -130,7 +130,7 @@ border: 1px solid hsl(0deg 0% 80%); box-shadow: inset 0 1px 1px hsl(0deg 0% 0% / 7.5%); transition: - border linear 0.2s, + border-color linear 0.2s, box-shadow linear 0.2s; border-radius: 4px; color: hsl(0deg 0% 33%); @@ -218,7 +218,8 @@ button { padding: 4px; padding-left: 14px; padding-right: 14px; - transition: all 0.2s ease; + transition: none 0.2s ease; + transition-property: background-color, border-color, color; &:hover, &:focus { @@ -271,10 +272,11 @@ input { .poll-edit-question, .todo-edit-task-list-title { - opacity: 0.4; + color: var(--color-message-action-visible); - &:hover { - opacity: 1; + &:hover, + &:focus-visible { + color: var(--color-message-action-interactive); } } diff --git a/web/styles/zulip.css b/web/styles/zulip.css index f0809a58bb..bd51c98dc7 100644 --- a/web/styles/zulip.css +++ b/web/styles/zulip.css @@ -229,7 +229,7 @@ p.n-margin { top: 0; left: calc(50vw - 220px); padding: 15px; - background-color: hsl(0deg 0% 98%); + background-color: var(--color-background-modal); border-radius: 5px; box-shadow: 0 0 30px hsl(0deg 0% 0% / 25%); z-index: 110; @@ -438,7 +438,7 @@ body.has-overlay-scrollbar { text-overflow: ellipsis; /* Button curvature and transitions. */ border-radius: 4px; - transition: all 100ms ease-out; + transition: transform 100ms ease-out; &:hover, &:focus { @@ -773,7 +773,7 @@ strong { .new_messages, .new_messages_fadeout { - transition: all 3s ease-in-out; + transition: background-color 3s ease-in-out; } .messagebox-content .slow-send-spinner { @@ -918,7 +918,7 @@ div.focused-message-list { border-radius: 4px; border: 1px solid hsl(0deg 0% 80%); transition: - border linear 0.2s, + border-color linear 0.2s, box-shadow linear 0.2s; &:focus { @@ -999,10 +999,10 @@ div.focused-message-list { background-color: transparent; } +.information-settings .user-profile-picture, +.user_sidebar_entry.with_avatar .user-profile-picture, .inline_profile_picture { display: inline-block; - width: var(--message-box-avatar-width); - height: var(--message-box-avatar-height); /* Don't inherit the line-height from message-avatar; this preserves the dimensions and rounded corners on the image itself. */ @@ -1017,6 +1017,17 @@ div.focused-message-list { } } +.inline_profile_picture { + width: var(--message-box-avatar-width); + height: var(--message-box-avatar-height); +} + +.information-settings .user-profile-picture, +.user_sidebar_entry.with_avatar .user-profile-picture { + width: var(--right-sidebar-avatar-width); + height: var(--right-sidebar-avatar-height); +} + .home-error-bar { margin-top: 5px; display: none; @@ -1261,7 +1272,7 @@ div.toggle_resolve_topic_spinner .loading_indicator_spinner { } } -#custom-expiration-time-input, +.custom-time-input-value, #invite-user-form { margin: 0; } @@ -1285,7 +1296,7 @@ div.toggle_resolve_topic_spinner .loading_indicator_spinner { width: 100%; } -#custom-expiration-time-input { +.custom-time-input-value { width: 5ch; margin-right: 15px; @@ -1295,7 +1306,7 @@ div.toggle_resolve_topic_spinner .loading_indicator_spinner { border: 1px solid hsl(0deg 0% 80%); box-shadow: inset 0 1px 1px hsl(0deg 0% 0% / 7.5%); transition: - border linear 0.2s, + border-color linear 0.2s, box-shadow linear 0.2s; &:focus { @@ -1307,7 +1318,7 @@ div.toggle_resolve_topic_spinner .loading_indicator_spinner { } } -#custom-expiration-time-unit { +.custom-time-input-unit { width: auto; } @@ -1321,6 +1332,7 @@ div.toggle_resolve_topic_spinner .loading_indicator_spinner { .empty-feed-notice-title { font-size: 1.5em; font-weight: 400; + line-height: 1; word-wrap: break-word; } diff --git a/web/templates/buddy_list/section_header.hbs b/web/templates/buddy_list/section_header.hbs index 6c3a0db8e3..8dabbd1ac9 100644 --- a/web/templates/buddy_list/section_header.hbs +++ b/web/templates/buddy_list/section_header.hbs @@ -1,4 +1,4 @@
    {{header_text}} ({{user_count}})
    - + diff --git a/web/templates/buddy_list/title_tooltip.hbs b/web/templates/buddy_list/title_tooltip.hbs index 2026bb335f..f07dd995ca 100644 --- a/web/templates/buddy_list/title_tooltip.hbs +++ b/web/templates/buddy_list/title_tooltip.hbs @@ -1,4 +1,4 @@ {{#tr}} -Search {total_user_count, plural, =1 {1 person} other {# people}} +Filter {total_user_count, plural, =1 {1 person} other {# people}} {{/tr}} {{tooltip_hotkey_hints "W"}} diff --git a/web/templates/confirm_dialog/confirm_deactivate_stream.hbs b/web/templates/confirm_dialog/confirm_deactivate_stream.hbs index c420712735..341e8cc7a0 100644 --- a/web/templates/confirm_dialog/confirm_deactivate_stream.hbs +++ b/web/templates/confirm_dialog/confirm_deactivate_stream.hbs @@ -1,7 +1,15 @@

    + {{#tr}}Archiving this channel will:{{/tr}} +

    +

    +

      +
    • {{#tr}}Remove it from the left sidebar for all users.{{/tr}}
    • +
    • {{#tr}}Prevent new messages from being sent to this channel.{{/tr}}
    • +
    • {{#tr}}Prevent messages in this channel from being edited, deleted, or moved.{{/tr}}
    • +
    {{#tr}} - Archiving channel will immediately unsubscribe everyone. This action cannot be undone. - {{#*inline "z-stream"}}{{{stream_name_with_privacy_symbol_html}}}{{/inline}} + Users can still search for messages in archived channels.
    + This action cannot be undone. {{/tr}}

    {{#if is_announcement_stream}} diff --git a/web/templates/dialog_widget.hbs b/web/templates/dialog_widget.hbs index f187e92b7d..ecba618cb4 100644 --- a/web/templates/dialog_widget.hbs +++ b/web/templates/dialog_widget.hbs @@ -20,7 +20,7 @@ {{/unless}}
    diff --git a/web/templates/inbox_view/inbox_stream_header_row.hbs b/web/templates/inbox_view/inbox_stream_header_row.hbs index 706034e9d7..779478298d 100644 --- a/web/templates/inbox_view/inbox_stream_header_row.hbs +++ b/web/templates/inbox_view/inbox_stream_header_row.hbs @@ -9,6 +9,11 @@ {{> ../stream_privacy }} {{stream_name}} + {{#if is_archived}} + + ({{t 'archived' }}) + + {{/if}}
    - {{#each expires_in_options}} - + {{/each}} -

    -
    - - - + -

    +

    - + + {{#if is_admin}} - - + + {{/if}} {{#if is_owner}} - + {{/if}}
    diff --git a/web/templates/keyboard_shortcuts.hbs b/web/templates/keyboard_shortcuts.hbs index fa26a96fc0..ddd9a7a872 100644 --- a/web/templates/keyboard_shortcuts.hbs +++ b/web/templates/keyboard_shortcuts.hbs @@ -30,7 +30,7 @@ {{t 'Cancel compose and save draft' }} - Esc or Ctrl + [ + Esc or Ctrl + [ {{t 'View drafts' }} @@ -66,7 +66,7 @@ {{t 'Go to your home view' }} - Ctrl + [ or Esc + Ctrl + [ or Esc @@ -86,7 +86,7 @@ Q - {{t 'Search people' }} + {{t 'Filter users' }} W @@ -238,7 +238,7 @@ {{t 'Cancel compose and save draft' }} - Esc or Ctrl + [ + Esc or Ctrl + [ diff --git a/web/templates/left_sidebar.hbs b/web/templates/left_sidebar.hbs index 65fc27ec2f..c3e3eeff5a 100644 --- a/web/templates/left_sidebar.hbs +++ b/web/templates/left_sidebar.hbs @@ -150,12 +150,12 @@
    diff --git a/web/templates/message_controls.hbs b/web/templates/message_controls.hbs index 1efbfbaf38..02ac760a68 100644 --- a/web/templates/message_controls.hbs +++ b/web/templates/message_controls.hbs @@ -1,13 +1,15 @@ -{{#if msg/sent_by_me}} -
    -{{/if}} +{{#unless is_archived}} + {{#if msg/sent_by_me}} +
    + {{/if}} -{{#unless msg/sent_by_me}} -
    -
    - + {{#unless msg/sent_by_me}} +
    +
    + +
    -
    + {{/unless}} {{/unless}}
    diff --git a/web/templates/message_group.hbs b/web/templates/message_group.hbs index 99cc243af2..a29fd19ee8 100644 --- a/web/templates/message_group.hbs +++ b/web/templates/message_group.hbs @@ -10,7 +10,7 @@ {{> recipient_row use_match_properties=../use_match_properties}} {{#each message_containers}} {{#with this}} - {{> single_message use_match_properties=../../use_match_properties message_list_id=../../message_list_id}} + {{> single_message use_match_properties=../../use_match_properties message_list_id=../../message_list_id is_archived=../is_archived}} {{/with}} {{/each}}
    diff --git a/web/templates/message_reaction.hbs b/web/templates/message_reaction.hbs index 91e9b20878..6df258a093 100644 --- a/web/templates/message_reaction.hbs +++ b/web/templates/message_reaction.hbs @@ -1,10 +1,12 @@ -
    - {{#if this.emoji_alt_code}} -
     :{{this.emoji_name}}:
    - {{else if this.is_realm_emoji}} - - {{else}} -
    - {{/if}} -
    {{this.vote_text}}
    +
    +
    + {{#if this.emoji_alt_code}} +
     :{{this.emoji_name}}:
    + {{else if this.is_realm_emoji}} + + {{else}} +
    + {{/if}} +
    {{this.vote_text}}
    +
    diff --git a/web/templates/message_reactions.hbs b/web/templates/message_reactions.hbs index 7828520d98..c1ab44c917 100644 --- a/web/templates/message_reactions.hbs +++ b/web/templates/message_reactions.hbs @@ -1,11 +1,13 @@
    {{#each this/msg/message_reactions}} - {{> message_reaction}} + {{> message_reaction is_archived=../is_archived}} {{/each}} -
    -
    - -
    +
    + {{#unless is_archived}} +
    +
    + +
    +
    +
    -
    + {{/unless}}
    diff --git a/web/templates/message_view_header.hbs b/web/templates/message_view_header.hbs index 9a4014aaba..87ca8190a9 100644 --- a/web/templates/message_view_header.hbs +++ b/web/templates/message_view_header.hbs @@ -1,4 +1,5 @@ {{#if stream_settings_link}} +{{#unless stream.is_archived}} {{> navbar_icon_and_title }} @@ -12,6 +13,11 @@ {{/unless}}
    +{{else}} + + {{> navbar_icon_and_title }} + +{{/unless}} {{#if rendered_narrow_description}} {{rendered_markdown rendered_narrow_description}} diff --git a/web/templates/modal_banner/modal_banner.hbs b/web/templates/modal_banner/modal_banner.hbs index 577d582573..e2fac4f432 100644 --- a/web/templates/modal_banner/modal_banner.hbs +++ b/web/templates/modal_banner/modal_banner.hbs @@ -6,8 +6,14 @@ {{/if}} {{#if button_text}} + {{#if button_link}} + + + + {{else}} {{/if}} + {{/if}}
    {{#unless hide_close_button}} diff --git a/web/templates/modal_banner/stream_info_banner.hbs b/web/templates/modal_banner/stream_info_banner.hbs new file mode 100644 index 0000000000..a2b3739e1f --- /dev/null +++ b/web/templates/modal_banner/stream_info_banner.hbs @@ -0,0 +1,5 @@ +{{#> modal_banner }} + +{{/modal_banner}} diff --git a/web/templates/modal_banner/user_group_info_banner.hbs b/web/templates/modal_banner/user_group_info_banner.hbs new file mode 100644 index 0000000000..417adc794c --- /dev/null +++ b/web/templates/modal_banner/user_group_info_banner.hbs @@ -0,0 +1,5 @@ +{{#> modal_banner }} + +{{/modal_banner}} diff --git a/web/templates/navbar_icon_and_title.hbs b/web/templates/navbar_icon_and_title.hbs index 84491b41b7..3f1ba27085 100644 --- a/web/templates/navbar_icon_and_title.hbs +++ b/web/templates/navbar_icon_and_title.hbs @@ -4,3 +4,10 @@ {{/if}} {{title}} +{{#if stream}} + {{#if stream.is_archived}} + + ({{t 'archived' }}) + + {{/if}} +{{/if}} diff --git a/web/templates/popovers/left_sidebar/left_sidebar_condensed_views_popover.hbs b/web/templates/popovers/left_sidebar/left_sidebar_condensed_views_popover.hbs index 9fadfe7229..ded91280b4 100644 --- a/web/templates/popovers/left_sidebar/left_sidebar_condensed_views_popover.hbs +++ b/web/templates/popovers/left_sidebar/left_sidebar_condensed_views_popover.hbs @@ -9,16 +9,20 @@
  • {{#if has_scheduled_messages }} {{/if}} diff --git a/web/templates/popovers/user_group_info_popover.hbs b/web/templates/popovers/user_group_info_popover.hbs index 105731eb46..13cb68dcbf 100644 --- a/web/templates/popovers/user_group_info_popover.hbs +++ b/web/templates/popovers/user_group_info_popover.hbs @@ -9,6 +9,13 @@
    {{group_description}}
    + {{#if (or members.length subgroups.length)}} +
  • + {{#tr}} + {members_count, plural, =1 {1 member} other {# members}} + {{/tr}} +
  • + {{/if}} {{#if deactivated}}
  • {{t "This group has been deactivated." }} @@ -16,8 +23,14 @@ {{/if}}
  • - {{#if members.length}} + {{#if (or members.length subgroups.length)}}
      + {{#each subgroups}} +
    • + + {{name}} +
    • + {{/each}} {{#each members}}
    • {{#if is_bot}} diff --git a/web/templates/presence_row.hbs b/web/templates/presence_row.hbs index 40c3b5abbd..3d2f4d5a69 100644 --- a/web/templates/presence_row.hbs +++ b/web/templates/presence_row.hbs @@ -1,11 +1,8 @@ -
    • +
    • + {{#unless user_list_style.WITH_AVATAR}} + {{/unless}}
    • diff --git a/web/templates/recipient_row.hbs b/web/templates/recipient_row.hbs index a17b7c9cce..eade9f3c76 100644 --- a/web/templates/recipient_row.hbs +++ b/web/templates/recipient_row.hbs @@ -10,7 +10,12 @@ {{~! Recipient (e.g. stream/topic or topic) ~}} - {{~display_recipient~}} + + {{~display_recipient~}} + + {{#if is_archived}} + ({{t 'archived' }}) + {{/if}} @@ -76,7 +81,7 @@ {{/if}} - {{{date}}} + {{{date}}} {{else}} @@ -96,7 +101,7 @@ {{/tr~}} - {{{date}}} + {{{date}}} {{/if}} diff --git a/web/templates/right_sidebar.hbs b/web/templates/right_sidebar.hbs index c717a5cff5..157a64a2f2 100644 --- a/web/templates/right_sidebar.hbs +++ b/web/templates/right_sidebar.hbs @@ -2,34 +2,28 @@ {{/with}} diff --git a/web/templates/stream_settings/stream_settings_overlay.hbs b/web/templates/stream_settings/stream_settings_overlay.hbs index 675929eb41..924a6e6668 100644 --- a/web/templates/stream_settings/stream_settings_overlay.hbs +++ b/web/templates/stream_settings/stream_settings_overlay.hbs @@ -63,19 +63,15 @@
      {{t 'Channel settings' }}
      - - {{#if can_create_streams}} - - {{#tr}} - First time? Read our guidelines for creating and naming channels. - {{#*inline "z-link"}}{{> @partial-block}}{{/inline}} - {{/tr}} - - {{else}} - - {{t 'You do not have permission to create channels in this organization.' }} - - {{/if}} +
      +
      + + {{#unless can_create_streams}} + + {{t 'You do not have permission to create channels.' }} + + {{/unless}} +
      {{!-- edit stream here --}} diff --git a/web/templates/stream_sidebar_row.hbs b/web/templates/stream_sidebar_row.hbs index bbbffe8a33..0808881a2e 100644 --- a/web/templates/stream_sidebar_row.hbs +++ b/web/templates/stream_sidebar_row.hbs @@ -11,9 +11,11 @@ {{name}}
      diff --git a/web/templates/user_group_settings/group_permissions.hbs b/web/templates/user_group_settings/group_permissions.hbs index bc61fdaa2f..a1167e510a 100644 --- a/web/templates/user_group_settings/group_permissions.hbs +++ b/web/templates/user_group_settings/group_permissions.hbs @@ -1,5 +1,5 @@
      - +
      diff --git a/web/templates/user_group_settings/user_group_settings.hbs b/web/templates/user_group_settings/user_group_settings.hbs index d3693d0322..fa6fc48fc0 100644 --- a/web/templates/user_group_settings/user_group_settings.hbs +++ b/web/templates/user_group_settings/user_group_settings.hbs @@ -36,7 +36,9 @@
      -

      {{t "Group permissions" }} +

      + {{t "Group permissions" }} + {{> ../help_link_widget link="/help/manage-user-groups#configure-group-permissions"}}

      {{> ../settings/settings_save_discard_widget section_name="group-permissions" }}
      diff --git a/web/templates/user_group_settings/user_group_settings_overlay.hbs b/web/templates/user_group_settings/user_group_settings_overlay.hbs index cdc1409d6e..6141c10923 100644 --- a/web/templates/user_group_settings/user_group_settings_overlay.hbs +++ b/web/templates/user_group_settings/user_group_settings_overlay.hbs @@ -50,15 +50,15 @@
      {{t 'User group settings' }}
      - {{#if can_create_user_groups}} - - - {{#tr}} - First time? Read our guidelines for creating user groups. - {{#*inline "z-link"}}{{> @partial-block}}{{/inline}} - {{/tr}} - - {{/if}} +
      +
      + + {{#unless can_create_user_groups}} + + {{t 'You do not have permission to create user groups.' }} + + {{/unless}} +
      {{!-- edit user group here --}} diff --git a/web/templates/user_profile_modal.hbs b/web/templates/user_profile_modal.hbs index 84cb9d77f8..508c5c0a39 100644 --- a/web/templates/user_profile_modal.hbs +++ b/web/templates/user_profile_modal.hbs @@ -140,7 +140,7 @@
      diff --git a/web/tests/activity.test.js b/web/tests/activity.test.js index 066614fc75..128cb96246 100644 --- a/web/tests/activity.test.js +++ b/web/tests/activity.test.js @@ -30,7 +30,6 @@ const electron_bridge = mock_esm("../src/electron_bridge"); const padded_widget = mock_esm("../src/padded_widget"); const pm_list = mock_esm("../src/pm_list"); const popovers = mock_esm("../src/popovers"); -const resize = mock_esm("../src/resize"); const settings_data = mock_esm("../src/settings_data"); const sidebar_ui = mock_esm("../src/sidebar_ui"); const scroll_util = mock_esm("../src/scroll_util"); @@ -137,7 +136,6 @@ function test(label, f) { }); stub_buddy_list_elements(); - helpers.override(buddy_list, "render_section_headers", noop); helpers.override(buddy_list, "render_view_user_list_links", noop); presence.presence_info.set(alice.user_id, {status: "active"}); @@ -271,13 +269,6 @@ test("presence_list_full_update", ({override, mock_template}) => { assert.equal(presence_rows[0].user_id, me.user_id); }); -function simulate_right_column_buddy_list() { - $("input.user-list-filter").closest = (selector) => { - assert.equal(selector, ".app-main [class^='column-']"); - return $.create("right-sidebar").addClass("column-right"); - }; -} - test("direct_message_update_dom_counts", () => { const $count = $.create("alice-unread-count"); const pm_key = alice.user_id.toString(); @@ -311,8 +302,6 @@ test("handlers", ({override, override_rewire, mock_template}) => { override(padded_widget, "update_padding", noop); override(popovers, "hide_all", noop); override(sidebar_ui, "hide_all", noop); - override(sidebar_ui, "show_userlist_sidebar", noop); - override(resize, "resize_sidebars", noop); // This is kind of weak coverage; we are mostly making sure that // keys and clicks got mapped to functions that don't crash. @@ -371,18 +360,6 @@ test("handlers", ({override, override_rewire, mock_template}) => { handler(e); })(); - (function test_click_header_filter() { - init(); - const e = {}; - const handler = $("#userlist-header-search").get_on_handler("click"); - - simulate_right_column_buddy_list(); - - handler(e); - // and click again - handler(e); - })(); - (function test_enter_key() { init(); @@ -524,9 +501,11 @@ test("insert_one_user_into_empty_list", ({override, mock_template}) => { user_id: 1, is_current_user: false, num_unread: 0, + profile_picture: "/avatar/1", user_circle_class: "user_circle_green", status_emoji_info: undefined, status_text: undefined, + has_status_text: false, user_list_style: { COMPACT: false, WITH_STATUS: true, @@ -732,7 +711,6 @@ test("realm_presence_disabled", ({override}) => { test("redraw_muted_user", () => { muted_users.add_muted_user(mark.user_id); activity_ui.redraw_user(mark.user_id); - assert.equal($("#buddy-list-users-matching-view").html(), "never-been-set"); }); test("update_presence_info", ({override, override_rewire}) => { diff --git a/web/tests/buddy_data.test.js b/web/tests/buddy_data.test.js index 982911494c..6a4ad2c81f 100644 --- a/web/tests/buddy_data.test.js +++ b/web/tests/buddy_data.test.js @@ -604,8 +604,10 @@ test("get_items_for_users", ({override}) => { is_current_user: true, name: "Human Myself", num_unread: 0, + profile_picture: "/avatar/1001", status_emoji_info, status_text: undefined, + has_status_text: false, user_circle_class: "user_circle_green", user_id: 1001, user_list_style, @@ -616,8 +618,10 @@ test("get_items_for_users", ({override}) => { is_current_user: false, name: "Alice Smith", num_unread: 0, + profile_picture: "/avatar/1002", status_emoji_info, status_text: undefined, + has_status_text: false, user_circle_class: "user_circle_empty", user_id: 1002, user_list_style, @@ -628,8 +632,10 @@ test("get_items_for_users", ({override}) => { is_current_user: false, name: "Fred Flintstone", num_unread: 0, + profile_picture: "/avatar/1003", status_emoji_info, status_text: undefined, + has_status_text: false, user_circle_class: "user_circle_empty", user_id: 1003, user_list_style, diff --git a/web/tests/copy_and_paste.test.js b/web/tests/copy_and_paste.test.js index 449151bec7..2b883c8725 100644 --- a/web/tests/copy_and_paste.test.js +++ b/web/tests/copy_and_paste.test.js @@ -12,6 +12,10 @@ stream_data.add_sub({ stream_id: 4, name: "Rome", }); +stream_data.add_sub({ + stream_id: 5, + name: "Romeo`s lair", +}); run_test("try_stream_topic_syntax_text", () => { const test_cases = [ @@ -29,23 +33,47 @@ run_test("try_stream_topic_syntax_text", () => { ], ["http://different.origin.com/#narrow/channel/4-Rome/topic/old.20FAILED.20EXPORT"], - + [ + "http://zulip.zulipdev.com/#narrow/channel/4-Rome/topic/old.20FAILED.20EXPORT/near/100", + "#**Rome>old FAILED EXPORT@100**", + ], // malformed urls ["http://zulip.zulipdev.com/narrow/channel/4-Rome/topic/old.20FAILED.20EXPORT"], ["http://zulip.zulipdev.com/#not_narrow/channel/4-Rome/topic/old.20FAILED.20EXPORT"], ["http://zulip.zulipdev.com/#narrow/not_stream/4-Rome/topic/old.20FAILED.20EXPORT"], ["http://zulip.zulipdev.com/#narrow/channel/4-Rome/not_topic/old.20FAILED.20EXPORT"], - ["http://zulip.zulipdev.com/#narrow/channel/4-Rome/topic/old.20FAILED.20EXPORT/near/100"], ["http://zulip.zulipdev.com/#narrow/channel/4-Rome/", "#**Rome**"], ["http://zulip.zulipdev.com/#narrow/channel/4-Rome/topic"], ["http://zulip.zulipdev.com/#narrow/topic/cheese"], ["http://zulip.zulipdev.com/#narrow/topic/pizza/stream/Rome"], + ["http://zulip.zulipdev.com/#narrow/channel/4-Rome/topic/old.20FAILED.20EXPORT/near/"], - // characters which are known to produce broken #**stream>topic** urls. - ["http://zulip.zulipdev.com/#narrow/channel/4-Rome/topic/100.25.20profits.60"], - ["http://zulip.zulipdev.com/#narrow/channel/4-Rome/topic/100.25.20*profits"], - ["http://zulip.zulipdev.com/#narrow/channel/4-Rome/topic/.24.24 100.25.20profits"], - ["http://zulip.zulipdev.com/#narrow/channel/4-Rome/topic/>100.25.20profits"], + // When a url containing characters which are known to produce broken + // #**stream>topic** urls is pasted, a normal markdown link syntax is produced. + [ + "http://zulip.zulipdev.com/#narrow/stream/4-Rome/topic/100.25.20profits.60", + "[#Rome > 100% profits`](#narrow/channel/4-Rome/topic/100.25.20profits.60)", + ], + [ + "http://zulip.zulipdev.com/#narrow/stream/4-Rome/topic/100.25.20*profits", + "[#Rome > 100% *profits](#narrow/channel/4-Rome/topic/100.25.20*profits)", + ], + [ + "http://zulip.zulipdev.com/#narrow/stream/4-Rome/topic/.24.24 100.25.20profits", + "[#Rome > $$ 100% profits](#narrow/channel/4-Rome/topic/.24.24.20100.25.20profits)", + ], + [ + "http://zulip.zulipdev.com/#narrow/stream/4-Rome/topic/>100.25.20profits", + "[#Rome > >100% profits](#narrow/channel/4-Rome/topic/.3E100.25.20profits)", + ], + [ + "http://zulip.zulipdev.com/#narrow/stream/5-Romeo.60s-lair/topic/normal", + "[#Romeo`s lair > normal](#narrow/channel/5-Romeo.60s-lair/topic/normal)", + ], + [ + "http://zulip.zulipdev.com/#narrow/stream/4-Rome/topic/100.25.20profits.60/near/20", + "[#Rome > 100% profits` @ 💬](#narrow/channel/4-Rome/topic/100.25.20profits.60/near/20)", + ], ]; for (const test_case of test_cases) { diff --git a/web/tests/dispatch.test.js b/web/tests/dispatch.test.js index c0dca90fb5..3b2d8b9cc0 100644 --- a/web/tests/dispatch.test.js +++ b/web/tests/dispatch.test.js @@ -34,6 +34,7 @@ const information_density = mock_esm("../src/information_density"); const linkifiers = mock_esm("../src/linkifiers"); const message_events = mock_esm("../src/message_events", { update_views_filtered_on_message_property: noop, + update_current_view_for_topic_visibility: noop, }); const message_lists = mock_esm("../src/message_lists"); const user_topics_ui = mock_esm("../src/user_topics_ui"); @@ -378,6 +379,20 @@ run_test("muted_topics", ({override}) => { assert_same(args.user_topic, event); }); +run_test("followed_topic", ({override}) => { + const event = event_fixtures.user_topic_with_followed_policy_change; + + const stub = make_stub(); + const discard_msg_list_stub = make_stub(); + override(user_topics_ui, "handle_topic_updates", stub.f); + override(message_events, "discard_cached_lists_with_term_type", discard_msg_list_stub.f); + dispatch(event); + assert.equal(stub.num_calls, 1); + assert.equal(discard_msg_list_stub.num_calls, 1); + const args = stub.get_args("user_topic"); + assert_same(args.user_topic, event); +}); + run_test("muted_users", ({override}) => { const event = event_fixtures.muted_users; @@ -465,7 +480,7 @@ run_test("realm settings", ({override}) => { override(current_user, "is_admin", true); override(realm, "realm_date_created", new Date("2023-01-01Z")); - override(settings_org, "check_disable_direct_message_initiator_group_dropdown", noop); + override(settings_org, "check_disable_direct_message_initiator_group_widget", noop); override(settings_org, "sync_realm_settings", noop); override(settings_bots, "update_bot_permissions_ui", noop); override(settings_emoji, "update_custom_emoji_ui", noop); @@ -587,10 +602,10 @@ run_test("realm settings", ({override}) => { override(realm, "realm_create_multiuse_invite_group", 1); override(realm, "realm_allow_message_editing", false); override(realm, "realm_message_content_edit_limit_seconds", 0); - override(realm, "realm_edit_topic_policy", 3); override(realm, "realm_authentication_methods", {Google: {enabled: false, available: true}}); override(realm, "realm_can_add_custom_emoji_group", 1); override(realm, "realm_can_create_public_channel_group", 1); + override(realm, "realm_can_move_messages_between_topics_group", 1); override(realm, "realm_direct_message_permission_group", 1); override(realm, "realm_plan_type", 2); override(realm, "realm_upload_quota_mib", 5000); @@ -600,12 +615,12 @@ run_test("realm settings", ({override}) => { assert_same(realm.realm_create_multiuse_invite_group, 3); assert_same(realm.realm_allow_message_editing, true); assert_same(realm.realm_message_content_edit_limit_seconds, 5); - assert_same(realm.realm_edit_topic_policy, 4); assert_same(realm.realm_authentication_methods, { Google: {enabled: true, available: true}, }); assert_same(realm.realm_can_add_custom_emoji_group, 3); assert_same(realm.realm_can_create_public_channel_group, 3); + assert_same(realm.realm_can_move_messages_between_topics_group, 3); assert_same(realm.realm_direct_message_permission_group, 3); assert_same(realm.realm_plan_type, 3); assert_same(realm.realm_upload_quota_mib, 50000); diff --git a/web/tests/dispatch_subs.test.js b/web/tests/dispatch_subs.test.js index 4063a9ac59..52814359cb 100644 --- a/web/tests/dispatch_subs.test.js +++ b/web/tests/dispatch_subs.test.js @@ -13,6 +13,8 @@ const test_user = events.test_user; const compose_recipient = mock_esm("../src/compose_recipient"); const message_lists = mock_esm("../src/message_lists"); +const message_live_update = mock_esm("../src/message_live_update"); +const message_view_header = mock_esm("../src/message_view_header"); const narrow_state = mock_esm("../src/narrow_state"); const overlays = mock_esm("../src/overlays"); const settings_org = mock_esm("../src/settings_org"); @@ -223,6 +225,8 @@ test("stream delete (normal)", ({override}) => { removed_sidebar_rows += 1; }); override(stream_list, "update_subscribe_to_more_streams_link", noop); + override(message_live_update, "rerender_messages_view", noop); + override(message_view_header, "maybe_rerender_title_area_for_stream", noop); dispatch(event); @@ -239,9 +243,12 @@ test("stream delete (special streams)", ({override}) => { const event = event_fixtures.stream__delete; for (const stream of event.streams) { + stream.is_archived = false; stream_data.add_sub(stream); } + stream_data.subscribe_myself(event.streams[0]); + // sanity check data assert.equal(event.streams.length, 2); override(realm, "realm_new_stream_announcements_stream_id", event.streams[0].stream_id); @@ -254,6 +261,8 @@ test("stream delete (special streams)", ({override}) => { override(message_lists.current, "update_trailing_bookend", noop); override(stream_list, "remove_sidebar_row", noop); override(stream_list, "update_subscribe_to_more_streams_link", noop); + override(message_live_update, "rerender_messages_view", noop); + override(message_view_header, "maybe_rerender_title_area_for_stream", noop); dispatch(event); @@ -268,6 +277,7 @@ test("stream delete (stream is selected in compose)", ({override}) => { const event = event_fixtures.stream__delete; for (const stream of event.streams) { + stream.is_archived = false; stream_data.add_sub(stream); } @@ -294,6 +304,8 @@ test("stream delete (stream is selected in compose)", ({override}) => { removed_sidebar_rows += 1; }); override(stream_list, "update_subscribe_to_more_streams_link", noop); + override(message_live_update, "rerender_messages_view", noop); + override(message_view_header, "maybe_rerender_title_area_for_stream", noop); dispatch(event); diff --git a/web/tests/echo.test.js b/web/tests/echo.test.js index ee91b04583..02c2f2dfaa 100644 --- a/web/tests/echo.test.js +++ b/web/tests/echo.test.js @@ -47,6 +47,7 @@ message_lists.current = { }, }, change_message_id: noop, + add_messages: noop, }; const home_msg_list = { view: { @@ -62,6 +63,7 @@ const home_msg_list = { }, preserver_rendered_state: true, change_message_id: noop, + add_messages: noop, }; message_lists.all_rendered_message_lists = () => [home_msg_list, message_lists.current]; message_lists.non_rendered_data = () => []; diff --git a/web/tests/filter.test.js b/web/tests/filter.test.js index afd0e8d820..b23dec1c55 100644 --- a/web/tests/filter.test.js +++ b/web/tests/filter.test.js @@ -2718,3 +2718,15 @@ run_test("can_newly_match_moved_messages", () => { filter = new Filter([{negated: true, operator: "is", operand: "followed"}]); assert.deepEqual(filter.can_newly_match_moved_messages("general", "test"), true); }); + +run_test("get_stringified_narrow_for_server_query", () => { + const filter = new Filter([ + {operator: "channel", operand: "1"}, + {operator: "topic", operand: "bar"}, + ]); + const narrow = filter.get_stringified_narrow_for_server_query(); + assert.equal( + narrow, + '[{"negated":false,"operator":"channel","operand":1},{"negated":false,"operator":"topic","operand":"bar"}]', + ); +}); diff --git a/web/tests/lib/events.js b/web/tests/lib/events.js index d0e2ef179d..2b47162242 100644 --- a/web/tests/lib/events.js +++ b/web/tests/lib/events.js @@ -40,6 +40,7 @@ const fake_now = 1596713966; exports.test_streams = { devel: { + is_archived: false, name: "devel", description: ":devel fun:", rendered_description: "devel fun", @@ -56,6 +57,7 @@ exports.test_streams = { can_remove_subscribers_group: 2, }, test: { + is_archived: false, name: "test", description: "test desc", rendered_description: "test desc", @@ -363,13 +365,13 @@ exports.fixtures = { data: { allow_message_editing: true, message_content_edit_limit_seconds: 5, - edit_topic_policy: 4, create_multiuse_invite_group: 3, authentication_methods: { Google: {enabled: true, available: true}, }, can_add_custom_emoji_group: 3, can_create_public_channel_group: 3, + can_move_messages_between_topics_group: 3, direct_message_permission_group: 3, plan_type: 3, upload_quota_mib: 50000, @@ -1162,6 +1164,14 @@ exports.fixtures = { visibility_policy: 1, }, + user_topic_with_followed_policy_change: { + type: "user_topic", + stream_id: 101, + topic_name: "js", + last_updated: fake_now, + visibility_policy: 3, + }, + web_reload_client: { type: "web_reload_client", immediate: true, diff --git a/web/tests/lib/index.js b/web/tests/lib/index.js index 94896a22f3..5a85014eac 100644 --- a/web/tests/lib/index.js +++ b/web/tests/lib/index.js @@ -25,9 +25,12 @@ global.DOMParser = dom.window.DOMParser; global.HTMLAnchorElement = dom.window.HTMLAnchorElement; global.HTMLElement = dom.window.HTMLElement; global.Window = dom.window.Window; -global.navigator = { - userAgent: "node.js", -}; +Object.defineProperty(global, "navigator", { + value: { + userAgent: "node.js", + }, + writable: true, +}); require("@babel/register")({ extensions: [".es6", ".es", ".jsx", ".js", ".mjs", ".ts"], diff --git a/web/tests/narrow_activate.test.js b/web/tests/narrow_activate.test.js deleted file mode 100644 index df0731e537..0000000000 --- a/web/tests/narrow_activate.test.js +++ /dev/null @@ -1,292 +0,0 @@ -"use strict"; - -const assert = require("node:assert/strict"); - -const {mock_esm, set_global, zrequire} = require("./lib/namespace"); -const {run_test, noop} = require("./lib/test"); -const $ = require("./lib/zjquery"); - -set_global("history", {}); -mock_esm("../src/resize", { - resize_stream_filters_container() {}, -}); -const {Filter} = zrequire("../src/filter"); -const all_messages_data = mock_esm("../src/all_messages_data"); -const browser_history = mock_esm("../src/browser_history", { - state: {changing_hash: false}, - get_current_state_show_more_topics: () => undefined, -}); -const compose_actions = mock_esm("../src/compose_actions"); -const compose_banner = mock_esm("../src/compose_banner"); -const compose_closed_ui = mock_esm("../src/compose_closed_ui"); -const compose_recipient = mock_esm("../src/compose_recipient"); -const compose_notifications = mock_esm("../src/compose_notifications"); -const message_fetch = mock_esm("../src/message_fetch"); -const message_list = mock_esm("../src/message_list"); -const message_lists = mock_esm("../src/message_lists", { - current: { - view: { - $list: { - remove: noop, - removeClass: noop, - addClass: noop, - }, - update_sticky_recipient_headers: noop, - }, - data: { - filter: new Filter([{operator: "in", operand: "all"}]), - }, - }, - update_current_message_list(msg_list) { - message_lists.current = msg_list; - }, - all_rendered_message_lists() { - return [message_lists.current]; - }, -}); -const message_feed_top_notices = mock_esm("../src/message_feed_top_notices"); -const message_feed_loading = mock_esm("../src/message_feed_loading"); -const message_view_header = mock_esm("../src/message_view_header"); -const message_viewport = mock_esm("../src/message_viewport"); -const narrow_history = mock_esm("../src/narrow_history"); -const narrow_title = mock_esm("../src/narrow_title"); -const stream_list = mock_esm("../src/stream_list", {is_zoomed_in: () => false}); -const left_sidebar_navigation_area = mock_esm("../src/left_sidebar_navigation_area"); -const typing_events = mock_esm("../src/typing_events"); -const unread_ops = mock_esm("../src/unread_ops"); -mock_esm("../src/pm_list", { - handle_narrow_activated() {}, -}); -mock_esm("../src/unread_ui", { - reset_unread_banner() {}, - update_unread_banner() {}, -}); - -// -// We have strange hacks in message_view.show to sleep 0 -// seconds. -set_global("setTimeout", (f, t) => { - assert.equal(t, 0); - f(); -}); - -mock_esm("../src/user_topics", { - is_topic_muted: () => false, -}); - -const {buddy_list} = zrequire("buddy_list"); -const activity_ui = zrequire("activity_ui"); -const narrow_state = zrequire("narrow_state"); -const stream_data = zrequire("stream_data"); -const message_view = zrequire("message_view"); -const people = zrequire("people"); -const {set_realm} = zrequire("state_data"); - -set_realm({}); - -const denmark = { - subscribed: false, - color: "blue", - name: "Denmark", - stream_id: 1, - is_muted: true, -}; -stream_data.add_sub(denmark); - -function test_helper({override}) { - const events = []; - - function stub(module, func_name) { - override(module, func_name, () => { - events.push([module, func_name]); - }); - } - - stub(browser_history, "set_hash"); - stub(compose_banner, "clear_message_sent_banners"); - stub(compose_actions, "on_narrow"); - stub(compose_closed_ui, "update_reply_recipient_label"); - stub(compose_recipient, "handle_middle_pane_transition"); - stub(narrow_history, "save_narrow_state_and_flush"); - stub(message_feed_loading, "hide_indicators"); - stub(message_feed_top_notices, "hide_top_of_narrow_notices"); - stub(narrow_title, "update_narrow_title"); - stub(stream_list, "handle_narrow_activated"); - stub(message_view_header, "render_title_area"); - stub(message_viewport, "stop_auto_scrolling"); - stub(left_sidebar_navigation_area, "handle_narrow_activated"); - stub(typing_events, "render_notifications_for_narrow"); - stub(unread_ops, "process_visible"); - stub(compose_closed_ui, "update_buttons_for_stream_views"); - stub(compose_closed_ui, "update_buttons_for_private"); - // We don't test the css calls; we just skip over them. - $("#mark_read_on_scroll_state_banner").toggleClass = noop; - - return { - assert_events(expected_events) { - assert.deepEqual(events, expected_events); - }, - }; -} - -function stub_message_list() { - message_list.MessageList = class MessageList { - constructor(opts) { - this.data = opts.data; - } - - view = { - set_message_offset(offset) { - this.offset = offset; - }, - - $list: { - remove: noop, - removeClass: noop, - addClass: noop, - }, - update_sticky_recipient_headers: noop, - }; - - get(msg_id) { - return this.data.get(msg_id); - } - - visibly_empty() { - return this.data.visibly_empty(); - } - - select_id(msg_id) { - this.selected_id = msg_id; - } - }; -} - -run_test("basics", ({override, override_rewire}) => { - stub_message_list(); - activity_ui.set_cursor_and_filter(); - - const me = { - email: "me@zulip.com", - user_id: 999, - full_name: "Me Myself", - }; - people.add_active_user(me); - people.initialize_current_user(me.user_id); - override(buddy_list, "populate", noop); - override_rewire(message_view, "try_rendering_locally_for_same_narrow", noop); - - const helper = test_helper({override}); - const terms = [{operator: "stream", operand: denmark.stream_id.toString()}]; - - const selected_id = 1000; - - const selected_message = { - id: selected_id, - type: "stream", - stream_id: denmark.stream_id, - topic: "whatever", - }; - - const messages = [selected_message]; - - const row = { - length: 1, - get_offset_to_window: () => ({top: 25}), - }; - - message_lists.current.selected_id = () => -1; - message_lists.current.get_row = () => row; - - all_messages_data.all_messages_data = { - all_messages: () => messages, - visibly_empty: () => false, - first: () => ({id: 900}), - last: () => ({id: 1100}), - filter: { - equals: () => false, - }, - }; - - $("#navbar-fixed-container").set_height(40); - $("#compose").get_offset_to_window = () => ({top: 200}); - - message_fetch.load_messages_for_narrow = (opts) => { - // Only validates the anchor and set of fields - assert.deepEqual(opts, { - cont: opts.cont, - msg_list: opts.msg_list, - anchor: 1000, - validate_filter_topic_post_fetch: false, - }); - - opts.cont(); - }; - - override( - compose_notifications, - "maybe_show_one_time_interleaved_view_messages_fading_banner", - noop, - ); - - message_view.show(terms, { - then_select_id: selected_id, - }); - - assert.equal(message_lists.current.selected_id, selected_id); - // 25 was the offset of the selected message but it is low for the - // message top to be visible, so we use set offset to navbar height + header height. - assert.equal(message_lists.current.view.offset, 80); - assert.equal(narrow_state.narrowed_to_pms(), false); - - helper.assert_events([ - [message_feed_top_notices, "hide_top_of_narrow_notices"], - [message_feed_loading, "hide_indicators"], - [compose_banner, "clear_message_sent_banners"], - [message_viewport, "stop_auto_scrolling"], - [browser_history, "set_hash"], - [compose_actions, "on_narrow"], - [unread_ops, "process_visible"], - [narrow_history, "save_narrow_state_and_flush"], - [typing_events, "render_notifications_for_narrow"], - [compose_closed_ui, "update_buttons_for_stream_views"], - [compose_closed_ui, "update_reply_recipient_label"], - [message_view_header, "render_title_area"], - [narrow_title, "update_narrow_title"], - [left_sidebar_navigation_area, "handle_narrow_activated"], - [stream_list, "handle_narrow_activated"], - [compose_recipient, "handle_middle_pane_transition"], - ]); - - message_lists.current.selected_id = () => -1; - message_lists.current.get_row = () => row; - - message_view.show([{operator: "is", operand: "private"}], { - then_select_id: selected_id, - }); - - assert.equal(narrow_state.narrowed_to_pms(), true); - - message_lists.current.selected_id = () => -1; - // Row offset is between navbar and compose, so we keep it in the same position. - row.get_offset_to_window = () => ({top: 100, bottom: 150}); - message_lists.current.get_row = () => row; - - message_view.show(terms, { - then_select_id: selected_id, - }); - - assert.equal(message_lists.current.view.offset, 100); - - message_lists.current.selected_id = () => -1; - // Row is below navbar and row bottom is below compose but since the message is - // visible enough, we don't scroll the message to a new position. - row.get_offset_to_window = () => ({top: 150, bottom: 250}); - message_lists.current.get_row = () => row; - - message_view.show(terms, { - then_select_id: selected_id, - }); - - assert.equal(message_lists.current.view.offset, 150); -}); diff --git a/web/tests/people.test.js b/web/tests/people.test.js index e7e5d69bbb..1c9c3e669c 100644 --- a/web/tests/people.test.js +++ b/web/tests/people.test.js @@ -214,6 +214,22 @@ const maria = { avatar_url: null, }; +const cedar = { + email: "Cedar@example.com", + user_id: 305, + full_name: "Cedar Athens", + // With client_gravatar enabled, requests that client compute gravatar + avatar_url: null, +}; + +const leo = { + email: "Leo@example.com", + user_id: 306, + full_name: "Leo Athens", + // With client_gravatar enabled, requests that client compute gravatar + avatar_url: null, +}; + const ashton = { email: "ashton@example.com", user_id: 303, @@ -889,6 +905,8 @@ test_people("concat_direct_message_group", () => { test_people("message_methods", () => { people.add_active_user(charles); people.add_active_user(maria); + people.add_active_user(cedar); + people.add_active_user(leo); people.add_active_user(ashton); // We don't rely on Maria to have all flags set explicitly-- @@ -899,10 +917,24 @@ test_people("message_methods", () => { people.small_avatar_url_for_person(maria), "https://secure.gravatar.com/avatar/6dbdd7946b58d8b11351fcb27e5cdd55?d=identicon", ); + assert.equal( + maria.avatar_url, + "https://secure.gravatar.com/avatar/6dbdd7946b58d8b11351fcb27e5cdd55?d=identicon", + ); + // This will use the cached gravatar url assert.equal( people.medium_avatar_url_for_person(maria), "https://secure.gravatar.com/avatar/6dbdd7946b58d8b11351fcb27e5cdd55?d=identicon&s=500", ); + // This will create a new gravatar url + assert.equal( + people.medium_avatar_url_for_person(cedar), + "https://secure.gravatar.com/avatar/2e6ed9fc1de54b7b5bc98ced46fe7a14?d=identicon&s=500", + ); + assert.equal( + cedar.avatar_url, + "https://secure.gravatar.com/avatar/2e6ed9fc1de54b7b5bc98ced46fe7a14?d=identicon", + ); assert.equal(people.medium_avatar_url_for_person(charles), "/avatar/301/medium?version=0"); assert.equal(people.medium_avatar_url_for_person(ashton), "/avatar/303/medium?version=0"); @@ -952,6 +984,16 @@ test_people("message_methods", () => { "https://secure.gravatar.com/avatar/6dbdd7946b58d8b11351fcb27e5cdd55?d=identicon", ); + // No gravatar url cached yet + message = { + avatar_url: undefined, + sender_id: leo.user_id, + }; + assert.equal( + people.small_avatar_url(message), + "https://secure.gravatar.com/avatar/ce9581fbf1beefbad43a4233aa65954c?d=identicon", + ); + blueslip.expect("error", "Unknown user_id in maybe_get_user_by_id"); message = { avatar_url: undefined, diff --git a/web/tests/pill_typeahead.test.js b/web/tests/pill_typeahead.test.js index b8723fa3a3..33fb250c8f 100644 --- a/web/tests/pill_typeahead.test.js +++ b/web/tests/pill_typeahead.test.js @@ -465,7 +465,11 @@ run_test("set_up_combined", ({mock_template, override, override_rewire}) => { }) .filter(Boolean); if (opts.user_group) { - expected_result = [...expected_result, ...group_items]; + if (opts.user_group_source) { + expected_result = [...expected_result, ...opts.user_group_source()]; + } else { + expected_result = [...expected_result, ...group_items]; + } } if (opts.user) { if (opts.user_source) { @@ -530,6 +534,8 @@ run_test("set_up_combined", ({mock_template, override, override_rewire}) => { {user: true, user_source: () => [fred_item, mark_item]}, {stream: true}, {user_group: true}, + // user and custom user group source. + {user_group: true, user_group_source: () => [admins_item]}, {user_group: true, stream: true}, {user_group: true, user: true}, {user: true, stream: true}, diff --git a/web/tests/popover_menus_data.test.js b/web/tests/popover_menus_data.test.js index 346c3b61c7..a7be16f87d 100644 --- a/web/tests/popover_menus_data.test.js +++ b/web/tests/popover_menus_data.test.js @@ -46,6 +46,7 @@ mock_esm("../src/hash_util", { }); mock_esm("../src/stream_data", { is_subscribed: () => true, + is_stream_archived: () => false, }); mock_esm("../src/group_permission_settings", { get_group_permission_setting_config() { @@ -141,7 +142,6 @@ function set_page_params_no_edit_restrictions({override}) { override(realm, "realm_allow_edit_history", true); override(realm, "realm_message_content_delete_limit_seconds", null); override(realm, "realm_enable_read_receipts", true); - override(realm, "realm_edit_topic_policy", 5); override(realm, "realm_move_messages_within_stream_limit_seconds", null); } @@ -167,6 +167,7 @@ test("my_message_all_actions", ({override}) => { set_page_params_no_edit_restrictions({override}); override(realm, "realm_can_delete_any_message_group", everyone.id); override(realm, "realm_can_delete_own_message_group", everyone.id); + override(realm, "realm_can_move_messages_between_topics_group", everyone.id); override(current_user, "user_id", me.user_id); // Get message with maximum permissions available // Initialize message list @@ -258,6 +259,8 @@ test("not_my_message_view_actions", ({override}) => { test("not_my_message_view_source_and_move", ({override}) => { set_page_params_no_edit_restrictions({override}); override(realm, "realm_can_delete_any_message_group", everyone.id); + override(realm, "realm_can_move_messages_between_topics_group", everyone.id); + override(current_user, "user_id", me.user_id); // Get message that is movable with viewable source const list = init_message_list(); diff --git a/web/tests/settings_data.test.js b/web/tests/settings_data.test.js index 668feaa0c7..1957b5e021 100644 --- a/web/tests/settings_data.test.js +++ b/web/tests/settings_data.test.js @@ -163,66 +163,6 @@ test_policy( settings_data.user_can_invite_users_by_email, ); -function test_message_policy(label, policy, validation_func) { - run_test(label, ({override}) => { - override(current_user, "is_admin", true); - override(realm, policy, settings_config.common_message_policy_values.by_admins_only.code); - assert.equal(validation_func(), true); - - override(current_user, "is_admin", false); - override(current_user, "is_moderator", true); - assert.equal(validation_func(), false); - - override( - realm, - policy, - settings_config.common_message_policy_values.by_moderators_only.code, - ); - assert.equal(validation_func(), true); - - override(current_user, "is_moderator", false); - assert.equal(validation_func(), false); - - override(current_user, "is_guest", true); - override(realm, policy, settings_config.common_message_policy_values.by_everyone.code); - assert.equal(validation_func(), true); - - override(realm, policy, settings_config.common_message_policy_values.by_members.code); - assert.equal(validation_func(), false); - - override(current_user, "is_guest", false); - assert.equal(validation_func(), true); - - override(realm, policy, settings_config.common_message_policy_values.by_full_members.code); - override(current_user, "user_id", 30); - isaac.date_joined = new Date(Date.now()); - override(realm, "realm_waiting_period_threshold", 10); - settings_data.initialize(isaac.date_joined); - assert.equal(validation_func(), false); - - isaac.date_joined = new Date(Date.now() - 20 * 86400000); - settings_data.initialize(isaac.date_joined); - assert.equal(validation_func(), true); - }); -} - -test_message_policy( - "user_can_move_messages_to_another_topic", - "realm_edit_topic_policy", - settings_data.user_can_move_messages_to_another_topic, -); - -run_test("user_can_move_messages_to_another_topic_nobody_case", ({override}) => { - override(current_user, "is_admin", true); - override(current_user, "is_guest", false); - override( - realm, - "realm_edit_topic_policy", - settings_config.edit_topic_policy_values.nobody.code, - ); - assert.equal(settings_data.user_can_move_messages_to_another_topic(), false); -}); - test_realm_group_settings( "realm_can_add_custom_emoji_group", settings_data.user_can_add_custom_emoji, @@ -243,6 +183,11 @@ test_realm_group_settings( settings_data.user_can_move_messages_between_streams, ); +test_realm_group_settings( + "realm_can_move_messages_between_topics_group", + settings_data.user_can_move_messages_to_another_topic, +); + run_test("using_dark_theme", ({override}) => { override(user_settings, "color_scheme", settings_config.color_scheme_values.dark.code); assert.equal(settings_data.using_dark_theme(), true); diff --git a/web/tests/settings_org.test.js b/web/tests/settings_org.test.js index d38e2e6134..14f13161c8 100644 --- a/web/tests/settings_org.test.js +++ b/web/tests/settings_org.test.js @@ -86,6 +86,7 @@ function createSaveButtons(subsection) { $save_button_controls.closest = () => $stub_save_button_header; $stub_save_button_header.set_find_results(".time-limit-setting", []); + $stub_save_button_header.set_find_results(".pill-container", []); $stub_save_button_header.set_find_results(".subsection-changes-save button", $stub_save_button); return { @@ -438,19 +439,11 @@ function test_discard_changes_button({override}, discard_changes) { }; override(realm, "realm_allow_edit_history", true); - override( - realm, - "realm_edit_topic_policy", - settings_config.common_message_policy_values.by_everyone.code, - ); override(realm, "realm_allow_message_editing", true); override(realm, "realm_message_content_edit_limit_seconds", 3600); override(realm, "realm_message_content_delete_limit_seconds", 120); const $allow_edit_history = $("#id_realm_allow_edit_history").prop("checked", false); - const $edit_topic_policy = $("#id_realm_edit_topic_policy").val( - settings_config.common_message_policy_values.by_admins_only.code, - ); const $msg_edit_limit_setting = $("#id_realm_message_content_edit_limit_seconds").val( "custom_period", ); @@ -467,7 +460,6 @@ function test_discard_changes_button({override}, discard_changes) { $allow_edit_history.attr("id", "id_realm_allow_edit_history"); $msg_edit_limit_setting.attr("id", "id_realm_message_content_edit_limit_seconds"); $msg_delete_limit_setting.attr("id", "id_realm_message_content_delete_limit_seconds"); - $edit_topic_policy.attr("id", "id_realm_edit_topic_policy"); $message_content_edit_limit_minutes.attr("id", "id_realm_message_content_edit_limit_minutes"); $message_content_delete_limit_minutes.attr( "id", @@ -479,7 +471,6 @@ function test_discard_changes_button({override}, discard_changes) { $allow_edit_history, $msg_edit_limit_setting, $msg_delete_limit_setting, - $edit_topic_policy, ]); const {$discard_button, $save_button_controls, props} = createSaveButtons("msg-editing"); @@ -493,10 +484,6 @@ function test_discard_changes_button({override}, discard_changes) { discard_changes.call({to_$: () => $(".save-discard-widget-button.discard-button")}, ev); assert.equal($allow_edit_history.prop("checked"), true); - assert.equal( - $edit_topic_policy.val(), - settings_config.common_message_policy_values.by_everyone.code, - ); assert.equal($msg_edit_limit_setting.val(), "3600"); assert.equal($message_content_edit_limit_minutes.val(), "60"); assert.equal($msg_delete_limit_setting.val(), "120"); @@ -505,8 +492,6 @@ function test_discard_changes_button({override}, discard_changes) { } test("set_up", ({override, override_rewire}) => { - override_rewire(settings_org, "check_disable_message_delete_limit_setting_dropdown", noop); - override_rewire(settings_org, "message_move_limit_setting_enabled", noop); override(realm, "realm_available_video_chat_providers", { jitsi_meet: { id: 1, @@ -529,6 +514,7 @@ test("set_up", ({override, override_rewire}) => { }; override_rewire(settings_org, "init_dropdown_widgets", noop); + override_rewire(settings_org, "initialize_group_setting_widgets", noop); $("#id_realm_message_content_edit_limit_minutes").set_parent( $.create(""), ); diff --git a/web/tests/stream_data.test.js b/web/tests/stream_data.test.js index b1cad1c581..cdd9fdeed7 100644 --- a/web/tests/stream_data.test.js +++ b/web/tests/stream_data.test.js @@ -579,24 +579,32 @@ test("default_stream_names", () => { test("delete_sub", () => { const canada = { + is_archived: false, stream_id: 101, name: "Canada", subscribed: true, }; stream_data.add_sub(canada); + const num_subscribed_subs = stream_data.num_subscribed_subs(); assert.ok(stream_data.is_subscribed(canada.stream_id)); assert.equal(stream_data.get_sub("Canada").stream_id, canada.stream_id); assert.equal(sub_store.get(canada.stream_id).name, "Canada"); + assert.equal(stream_data.is_stream_archived(canada.stream_id), false); stream_data.delete_sub(canada.stream_id); - assert.ok(!stream_data.is_subscribed(canada.stream_id)); - assert.ok(!stream_data.get_sub("Canada")); - assert.ok(!sub_store.get(canada.stream_id)); + assert.ok(stream_data.is_stream_archived(canada.stream_id)); + assert.ok(stream_data.is_subscribed(canada.stream_id)); + assert.ok(stream_data.get_sub("Canada")); + assert.ok(sub_store.get(canada.stream_id)); + assert.equal(stream_data.num_subscribed_subs(), num_subscribed_subs - 1); blueslip.expect("warn", "Failed to archive stream 99999"); stream_data.delete_sub(99999); + + blueslip.expect("warn", "Can't subscribe to an archived stream."); + stream_data.subscribe_myself(canada); }); test("notifications", ({override}) => { @@ -1108,6 +1116,9 @@ test("can_post_messages_in_stream", ({override}) => { page_params.is_spectator = true; assert.equal(stream_data.can_post_messages_in_stream(social), false); + + social.is_archived = true; + assert.equal(stream_data.can_post_messages_in_stream(social), false); }); test("can_unsubscribe_others", ({override}) => { diff --git a/web/tests/stream_list.test.js b/web/tests/stream_list.test.js index 0d77fbc049..5d23a52155 100644 --- a/web/tests/stream_list.test.js +++ b/web/tests/stream_list.test.js @@ -7,9 +7,14 @@ const {run_test, noop} = require("./lib/test"); const $ = require("./lib/zjquery"); const {page_params} = require("./lib/zpage_params"); +const people = zrequire("people"); +const {set_current_user} = zrequire("state_data"); + set_global("document", "document-stub"); page_params.realm_users = []; +const current_user = {}; +set_current_user(current_user); // We use this with override. let unread_unmuted_count; @@ -39,6 +44,16 @@ const {initialize_user_settings} = zrequire("user_settings"); const user_settings = {}; initialize_user_settings({user_settings}); +const me = { + email: "me@example.com", + user_id: 30, + full_name: "Me Myself", + date_joined: new Date(), +}; + +people.add_active_user(me); +people.initialize_current_user(me.user_id); + const devel = { name: "devel", stream_id: 100, @@ -673,6 +688,7 @@ test_ui("rename_stream", ({mock_template, override}) => { color: payload.color, pin_to_top: true, hide_unread_count: true, + can_post_messages: true, }); return {to_$: () => $li_stub}; }); diff --git a/web/tests/stream_list_sort.test.js b/web/tests/stream_list_sort.test.js index 5da734e76b..305647c8a4 100644 --- a/web/tests/stream_list_sort.test.js +++ b/web/tests/stream_list_sort.test.js @@ -77,6 +77,13 @@ const muted_pinned = { pin_to_top: true, is_muted: true, }; +const archived = { + subscribed: true, + name: "archived channel", + stream_id: 9, + pin_to_top: true, + is_archived: true, +}; function sort_groups(query) { const streams = stream_data.subscribed_stream_ids(); @@ -112,6 +119,7 @@ test("basics", ({override_rewire}) => { stream_data.add_sub(stream_hyphen_underscore_slash_colon); stream_data.add_sub(muted_active); stream_data.add_sub(muted_pinned); + stream_data.add_sub(archived); override_rewire(stream_list_sort, "has_recent_activity", (sub) => sub.name !== "pneumonia"); diff --git a/web/tests/topic_link_util.test.js b/web/tests/topic_link_util.test.js index 905e5db0c3..c28df75343 100644 --- a/web/tests/topic_link_util.test.js +++ b/web/tests/topic_link_util.test.js @@ -47,31 +47,31 @@ run_test("stream_topic_link_syntax_test", () => { ); assert.equal( topic_link_util.get_stream_topic_link_syntax("#**Sweden>t", "test `test` test"), - "[#Sweden>test `test` test](#narrow/channel/1-Sweden/topic/test.20.60test.60.20test)", + "[#Sweden > test `test` test](#narrow/channel/1-Sweden/topic/test.20.60test.60.20test)", ); assert.equal( topic_link_util.get_stream_topic_link_syntax("#**Denmark>t", "test `test` test`s"), - "[#Denmark>test `test` test`s](#narrow/channel/2-Denmark/topic/test.20.60test.60.20test.60s)", + "[#Denmark > test `test` test`s](#narrow/channel/2-Denmark/topic/test.20.60test.60.20test.60s)", ); assert.equal( topic_link_util.get_stream_topic_link_syntax("#**Sweden>typeah", "error due to *"), - "[#Sweden>error due to *](#narrow/channel/1-Sweden/topic/error.20due.20to.20*)", + "[#Sweden > error due to *](#narrow/channel/1-Sweden/topic/error.20due.20to.20*)", ); assert.equal( topic_link_util.get_stream_topic_link_syntax("#**Sweden>t", "*asterisk"), - "[#Sweden>*asterisk](#narrow/channel/1-Sweden/topic/*asterisk)", + "[#Sweden > *asterisk](#narrow/channel/1-Sweden/topic/*asterisk)", ); assert.equal( topic_link_util.get_stream_topic_link_syntax("#**Sweden>gibberish", "greaterthan>"), - "[#Sweden>greaterthan>](#narrow/channel/1-Sweden/topic/greaterthan.3E)", + "[#Sweden > greaterthan>](#narrow/channel/1-Sweden/topic/greaterthan.3E)", ); assert.equal( topic_link_util.get_stream_topic_link_syntax("#**$$MONEY$$>t", "dollar"), - "[#$$MONEY$$>dollar](#narrow/channel/6-.24.24MONEY.24.24/topic/dollar)", + "[#$$MONEY$$ > dollar](#narrow/channel/6-.24.24MONEY.24.24/topic/dollar)", ); assert.equal( topic_link_util.get_stream_topic_link_syntax("#**Sweden>t", "swe$$dish"), - "[#Sweden>swe$$dish](#narrow/channel/1-Sweden/topic/swe.24.24dish)", + "[#Sweden > swe$$dish](#narrow/channel/1-Sweden/topic/swe.24.24dish)", ); assert.equal( topic_link_util.get_fallback_markdown_link("Sweden"), @@ -83,6 +83,11 @@ run_test("stream_topic_link_syntax_test", () => { "[#$$MONEY$$](#narrow/channel/6-.24.24MONEY.24.24)", ); + assert.equal( + topic_link_util.get_stream_topic_link_syntax("#**Sweden>&ab", "&ab"), + "[#Sweden > &ab](#narrow/channel/1-Sweden/topic/.26ab)", + ); + // Only for full coverage of the module. assert.equal(topic_link_util.escape_invalid_stream_topic_characters("Sweden"), "Sweden"); }); diff --git a/web/tests/user_events.test.js b/web/tests/user_events.test.js index 78c1af996e..c4cc9f0e7d 100644 --- a/web/tests/user_events.test.js +++ b/web/tests/user_events.test.js @@ -43,6 +43,7 @@ mock_esm("../src/settings_linkifiers", { }); mock_esm("../src/settings_org", { maybe_disable_widgets() {}, + enable_or_disable_group_permission_settings() {}, }); mock_esm("../src/settings_profile_fields", { maybe_disable_widgets() {}, diff --git a/web/tests/user_groups.test.js b/web/tests/user_groups.test.js index 51825e8112..48a6121eb6 100644 --- a/web/tests/user_groups.test.js +++ b/web/tests/user_groups.test.js @@ -583,3 +583,74 @@ run_test("get_display_group_name", () => { assert.equal(user_groups.get_display_group_name(all.name), "translated: Everyone"); assert.equal(user_groups.get_display_group_name(students.name), "Students"); }); + +run_test("get_potential_subgroups", () => { + // Remove existing groups. + user_groups.init(); + + const admins = { + name: "Administrators", + id: 1, + members: new Set([1]), + is_system_group: false, + direct_subgroup_ids: new Set([4]), + }; + const all = { + name: "Everyone", + id: 2, + members: new Set([2, 3]), + is_system_group: false, + direct_subgroup_ids: new Set([1, 3]), + }; + const students = { + name: "Students", + id: 3, + members: new Set([4, 5]), + is_system_group: false, + direct_subgroup_ids: new Set([]), + }; + const teachers = { + name: "Teachers", + id: 4, + members: new Set([6, 7, 8]), + is_system_group: false, + direct_subgroup_ids: new Set([]), + }; + const science = { + name: "Science", + id: 5, + members: new Set([9]), + is_system_group: false, + direct_subgroup_ids: new Set([]), + }; + + user_groups.initialize({ + realm_user_groups: [admins, all, students, teachers, science], + }); + + function get_potential_subgroup_ids(group_id) { + return user_groups + .get_potential_subgroups(group_id) + .map((subgroup) => subgroup.id) + .sort(); + } + + assert.deepEqual(get_potential_subgroup_ids(all.id), [teachers.id, science.id]); + assert.deepEqual(get_potential_subgroup_ids(admins.id), [students.id, science.id]); + assert.deepEqual(get_potential_subgroup_ids(teachers.id), [students.id, science.id]); + assert.deepEqual(get_potential_subgroup_ids(students.id), [admins.id, teachers.id, science.id]); + assert.deepEqual(get_potential_subgroup_ids(science.id), [ + admins.id, + all.id, + students.id, + teachers.id, + ]); + + user_groups.add_subgroups(all.id, [teachers.id]); + user_groups.add_subgroups(teachers.id, [science.id]); + assert.deepEqual(get_potential_subgroup_ids(all.id), [science.id]); + assert.deepEqual(get_potential_subgroup_ids(admins.id), [students.id, science.id]); + assert.deepEqual(get_potential_subgroup_ids(teachers.id), [students.id]); + assert.deepEqual(get_potential_subgroup_ids(students.id), [admins.id, teachers.id, science.id]); + assert.deepEqual(get_potential_subgroup_ids(science.id), [students.id]); +}); diff --git a/web/tests/user_search.test.js b/web/tests/user_search.test.js index 52f7eb2ac6..e372cc2a81 100644 --- a/web/tests/user_search.test.js +++ b/web/tests/user_search.test.js @@ -25,20 +25,13 @@ mock_esm("../src/buddy_list", { }); function mock_setTimeout() { - let set_timeout_function_called = false; set_global("setTimeout", (func) => { - if (set_timeout_function_called) { - // This conditional is needed to avoid indefinite calls. - return; - } - set_timeout_function_called = true; func(); }); } const popovers = mock_esm("../src/popovers"); const presence = mock_esm("../src/presence"); -const resize = mock_esm("../src/resize"); const sidebar_ui = mock_esm("../src/sidebar_ui"); const activity_ui = zrequire("activity_ui"); @@ -103,7 +96,6 @@ test("clear_search", ({override}) => { override(presence, "get_status", () => "active"); override(presence, "get_user_ids", () => all_user_ids); override(popovers, "hide_all", noop); - override(resize, "resize_sidebars", noop); stub_buddy_list_empty_list_message_lengths(); @@ -112,7 +104,6 @@ test("clear_search", ({override}) => { assert.deepEqual(user_ids, {all_user_ids: []}); }); set_input_val("somevalue"); - assert.ok(!$("#user_search_section").hasClass("notdisplayed")); // Now we're clearing the search string and everyone shows up again. override(fake_buddy_list, "populate", (user_ids) => { @@ -121,21 +112,18 @@ test("clear_search", ({override}) => { $("#clear_search_people_button").trigger("click"); assert.equal($("input.user-list-filter").val(), ""); $("#clear_search_people_button").trigger("click"); - assert.ok($("#user_search_section").hasClass("notdisplayed")); }); -test("escape_search", ({override}) => { +test("clear_search", ({override}) => { override(realm, "realm_presence_disabled", true); - override(resize, "resize_sidebars", noop); override(popovers, "hide_all", noop); stub_buddy_list_empty_list_message_lengths(); set_input_val("somevalue"); - activity_ui.escape_search(); + activity_ui.clear_search(); assert.equal($("input.user-list-filter").val(), ""); - activity_ui.escape_search(); - assert.ok($("#user_search_section").hasClass("notdisplayed")); + activity_ui.clear_search(); // We need to reset this because the unit tests aren't isolated from each other. set_input_val(""); @@ -144,7 +132,6 @@ test("escape_search", ({override}) => { test("blur search right", ({override}) => { override(sidebar_ui, "show_userlist_sidebar", noop); override(popovers, "hide_all", noop); - override(resize, "resize_sidebars", noop); mock_setTimeout(); $("input.user-list-filter").closest = (selector) => { @@ -161,7 +148,6 @@ test("blur search right", ({override}) => { test("blur search left", ({override}) => { override(sidebar_ui, "show_streamlist_sidebar", noop); override(popovers, "hide_all", noop); - override(resize, "resize_sidebars", noop); mock_setTimeout(); $("input.user-list-filter").closest = (selector) => { @@ -229,32 +215,6 @@ test("filter_user_ids", ({override}) => { test_filter("fr,al", [alice, fred]); }); -test("click on user header to toggle display", ({override}) => { - const $user_filter = $("input.user-list-filter"); - - override(popovers, "hide_all", noop); - override(sidebar_ui, "show_userlist_sidebar", noop); - override(resize, "resize_sidebars", noop); - - override(realm, "realm_presence_disabled", true); - - assert.ok(!$("#user_search_section").hasClass("notdisplayed")); - - $user_filter.val("bla"); - - $("#userlist-header-search").trigger("click"); - assert.ok($("#user_search_section").hasClass("notdisplayed")); - assert.equal($user_filter.val(), ""); - - $("input.user-list-filter").closest = (selector) => { - assert.equal(selector, ".app-main [class^='column-']"); - return $.create("sidebar").addClass("column-right"); - }; - - $("#userlist-header-search").trigger("click"); - assert.equal($("#user_search_section").hasClass("notdisplayed"), false); -}); - test("searching", () => { assert.equal(activity_ui.searching(), false); $("input.user-list-filter").trigger("focus"); diff --git a/web/tests/util.test.js b/web/tests/util.test.js index 255a356ac7..9744eb8e40 100644 --- a/web/tests/util.test.js +++ b/web/tests/util.test.js @@ -394,14 +394,13 @@ run_test("get_custom_time_in_minutes", () => { assert.equal(util.get_custom_time_in_minutes("days", time_input), time_input * 24 * 60); assert.equal(util.get_custom_time_in_minutes("hours", time_input), time_input * 60); assert.equal(util.get_custom_time_in_minutes("minutes", time_input), time_input); - // Unknown time unit returns same time input + // Unknown time unit string throws an error, but we still return + // the time input that was passed to the function. + blueslip.expect("error", "Unexpected custom time unit: invalid"); assert.equal(util.get_custom_time_in_minutes("invalid", time_input), time_input); /// NaN time input returns NaN const invalid_time_input = Number.NaN; - assert.equal( - util.get_custom_time_in_minutes("minutes", invalid_time_input), - invalid_time_input, - ); + assert.equal(util.get_custom_time_in_minutes("hours", invalid_time_input), invalid_time_input); }); run_test("check_and_validate_custom_time_input", () => { @@ -444,3 +443,29 @@ run_test("the", () => { // were previously typed wrong but not breaking the app. assert.equal(util.the([]), undefined); }); + +run_test("compare_a_b", () => { + const user1 = { + id: 1, + name: "sally", + }; + const user2 = { + id: 2, + name: "jenny", + }; + const user3 = { + id: 3, + name: "max", + }; + const user4 = { + id: 4, + name: "max", + }; + const unsorted = [user2, user1, user4, user3]; + + const sorted_by_id = [...unsorted].sort((a, b) => util.compare_a_b(a.id, b.id)); + assert.deepEqual(sorted_by_id, [user1, user2, user3, user4]); + + const sorted_by_name = [...unsorted].sort((a, b) => util.compare_a_b(a.name, b.name)); + assert.deepEqual(sorted_by_name, [user2, user4, user3, user1]); +}); diff --git a/zerver/actions/create_realm.py b/zerver/actions/create_realm.py index edd03d6de0..4d2d1c6f88 100644 --- a/zerver/actions/create_realm.py +++ b/zerver/actions/create_realm.py @@ -9,7 +9,6 @@ from django.utils.translation import gettext as _ from django.utils.translation import override as override_language from confirmation import settings as confirmation_settings -from zerver.actions.message_send import internal_send_stream_message from zerver.actions.realm_settings import ( do_add_deactivated_redirect, do_change_realm_plan_type, @@ -19,7 +18,7 @@ from zerver.lib.bulk_create import create_users from zerver.lib.push_notifications import sends_notifications_directly from zerver.lib.remote_server import maybe_enqueue_audit_log_upload from zerver.lib.server_initialization import create_internal_realm, server_initialized -from zerver.lib.streams import ensure_stream, get_signups_stream +from zerver.lib.streams import ensure_stream from zerver.lib.user_groups import ( create_system_user_groups_for_realm, get_role_based_system_groups_dict, @@ -32,19 +31,12 @@ from zerver.models import ( RealmAuditLog, RealmAuthenticationMethod, RealmUserDefault, - Stream, UserProfile, ) from zerver.models.groups import SystemGroups from zerver.models.presence import PresenceSequence from zerver.models.realm_audit_logs import AuditLogEventType -from zerver.models.realms import ( - CommonPolicyEnum, - InviteToRealmPolicyEnum, - get_org_type_display_name, - get_realm, -) -from zerver.models.users import get_system_bot +from zerver.models.realms import CommonPolicyEnum, InviteToRealmPolicyEnum from zproject.backends import all_default_backend_names @@ -69,7 +61,7 @@ def do_change_realm_subdomain( # deleting, clear that state. realm.demo_organization_scheduled_deletion_date = None realm.string_id = new_subdomain - with transaction.atomic(): + with transaction.atomic(durable=True): realm.save(update_fields=["string_id", "demo_organization_scheduled_deletion_date"]) RealmAuditLog.objects.create( realm=realm, @@ -240,7 +232,7 @@ def do_create_realm( # is required to do so correctly. kwargs["push_notifications_enabled"] = sends_notifications_directly() - with transaction.atomic(): + with transaction.atomic(durable=True): realm = Realm(string_id=string_id, name=name, **kwargs) if is_demo_organization: realm.demo_organization_scheduled_deletion_date = realm.date_created + timedelta( @@ -369,32 +361,12 @@ def do_create_realm( prereg_realm.created_realm = realm prereg_realm.save(update_fields=["status", "created_realm"]) - # Send a notification to the admin realm when a new organization registers. if settings.CORPORATE_ENABLED: - from corporate.lib.support import get_realm_support_url + # Send a notification to the admin realm when a new organization registers. + from corporate.lib.stripe import RealmBillingSession - admin_realm = get_realm(settings.SYSTEM_BOT_REALM) - sender = get_system_bot(settings.NOTIFICATION_BOT, admin_realm.id) - - support_url = get_realm_support_url(realm) - organization_type = get_org_type_display_name(realm.org_type) - - message = f"[{realm.name}]({support_url}) ([{realm.display_subdomain}]({realm.url})). Organization type: {organization_type}" - topic_name = "new organizations" - - try: - signups_stream = get_signups_stream(admin_realm) - - internal_send_stream_message( - sender, - signups_stream, - topic_name, - message, - ) - except Stream.DoesNotExist: # nocoverage - # If the signups stream hasn't been created in the admin - # realm, don't auto-create it to send to it; just do nothing. - pass + billing_session = RealmBillingSession(user=None, realm=realm) + billing_session.send_realm_created_internal_admin_message() setup_realm_internal_bots(realm) return realm diff --git a/zerver/actions/create_user.py b/zerver/actions/create_user.py index d7a945fc74..297622dccf 100644 --- a/zerver/actions/create_user.py +++ b/zerver/actions/create_user.py @@ -1,10 +1,11 @@ from collections import defaultdict from collections.abc import Iterable, Sequence +from contextlib import suppress from datetime import timedelta from typing import Any from django.conf import settings -from django.db import transaction +from django.db import IntegrityError, transaction from django.db.models import F from django.utils.timezone import now as timezone_now from django.utils.translation import gettext as _ @@ -351,7 +352,17 @@ def process_new_human_user( # The 'visibility_policy_banner' is only displayed to existing users. # Mark it as read for a new user. - OnboardingStep.objects.create(user=user_profile, onboarding_step="visibility_policy_banner") + # + # If the new user opted to import settings from an existing account, and + # 'visibility_policy_banner' is already marked as read for the existing account, + # 'copy_onboarding_steps' function already did the needed copying. + # Simply ignore the IntegrityError in that case. + # + # The extremely brief nature of this subtransaction makes a savepoint safe. + # See https://postgres.ai/blog/20210831-postgresql-subtransactions-considered-harmful + # for context on risks around savepoints. + with suppress(IntegrityError), transaction.atomic(savepoint=True): + OnboardingStep.objects.create(user=user_profile, onboarding_step="visibility_policy_banner") def notify_created_user(user_profile: UserProfile, notify_user_ids: list[int]) -> None: @@ -487,6 +498,7 @@ def notify_created_bot(user_profile: UserProfile) -> None: send_event_on_commit(user_profile.realm, event, bot_owner_user_ids(user_profile)) +@transaction.atomic(durable=True) def do_create_user( email: str, password: str | None, @@ -516,89 +528,86 @@ def do_create_user( if settings.BILLING_ENABLED: from corporate.lib.stripe import RealmBillingSession - with transaction.atomic(): - user_profile = create_user( - email=email, - password=password, - realm=realm, - full_name=full_name, - role=role, - bot_type=bot_type, - bot_owner=bot_owner, - tos_version=tos_version, - timezone=timezone, - avatar_source=avatar_source, - default_language=default_language, - default_sending_stream=default_sending_stream, - default_events_register_stream=default_events_register_stream, - default_all_public_streams=default_all_public_streams, - source_profile=source_profile, - enable_marketing_emails=enable_marketing_emails, - email_address_visibility=email_address_visibility, - ) + user_profile = create_user( + email=email, + password=password, + realm=realm, + full_name=full_name, + role=role, + bot_type=bot_type, + bot_owner=bot_owner, + tos_version=tos_version, + timezone=timezone, + avatar_source=avatar_source, + default_language=default_language, + default_sending_stream=default_sending_stream, + default_events_register_stream=default_events_register_stream, + default_all_public_streams=default_all_public_streams, + source_profile=source_profile, + enable_marketing_emails=enable_marketing_emails, + email_address_visibility=email_address_visibility, + ) - event_time = user_profile.date_joined - if not acting_user: - acting_user = user_profile - RealmAuditLog.objects.create( + event_time = user_profile.date_joined + if not acting_user: + acting_user = user_profile + RealmAuditLog.objects.create( + realm=user_profile.realm, + acting_user=acting_user, + modified_user=user_profile, + event_type=AuditLogEventType.USER_CREATED, + event_time=event_time, + extra_data={ + RealmAuditLog.ROLE_COUNT: realm_user_count_by_role(user_profile.realm), + }, + ) + maybe_enqueue_audit_log_upload(user_profile.realm) + + if realm_creation: + # If this user just created a realm, make sure they are + # properly tagged as the creator of the realm. + realm_creation_audit_log = ( + RealmAuditLog.objects.filter(event_type=AuditLogEventType.REALM_CREATED, realm=realm) + .order_by("id") + .last() + ) + assert realm_creation_audit_log is not None + realm_creation_audit_log.acting_user = user_profile + realm_creation_audit_log.save(update_fields=["acting_user"]) + + if settings.BILLING_ENABLED: + billing_session = RealmBillingSession(user=user_profile, realm=user_profile.realm) + billing_session.update_license_ledger_if_needed(event_time) + + system_user_group = get_system_user_group_for_user(user_profile) + UserGroupMembership.objects.create(user_profile=user_profile, user_group=system_user_group) + RealmAuditLog.objects.create( + realm=user_profile.realm, + modified_user=user_profile, + modified_user_group=system_user_group, + event_type=AuditLogEventType.USER_GROUP_DIRECT_USER_MEMBERSHIP_ADDED, + event_time=event_time, + acting_user=acting_user, + ) + + if user_profile.role == UserProfile.ROLE_MEMBER and not user_profile.is_provisional_member: + full_members_system_group = NamedUserGroup.objects.get( + name=SystemGroups.FULL_MEMBERS, realm=user_profile.realm, - acting_user=acting_user, - modified_user=user_profile, - event_type=AuditLogEventType.USER_CREATED, - event_time=event_time, - extra_data={ - RealmAuditLog.ROLE_COUNT: realm_user_count_by_role(user_profile.realm), - }, + is_system_group=True, + ) + UserGroupMembership.objects.create( + user_profile=user_profile, user_group=full_members_system_group ) - maybe_enqueue_audit_log_upload(user_profile.realm) - - if realm_creation: - # If this user just created a realm, make sure they are - # properly tagged as the creator of the realm. - realm_creation_audit_log = ( - RealmAuditLog.objects.filter( - event_type=AuditLogEventType.REALM_CREATED, realm=realm - ) - .order_by("id") - .last() - ) - assert realm_creation_audit_log is not None - realm_creation_audit_log.acting_user = user_profile - realm_creation_audit_log.save(update_fields=["acting_user"]) - - if settings.BILLING_ENABLED: - billing_session = RealmBillingSession(user=user_profile, realm=user_profile.realm) - billing_session.update_license_ledger_if_needed(event_time) - - system_user_group = get_system_user_group_for_user(user_profile) - UserGroupMembership.objects.create(user_profile=user_profile, user_group=system_user_group) RealmAuditLog.objects.create( realm=user_profile.realm, modified_user=user_profile, - modified_user_group=system_user_group, + modified_user_group=full_members_system_group, event_type=AuditLogEventType.USER_GROUP_DIRECT_USER_MEMBERSHIP_ADDED, event_time=event_time, acting_user=acting_user, ) - if user_profile.role == UserProfile.ROLE_MEMBER and not user_profile.is_provisional_member: - full_members_system_group = NamedUserGroup.objects.get( - name=SystemGroups.FULL_MEMBERS, - realm=user_profile.realm, - is_system_group=True, - ) - UserGroupMembership.objects.create( - user_profile=user_profile, user_group=full_members_system_group - ) - RealmAuditLog.objects.create( - realm=user_profile.realm, - modified_user=user_profile, - modified_user_group=full_members_system_group, - event_type=AuditLogEventType.USER_GROUP_DIRECT_USER_MEMBERSHIP_ADDED, - event_time=event_time, - acting_user=acting_user, - ) - # Note that for bots, the caller will send an additional event # with bot-specific info like services. notify_created_user(user_profile, []) @@ -650,7 +659,7 @@ def do_activate_mirror_dummy_user( if settings.BILLING_ENABLED: from corporate.lib.stripe import RealmBillingSession - with transaction.atomic(): + with transaction.atomic(savepoint=False): change_user_is_active(user_profile, True) user_profile.is_mirror_dummy = False user_profile.set_unusable_password() diff --git a/zerver/actions/invites.py b/zerver/actions/invites.py index 2197ac0da3..e2c783dcae 100644 --- a/zerver/actions/invites.py +++ b/zerver/actions/invites.py @@ -173,7 +173,7 @@ def check_invite_limit(realm: Realm, num_invitees: int) -> None: ) -@transaction.atomic +@transaction.atomic(durable=True) def do_invite_users( user_profile: UserProfile, invitee_emails: Collection[str], @@ -362,7 +362,7 @@ def do_get_invites_controlled_by_user(user_profile: UserProfile) -> list[dict[st return invites -@transaction.atomic +@transaction.atomic(durable=True) def do_create_multiuse_invite_link( referred_by: UserProfile, invited_as: int, @@ -386,7 +386,7 @@ def do_create_multiuse_invite_link( ) -@transaction.atomic +@transaction.atomic(durable=True) def do_revoke_user_invite(prereg_user: PreregistrationUser) -> None: email = prereg_user.email realm = prereg_user.realm @@ -403,7 +403,7 @@ def do_revoke_user_invite(prereg_user: PreregistrationUser) -> None: notify_invites_changed(realm, changed_invite_referrer=prereg_user.referred_by) -@transaction.atomic +@transaction.atomic(durable=True) def do_revoke_multi_use_invite(multiuse_invite: MultiuseInvite) -> None: realm = multiuse_invite.referred_by.realm @@ -414,7 +414,7 @@ def do_revoke_multi_use_invite(multiuse_invite: MultiuseInvite) -> None: notify_invites_changed(realm, changed_invite_referrer=multiuse_invite.referred_by) -@transaction.atomic +@transaction.atomic(savepoint=False) def do_send_user_invite_email( prereg_user: PreregistrationUser, *, diff --git a/zerver/actions/message_send.py b/zerver/actions/message_send.py index f521af32aa..6753e1ab66 100644 --- a/zerver/actions/message_send.py +++ b/zerver/actions/message_send.py @@ -1682,6 +1682,7 @@ def check_message( mention_backend: MentionBackend | None = None, limit_unread_user_ids: set[int] | None = None, disable_external_notifications: bool = False, + archived_channel_notice: bool = False, ) -> SendMessageRequest: """See https://zulip.readthedocs.io/en/latest/subsystems/sending-messages.html @@ -1727,7 +1728,10 @@ def check_message( if not skip_stream_access_check: access_stream_for_send_message( - sender=sender, stream=stream, forwarder_user_profile=forwarder_user_profile + sender=sender, + stream=stream, + forwarder_user_profile=forwarder_user_profile, + archived_channel_notice=archived_channel_notice, ) else: # Defensive assertion - the only currently supported use case @@ -1859,6 +1863,7 @@ def _internal_prep_message( disable_external_notifications: bool = False, forged: bool = False, forged_timestamp: float | None = None, + archived_channel_notice: bool = False, ) -> SendMessageRequest | None: """ Create a message object and checks it, but doesn't send it or save it to the database. @@ -1890,6 +1895,7 @@ def _internal_prep_message( disable_external_notifications=disable_external_notifications, forged=forged, forged_timestamp=forged_timestamp, + archived_channel_notice=archived_channel_notice, ) except JsonableError as e: logging.exception( @@ -1913,6 +1919,7 @@ def internal_prep_stream_message( limit_unread_user_ids: set[int] | None = None, forged: bool = False, forged_timestamp: float | None = None, + archived_channel_notice: bool = False, ) -> SendMessageRequest | None: """ See _internal_prep_message for details of how this works. @@ -1930,6 +1937,7 @@ def internal_prep_stream_message( limit_unread_user_ids=limit_unread_user_ids, forged=forged, forged_timestamp=forged_timestamp, + archived_channel_notice=archived_channel_notice, ) @@ -2008,6 +2016,7 @@ def internal_send_stream_message( email_gateway: bool = False, message_type: int = Message.MessageType.NORMAL, limit_unread_user_ids: set[int] | None = None, + archived_channel_notice: bool = False, ) -> int | None: message = internal_prep_stream_message( sender, @@ -2017,6 +2026,7 @@ def internal_send_stream_message( email_gateway=email_gateway, message_type=message_type, limit_unread_user_ids=limit_unread_user_ids, + archived_channel_notice=archived_channel_notice, ) if message is None: diff --git a/zerver/actions/realm_settings.py b/zerver/actions/realm_settings.py index babcce7055..f1e39332c4 100644 --- a/zerver/actions/realm_settings.py +++ b/zerver/actions/realm_settings.py @@ -82,7 +82,6 @@ def do_set_realm_property( # These settings have a different event format due to their history. message_edit_settings = [ "allow_message_editing", - "edit_topic_policy", "message_content_edit_limit_seconds", ] if name in message_edit_settings: @@ -517,7 +516,7 @@ def do_deactivate_realm( if settings.BILLING_ENABLED: from corporate.lib.stripe import RealmBillingSession - with transaction.atomic(): + with transaction.atomic(durable=True): realm.deactivated = True realm.save(update_fields=["deactivated"]) @@ -576,7 +575,7 @@ def do_reactivate_realm(realm: Realm) -> None: return realm.deactivated = False - with transaction.atomic(): + with transaction.atomic(durable=True): realm.save(update_fields=["deactivated"]) event_time = timezone_now() diff --git a/zerver/actions/streams.py b/zerver/actions/streams.py index 2a666c9283..2271759614 100644 --- a/zerver/actions/streams.py +++ b/zerver/actions/streams.py @@ -1,4 +1,3 @@ -import hashlib from collections import defaultdict from collections.abc import Collection, Iterable, Mapping from typing import Any, TypeAlias @@ -24,7 +23,7 @@ from zerver.lib.cache import ( from zerver.lib.exceptions import JsonableError from zerver.lib.mention import silent_mention_syntax_for_user from zerver.lib.message import get_last_message_id -from zerver.lib.queue import queue_event_on_commit, queue_json_publish +from zerver.lib.queue import queue_event_on_commit from zerver.lib.stream_color import pick_colors from zerver.lib.stream_subscription import ( SubInfo, @@ -72,100 +71,25 @@ from zerver.models.users import active_non_guest_user_ids, active_user_ids, get_ from zerver.tornado.django_api import send_event_on_commit -def send_user_remove_events_on_stream_deactivation( - stream: Stream, subscribed_users: list[UserProfile] -) -> None: - guest_subscribed_users = [user for user in subscribed_users if user.is_guest] - - if len(guest_subscribed_users) == 0: # nocoverage - # Save a few database queries in the case that the stream - # didn't contain any guest users. - return - - other_subscriptions_of_subscribed_users = get_subscribers_of_target_user_subscriptions( - guest_subscribed_users - ) - users_involved_in_dms_dict = get_users_involved_in_dms_with_target_users( - guest_subscribed_users, stream.realm - ) - - subscriber_ids = {user.id for user in subscribed_users} - inaccessible_user_dict: dict[int, set[int]] = defaultdict(set) - for guest_user in guest_subscribed_users: - users_accessible_by_guest = ( - {guest_user.id} - | other_subscriptions_of_subscribed_users[guest_user.id] - | users_involved_in_dms_dict[guest_user.id] - ) - users_inaccessible_to_guest = subscriber_ids - users_accessible_by_guest - for user_id in users_inaccessible_to_guest: - inaccessible_user_dict[user_id].add(guest_user.id) - - for user_id, notify_user_ids in inaccessible_user_dict.items(): - event_remove_user = dict( - type="realm_user", - op="remove", - person=dict(user_id=user_id, full_name=str(UserProfile.INACCESSIBLE_USER_NAME)), - ) - send_event_on_commit(stream.realm, event_remove_user, list(notify_user_ids)) - - @transaction.atomic(savepoint=False) def do_deactivate_stream(stream: Stream, *, acting_user: UserProfile | None) -> None: # If the stream is already deactivated, this is a no-op if stream.deactivated is True: raise JsonableError(_("Channel is already deactivated")) - # We want to mark all messages in the to-be-deactivated stream as - # read for all users; otherwise they will pollute queries like - # "Get the user's first unread message". Since this can be an - # expensive operation, we do it via the deferred_work queue - # processor. - deferred_work_event = { - "type": "mark_stream_messages_as_read_for_everyone", - "stream_recipient_id": stream.recipient_id, - } - transaction.on_commit(lambda: queue_json_publish("deferred_work", deferred_work_event)) - # Get the affected user ids *before* we deactivate everybody. affected_user_ids = can_access_stream_user_ids(stream) - stream_subscribers = get_active_subscriptions_for_stream_id( - stream.id, include_deactivated_users=True - ).select_related("user_profile") - subscribed_users = [sub.user_profile for sub in stream_subscribers] - stream_subscribers.update(active=False) - - was_invite_only = stream.invite_only was_public = stream.is_public() was_web_public = stream.is_web_public - - # We do not use do_change_stream_permission because no users need to - # be notified, and we do not want to create audit log entries for - # changing stream privacy. And due to this we also need to duplicate - # the code to unset is_web_public field on attachments below. stream.deactivated = True - stream.invite_only = True - # Preserve as much as possible the original stream name while giving it a - # special prefix that both indicates that the stream is deactivated and - # frees up the original name for reuse. - old_name = stream.name - - # Prepend a substring of the hashed stream ID to the new stream name - streamID = str(stream.id) - stream_id_hash_object = hashlib.sha512(streamID.encode()) - hashed_stream_id = stream_id_hash_object.hexdigest()[0:7] - - new_name = (hashed_stream_id + "!DEACTIVATED:" + old_name)[: Stream.MAX_NAME_LENGTH] - - stream.name = new_name[: Stream.MAX_NAME_LENGTH] - stream.save(update_fields=["name", "deactivated", "invite_only"]) + stream.save(update_fields=["deactivated"]) assert stream.recipient_id is not None if was_web_public: assert was_public # Unset the is_web_public and is_realm_public cache on attachments, - # since the stream is now private. + # since the stream is now archived. Attachment.objects.filter(messages__recipient_id=stream.recipient_id).update( is_web_public=None, is_realm_public=None ) @@ -173,7 +97,7 @@ def do_deactivate_stream(stream: Stream, *, acting_user: UserProfile | None) -> is_web_public=None, is_realm_public=None ) elif was_public: - # Unset the is_realm_public cache on attachments, since the stream is now private. + # Unset the is_realm_public cache on attachments, since the stream is now archived. Attachment.objects.filter(messages__recipient_id=stream.recipient_id).update( is_realm_public=None ) @@ -191,13 +115,9 @@ def do_deactivate_stream(stream: Stream, *, acting_user: UserProfile | None) -> do_remove_streams_from_default_stream_group(stream.realm, group, [stream]) stream_dict = stream_to_dict(stream) - stream_dict.update(dict(name=old_name, invite_only=was_invite_only)) event = dict(type="stream", op="delete", streams=[stream_dict]) send_event_on_commit(stream.realm, event, affected_user_ids) - if stream.realm.can_access_all_users_group.named_user_group.name != SystemGroups.EVERYONE: - send_user_remove_events_on_stream_deactivation(stream, subscribed_users) - event_time = timezone_now() RealmAuditLog.objects.create( realm=stream.realm, @@ -207,6 +127,16 @@ def do_deactivate_stream(stream: Stream, *, acting_user: UserProfile | None) -> event_time=event_time, ) + sender = get_system_bot(settings.NOTIFICATION_BOT, stream.realm_id) + with override_language(stream.realm.default_language): + internal_send_stream_message( + sender, + stream, + topic_name=str(Realm.STREAM_EVENTS_NOTIFICATION_TOPIC_NAME), + content=_("Channel {channel_name} has been archived.").format(channel_name=stream.name), + archived_channel_notice=True, + ) + def deactivated_streams_by_old_name(realm: Realm, stream_name: str) -> QuerySet[Stream]: fixed_length_prefix = ".......!DEACTIVATED:" @@ -222,7 +152,8 @@ def deactivated_streams_by_old_name(realm: Realm, stream_name: str) -> QuerySet[ # characters, followed by `!DEACTIVATED:`, followed by at # most MAX_NAME_LENGTH-(length of the prefix) of the name # they provided: - Q(name__regex=rf"^{fixed_length_prefix}{truncated_name}") + Q(name=stream_name) + | Q(name__regex=rf"^{fixed_length_prefix}{truncated_name}") # Finally, we go looking for the pre-1b6f68bb59dc version, # which is any number of `!` followed by `DEACTIVATED:` # and a prefix of the old stream name @@ -235,32 +166,33 @@ def deactivated_streams_by_old_name(realm: Realm, stream_name: str) -> QuerySet[ @transaction.atomic(savepoint=False) def do_unarchive_stream(stream: Stream, new_name: str, *, acting_user: UserProfile | None) -> None: realm = stream.realm + stream_subscribers = get_active_subscriptions_for_stream_id( + stream.id, include_deactivated_users=True + ).select_related("user_profile") + if not stream.deactivated: raise JsonableError(_("Channel is not currently deactivated")) - if Stream.objects.filter(realm=realm, name=new_name).exists(): + if stream.name != new_name and Stream.objects.filter(realm=realm, name=new_name).exists(): raise JsonableError( _("Channel named {channel_name} already exists").format(channel_name=new_name) ) + if stream.invite_only and not stream_subscribers: + raise JsonableError(_("Channel is private and have no subscribers")) assert stream.recipient_id is not None stream.deactivated = False stream.name = new_name + if stream.invite_only and stream.is_web_public: + # Previously, because archiving a channel set invite_only=True + # without mutating is_web_public, it was possible for archived + # channels to have this invalid state. Fix that. + stream.is_web_public = False - # We only set invite_only=True during deactivation, which can lead - # to the invalid state of to invite-only but also web-public - # streams. Explicitly reset the access; we do not use - # do_change_stream_permission because no users need be notified, - # and it cannot handle the broken state that may currently exist. - stream.is_web_public = False - stream.invite_only = True - stream.history_public_to_subscribers = True stream.save( update_fields=[ "name", "deactivated", "is_web_public", - "invite_only", - "history_public_to_subscribers", ] ) @@ -292,17 +224,12 @@ def do_unarchive_stream(stream: Stream, new_name: str, *, acting_user: UserProfi recent_traffic = get_streams_traffic({stream.id}, realm) - # All admins always get to know about private streams' existence, - # but we only subscribe the realm owners. - send_stream_creation_event( - realm, stream, [user.id for user in realm.get_admin_users_and_bots()], recent_traffic - ) - bulk_add_subscriptions( - realm=realm, - streams=[stream], - users=realm.get_human_owner_users(), - acting_user=acting_user, - ) + subscribed_users = {sub.user_profile for sub in stream_subscribers} + admin_users_and_bots = set(realm.get_admin_users_and_bots()) + + notify_users = admin_users_and_bots | subscribed_users + + send_stream_creation_event(realm, stream, [user.id for user in notify_users], recent_traffic) sender = get_system_bot(settings.NOTIFICATION_BOT, stream.realm_id) with override_language(stream.realm.default_language): @@ -447,6 +374,7 @@ def send_subscription_add_events( stream_weekly_traffic=stream_dict["stream_weekly_traffic"], subscribers=stream_subscribers, # Fields from Stream.API_FIELDS + is_archived=stream_dict["is_archived"], can_remove_subscribers_group=stream_dict["can_remove_subscribers_group"], creator_id=stream_dict["creator_id"], date_created=stream_dict["date_created"], @@ -1051,7 +979,7 @@ def bulk_remove_subscriptions( streams_to_unsubscribe = [sub_info.stream for sub_info in subs_to_deactivate] # We do all the database changes in a transaction to ensure # RealmAuditLog entries are atomically created when making changes. - with transaction.atomic(): + with transaction.atomic(savepoint=False): Subscription.objects.filter( id__in=sub_ids_to_deactivate, ).update(active=False) diff --git a/zerver/actions/user_groups.py b/zerver/actions/user_groups.py index e6fc48dc66..42d66ee0c6 100644 --- a/zerver/actions/user_groups.py +++ b/zerver/actions/user_groups.py @@ -366,7 +366,7 @@ def do_send_subgroups_update_event( send_event_on_commit(user_group.realm, event, active_user_ids(user_group.realm_id)) -@transaction.atomic +@transaction.atomic(savepoint=False) def add_subgroups_to_user_group( user_group: NamedUserGroup, subgroups: list[NamedUserGroup], @@ -406,7 +406,7 @@ def add_subgroups_to_user_group( do_send_subgroups_update_event("add_subgroups", user_group, subgroup_ids) -@transaction.atomic +@transaction.atomic(savepoint=False) def remove_subgroups_from_user_group( user_group: NamedUserGroup, subgroups: list[NamedUserGroup], diff --git a/zerver/actions/users.py b/zerver/actions/users.py index 81ae6a0ef7..a215030f03 100644 --- a/zerver/actions/users.py +++ b/zerver/actions/users.py @@ -80,7 +80,7 @@ def do_delete_user(user_profile: UserProfile, *, acting_user: UserProfile | None date_joined = user_profile.date_joined personal_recipient = user_profile.recipient - with transaction.atomic(): + with transaction.atomic(durable=True): user_profile.delete() # Recipient objects don't get deleted through CASCADE, so we need to handle # the user's personal recipient manually. This will also delete all Messages pointing @@ -180,7 +180,7 @@ def do_delete_user_preserving_messages(user_profile: UserProfile) -> None: realm = user_profile.realm date_joined = user_profile.date_joined - with transaction.atomic(): + with transaction.atomic(durable=True): # The strategy is that before calling user_profile.delete(), we need to # reassign Messages sent by the user to a dummy user, so that they don't # get affected by CASCADE. We cannot yet create a dummy user with .id @@ -486,7 +486,7 @@ def do_deactivate_user( for profile in bot_profiles: do_deactivate_user(profile, _cascade=False, acting_user=acting_user) - with transaction.atomic(): + with transaction.atomic(savepoint=False): if user_profile.realm.is_zephyr_mirror_realm: # nocoverage # For zephyr mirror users, we need to make them a mirror dummy # again; otherwise, other users won't get the correct behavior @@ -749,6 +749,28 @@ def do_change_can_create_users(user_profile: UserProfile, value: bool) -> None: ) +@transaction.atomic(savepoint=False) +def do_change_can_change_user_emails(user_profile: UserProfile, value: bool) -> None: + event_time = timezone_now() + old_value = user_profile.can_change_user_emails + + user_profile.can_change_user_emails = value + user_profile.save(update_fields=["can_change_user_emails"]) + + RealmAuditLog.objects.create( + realm=user_profile.realm, + event_type=AuditLogEventType.USER_SPECIAL_PERMISSION_CHANGED, + event_time=event_time, + acting_user=None, + modified_user=user_profile, + extra_data={ + RealmAuditLog.OLD_VALUE: old_value, + RealmAuditLog.NEW_VALUE: value, + "property": "can_change_user_emails", + }, + ) + + @transaction.atomic(durable=True) def do_update_outgoing_webhook_service( bot_profile: UserProfile, service_interface: int, service_payload_url: str diff --git a/zerver/data_import/import_util.py b/zerver/data_import/import_util.py index 3953f58697..630ba4fdc2 100644 --- a/zerver/data_import/import_util.py +++ b/zerver/data_import/import_util.py @@ -17,6 +17,7 @@ from django.utils.timezone import now as timezone_now from zerver.data_import.sequencer import NEXT_ID from zerver.lib.avatar_hash import user_avatar_base_path_from_ids +from zerver.lib.message import normalize_body_for_import from zerver.lib.mime_types import guess_extension from zerver.lib.partial import partial from zerver.lib.stream_color import STREAM_ASSIGNMENT_COLORS as STREAM_COLORS @@ -499,6 +500,8 @@ def build_message( has_link: bool = False, has_attachment: bool = True, ) -> ZerverFieldsT: + # check and remove NULL Bytes if any. + content = normalize_body_for_import(content) zulip_message = Message( rendered_content_version=1, # this is Zulip specific id=message_id, diff --git a/zerver/data_import/mattermost.py b/zerver/data_import/mattermost.py index 9dcf0feeb0..af5a271a33 100644 --- a/zerver/data_import/mattermost.py +++ b/zerver/data_import/mattermost.py @@ -440,7 +440,7 @@ def process_raw_message_batch( ) # html2text is GPL licensed, so run it as a subprocess. - content = subprocess.check_output(["html2text"], input=content, text=True) + content = subprocess.check_output(["html2text", "--unicode-snob"], input=content, text=True) if len(content) > 10000: # nocoverage logging.info("skipping too-long message of length %s", len(content)) diff --git a/zerver/data_import/slack_message_conversion.py b/zerver/data_import/slack_message_conversion.py index d023e443b4..9887590523 100644 --- a/zerver/data_import/slack_message_conversion.py +++ b/zerver/data_import/slack_message_conversion.py @@ -157,13 +157,20 @@ def convert_markdown_syntax(text: str, regex: str, zulip_keyword: str) -> str: def convert_link_format(text: str) -> tuple[str, bool]: """ 1. Converts '' to 'https://foo.com' - 2. Converts '' to 'https://foo.com|foo' + 2. Converts '' to '[foo](https://foo.com)' """ has_link = False for match in re.finditer(LINK_REGEX, text, re.VERBOSE): - converted_text = match.group(0).replace(">", "").replace("<", "") + slack_url = match.group(0) + url_parts = slack_url[1:-1].split("|", maxsplit=1) + # Check if there's a pipe with text after it + if len(url_parts) == 2: + converted_url = f"[{url_parts[1]}]({url_parts[0]})" + else: + converted_url = url_parts[0] + has_link = True - text = text.replace(match.group(0), converted_text) + text = text.replace(slack_url, converted_url) return text, has_link diff --git a/zerver/forms.py b/zerver/forms.py index f25338d9df..fe6c3875d1 100644 --- a/zerver/forms.py +++ b/zerver/forms.py @@ -52,6 +52,7 @@ MIT_VALIDATION_ERROR = Markup( ' contact us.' ) +INVALID_ACCOUNT_CREDENTIALS_ERROR = gettext_lazy("Incorrect email or password.") DEACTIVATED_ACCOUNT_ERROR = gettext_lazy( "Your account {username} has been deactivated." " Please contact your organization administrator to reactivate it." @@ -514,9 +515,7 @@ class OurAuthenticationForm(AuthenticationForm): if self.user_cache is None: raise forms.ValidationError( - self.error_messages["invalid_login"], - code="invalid_login", - params={"username": self.username_field.verbose_name}, + INVALID_ACCOUNT_CREDENTIALS_ERROR, ) self.confirm_login_allowed(self.user_cache) diff --git a/zerver/lib/avatar.py b/zerver/lib/avatar.py index f33cdc6c22..d13584efe2 100644 --- a/zerver/lib/avatar.py +++ b/zerver/lib/avatar.py @@ -12,14 +12,11 @@ from zerver.lib.thumbnail import MEDIUM_AVATAR_SIZE from zerver.lib.upload import get_avatar_url from zerver.lib.url_encoding import append_url_query_string from zerver.models import UserProfile +from zerver.models.users import is_cross_realm_bot_email -SYSTEM_BOTS_AVATAR_FILES = { - # This is also used in zerver/lib/storage.py to ensure - # these files are hashed when served as static files. - settings.WELCOME_BOT: "images/welcome-bot.png", - settings.NOTIFICATION_BOT: "images/logo/zulip-icon-square.svg", - settings.EMAIL_GATEWAY_BOT: "images/email-gateway-bot.png", -} +STATIC_AVATARS_DIR = "images/static_avatars/" + +DEFAULT_AVATAR_FILE = "images/default-avatar.png" def avatar_url( @@ -36,6 +33,36 @@ def avatar_url( ) +def get_system_bots_avatar_file_name(email: str) -> str: + system_bot_avatar_name_map = { + settings.WELCOME_BOT: "welcome-bot", + settings.NOTIFICATION_BOT: "notification-bot", + settings.EMAIL_GATEWAY_BOT: "emailgateway", + } + return urljoin(STATIC_AVATARS_DIR, system_bot_avatar_name_map.get(email, "unknown")) + + +def get_static_avatar_url(email: str, medium: bool) -> str: + avatar_file_name = get_system_bots_avatar_file_name(email) + avatar_file_name += "-medium.png" if medium else ".png" + + if settings.DEBUG: + # This find call may not be cheap, so we only do it in the + # development environment to do an assertion. + from django.contrib.staticfiles.finders import find + + if not find(avatar_file_name): + raise AssertionError(f"Unknown avatar file for: {email}") + elif settings.STATIC_ROOT and not staticfiles_storage.exists(avatar_file_name): + # Fallback for the case where no avatar exists; this should + # never happen in practice. This logic cannot be executed + # while STATIC_ROOT is not defined, so the above STATIC_ROOT + # check is important. + return DEFAULT_AVATAR_FILE + + return staticfiles_storage.url(avatar_file_name) + + def get_avatar_field( user_id: int, realm_id: int, @@ -63,9 +90,8 @@ def get_avatar_field( """ # System bots have hardcoded avatars - system_bot_avatar = SYSTEM_BOTS_AVATAR_FILES.get(email) - if system_bot_avatar: - return staticfiles_storage.url(system_bot_avatar) + if is_cross_realm_bot_email(email): + return get_static_avatar_url(email, medium) """ If our client knows how to calculate gravatar hashes, we diff --git a/zerver/lib/digest.py b/zerver/lib/digest.py index f233017cf0..69f5cd7fd9 100644 --- a/zerver/lib/digest.py +++ b/zerver/lib/digest.py @@ -387,7 +387,7 @@ def get_digest_context(user: UserProfile, cutoff: float) -> dict[str, Any]: raise AssertionError("Unreachable") -@transaction.atomic +@transaction.atomic(durable=True) def bulk_handle_digest_email(user_ids: list[int], cutoff: float) -> None: # We go directly to the database to get user objects, # since inactive users are likely to not be in the cache. diff --git a/zerver/lib/email_notifications.py b/zerver/lib/email_notifications.py index f487605421..d870d7c13c 100644 --- a/zerver/lib/email_notifications.py +++ b/zerver/lib/email_notifications.py @@ -938,7 +938,7 @@ def enqueue_welcome_emails(user: UserProfile, realm_creation: bool = False) -> N def convert_html_to_markdown(html: str) -> str: # html2text is GPL licensed, so run it as a subprocess. markdown = subprocess.check_output( - [os.path.join(sys.prefix, "bin", "html2text")], input=html, text=True + [os.path.join(sys.prefix, "bin", "html2text"), "--unicode-snob"], input=html, text=True ).strip() # We want images to get linked and inline previewed, but html2text will turn diff --git a/zerver/lib/event_schema.py b/zerver/lib/event_schema.py index 5297b0566a..5d47f3505b 100644 --- a/zerver/lib/event_schema.py +++ b/zerver/lib/event_schema.py @@ -50,6 +50,7 @@ from zerver.models import Realm, RealmUserDefault, Stream, UserProfile # These fields are used for "stream" events, and are included in the # larger "subscription" events that also contain personal settings. default_stream_fields = [ + ("is_archived", bool), ("can_remove_subscribers_group", int), ("creator_id", OptionalType(int)), ("date_created", int), @@ -1038,12 +1039,6 @@ message_content_edit_limit_seconds_data = DictType( ] ) -edit_topic_policy_data = DictType( - required_keys=[ - ("edit_topic_policy", int), - ] -) - night_logo_data = DictType( required_keys=[ ("night_logo_url", str), @@ -1066,8 +1061,8 @@ group_setting_type = UnionType( group_setting_update_data_type = DictType( required_keys=[], optional_keys=[ - ("create_multiuse_invite_group", int), - ("can_access_all_users_group", int), + ("create_multiuse_invite_group", group_setting_type), + ("can_access_all_users_group", group_setting_type), ("can_add_custom_emoji_group", group_setting_type), ("can_create_groups", group_setting_type), ("can_create_public_channel_group", group_setting_type), @@ -1077,6 +1072,7 @@ group_setting_update_data_type = DictType( ("can_delete_own_message_group", group_setting_type), ("can_manage_all_groups", group_setting_type), ("can_move_messages_between_channels_group", group_setting_type), + ("can_move_messages_between_topics_group", group_setting_type), ("direct_message_initiator_group", group_setting_type), ("direct_message_permission_group", group_setting_type), ], @@ -1094,7 +1090,6 @@ update_dict_data = UnionType( [ allow_message_editing_data, authentication_data, - edit_topic_policy_data, icon_data, logo_data, message_content_edit_limit_seconds_data, @@ -1129,8 +1124,6 @@ def check_realm_update_dict( sub_type = allow_message_editing_data elif "message_content_edit_limit_seconds" in event["data"]: sub_type = message_content_edit_limit_seconds_data - elif "edit_topic_policy" in event["data"]: - sub_type = edit_topic_policy_data elif "authentication_methods" in event["data"]: sub_type = authentication_data elif any( diff --git a/zerver/lib/events.py b/zerver/lib/events.py index 2cbe2f9ed4..704243d9fd 100644 --- a/zerver/lib/events.py +++ b/zerver/lib/events.py @@ -96,7 +96,6 @@ from zerver.models.linkifiers import linkifiers_for_realm from zerver.models.realm_emoji import get_all_custom_emoji_for_realm from zerver.models.realm_playgrounds import get_realm_playgrounds from zerver.models.realms import ( - EditTopicPolicyEnum, get_corresponding_policy_value_for_group_setting, get_realm_domains, get_realm_with_settings, @@ -144,6 +143,7 @@ def fetch_initial_state_data( linkifier_url_template: bool = False, user_list_incomplete: bool = False, include_deactivated_groups: bool = False, + archived_channels: bool = False, ) -> dict[str, Any]: """When `event_types` is None, fetches the core data powering the web app's `page_params` and `/api/v1/register` (for mobile/terminal @@ -292,16 +292,9 @@ def fetch_initial_state_data( for property_name in Realm.property_types: state["realm_" + property_name] = getattr(realm, property_name) - for ( - setting_name, - permission_configuration, - ) in Realm.REALM_PERMISSION_GROUP_SETTINGS.items(): - if setting_name in Realm.REALM_PERMISSION_GROUP_SETTINGS_WITH_NEW_API_FORMAT: - setting_value = getattr(realm, setting_name) - state["realm_" + setting_name] = get_group_setting_value_for_api(setting_value) - continue - - state["realm_" + setting_name] = getattr(realm, permission_configuration.id_field_name) + for setting_name in Realm.REALM_PERMISSION_GROUP_SETTINGS: + setting_value = getattr(realm, setting_name) + state["realm_" + setting_name] = get_group_setting_value_for_api(setting_value) state["realm_create_public_stream_policy"] = ( get_corresponding_policy_value_for_group_setting( @@ -338,9 +331,6 @@ def fetch_initial_state_data( state["realm_allow_message_editing"] = ( False if user_profile is None else realm.allow_message_editing ) - state["realm_edit_topic_policy"] = ( - EditTopicPolicyEnum.ADMINS_ONLY if user_profile is None else realm.edit_topic_policy - ) # This setting determines whether to send presence and also # whether to display of users list in the right sidebar; we @@ -631,7 +621,16 @@ def fetch_initial_state_data( "name": integration.name, "display_name": integration.display_name, "all_event_types": get_all_event_types_for_integration(integration), - "config": {c[1]: c[0] for c in integration.config_options}, + "config_options": [ + { + "key": c.name, + "label": c.description, + "validator": c.validator.__name__, + } + for c in integration.config_options + ] + if integration.config_options + else [], } for integration in WEBHOOK_INTEGRATIONS if integration.legacy is False @@ -661,6 +660,7 @@ def fetch_initial_state_data( sub_info = gather_subscriptions_helper( user_profile, include_subscribers=include_subscribers, + include_archived_channels=archived_channels, ) else: sub_info = get_web_public_subs(realm) @@ -794,6 +794,7 @@ def apply_events( linkifier_url_template: bool, user_list_incomplete: bool, include_deactivated_groups: bool, + archived_channels: bool = False, ) -> None: for event in events: if fetch_event_types is not None and event["type"] not in fetch_event_types: @@ -816,6 +817,7 @@ def apply_events( linkifier_url_template=linkifier_url_template, user_list_incomplete=user_list_incomplete, include_deactivated_groups=include_deactivated_groups, + archived_channels=archived_channels, ) @@ -830,6 +832,7 @@ def apply_event( linkifier_url_template: bool, user_list_incomplete: bool, include_deactivated_groups: bool, + archived_channels: bool = False, ) -> None: if event["type"] == "message": state["max_message_id"] = max(state["max_message_id"], event["message"]["id"]) @@ -1114,7 +1117,7 @@ def apply_event( if user_id != person_user_id ] - for setting_name in Realm.REALM_PERMISSION_GROUP_SETTINGS_WITH_NEW_API_FORMAT: + for setting_name in Realm.REALM_PERMISSION_GROUP_SETTINGS: if not isinstance(state["realm_" + setting_name], int): state["realm_" + setting_name].direct_members = [ user_id @@ -1217,17 +1220,30 @@ def apply_event( s for s in state["streams"] if s["stream_id"] not in deleted_stream_ids ] - state["subscriptions"] = [ - stream - for stream in state["subscriptions"] - if stream["stream_id"] not in deleted_stream_ids - ] + if archived_channels: + for stream in state["subscriptions"]: + if stream["stream_id"] in deleted_stream_ids: + stream["is_archived"] = True - state["unsubscribed"] = [ - stream - for stream in state["unsubscribed"] - if stream["stream_id"] not in deleted_stream_ids - ] + for stream in state["unsubscribed"]: + if stream["stream_id"] in deleted_stream_ids: + stream["is_archived"] = True + stream["first_message_id"] = Stream.objects.get( + id=stream["stream_id"] + ).first_message_id + + else: + state["subscriptions"] = [ + stream + for stream in state["subscriptions"] + if stream["stream_id"] not in deleted_stream_ids + ] + + state["unsubscribed"] = [ + stream + for stream in state["unsubscribed"] + if stream["stream_id"] not in deleted_stream_ids + ] state["never_subscribed"] = [ stream @@ -1727,6 +1743,7 @@ class ClientCapabilities(TypedDict): linkifier_url_template: NotRequired[bool] user_list_incomplete: NotRequired[bool] include_deactivated_groups: NotRequired[bool] + archived_channels: NotRequired[bool] def do_events_register( @@ -1764,6 +1781,7 @@ def do_events_register( linkifier_url_template = client_capabilities.get("linkifier_url_template", False) user_list_incomplete = client_capabilities.get("user_list_incomplete", False) include_deactivated_groups = client_capabilities.get("include_deactivated_groups", False) + archived_channels = client_capabilities.get("archived_channels", False) if fetch_event_types is not None: event_types_set: set[str] | None = set(fetch_event_types) @@ -1796,6 +1814,7 @@ def do_events_register( user_avatar_url_field_optional=user_avatar_url_field_optional, user_settings_object=user_settings_object, user_list_incomplete=user_list_incomplete, + archived_channels=archived_channels, # These presence params are a noop, because presence is not included. slim_presence=True, presence_last_update_id_fetched_by_client=None, @@ -1835,6 +1854,7 @@ def do_events_register( linkifier_url_template=linkifier_url_template, user_list_incomplete=user_list_incomplete, include_deactivated_groups=include_deactivated_groups, + archived_channels=archived_channels, ) if queue_id is None: @@ -1857,6 +1877,7 @@ def do_events_register( linkifier_url_template=linkifier_url_template, user_list_incomplete=user_list_incomplete, include_deactivated_groups=include_deactivated_groups, + archived_channels=archived_channels, ) # Apply events that came in while we were fetching initial data diff --git a/zerver/lib/export.py b/zerver/lib/export.py index e8be4e7771..568a856ab2 100644 --- a/zerver/lib/export.py +++ b/zerver/lib/export.py @@ -260,6 +260,9 @@ NON_EXPORTED_TABLES = { "zerver_submessage", # Drafts don't need to be exported as they are supposed to be more ephemeral. "zerver_draft", + # The importer cannot trust ImageAttachment objects anyway and needs to check + # and process images for thumbnailing on its own. + "zerver_imageattachment", # For any tables listed below here, it's a bug that they are not present in the export. } diff --git a/zerver/lib/home.py b/zerver/lib/home.py index 30aef3b275..57eec0bc00 100644 --- a/zerver/lib/home.py +++ b/zerver/lib/home.py @@ -156,6 +156,7 @@ def build_page_params_for_home_page_load( linkifier_url_template=True, user_list_incomplete=True, include_deactivated_groups=True, + archived_channels=True, ) if user_profile is not None: diff --git a/zerver/lib/import_realm.py b/zerver/lib/import_realm.py index d58cbda996..c3ef0a94ec 100644 --- a/zerver/lib/import_realm.py +++ b/zerver/lib/import_realm.py @@ -8,6 +8,7 @@ from typing import Any import bmemcached import orjson +import pyvips from bs4 import BeautifulSoup from django.conf import settings from django.core.cache import cache @@ -34,7 +35,7 @@ from zerver.lib.push_notifications import sends_notifications_directly from zerver.lib.remote_server import maybe_enqueue_audit_log_upload from zerver.lib.server_initialization import create_internal_realm, server_initialized from zerver.lib.streams import render_stream_description -from zerver.lib.thumbnail import BadImageError +from zerver.lib.thumbnail import THUMBNAIL_ACCEPT_IMAGE_TYPES, BadImageError, maybe_thumbnail from zerver.lib.timestamp import datetime_to_timestamp from zerver.lib.upload import ensure_avatar_image, sanitize_name, upload_backend, upload_emoji_image from zerver.lib.upload.s3 import get_bucket @@ -161,7 +162,16 @@ id_map_to_list: dict[str, dict[int, list[int]]] = { } path_maps: dict[str, dict[str, str]] = { - "attachment_path": {}, + # Maps original attachment path pre-import to the final, post-import + # attachment path. + "old_attachment_path_to_new_path": {}, + # Inverse of old_attachment_path_to_new_path. + "new_attachment_path_to_old_path": {}, + # Maps the new (post-import) attachment path to the absolute path to the file + # in the on-disk export data that we're importing. + # Allows code running after this is filled to access file contents + # without needing to go through S3 to get it. + "new_attachment_path_to_local_data_path": {}, } message_id_to_attachments: dict[str, dict[int, list[str]]] = { @@ -212,13 +222,12 @@ def fix_upload_links(data: TableData, message_table: TableName) -> None: for message in data[message_table]: if message["has_attachment"] is True: for attachment_path in message_id_to_attachments[message_table][message["id"]]: - message["content"] = message["content"].replace( - attachment_path, path_maps["attachment_path"][attachment_path] - ) + old_path = path_maps["new_attachment_path_to_old_path"][attachment_path] + message["content"] = message["content"].replace(old_path, attachment_path) if message["rendered_content"]: message["rendered_content"] = message["rendered_content"].replace( - attachment_path, path_maps["attachment_path"][attachment_path] + old_path, attachment_path ) @@ -976,7 +985,11 @@ def import_uploads( relative_path = upload_backend.generate_message_upload_path( str(record["realm_id"]), sanitize_name(os.path.basename(record["path"])) ) - path_maps["attachment_path"][record["s3_path"]] = relative_path + path_maps["old_attachment_path_to_new_path"][record["s3_path"]] = relative_path + path_maps["new_attachment_path_to_old_path"][relative_path] = record["s3_path"] + path_maps["new_attachment_path_to_local_data_path"][relative_path] = os.path.join( + import_dir, record["path"] + ) if s3_uploads: key = bucket.Object(relative_path) @@ -1612,6 +1625,22 @@ def do_import_realm(import_dir: Path, subdomain: str, processes: int = 1) -> Rea with open(attachments_file, "rb") as f: attachment_data = orjson.loads(f.read()) + # We need to import ImageAttachments before messages, as the message rendering logic + # checks for existence of ImageAttachment records to determine if HTML content for image + # preview needs to be added to a message. + # In order for ImageAttachments to be correctly created, we need to know the new path_ids + # and content_types of the attachments. + # + # Begin by fixing up the Attachment data. + fix_attachments_data(attachment_data) + # Now we're ready create ImageAttachment rows and enqueue thumbnailing + # for the images. + # This order ensures that during message import, rendered_content will be generated + # correctly with image previews. + # The important detail here is that we **only** care about having ImageAttachment + # rows ready at the time of message import. Thumbnailing happens in a queue worker + # in a different process, and we don't care about when it'll complete. + create_image_attachments_and_maybe_enqueue_thumbnailing(realm, attachment_data) map_messages_to_attachments(attachment_data) # Import zerver_message and zerver_usermessage @@ -1930,10 +1959,6 @@ def import_attachments(data: TableData) -> None: "scheduledmessage_id", ) - # Update 'path_id' for the attachments - for attachment in data[parent_db_table_name]: - attachment["path_id"] = path_maps["attachment_path"][attachment["path_id"]] - # Next, load the parent rows. bulk_import_model(data, parent_model) @@ -1960,6 +1985,36 @@ def import_attachments(data: TableData) -> None: logging.info("Successfully imported M2M table %s", m2m_table_name) +def fix_attachments_data(attachment_data: TableData) -> None: + for attachment in attachment_data["zerver_attachment"]: + attachment["path_id"] = path_maps["old_attachment_path_to_new_path"][attachment["path_id"]] + + # In the case of images, content_type needs to be set for thumbnailing. + # Zulip exports set this, but third-party exports may not. + if attachment.get("content_type") is None: + guessed_content_type = guess_type(attachment["path_id"])[0] + if guessed_content_type in THUMBNAIL_ACCEPT_IMAGE_TYPES: + attachment["content_type"] = guessed_content_type + + +def create_image_attachments_and_maybe_enqueue_thumbnailing( + realm: Realm, attachment_data: TableData +) -> None: + for attachment in attachment_data["zerver_attachment"]: + if attachment["content_type"] not in THUMBNAIL_ACCEPT_IMAGE_TYPES: + continue + + path_id = attachment["path_id"] + content_type = attachment["content_type"] + + # We don't have to go to S3 to obtain the file. We still have the export + # data on disk and stored the absolute path to it. + local_filename = path_maps["new_attachment_path_to_local_data_path"][path_id] + pyvips_source = pyvips.Source.new_from_file(local_filename) + maybe_thumbnail(pyvips_source, content_type, path_id, realm.id) + continue + + def import_analytics_data(realm: Realm, import_dir: Path, crossrealm_user_ids: set[int]) -> None: analytics_filename = os.path.join(import_dir, "analytics.json") if not os.path.exists(analytics_filename): diff --git a/zerver/lib/integrations.py b/zerver/lib/integrations.py index aa0c61d759..a8274eb413 100644 --- a/zerver/lib/integrations.py +++ b/zerver/lib/integrations.py @@ -12,6 +12,8 @@ from django.views.decorators.csrf import csrf_exempt from django_stubs_ext import StrPromise from zerver.lib.storage import static_path +from zerver.lib.validator import check_bool, check_string +from zerver.lib.webhooks.common import WebhookConfigOption """This module declares all of the (documented) integrations available in the Zulip server. The Integration class is used as part of @@ -34,7 +36,7 @@ Over time, we expect this registry to grow additional convenience features for writing and configuring integrations efficiently. """ -OptionValidator: TypeAlias = Callable[[str, str], str | None] +OptionValidator: TypeAlias = Callable[[str, str], str | bool | None] META_CATEGORY: dict[str, StrPromise] = { "meta-integration": gettext_lazy("Integration frameworks"), @@ -75,7 +77,7 @@ class Integration: doc: str | None = None, stream_name: str | None = None, legacy: bool = False, - config_options: Sequence[tuple[str, str, OptionValidator]] = [], + config_options: Sequence[WebhookConfigOption] = [], ) -> None: self.name = name self.client_name = client_name @@ -198,7 +200,7 @@ class WebhookIntegration(Integration): doc: str | None = None, stream_name: str | None = None, legacy: bool = False, - config_options: Sequence[tuple[str, str, OptionValidator]] = [], + config_options: Sequence[WebhookConfigOption] = [], dir_name: str | None = None, ) -> None: if client_name is None: @@ -341,6 +343,7 @@ EMBEDDED_BOTS: list[EmbeddedBotIntegration] = [ WEBHOOK_INTEGRATIONS: list[WebhookIntegration] = [ WebhookIntegration("airbrake", ["monitoring"]), + WebhookIntegration("airbyte", ["monitoring"]), WebhookIntegration( "alertmanager", ["monitoring"], @@ -408,6 +411,18 @@ WEBHOOK_INTEGRATIONS: list[WebhookIntegration] = [ logo="images/integrations/logos/github.svg", function="zerver.webhooks.github.view.api_github_webhook", stream_name="github", + config_options=[ + WebhookConfigOption( + name="branches", + description="Filter by branches (comma-separated list)", + validator=check_string, + ), + WebhookConfigOption( + name="ignore_private_repositories", + description="Exclude notifications from private repositories", + validator=check_bool, + ), + ], ), WebhookIntegration( "githubsponsors", @@ -698,6 +713,7 @@ NO_SCREENSHOT_WEBHOOKS = { DOC_SCREENSHOT_CONFIG: dict[str, list[BaseScreenshotConfig]] = { "airbrake": [ScreenshotConfig("error_message.json")], + "airbyte": [ScreenshotConfig("airbyte_job_payload_success.json")], "alertmanager": [ ScreenshotConfig("alert.json", extra_params={"name": "topic", "desc": "description"}) ], diff --git a/zerver/lib/markdown/__init__.py b/zerver/lib/markdown/__init__.py index 74962206f8..bffebd7634 100644 --- a/zerver/lib/markdown/__init__.py +++ b/zerver/lib/markdown/__init__.py @@ -194,6 +194,31 @@ def get_compiled_stream_topic_link_regex() -> Pattern[str]: ) +STREAM_TOPIC_MESSAGE_LINK_REGEX = rf""" + {BEFORE_MENTION_ALLOWED_REGEX} # Start after whitespace or specified chars + \#\*\* # and after hash sign followed by double asterisks + (?P[^\*>]+) # stream name can contain anything except > + > # > acts as separator + (?P[^\*]+) # topic name can contain anything + @ + (?P\d+) # message id + \*\* # ends by double asterisks + """ + + +@lru_cache(None) +def get_compiled_stream_topic_message_link_regex() -> Pattern[str]: + # Not using verbose_compile as it adds ^(.*?) and + # (.*?)$ which cause extra overhead of matching + # pattern which is not required. + # With new InlineProcessor these extra patterns + # are not required. + return re.compile( + STREAM_TOPIC_MESSAGE_LINK_REGEX, + re.DOTALL | re.VERBOSE, + ) + + @lru_cache(None) def get_web_link_regex() -> Pattern[str]: # We create this one time, but not at startup. So the @@ -1984,7 +2009,7 @@ class UserGroupMentionPattern(CompiledInlineProcessor): return None, None, None -class StreamPattern(CompiledInlineProcessor): +class StreamTopicMessageProcessor(CompiledInlineProcessor): def find_stream_id(self, name: str) -> int | None: db_data: DbData | None = self.zmd.zulip_db_data if db_data is None: @@ -1992,6 +2017,8 @@ class StreamPattern(CompiledInlineProcessor): stream_id = db_data.stream_names.get(name) return stream_id + +class StreamPattern(StreamTopicMessageProcessor): @override def handleMatch( # type: ignore[override] # https://github.com/python/mypy/issues/10197 self, m: Match[str], data: str @@ -2016,14 +2043,7 @@ class StreamPattern(CompiledInlineProcessor): return el, m.start(), m.end() -class StreamTopicPattern(CompiledInlineProcessor): - def find_stream_id(self, name: str) -> int | None: - db_data: DbData | None = self.zmd.zulip_db_data - if db_data is None: - return None - stream_id = db_data.stream_names.get(name) - return stream_id - +class StreamTopicPattern(StreamTopicMessageProcessor): @override def handleMatch( # type: ignore[override] # https://github.com/python/mypy/issues/10197 self, m: Match[str], data: str @@ -2046,6 +2066,29 @@ class StreamTopicPattern(CompiledInlineProcessor): return el, m.start(), m.end() +class StreamTopicMessagePattern(StreamTopicMessageProcessor): + @override + def handleMatch( # type: ignore[override] # https://github.com/python/mypy/issues/10197 + self, m: Match[str], data: str + ) -> tuple[Element | str | None, int | None, int | None]: + stream_name = m.group("stream_name") + topic_name = m.group("topic_name") + message_id = m.group("message_id") + + stream_id = self.find_stream_id(stream_name) + if stream_id is None or topic_name is None: + return None, None, None + el = Element("a") + el.set("class", "message-link") + stream_url = encode_stream(stream_id, stream_name) + topic_url = hash_util_encode(topic_name) + link = f"/#narrow/channel/{stream_url}/topic/{topic_url}/near/{message_id}" + el.set("href", link) + text = f"#{stream_name} > {topic_name} @ 💬" + el.text = markdown.util.AtomicString(text) + return el, m.start(), m.end() + + def possible_linked_stream_names(content: str) -> set[str]: return { *re.findall(STREAM_LINK_REGEX, content, re.VERBOSE), @@ -2310,6 +2353,11 @@ class ZulipMarkdown(markdown.Markdown): ) reg.register(UserMentionPattern(mention.MENTIONS_RE, self), "usermention", 95) reg.register(Tex(TEX_RE, self), "tex", 90) + reg.register( + StreamTopicMessagePattern(get_compiled_stream_topic_message_link_regex(), self), + "stream_topic_message", + 89, + ) reg.register(StreamTopicPattern(get_compiled_stream_topic_link_regex(), self), "topic", 87) reg.register(StreamPattern(get_compiled_stream_link_regex(), self), "stream", 85) reg.register(Timestamp(TIMESTAMP_RE), "timestamp", 75) diff --git a/zerver/lib/message.py b/zerver/lib/message.py index 165866f826..07cb28f6a4 100644 --- a/zerver/lib/message.py +++ b/zerver/lib/message.py @@ -196,6 +196,12 @@ def normalize_body(body: str) -> str: return truncate_content(body, settings.MAX_MESSAGE_LENGTH, "\n[message truncated]") +def normalize_body_for_import(body: str) -> str: + if "\x00" in body: + body = re.sub(r"\x00", "", body) + return truncate_content(body, settings.MAX_MESSAGE_LENGTH, "\n[message truncated]") + + def truncate_topic(topic_name: str) -> str: return truncate_content(topic_name, MAX_TOPIC_NAME_LENGTH, "...") @@ -386,6 +392,10 @@ def has_message_access( # You can't access public stream messages in other realms return False + if stream.deactivated: + # You can't access messages in deactivated streams + return False + def is_subscribed_helper() -> bool: if is_subscribed is not None: return is_subscribed diff --git a/zerver/lib/onboarding.py b/zerver/lib/onboarding.py index 01165f3ab6..4bb82594a5 100644 --- a/zerver/lib/onboarding.py +++ b/zerver/lib/onboarding.py @@ -229,7 +229,7 @@ def send_welcome_bot_response(send_request: SendMessageRequest) -> None: ) -@transaction.atomic +@transaction.atomic(savepoint=False) def send_initial_realm_messages(realm: Realm) -> None: # Sends the initial messages for a new organization. # diff --git a/zerver/lib/retention.py b/zerver/lib/retention.py index f18554811a..f64a185606 100644 --- a/zerver/lib/retention.py +++ b/zerver/lib/retention.py @@ -141,7 +141,7 @@ def run_archiving( message_count = 0 while True: start_time = time.time() - with transaction.atomic(): + with transaction.atomic(savepoint=False): archive_transaction = ArchiveTransaction.objects.create(type=type, realm=realm) new_chunk = move_rows( Message, @@ -396,6 +396,7 @@ def archive_stream_messages( logger.info("Done. Archived %s messages.", message_count) +@transaction.atomic(durable=True) def archive_messages(chunk_size: int = MESSAGE_BATCH_SIZE) -> None: logger.info("Starting the archiving process with chunk_size %s", chunk_size) @@ -595,7 +596,7 @@ def restore_data_from_archive(archive_transaction: ArchiveTransaction) -> int: # so that when we log "Finished", the process has indeed finished - and that happens only after # leaving the atomic block - Django does work committing the changes to the database when # the block ends. - with transaction.atomic(): + with transaction.atomic(durable=True): msg_ids = restore_messages_from_archive(archive_transaction.id) restore_models_with_message_key_from_archive(archive_transaction.id) restore_attachments_from_archive(archive_transaction.id) diff --git a/zerver/lib/send_email.py b/zerver/lib/send_email.py index 8803672843..348e192565 100644 --- a/zerver/lib/send_email.py +++ b/zerver/lib/send_email.py @@ -378,7 +378,7 @@ def send_future_email( # For logging the email assert (to_user_ids is None) ^ (to_emails is None) - with transaction.atomic(): + with transaction.atomic(savepoint=False): email = ScheduledEmail.objects.create( type=EMAIL_TYPES[template_name], scheduled_timestamp=timezone_now() + delay, diff --git a/zerver/lib/soft_deactivation.py b/zerver/lib/soft_deactivation.py index 7dae6442e7..b8580d7d24 100644 --- a/zerver/lib/soft_deactivation.py +++ b/zerver/lib/soft_deactivation.py @@ -277,7 +277,7 @@ def do_soft_deactivate_users( (user_batch, users) = (users[0:BATCH_SIZE], users[BATCH_SIZE:]) if len(user_batch) == 0: break - with transaction.atomic(): + with transaction.atomic(durable=True): realm_logs = [] for user in user_batch: do_soft_deactivate_user(user) diff --git a/zerver/lib/storage.py b/zerver/lib/storage.py index 956b62ed96..6519eb2b24 100644 --- a/zerver/lib/storage.py +++ b/zerver/lib/storage.py @@ -10,7 +10,7 @@ from django.core.files.base import File from django.core.files.storage import FileSystemStorage from typing_extensions import override -from zerver.lib.avatar import SYSTEM_BOTS_AVATAR_FILES +from zerver.lib.avatar import STATIC_AVATARS_DIR if settings.DEBUG: from django.contrib.staticfiles.finders import find @@ -25,6 +25,58 @@ else: class IgnoreBundlesManifestStaticFilesStorage(ManifestStaticFilesStorage): + def process_static_avatars_name( + self, + name: str, + content: Optional["File[bytes]"] = None, + filename: str | None = None, + ) -> str: + """ + Because the protocol for getting medium-size avatar URLs + was never fully documented, the mobile apps use a + substitution of the form s/.png/-medium.png/ to get the + medium-size avatar URLs. + + This function hashes system bots' avatar files in a way + that follows the pattern used for user-uploaded avatars. + + It ensures the following: + + * Hashed filenames for system bot avatars follow this + naming convention: + - avatar.png -> avatar-medium.png + + * The system bots' default avatar file and its medium + version share the same hash: + - bot.36f721bad3d0.png -> bot.36f721bad3d0-medium.png + """ + + def reformat_medium_filename(hashed_name: str) -> str: + name_parts = hashed_name.rsplit(".", 1) + base_name = name_parts[0] + + if len(name_parts) != 2 or "-medium" not in base_name: + return hashed_name + extension = name_parts[1].replace("png", "medium.png") + base_name = base_name.replace("-medium", "") + return f"{base_name}-{extension}" + + if name.endswith("-medium.png"): + hashed_medium_file = reformat_medium_filename( + super().hashed_name(name, content, filename) + ) + return hashed_medium_file + else: + medium_name = name.replace(".png", "-medium.png") + from django.core.files import File + + with File(open(self.path(medium_name), "rb")) as medium_content: + hashed_medium_file = reformat_medium_filename( + super().hashed_name(medium_name, medium_content, filename) + ) + hashed_default_file = hashed_medium_file.replace("-medium.png", ".png") + return hashed_default_file + @override def hashed_name( self, name: str, content: Optional["File[bytes]"] = None, filename: str | None = None @@ -40,12 +92,13 @@ class IgnoreBundlesManifestStaticFilesStorage(ManifestStaticFilesStorage): # use a no-op hash function for these already-hashed # assets. return name - if name in SYSTEM_BOTS_AVATAR_FILES.values(): + if name.startswith(STATIC_AVATARS_DIR): # For these avatar files, we want to make sure they are # so they can hit our Nginx caching block for static files. - # We don't need to worry about stale caches since system bot - # avatars rarely change. - return super().hashed_name(name, content, filename) + # We don't need to worry about stale caches since these are + # only used by the system bots. + return self.process_static_avatars_name(name, content, filename) + if name == "generated/emoji/emoji_api.json": # Unlike most .json files, we do want to hash this file; # its hashed URL is returned as part of the API. See diff --git a/zerver/lib/streams.py b/zerver/lib/streams.py index 275f596552..7c9be859d8 100644 --- a/zerver/lib/streams.py +++ b/zerver/lib/streams.py @@ -263,7 +263,10 @@ def check_stream_access_based_on_stream_post_policy(sender: UserProfile, stream: def access_stream_for_send_message( - sender: UserProfile, stream: Stream, forwarder_user_profile: UserProfile | None + sender: UserProfile, + stream: Stream, + forwarder_user_profile: UserProfile | None, + archived_channel_notice: bool = False, ) -> None: # Our caller is responsible for making sure that `stream` actually # matches the realm of the sender. @@ -287,6 +290,14 @@ def access_stream_for_send_message( else: raise JsonableError(_("User not authorized for this query")) + # Deactivated streams are not accessible. + if stream.deactivated: + if archived_channel_notice: + return + raise JsonableError( + _("Not authorized to send to channel '{channel_name}'").format(channel_name=stream.name) + ) + if is_cross_realm_bot_email(sender.delivery_email): return @@ -425,7 +436,9 @@ def access_stream_common( except Subscription.DoesNotExist: sub = None - if check_basic_stream_access(user_profile, stream, sub, allow_realm_admin=allow_realm_admin): + if not stream.deactivated and check_basic_stream_access( + user_profile, stream, sub, allow_realm_admin=allow_realm_admin + ): return sub # Otherwise it is a private stream and you're not on it, so throw @@ -661,6 +674,11 @@ def filter_stream_authorization( unauthorized_streams: list[Stream] = [] for stream in streams: + # Deactivated streams are not accessible + if stream.deactivated: + unauthorized_streams.append(stream) + continue + # The user is authorized for their own streams if stream.recipient_id in subscribed_recipient_ids: continue @@ -817,11 +835,6 @@ def get_stream_by_narrow_operand_access_unchecked(operand: str | int, realm: Rea return get_stream_by_id_in_realm(operand, realm) -def get_signups_stream(realm: Realm) -> Stream: - # This one-liner helps us work around a lint rule. - return get_stream("signups", realm) - - def ensure_stream( realm: Realm, stream_name: str, @@ -872,6 +885,7 @@ def stream_to_dict(stream: Stream, recent_traffic: dict[int, int] | None = None) stream_weekly_traffic = None return APIStreamDict( + is_archived=stream.deactivated, can_remove_subscribers_group=stream.can_remove_subscribers_group_id, creator_id=stream.creator_id, date_created=datetime_to_timestamp(stream.date_created), @@ -902,6 +916,7 @@ def get_streams_for_user( include_public: bool = True, include_web_public: bool = False, include_subscribed: bool = True, + exclude_archived: bool = True, include_all_active: bool = False, include_owner_subscribed: bool = False, ) -> list[Stream]: @@ -910,8 +925,11 @@ def get_streams_for_user( include_public = include_public and user_profile.can_access_public_streams() - # Start out with all active streams in the realm. - query = Stream.objects.filter(realm=user_profile.realm, deactivated=False) + # Start out with all streams in the realm. + query = Stream.objects.filter(realm=user_profile.realm) + + if exclude_archived: + query = query.filter(deactivated=False) if include_all_active: streams = query.only(*Stream.API_FIELDS) @@ -965,6 +983,7 @@ def do_get_streams( include_public: bool = True, include_web_public: bool = False, include_subscribed: bool = True, + exclude_archived: bool = True, include_all_active: bool = False, include_default: bool = False, include_owner_subscribed: bool = False, @@ -976,6 +995,7 @@ def do_get_streams( include_public, include_web_public, include_subscribed, + exclude_archived, include_all_active, include_owner_subscribed, ) diff --git a/zerver/lib/subscription_info.py b/zerver/lib/subscription_info.py index a6b13de712..c21290b868 100644 --- a/zerver/lib/subscription_info.py +++ b/zerver/lib/subscription_info.py @@ -27,7 +27,7 @@ from zerver.lib.types import ( SubscriptionStreamDict, ) from zerver.models import Realm, Stream, Subscription, UserProfile -from zerver.models.streams import get_active_streams +from zerver.models.streams import get_all_streams def get_web_public_subs(realm: Realm) -> SubscriptionInfo: @@ -42,6 +42,7 @@ def get_web_public_subs(realm: Realm) -> SubscriptionInfo: subscribed = [] for stream in get_web_public_streams_queryset(realm): # Add Stream fields. + is_archived = stream.deactivated can_remove_subscribers_group_id = stream.can_remove_subscribers_group_id creator_id = stream.creator_id date_created = datetime_to_timestamp(stream.date_created) @@ -73,6 +74,7 @@ def get_web_public_subs(realm: Realm) -> SubscriptionInfo: wildcard_mentions_notify = True sub = SubscriptionStreamDict( + is_archived=is_archived, audible_notifications=audible_notifications, can_remove_subscribers_group=can_remove_subscribers_group_id, color=color, @@ -115,6 +117,7 @@ def build_unsubscribed_sub_from_stream_dict( can_remove_subscribers_group_id=stream_dict["can_remove_subscribers_group"], creator_id=stream_dict["creator_id"], date_created=timestamp_to_datetime(stream_dict["date_created"]), + deactivated=stream_dict["is_archived"], description=stream_dict["description"], first_message_id=stream_dict["first_message_id"], history_public_to_subscribers=stream_dict["history_public_to_subscribers"], @@ -144,6 +147,7 @@ def build_stream_dict_for_sub( recent_traffic: dict[int, int] | None, ) -> SubscriptionStreamDict: # Handle Stream.API_FIELDS + is_archived = raw_stream_dict["deactivated"] can_remove_subscribers_group_id = raw_stream_dict["can_remove_subscribers_group_id"] creator_id = raw_stream_dict["creator_id"] date_created = datetime_to_timestamp(raw_stream_dict["date_created"]) @@ -187,6 +191,7 @@ def build_stream_dict_for_sub( # Our caller may add a subscribers field. return SubscriptionStreamDict( + is_archived=is_archived, audible_notifications=audible_notifications, can_remove_subscribers_group=can_remove_subscribers_group_id, color=color, @@ -218,6 +223,7 @@ def build_stream_dict_for_never_sub( raw_stream_dict: RawStreamDict, recent_traffic: dict[int, int] | None, ) -> NeverSubscribedStreamDict: + is_archived = raw_stream_dict["deactivated"] can_remove_subscribers_group_id = raw_stream_dict["can_remove_subscribers_group_id"] creator_id = raw_stream_dict["creator_id"] date_created = datetime_to_timestamp(raw_stream_dict["date_created"]) @@ -244,6 +250,7 @@ def build_stream_dict_for_never_sub( # Our caller may add a subscribers field. return NeverSubscribedStreamDict( + is_archived=is_archived, can_remove_subscribers_group=can_remove_subscribers_group_id, creator_id=creator_id, date_created=date_created, @@ -447,9 +454,12 @@ def has_metadata_access_to_previously_subscribed_stream( def gather_subscriptions_helper( user_profile: UserProfile, include_subscribers: bool = True, + include_archived_channels: bool = False, ) -> SubscriptionInfo: realm = user_profile.realm - all_streams = get_active_streams(realm).values( + all_streams = get_all_streams( + realm, include_archived_channels=include_archived_channels + ).values( *Stream.API_FIELDS, # The realm_id and recipient_id are generally not needed in the API. "realm_id", @@ -517,7 +527,9 @@ def gather_subscriptions_helper( unsubscribed.append(stream_dict) if user_profile.can_access_public_streams(): - never_subscribed_stream_ids = set(all_streams_map) - sub_unsub_stream_ids + never_subscribed_stream_ids = { + stream["id"] for stream in all_streams if not stream["deactivated"] + } - sub_unsub_stream_ids else: web_public_stream_ids = {stream["id"] for stream in all_streams if stream["is_web_public"]} never_subscribed_stream_ids = web_public_stream_ids - sub_unsub_stream_ids diff --git a/zerver/lib/test_classes.py b/zerver/lib/test_classes.py index b5eb1deec4..fe935b2082 100644 --- a/zerver/lib/test_classes.py +++ b/zerver/lib/test_classes.py @@ -72,6 +72,7 @@ from zerver.lib.test_console_output import ( from zerver.lib.test_helpers import ( cache_tries_captured, find_key_by_email, + get_test_image_file, instrument_url, queries_captured, ) @@ -2199,6 +2200,20 @@ class ZulipTestCase(ZulipTestCaseMixin, TestCase): ) return message_id + def upload_image(self, image_name: str) -> str: + with get_test_image_file(image_name) as image_file: + response = self.assert_json_success( + self.client_post("/json/user_uploads", {"file": image_file}) + ) + return re.sub(r"/user_uploads/", "", response["url"]) + + def upload_and_thumbnail_image(self, image_name: str) -> str: + with self.captureOnCommitCallbacks(execute=True): + # Running captureOnCommitCallbacks includes inserting into + # the Rabbitmq queue, which in testing means we + # immediately run the worker for it, producing the thumbnails. + return self.upload_image(image_name) + def get_row_ids_in_all_tables() -> Iterator[tuple[str, set[int]]]: all_models = apps.get_models(include_auto_created=True) diff --git a/zerver/lib/test_helpers.py b/zerver/lib/test_helpers.py index d5b5f375a5..b17169b9f6 100644 --- a/zerver/lib/test_helpers.py +++ b/zerver/lib/test_helpers.py @@ -603,8 +603,8 @@ def use_s3_backend(method: Callable[P, None]) -> Callable[P, None]: def new_method(*args: P.args, **kwargs: P.kwargs) -> None: backend = S3UploadBackend() with ( - mock.patch("zerver.lib.upload.upload_backend", backend), mock.patch("zerver.worker.thumbnail.upload_backend", backend), + mock.patch("zerver.lib.upload.upload_backend", backend), mock.patch("zerver.views.tusd.upload_backend", backend), ): return method(*args, **kwargs) diff --git a/zerver/lib/thumbnail.py b/zerver/lib/thumbnail.py index 4bba1ee9dd..ce565d5817 100644 --- a/zerver/lib/thumbnail.py +++ b/zerver/lib/thumbnail.py @@ -14,7 +14,7 @@ from typing_extensions import override from zerver.lib.exceptions import ErrorCode, JsonableError from zerver.lib.queue import queue_event_on_commit -from zerver.models import AbstractAttachment, ImageAttachment +from zerver.models import ImageAttachment DEFAULT_AVATAR_SIZE = 100 MEDIUM_AVATAR_SIZE = 500 @@ -279,9 +279,9 @@ def missing_thumbnails(image_attachment: ImageAttachment) -> list[ThumbnailForma def maybe_thumbnail( - attachment: AbstractAttachment, content: bytes | pyvips.Source + content: bytes | pyvips.Source, content_type: str | None, path_id: str, realm_id: int ) -> ImageAttachment | None: - if attachment.content_type not in THUMBNAIL_ACCEPT_IMAGE_TYPES: + if content_type not in THUMBNAIL_ACCEPT_IMAGE_TYPES: # If it doesn't self-report as an image file that we might want # to thumbnail, don't parse the bytes at all. return None @@ -301,8 +301,8 @@ def maybe_thumbnail( (width, height) = (image.width, image.height) image_row = ImageAttachment.objects.create( - realm_id=attachment.realm_id, - path_id=attachment.path_id, + realm_id=realm_id, + path_id=path_id, original_width_px=width, original_height_px=height, frames=image.get_n_pages(), diff --git a/zerver/lib/types.py b/zerver/lib/types.py index 6937efeee9..f6af9f30fc 100644 --- a/zerver/lib/types.py +++ b/zerver/lib/types.py @@ -144,6 +144,7 @@ class RawStreamDict(TypedDict): can_remove_subscribers_group_id: int creator_id: int | None date_created: datetime + deactivated: bool description: str first_message_id: int | None history_public_to_subscribers: bool @@ -193,6 +194,7 @@ class SubscriptionStreamDict(TypedDict): in_home_view: bool invite_only: bool is_announcement_only: bool + is_archived: bool is_muted: bool is_web_public: bool message_retention_days: int | None @@ -208,6 +210,7 @@ class SubscriptionStreamDict(TypedDict): class NeverSubscribedStreamDict(TypedDict): + is_archived: bool can_remove_subscribers_group: int creator_id: int | None date_created: int @@ -232,6 +235,7 @@ class DefaultStreamDict(TypedDict): with few exceptions and possible additional fields. """ + is_archived: bool can_remove_subscribers_group: int creator_id: int | None date_created: int diff --git a/zerver/lib/upload/__init__.py b/zerver/lib/upload/__init__.py index e1e5d83fa5..84a61897a9 100644 --- a/zerver/lib/upload/__init__.py +++ b/zerver/lib/upload/__init__.py @@ -70,7 +70,7 @@ def create_attachment( size=file_size, content_type=content_type, ) - maybe_thumbnail(attachment, file_real_data) + maybe_thumbnail(file_real_data, content_type, path_id, realm.id) from zerver.actions.uploads import notify_attachment_update notify_attachment_update(user_profile, "add", attachment.to_dict()) @@ -158,7 +158,7 @@ def upload_message_attachment( str(target_realm.id), sanitize_name(uploaded_file_name) ) - with transaction.atomic(): + with transaction.atomic(durable=True): upload_backend.upload_message_attachment( path_id, uploaded_file_name, diff --git a/zerver/lib/users.py b/zerver/lib/users.py index c5e1a9d68e..082dac8750 100644 --- a/zerver/lib/users.py +++ b/zerver/lib/users.py @@ -130,7 +130,9 @@ def check_valid_bot_config( for integration in WEBHOOK_INTEGRATIONS: if integration.name == service_name: # key: validator - config_options = {c[1]: c[2] for c in integration.config_options} + config_options = { + option.name: option.validator for option in integration.config_options + } break if not config_options: raise JsonableError( diff --git a/zerver/lib/webhooks/common.py b/zerver/lib/webhooks/common.py index a95884468a..5e8d146caf 100644 --- a/zerver/lib/webhooks/common.py +++ b/zerver/lib/webhooks/common.py @@ -1,6 +1,7 @@ import fnmatch import importlib from collections.abc import Callable +from dataclasses import dataclass from datetime import datetime from typing import Annotated, Any, TypeAlias from urllib.parse import unquote @@ -49,6 +50,13 @@ SETUP_MESSAGE_USER_PART = " by {user_name}" OptionalUserSpecifiedTopicStr: TypeAlias = Annotated[str | None, ApiParamConfig("topic")] +@dataclass +class WebhookConfigOption: + name: str + description: str + validator: Callable[[str, str], str | bool | None] + + def get_setup_webhook_message(integration: str, user_name: str | None = None) -> str: content = SETUP_MESSAGE_TEMPLATE.format(integration=integration) if user_name: diff --git a/zerver/lib/webhooks/git.py b/zerver/lib/webhooks/git.py index 660719b789..8a15e24b0c 100644 --- a/zerver/lib/webhooks/git.py +++ b/zerver/lib/webhooks/git.py @@ -58,10 +58,7 @@ ISSUE_LABELED_OR_UNLABELED_MESSAGE_TEMPLATE_WITH_TITLE = "[{user_name}]({user_ur ISSUE_MILESTONED_OR_DEMILESTONED_MESSAGE_TEMPLATE = "[{user_name}]({user_url}) {action} milestone [{milestone_name}]({milestone_url}) {preposition} [issue #{id}]({url})." ISSUE_MILESTONED_OR_DEMILESTONED_MESSAGE_TEMPLATE_WITH_TITLE = "[{user_name}]({user_url}) {action} milestone [{milestone_name}]({milestone_url}) {preposition} [issue #{id} {title}]({url})." -PULL_REQUEST_OR_ISSUE_MESSAGE_TEMPLATE = "{user_name} {action} [{type}{id}]({url})" -PULL_REQUEST_OR_ISSUE_MESSAGE_TEMPLATE_WITH_TITLE = ( - "{user_name} {action} [{type}{id} {title}]({url})" -) +PULL_REQUEST_OR_ISSUE_MESSAGE_TEMPLATE = "{user_name} {action}{assignee} [{type}{id}{title}]({url})" PULL_REQUEST_OR_ISSUE_ASSIGNEE_INFO_TEMPLATE = "(assigned to {assignee})" PULL_REQUEST_REVIEWER_INFO_TEMPLATE = "(assigned reviewers: {reviewer})" PULL_REQUEST_BRANCH_INFO_TEMPLATE = "from `{target}` to `{base}`" @@ -204,6 +201,7 @@ def get_pull_request_event_message( message: str | None = None, assignee: str | None = None, assignees: list[dict[str, Any]] | None = None, + assignee_updated: str | None = None, reviewer: str | None = None, type: str = "PR", title: str | None = None, @@ -214,13 +212,14 @@ def get_pull_request_event_message( "type": type, "url": url, "id": f" #{number}" if number is not None else "", - "title": title, + "title": f" {title}" if title is not None else "", + "assignee": { + "assigned": f" {assignee_updated} to", + "unassigned": f" {assignee_updated} from", + }.get(action, ""), } - if title is not None: - main_message = PULL_REQUEST_OR_ISSUE_MESSAGE_TEMPLATE_WITH_TITLE.format(**kwargs) - else: - main_message = PULL_REQUEST_OR_ISSUE_MESSAGE_TEMPLATE.format(**kwargs) + main_message = PULL_REQUEST_OR_ISSUE_MESSAGE_TEMPLATE.format(**kwargs) if target_branch and base_branch: branch_info = PULL_REQUEST_BRANCH_INFO_TEMPLATE.format( @@ -270,6 +269,7 @@ def get_issue_event_message( message: str | None = None, assignee: str | None = None, assignees: list[dict[str, Any]] | None = None, + assignee_updated: str | None = None, title: str | None = None, ) -> str: return get_pull_request_event_message( @@ -280,6 +280,7 @@ def get_issue_event_message( message=message, assignee=assignee, assignees=assignees, + assignee_updated=assignee_updated, type="issue", title=title, ) diff --git a/zerver/lib/zulip_update_announcements.py b/zerver/lib/zulip_update_announcements.py index 1c9c83dc2c..ab456f62cf 100644 --- a/zerver/lib/zulip_update_announcements.py +++ b/zerver/lib/zulip_update_announcements.py @@ -187,6 +187,40 @@ feature highlights in Zulip Server 9.0, and other Zulip project updates. blog_post_9_0_url="https://blog.zulip.com/zulip-server-9-0", ), ), + ZulipUpdateAnnouncement( + level=9, + message=( + ( + """ +- You can now [upload large files]({file_upload_limits_help_url}) up to + 1 GB in organizations on Zulip Cloud + Standard or Zulip Cloud Plus [plans]({cloud_plans_url}). +""" + if settings.CORPORATE_ENABLED + else """ +- You can now [upload large files]({file_upload_limits_help_url}), up to + the limit configured by your server's administrator (currently {max_file_upload_size} MB). +""" + ) + + """ + +**Web and desktop updates** +- You can now start a new conversation from the left sidebar. Click the `+` +button next to the name of a channel to [start a new +topic]({how_to_start_a_new_topic_help_url}) in that channel, or the `+` next to +DIRECT MESSAGES to [start a DM]({starting_a_new_direct_message_help_url}). +- The [user list]({user_list_help_url}) now shows recent participants in the + conversation you're viewing. +""" + ).format( + how_to_start_a_new_topic_help_url="/help/introduction-to-topics#how-to-start-a-new-topic", + starting_a_new_direct_message_help_url="/help/starting-a-new-direct-message", + user_list_help_url="/help/user-list", + cloud_plans_url="/plans/", + file_upload_limits_help_url="/help/share-and-upload-files#file-upload-limits", + max_file_upload_size=settings.MAX_FILE_UPLOAD_SIZE, + ), + ), ] diff --git a/zerver/management/commands/change_user_role.py b/zerver/management/commands/change_user_role.py index e0c49256b1..b8ff9518e5 100644 --- a/zerver/management/commands/change_user_role.py +++ b/zerver/management/commands/change_user_role.py @@ -6,6 +6,7 @@ from django.core.management.base import CommandError from typing_extensions import override from zerver.actions.users import ( + do_change_can_change_user_emails, do_change_can_create_users, do_change_can_forge_sender, do_change_is_billing_admin, @@ -23,6 +24,7 @@ ROLE_CHOICES = [ "guest", "can_forge_sender", "can_create_users", + "can_change_user_emails", "is_billing_admin", ] @@ -65,7 +67,12 @@ ONLY perform this on customer request from an authorized person. "guest": UserProfile.ROLE_GUEST, } - if options["new_role"] not in ["can_forge_sender", "can_create_users", "is_billing_admin"]: + if options["new_role"] not in [ + "can_forge_sender", + "can_create_users", + "can_change_user_emails", + "is_billing_admin", + ]: new_role = user_role_map[options["new_role"]] if not options["grant"]: raise CommandError( @@ -109,6 +116,12 @@ ONLY perform this on customer request from an authorized person. elif not user.can_create_users and not options["grant"]: raise CommandError("User can't create users for this realm.") do_change_can_create_users(user, options["grant"]) + elif options["new_role"] == "can_change_user_emails": + if user.can_change_user_emails and options["grant"]: + raise CommandError("User can already change user emails for this realm.") + elif not user.can_change_user_emails and not options["grant"]: + raise CommandError("User can't change user emails for this realm.") + do_change_can_change_user_emails(user, options["grant"]) else: assert options["new_role"] == "is_billing_admin" if user.is_billing_admin and options["grant"]: diff --git a/zerver/management/commands/deliver_scheduled_emails.py b/zerver/management/commands/deliver_scheduled_emails.py index 66e1b44143..a7a3f2824d 100644 --- a/zerver/management/commands/deliver_scheduled_emails.py +++ b/zerver/management/commands/deliver_scheduled_emails.py @@ -37,7 +37,7 @@ Usage: ./manage.py deliver_scheduled_emails def handle(self, *args: Any, **options: Any) -> None: try: while True: - with transaction.atomic(): + with transaction.atomic(durable=True): job = ( ScheduledEmail.objects.filter(scheduled_timestamp__lte=timezone_now()) .prefetch_related("users") diff --git a/zerver/management/commands/send_custom_email.py b/zerver/management/commands/send_custom_email.py index 1425517903..766d595906 100644 --- a/zerver/management/commands/send_custom_email.py +++ b/zerver/management/commands/send_custom_email.py @@ -85,6 +85,10 @@ class Command(ZulipBaseCommand): "--from-name", help="From line for the email. It can be declared in Markdown file in headers", ) + parser.add_argument( + "--from-address", + help="From email address", + ) parser.add_argument("--reply-to", help="Optional reply-to line for the email") parser.add_argument( diff --git a/zerver/management/commands/send_zulip_update_announcements.py b/zerver/management/commands/send_zulip_update_announcements.py index 350883cfd8..fd3b9cdec4 100644 --- a/zerver/management/commands/send_zulip_update_announcements.py +++ b/zerver/management/commands/send_zulip_update_announcements.py @@ -1,10 +1,12 @@ from argparse import ArgumentParser from typing import Any +from django.conf import settings from typing_extensions import override from zerver.lib.management import ZulipBaseCommand, abort_unless_locked from zerver.lib.zulip_update_announcements import send_zulip_update_announcements +from zerver.models import Realm class Command(ZulipBaseCommand): @@ -17,8 +19,19 @@ class Command(ZulipBaseCommand): action="store_true", help="Immediately send updates if 'zulip_update_announcements_stream' is configured.", ) + parser.add_argument( + "--reset-level", + type=int, + help="The level to reset all active realms to.", + ) @override @abort_unless_locked def handle(self, *args: Any, **options: Any) -> None: + if options["reset_level"] is not None: + Realm.objects.filter(deactivated=False).exclude( + string_id=settings.SYSTEM_BOT_REALM + ).update(zulip_update_announcements_level=options["reset_level"]) + return + send_zulip_update_announcements(skip_delay=options["skip_delay"]) diff --git a/zerver/management/commands/sync_ldap_user_data.py b/zerver/management/commands/sync_ldap_user_data.py index 5b885e364d..0f4f14f57b 100644 --- a/zerver/management/commands/sync_ldap_user_data.py +++ b/zerver/management/commands/sync_ldap_user_data.py @@ -19,7 +19,7 @@ log_to_file(logger, settings.LDAP_SYNC_LOG_PATH) # Run this on a cron job to pick up on name changes. -@transaction.atomic +@transaction.atomic(durable=True) def sync_ldap_user_data( user_profiles: QuerySet[UserProfile], deactivation_protection: bool = True ) -> None: diff --git a/zerver/migrations/0001_squashed_0569.py b/zerver/migrations/0001_squashed_0569.py index 346f64db5d..3c323e9dbc 100644 --- a/zerver/migrations/0001_squashed_0569.py +++ b/zerver/migrations/0001_squashed_0569.py @@ -30,6 +30,15 @@ class LegacyCommonMessagePolicyEnum(IntEnum): EVERYONE = 5 +class LegacyEditTopicPolicyEnum(IntEnum): + MEMBERS_ONLY = 1 + ADMINS_ONLY = 2 + FULL_MEMBERS_ONLY = 3 + MODERATORS_ONLY = 4 + EVERYONE = 5 + NOBODY = 6 + + def get_fts_sql() -> str: if settings.POSTGRESQL_MISSING_DICTIONARIES: fts_sql = """ @@ -743,9 +752,7 @@ class Migration(migrations.Migration): ), ( "edit_topic_policy", - models.PositiveSmallIntegerField( - default=zerver.models.realms.EditTopicPolicyEnum["EVERYONE"] - ), + models.PositiveSmallIntegerField(default=LegacyEditTopicPolicyEnum["EVERYONE"]), ), ( "invite_to_realm_policy", diff --git a/zerver/migrations/0576_backfill_imageattachment.py b/zerver/migrations/0576_backfill_imageattachment.py index 80d2d8116f..9bcfaac358 100644 --- a/zerver/migrations/0576_backfill_imageattachment.py +++ b/zerver/migrations/0576_backfill_imageattachment.py @@ -77,7 +77,8 @@ def backfill_imageattachment(apps: StateApps, schema_editor: BaseDatabaseSchemaE source: pyvips.Source = pyvips.SourceCustom() source.on_read(partial(s3_read, metadata["Body"])) else: - attachment_path = os.path.join(settings.LOCAL_UPLOADS_DIR, attachment.path_id) + assert settings.LOCAL_FILES_DIR is not None + attachment_path = os.path.join(settings.LOCAL_FILES_DIR, attachment.path_id) if not os.path.exists(attachment_path): print(f"{attachment.path_id}: Missing!") continue diff --git a/zerver/migrations/0616_userprofile_can_change_user_emails.py b/zerver/migrations/0616_userprofile_can_change_user_emails.py new file mode 100644 index 0000000000..b29d1ca843 --- /dev/null +++ b/zerver/migrations/0616_userprofile_can_change_user_emails.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.8 on 2024-09-02 20:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("zerver", "0615_system_bot_avatars"), + ] + + operations = [ + migrations.AddField( + model_name="userprofile", + name="can_change_user_emails", + field=models.BooleanField(db_index=True, default=False), + ), + ] diff --git a/zerver/migrations/0617_remove_prefix_from_archived_streams.py b/zerver/migrations/0617_remove_prefix_from_archived_streams.py new file mode 100644 index 0000000000..0083516fbd --- /dev/null +++ b/zerver/migrations/0617_remove_prefix_from_archived_streams.py @@ -0,0 +1,58 @@ +import hashlib + +from django.db import migrations +from django.db.backends.base.schema import BaseDatabaseSchemaEditor +from django.db.migrations.state import StateApps + + +def remove_prefix_from_archived_streams( + apps: StateApps, schema_editor: BaseDatabaseSchemaEditor +) -> None: + Stream = apps.get_model("zerver", "Stream") + archived_streams = Stream.objects.filter(deactivated=True) + + for archived_stream in archived_streams: + old_prefix = "!DEACTIVATED:" + streamID = str(archived_stream.id) + stream_id_hash_object = hashlib.sha512(streamID.encode()) + hashed_stream_id = stream_id_hash_object.hexdigest()[0:7] + prefix = hashed_stream_id + old_prefix + prefix_length = len(prefix) + old_name = archived_stream.name + new_name = old_name + if old_name.startswith(prefix): + new_name = old_name[prefix_length:] + + # Check for archived streams before commit 1b6f68b. + elif old_prefix in old_name: + prefix_end_index = old_name.find(old_prefix) + len(old_prefix) + new_name = old_name[prefix_end_index:] + + else: + continue + + # Check if there's an active stream or another archived stream with the new name + if not Stream.objects.filter( + realm_id=archived_stream.realm_id, name__iexact=new_name + ).exists(): + archived_stream.name = new_name + archived_stream.save(update_fields=["name"]) + + +class Migration(migrations.Migration): + atomic = False + + dependencies = [ + ( + "zerver", + "0616_userprofile_can_change_user_emails", + ), + ] + + operations = [ + migrations.RunPython( + remove_prefix_from_archived_streams, + reverse_code=migrations.RunPython.noop, + elidable=True, + ), + ] diff --git a/zerver/migrations/0618_realm_can_move_messages_between_topics_group.py b/zerver/migrations/0618_realm_can_move_messages_between_topics_group.py new file mode 100644 index 0000000000..95a082159a --- /dev/null +++ b/zerver/migrations/0618_realm_can_move_messages_between_topics_group.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.9 on 2024-10-25 14:21 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("zerver", "0617_remove_prefix_from_archived_streams"), + ] + + operations = [ + migrations.AddField( + model_name="realm", + name="can_move_messages_between_topics_group", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.RESTRICT, + related_name="+", + to="zerver.usergroup", + ), + ), + ] diff --git a/zerver/migrations/0619_set_default_value_for_can_move_messages_between_topics_group.py b/zerver/migrations/0619_set_default_value_for_can_move_messages_between_topics_group.py new file mode 100644 index 0000000000..f0c5ca9993 --- /dev/null +++ b/zerver/migrations/0619_set_default_value_for_can_move_messages_between_topics_group.py @@ -0,0 +1,47 @@ +# Generated by Django 4.2.1 on 2023-06-12 10:47 + +from django.db import migrations +from django.db.backends.base.schema import BaseDatabaseSchemaEditor +from django.db.migrations.state import StateApps +from django.db.models import OuterRef + + +def set_default_value_for_can_move_messages_between_topics_group( + apps: StateApps, schema_editor: BaseDatabaseSchemaEditor +) -> None: + Realm = apps.get_model("zerver", "Realm") + NamedUserGroup = apps.get_model("zerver", "NamedUserGroup") + + edit_topic_policy_to_group_name = { + 1: "role:members", + 2: "role:administrators", + 3: "role:fullmembers", + 4: "role:moderators", + 5: "role:everyone", + 6: "role:nobody", + } + + for id, group_name in edit_topic_policy_to_group_name.items(): + Realm.objects.filter( + can_move_messages_between_topics_group=None, edit_topic_policy=id + ).update( + can_move_messages_between_topics_group=NamedUserGroup.objects.filter( + name=group_name, realm=OuterRef("id"), is_system_group=True + ).values("pk") + ) + + +class Migration(migrations.Migration): + atomic = False + + dependencies = [ + ("zerver", "0618_realm_can_move_messages_between_topics_group"), + ] + + operations = [ + migrations.RunPython( + set_default_value_for_can_move_messages_between_topics_group, + elidable=True, + reverse_code=migrations.RunPython.noop, + ) + ] diff --git a/zerver/migrations/0620_alter_realm_can_move_messages_between_topics_group.py b/zerver/migrations/0620_alter_realm_can_move_messages_between_topics_group.py new file mode 100644 index 0000000000..1870e8d00e --- /dev/null +++ b/zerver/migrations/0620_alter_realm_can_move_messages_between_topics_group.py @@ -0,0 +1,22 @@ +# Generated by Django 5.0.9 on 2024-10-25 14:25 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("zerver", "0619_set_default_value_for_can_move_messages_between_topics_group"), + ] + + operations = [ + migrations.AlterField( + model_name="realm", + name="can_move_messages_between_topics_group", + field=models.ForeignKey( + on_delete=django.db.models.deletion.RESTRICT, + related_name="+", + to="zerver.usergroup", + ), + ), + ] diff --git a/zerver/migrations/0621_remove_realm_edit_topic_policy.py b/zerver/migrations/0621_remove_realm_edit_topic_policy.py new file mode 100644 index 0000000000..6cc29718cf --- /dev/null +++ b/zerver/migrations/0621_remove_realm_edit_topic_policy.py @@ -0,0 +1,16 @@ +# Generated by Django 5.0.9 on 2024-10-27 16:28 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("zerver", "0620_alter_realm_can_move_messages_between_topics_group"), + ] + + operations = [ + migrations.RemoveField( + model_name="realm", + name="edit_topic_policy", + ), + ] diff --git a/zerver/migrations/0622_backfill_imageattachment_again.py b/zerver/migrations/0622_backfill_imageattachment_again.py new file mode 100644 index 0000000000..fcc6f3dbce --- /dev/null +++ b/zerver/migrations/0622_backfill_imageattachment_again.py @@ -0,0 +1,131 @@ +# Duplicate of database migration 0576, because the original used the +# wrong path for servers using the local file upload backend, and +# many servers had already upgraded to 9.2 where it was backported. + +import os +from functools import reduce +from operator import or_ + +import boto3 +import pyvips +from botocore.client import Config +from botocore.exceptions import ClientError +from botocore.response import StreamingBody +from django.conf import settings +from django.db import migrations +from django.db.backends.base.schema import BaseDatabaseSchemaEditor +from django.db.migrations.state import StateApps +from django.db.models import Exists, OuterRef, Q + +from zerver.lib.partial import partial + + +def backfill_imageattachment(apps: StateApps, schema_editor: BaseDatabaseSchemaEditor) -> None: + ImageAttachment = apps.get_model("zerver", "ImageAttachment") + Attachment = apps.get_model("zerver", "Attachment") + + if settings.LOCAL_UPLOADS_DIR is None: + upload_bucket = boto3.resource( + "s3", + aws_access_key_id=settings.S3_KEY, + aws_secret_access_key=settings.S3_SECRET_KEY, + region_name=settings.S3_REGION, + endpoint_url=settings.S3_ENDPOINT_URL, + config=Config( + signature_version=None, + s3={"addressing_style": settings.S3_ADDRESSING_STYLE}, + ), + ).Bucket(settings.S3_AUTH_UPLOADS_BUCKET) + + # Historical attachments do not have a mime_type value, so we used + # to rely on the file extension. We replicate that when + # backfilling. This is the value from zerver.lib.markdown: + IMAGE_EXTENSIONS = [".bmp", ".gif", ".jpe", ".jpeg", ".jpg", ".png", ".webp"] + + extension_limits = Q() + extension_limits = reduce( + or_, + [Q(file_name__endswith=extension) for extension in IMAGE_EXTENSIONS], + extension_limits, + ) + + min_id: int | None = 0 + while True: + attachments = ( + Attachment.objects.alias( + has_imageattachment=Exists( + ImageAttachment.objects.filter(path_id=OuterRef("path_id")) + ) + ) + .filter(extension_limits, has_imageattachment=False, id__gt=min_id) + .order_by("id") + )[:1000] + + min_id = None + for attachment in attachments: + min_id = attachment.id + + if settings.LOCAL_UPLOADS_DIR is None: + try: + metadata = upload_bucket.Object(attachment.path_id).get() + except ClientError: + print(f"{attachment.path_id}: Missing!") + continue + + def s3_read(streamingbody: StreamingBody, size: int) -> bytes: + return streamingbody.read(amt=size) + + # We use the streaming body to only pull down as much + # of the image as we need to examine the headers -- + # generally about 40k + source: pyvips.Source = pyvips.SourceCustom() + source.on_read(partial(s3_read, metadata["Body"])) + else: + assert settings.LOCAL_FILES_DIR is not None + attachment_path = os.path.join(settings.LOCAL_FILES_DIR, attachment.path_id) + if not os.path.exists(attachment_path): + print(f"{attachment.path_id}: Missing!") + continue + source = pyvips.Source.new_from_file(attachment_path) + try: + image = pyvips.Image.new_from_source(source, "", access="sequential") + + # "original_width_px" and "original_height_px" here are + # _as rendered_, after applying the orientation + # information which the image may contain. + if ( + "orientation" in image.get_fields() + and image.get("orientation") >= 5 + and image.get("orientation") <= 8 + ): + (width, height) = (image.height, image.width) + else: + (width, height) = (image.width, image.height) + + ImageAttachment.objects.create( + realm_id=attachment.realm_id, + path_id=attachment.path_id, + original_width_px=width, + original_height_px=height, + frames=image.get_n_pages(), + thumbnail_metadata=[], + ) + except pyvips.Error: + pass + + if min_id is None: + break + + +class Migration(migrations.Migration): + atomic = False + dependencies = [ + # Because this will be backported to 9.x, we only depend on the last migration in 9.x + ("zerver", "0576_backfill_imageattachment"), + ] + + operations = [ + migrations.RunPython( + backfill_imageattachment, reverse_code=migrations.RunPython.noop, elidable=True + ) + ] diff --git a/zerver/migrations/0623_merge_20241030_1835.py b/zerver/migrations/0623_merge_20241030_1835.py new file mode 100644 index 0000000000..1a4bfe0b9f --- /dev/null +++ b/zerver/migrations/0623_merge_20241030_1835.py @@ -0,0 +1,12 @@ +# Generated by Django 5.0.9 on 2024-10-30 18:35 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("zerver", "0621_remove_realm_edit_topic_policy"), + ("zerver", "0622_backfill_imageattachment_again"), + ] + + operations = [] diff --git a/zerver/models/realms.py b/zerver/models/realms.py index 43cd87785e..8a6ac401ed 100644 --- a/zerver/models/realms.py +++ b/zerver/models/realms.py @@ -106,15 +106,6 @@ class CommonPolicyEnum(IntEnum): MODERATORS_ONLY = 4 -class EditTopicPolicyEnum(IntEnum): - MEMBERS_ONLY = 1 - ADMINS_ONLY = 2 - FULL_MEMBERS_ONLY = 3 - MODERATORS_ONLY = 4 - EVERYONE = 5 - NOBODY = 6 - - class InviteToRealmPolicyEnum(IntEnum): MEMBERS_ONLY = 1 ADMINS_ONLY = 2 @@ -268,8 +259,6 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub field.value for field in CreateWebPublicStreamPolicyEnum ] - EDIT_TOPIC_POLICY_TYPES = [field.value for field in EditTopicPolicyEnum] - DEFAULT_MOVE_MESSAGE_LIMIT_SECONDS = 7 * SECONDS_PER_DAY move_messages_within_stream_limit_seconds = models.PositiveIntegerField( @@ -306,8 +295,10 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub "UserGroup", on_delete=models.RESTRICT, related_name="+" ) - # Who in the organization is allowed to edit topics of any message. - edit_topic_policy = models.PositiveSmallIntegerField(default=EditTopicPolicyEnum.EVERYONE) + # UserGroup which is allowed to move messages between topics. + can_move_messages_between_topics_group = models.ForeignKey( + "UserGroup", on_delete=models.RESTRICT, related_name="+" + ) # Who in the organization is allowed to invite other users to organization. invite_to_realm_policy = models.PositiveSmallIntegerField( @@ -649,7 +640,6 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub digest_emails_enabled=bool, digest_weekday=int, disallow_disposable_email_addresses=bool, - edit_topic_policy=int, email_changes_disabled=bool, emails_restricted_to_domains=bool, enable_guest_user_indicator=bool, @@ -682,7 +672,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub REALM_PERMISSION_GROUP_SETTINGS: dict[str, GroupPermissionSetting] = dict( create_multiuse_invite_group=GroupPermissionSetting( - require_system_group=True, + require_system_group=False, allow_internet_group=False, allow_owners_group=False, allow_nobody_group=True, @@ -701,7 +691,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub allowed_system_groups=[SystemGroups.EVERYONE, SystemGroups.MEMBERS], ), can_add_custom_emoji_group=GroupPermissionSetting( - require_system_group=not settings.ALLOW_GROUP_VALUED_SETTINGS, + require_system_group=False, allow_internet_group=False, allow_owners_group=False, allow_nobody_group=False, @@ -710,7 +700,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub id_field_name="can_add_custom_emoji_group_id", ), can_create_groups=GroupPermissionSetting( - require_system_group=not settings.ALLOW_GROUP_VALUED_SETTINGS, + require_system_group=False, allow_internet_group=False, allow_owners_group=True, allow_nobody_group=False, @@ -719,7 +709,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub id_field_name="can_create_groups_id", ), can_create_public_channel_group=GroupPermissionSetting( - require_system_group=not settings.ALLOW_GROUP_VALUED_SETTINGS, + require_system_group=False, allow_internet_group=False, allow_owners_group=False, allow_nobody_group=False, @@ -728,7 +718,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub id_field_name="can_create_public_channel_group_id", ), can_create_private_channel_group=GroupPermissionSetting( - require_system_group=not settings.ALLOW_GROUP_VALUED_SETTINGS, + require_system_group=False, allow_internet_group=False, allow_owners_group=False, allow_nobody_group=False, @@ -752,7 +742,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub ], ), can_delete_any_message_group=GroupPermissionSetting( - require_system_group=not settings.ALLOW_GROUP_VALUED_SETTINGS, + require_system_group=False, allow_internet_group=False, allow_owners_group=False, allow_nobody_group=False, @@ -761,7 +751,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub id_field_name="can_delete_any_message_group_id", ), can_delete_own_message_group=GroupPermissionSetting( - require_system_group=not settings.ALLOW_GROUP_VALUED_SETTINGS, + require_system_group=False, allow_internet_group=False, allow_owners_group=False, allow_nobody_group=False, @@ -770,7 +760,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub id_field_name="can_delete_own_message_group_id", ), can_manage_all_groups=GroupPermissionSetting( - require_system_group=not settings.ALLOW_GROUP_VALUED_SETTINGS, + require_system_group=False, allow_internet_group=False, allow_owners_group=True, allow_nobody_group=False, @@ -779,7 +769,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub id_field_name="can_manage_all_groups_id", ), can_move_messages_between_channels_group=GroupPermissionSetting( - require_system_group=not settings.ALLOW_GROUP_VALUED_SETTINGS, + require_system_group=False, allow_internet_group=False, allow_owners_group=False, allow_nobody_group=True, @@ -787,8 +777,17 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub default_group_name=SystemGroups.MEMBERS, id_field_name="can_move_messages_between_channels_group_id", ), + can_move_messages_between_topics_group=GroupPermissionSetting( + require_system_group=False, + allow_internet_group=False, + allow_owners_group=False, + allow_nobody_group=True, + allow_everyone_group=True, + default_group_name=SystemGroups.EVERYONE, + id_field_name="can_move_messages_between_topics_group_id", + ), direct_message_initiator_group=GroupPermissionSetting( - require_system_group=not settings.ALLOW_GROUP_VALUED_SETTINGS, + require_system_group=False, allow_internet_group=False, allow_owners_group=True, allow_nobody_group=True, @@ -797,7 +796,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub id_field_name="direct_message_initiator_group_id", ), direct_message_permission_group=GroupPermissionSetting( - require_system_group=not settings.ALLOW_GROUP_VALUED_SETTINGS, + require_system_group=False, allow_internet_group=False, allow_owners_group=True, allow_nobody_group=True, @@ -807,20 +806,6 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub ), ) - REALM_PERMISSION_GROUP_SETTINGS_WITH_NEW_API_FORMAT = [ - "can_add_custom_emoji_group", - "can_create_groups", - "can_create_private_channel_group", - "can_create_public_channel_group", - "can_create_web_public_channel_group", - "can_delete_any_message_group", - "can_delete_own_message_group", - "can_manage_all_groups", - "can_move_messages_between_channels_group", - "direct_message_initiator_group", - "direct_message_permission_group", - ] - DIGEST_WEEKDAY_VALUES = [0, 1, 2, 3, 4, 5, 6] # Icon is the square mobile icon. @@ -1189,12 +1174,14 @@ def get_realm_by_id(realm_id: int) -> Realm: def get_realm_with_settings(realm_id: int) -> Realm: # Prefetch the following settings: - # * All the settings that can be set to anonymous groups. # This also prefetches can_access_all_users_group setting, # even when it cannot be set to anonymous groups because # the setting is used when fetching users in the realm. + # * All the settings that can be set to anonymous groups. # * Announcements streams. return Realm.objects.select_related( + "create_multiuse_invite_group", + "create_multiuse_invite_group__named_user_group", "can_access_all_users_group", "can_access_all_users_group__named_user_group", "can_add_custom_emoji_group", @@ -1215,6 +1202,8 @@ def get_realm_with_settings(realm_id: int) -> Realm: "can_manage_all_groups__named_user_group", "can_move_messages_between_channels_group", "can_move_messages_between_channels_group__named_user_group", + "can_move_messages_between_topics_group", + "can_move_messages_between_topics_group__named_user_group", "direct_message_initiator_group", "direct_message_initiator_group__named_user_group", "direct_message_permission_group", diff --git a/zerver/models/recipients.py b/zerver/models/recipients.py index 38a4bc5c8f..c49f5d774f 100644 --- a/zerver/models/recipients.py +++ b/zerver/models/recipients.py @@ -165,7 +165,7 @@ def get_or_create_direct_message_group(id_list: list[int]) -> DirectMessageGroup from zerver.models import Subscription, UserProfile direct_message_group_hash = get_direct_message_group_hash(id_list) - with transaction.atomic(): + with transaction.atomic(savepoint=False): (direct_message_group, created) = DirectMessageGroup.objects.get_or_create( huddle_hash=direct_message_group_hash, group_size=len(id_list), diff --git a/zerver/models/streams.py b/zerver/models/streams.py index 06b9a71f7e..0036d4592e 100644 --- a/zerver/models/streams.py +++ b/zerver/models/streams.py @@ -182,6 +182,7 @@ class Stream(models.Model): API_FIELDS = [ "creator_id", "date_created", + "deactivated", "description", "first_message_id", "history_public_to_subscribers", @@ -197,6 +198,7 @@ class Stream(models.Model): def to_dict(self) -> DefaultStreamDict: return DefaultStreamDict( + is_archived=self.deactivated, can_remove_subscribers_group=self.can_remove_subscribers_group_id, creator_id=self.creator_id, date_created=datetime_to_timestamp(self.date_created), @@ -229,6 +231,16 @@ def get_active_streams(realm: Realm) -> QuerySet[Stream]: return Stream.objects.filter(realm=realm, deactivated=False) +def get_all_streams(realm: Realm, include_archived_channels: bool = True) -> QuerySet[Stream]: + """ + Return all streams for `include_archived_channels`= true (including invite-only and deactivated streams). + """ + if not include_archived_channels: + return get_active_streams(realm) + + return Stream.objects.filter(realm=realm) + + def get_linkable_streams(realm_id: int) -> QuerySet[Stream]: """ This returns the streams that we are allowed to linkify using diff --git a/zerver/models/users.py b/zerver/models/users.py index b32e3efe4c..4efe99a9b1 100644 --- a/zerver/models/users.py +++ b/zerver/models/users.py @@ -571,6 +571,8 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings): can_forge_sender = models.BooleanField(default=False, db_index=True) # Users with this flag set can create other users via API. can_create_users = models.BooleanField(default=False, db_index=True) + # Users with this flag can change email addresses of users in the realm via the API. + can_change_user_emails = models.BooleanField(default=False, db_index=True) # Used for rate-limiting certain automated messages generated by bots last_reminder = models.DateTimeField(default=None, null=True) @@ -815,7 +817,6 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings): from zerver.models import Realm if policy_name not in Realm.REALM_PERMISSION_GROUP_SETTINGS and policy_name not in [ - "edit_topic_policy", "invite_to_stream_policy", "invite_to_realm_policy", ]: @@ -895,7 +896,7 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings): return self.has_permission("can_manage_all_groups") def can_move_messages_to_another_topic(self) -> bool: - return self.has_permission("edit_topic_policy") + return self.has_permission("can_move_messages_between_topics_group") def can_add_custom_emoji(self) -> bool: return self.has_permission("can_add_custom_emoji_group") diff --git a/zerver/openapi/curl_param_value_generators.py b/zerver/openapi/curl_param_value_generators.py index 7d0ecc416d..0a17ed9bd5 100644 --- a/zerver/openapi/curl_param_value_generators.py +++ b/zerver/openapi/curl_param_value_generators.py @@ -23,7 +23,7 @@ from zerver.lib.upload import upload_message_attachment from zerver.lib.users import get_api_key from zerver.models import Client, Message, NamedUserGroup, UserPresence from zerver.models.realms import get_realm -from zerver.models.users import get_user +from zerver.models.users import UserProfile, get_user from zerver.openapi.openapi import Parameter GENERATOR_FUNCTIONS: dict[str, Callable[[], dict[str, object]]] = {} @@ -126,7 +126,7 @@ def add_emoji_to_message() -> dict[str, object]: # The message ID here is hardcoded based on the corresponding value # for the example message IDs we use in zulip.yaml. - message_id = 47 + message_id = 48 emoji_name = "octopus" emoji_code = "1f419" reaction_type = "unicode_emoji" @@ -252,6 +252,19 @@ def create_user() -> dict[str, object]: } +@openapi_param_value_generator(["/users/{email]:patch", "/users/{user_id}:patch"]) +def new_email_value() -> dict[str, object]: + count = 0 + exists = True + while exists: + email = f"new{count}@zulip.com" + exists = UserProfile.objects.filter(delivery_email=email).exists() + count += 1 + return { + "new_email": email, + } + + @openapi_param_value_generator(["/user_groups/create:post"]) def create_user_group_data() -> dict[str, object]: return { diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index f3b3762193..465168db08 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -694,6 +694,7 @@ paths: { "name": "test", "stream_id": 9, + "is_archived": false, "creator_id": null, "description": "", "rendered_description": "", @@ -1337,6 +1338,7 @@ paths: { "name": "private", "stream_id": 12, + "is_archived": false, "description": "", "rendered_description": "", "date_created": 1691057093, @@ -1395,6 +1397,7 @@ paths: { "name": "private", "stream_id": 12, + "is_archived": true, "description": "", "rendered_description": "", "date_created": 1691057093, @@ -1994,6 +1997,7 @@ paths: { "name": "Scotland", "stream_id": 3, + "is_archived": false, "description": "Located in the United Kingdom", "rendered_description": "

      Located in the United Kingdom

      ", "date_created": 1691057093, @@ -2010,6 +2014,7 @@ paths: { "name": "Denmark", "stream_id": 1, + "is_archived": false, "description": "A Scandinavian country", "rendered_description": "

      A Scandinavian country

      ", "date_created": 1691057093, @@ -2026,6 +2031,7 @@ paths: { "name": "Verona", "stream_id": 5, + "is_archived": false, "description": "A city in Italy", "rendered_description": "

      A city in Italy

      ", "date_created": 1691057093, @@ -2073,6 +2079,7 @@ paths: { "name": "Scotland", "stream_id": 3, + "is_archived": false, "description": "Located in the United Kingdom", "rendered_description": "

      Located in the United Kingdom

      ", "date_created": 1691057093, @@ -4350,7 +4357,11 @@ paths: description: | An object containing the properties that have changed. - **Changes**: In Zulip 7.0 (feature level 183), the + **Changes**: In Zulip 10.0 (feature level 316), `edit_topic_policy` + property was removed and replaced by `can_move_messages_between_topics_group` + realm setting. + + In Zulip 7.0 (feature level 183), the `community_topic_editing_limit_seconds` property was removed. It was documented as potentially returned as a changed property in this event, but in fact it was only ever returned in the @@ -4401,15 +4412,17 @@ paths: The [policy](/api/roles-and-permissions#permission-levels) for which users can create bot users in this organization. can_access_all_users_group: - type: integer - description: | - The ID of the [user group](/api/get-user-groups) whose members - are allowed to access all users in the organization. + allOf: + - $ref: "#/components/schemas/GroupSettingValue" + - description: | + A [group-setting value](/api/group-setting-values) defining the + set of users who are allowed to access all users in the + organization. - This setting can currently only be set to `"role:everyone"` - system group. + **Changes**: Prior to Zulip 10.0 (feature level 314), this value used + to be of type integer and did not accept anonymous user groups. - **Changes**: New in Zulip 8.0 (feature level 225). + New in Zulip 8.0 (feature level 225). can_create_groups: allOf: - $ref: "#/components/schemas/GroupSettingValue" @@ -4506,6 +4519,20 @@ paths: In Zulip 7.0 (feature level 159), `Nobody` was added as an option to `move_messages_between_streams_policy` enum. - $ref: "#/components/schemas/GroupSettingValue" + can_move_messages_between_topics_group: + allOf: + - description: | + A [group-setting value](/api/group-setting-values) defining the set of + users who have permission to move messages from one topic to another + within a channel in the organization. + + **Changes**: New in Zulip 10.0 (feature level 316). Previously, this + permission was controlled by the enum `edit_topic_policy`. Values were + 1=Members, 2=Admins, 3=Full members, 4=Moderators, 5=Everyone, 6=Nobody. + + In Zulip 7.0 (feature level 159), `Nobody` was added as an option to + `edit_topic_policy` enum. + - $ref: "#/components/schemas/GroupSettingValue" can_manage_all_groups: allOf: - $ref: "#/components/schemas/GroupSettingValue" @@ -4532,18 +4559,18 @@ paths: [calc-full-member]: /api/roles-and-permissions#determining-if-a-user-is-a-full-member create_multiuse_invite_group: - type: integer - description: | - The ID of the [user group](/api/get-user-groups) whose members are - allowed to create [reusable invitation - links](/help/invite-new-users#create-a-reusable-invitation-link) - to the organization. + allOf: + - $ref: "#/components/schemas/GroupSettingValue" + - description: | + A [group-setting value](/api/group-setting-values) defining the + set of users who are allowed to create [reusable invitation + links](/help/invite-new-users#create-a-reusable-invitation-link) + to the organization. - This setting can currently only be set to user groups that are - system groups, except for the system groups named - `"role:internet"` and `"role:owners"`. + **Changes**: Prior to Zulip 10.0 (feature level 314), this value used + to be of type integer and did not accept anonymous user groups. - **Changes**: New in Zulip 8.0 (feature level 209). + New in Zulip 8.0 (feature level 209). default_code_block_language: type: string description: | @@ -4610,27 +4637,6 @@ paths: description: | Whether the organization disallows disposable email addresses. - edit_topic_policy: - type: integer - description: | - The [policy][permission-level] for which users can edit topics of any message. - - - 1 = Members only - - 2 = Admins only - - 3 = [Full members][calc-full-member] only - - 4 = Moderators only - - 5 = Everyone - - 6 = Nobody - - See [`PATCH /messages/{message_id}`](/api/update-message) for details and history - of how message editing permissions work. - - **Changes**: Nobody added as an option in Zulip 7.0 (feature level 159). - - New in Zulip 5.0 (feature level 75). - - [permission-level]: /api/roles-and-permissions#permission-levels - [calc-full-member]: /api/roles-and-permissions#determining-if-a-user-is-a-full-member email_changes_disabled: type: boolean description: | @@ -8294,7 +8300,7 @@ paths: - `allow_message_editing` - `can_move_messages_between_channels_group` - - `edit_topic_policy` + - `can_move_messages_between_topics_group` - `message_content_edit_limit_seconds` - `move_messages_within_stream_limit_seconds` - `move_messages_between_streams_limit_seconds` @@ -8304,6 +8310,10 @@ paths: of the [`realm op: update_dict`](/api/get-events#realm-update_dict) event in [`GET /events`](/api/get-events). + **Changes**: In Zulip 10.0 (feature level 316), `edit_topic_policy` + was removed and replaced by `can_move_messages_between_topics_group` + realm setting. + **Changes**: In Zulip 10.0 (feature level 310), `move_messages_between_streams_policy` was removed and replaced by `can_move_messages_between_channels_group` realm setting. @@ -10176,6 +10186,7 @@ paths: "creator_id": null, "description": "A Scandinavian country", "desktop_notifications": true, + "is_archived": false, "is_muted": false, "invite_only": false, "name": "Denmark", @@ -10190,6 +10201,7 @@ paths: "creator_id": 8, "description": "Located in the United Kingdom", "desktop_notifications": true, + "is_archived": false, "is_muted": false, "invite_only": false, "name": "Scotland", @@ -12634,6 +12646,52 @@ paths: "delivery_email": null, }, } + patch: + operationId: update-user-by-email + summary: Update a user by email + tags: ["users"] + x-requires-administrator: true + description: | + Administrative endpoint to update the details of another user in the organization by their email address. + Works the same way as [`PATCH /users/{user_id}`](/api/update-user) but fetching the target user by their + real email address. + + The requester needs to have permission to view the target user's real email address, subject to the + user's email address visibility setting. Otherwise, the dummy address of the format + `user{id}@{realm.host}` needs be used. This follows the same rules as `GET /users/{email}`. + + **Changes**: New in Zulip 10.0 (feature level 313). + parameters: + - name: email + in: path + required: true + description: | + The email address of the user, specified following the same rules as + [`GET /users/{email}`](/api/get-user-by-email). + schema: + type: string + example: hamlet@zulip.com + requestBody: + $ref: "#/components/requestBodies/UpdateUser" + responses: + "200": + $ref: "#/components/responses/SimpleSuccess" + + "400": + description: Bad request. + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/CodedError" + - example: + { + "result": "error", + "msg": "Guests cannot be organization administrators", + "code": "BAD_REQUEST", + } + description: | + A typical unsuccessful JSON response: /users/{user_id}: get: operationId: get-user @@ -12738,52 +12796,7 @@ paths: parameters: - $ref: "#/components/parameters/UserId" requestBody: - required: false - content: - application/x-www-form-urlencoded: - schema: - type: object - properties: - full_name: - description: | - The user's full name. - - **Changes**: Removed unnecessary JSON-encoding of this parameter in - Zulip 5.0 (feature level 106). - type: string - example: NewName - role: - description: | - New [role](/api/roles-and-permissions) for the user. Roles are encoded as: - - - Organization owner: 100 - - Organization administrator: 200 - - Organization moderator: 300 - - Member: 400 - - Guest: 600 - - Only organization owners can add or remove the owner role. - - The owner role cannot be removed from the only organization owner. - - **Changes**: New in Zulip 3.0 (feature level 8), replacing the previous - pair of `is_admin` and `is_guest` boolean parameters. Organization moderator - role added in Zulip 4.0 (feature level 60). - type: integer - example: 400 - profile_data: - description: | - A dictionary containing the to be updated custom profile field data for the user. - type: array - items: - type: object - example: - [{"id": 4, "value": "0"}, {"id": 5, "value": "1909-04-05"}] - encoding: - role: - contentType: application/json - profile_data: - contentType: application/json + $ref: "#/components/requestBodies/UpdateUser" responses: "200": @@ -14076,6 +14089,15 @@ paths: **Changes**: New in Zulip 10.0 (feature level 294). This capability is for backwards-compatibility. + - `archived_channels`: Boolean for whether the client supports processing + [archived channels](/help/archive-a-channel) in the `stream` and + `subscription` event types. If `false`, the server will not include data + related to archived channels in the `register` response or in events. +
      + **Changes**: New in Zulip 10.0 (feature level 315). This allows clients to + access archived channels, without breaking backwards-compatibility for + existing clients. + [help-linkifiers]: /help/add-a-custom-linkifier [rfc6570]: https://www.rfc-editor.org/rfc/rfc6570.html [events-linkifiers]: /api/get-events#realm_linkifiers @@ -14692,8 +14714,8 @@ paths: such filtering. **Changes**: New in Zulip 8.0 (feature level 207). - config: - $ref: "#/components/schemas/BotConfiguration" + config_options: + $ref: "#/components/schemas/WebhookConfigOption" recent_private_conversations: description: | Present if `recent_private_conversations` is present in `fetch_event_types`. @@ -14781,6 +14803,7 @@ paths: properties: stream_id: {} name: {} + is_archived: {} description: {} date_created: {} creator_id: @@ -16329,6 +16352,22 @@ paths: In Zulip 7.0 (feature level 159), `Nobody` was added as an option to `move_messages_between_streams_policy` enum. - $ref: "#/components/schemas/GroupSettingValue" + realm_can_move_messages_between_topics_group: + allOf: + - description: | + Present if `realm` is present in `fetch_event_types`. + + A [group-setting value](/api/group-setting-values) defining the set of + users who have permission to move messages from one topic to another + within a channel in the organization. + + **Changes**: New in Zulip 10.0 (feature level 316). Previously, this + permission was controlled by the enum `edit_topic_policy`. Values were + 1=Members, 2=Admins, 3=Full members, 4=Moderators, 5=Everyone, 6=Nobody. + + In Zulip 7.0 (feature level 159), `Nobody` was added as an option to + `edit_topic_policy` enum. + - $ref: "#/components/schemas/GroupSettingValue" realm_bot_creation_policy: type: integer description: | @@ -16590,18 +16629,18 @@ paths: [permission-level]: /api/roles-and-permissions#permission-levels [calc-full-member]: /api/roles-and-permissions#determining-if-a-user-is-a-full-member realm_create_multiuse_invite_group: - type: integer - description: | - The ID of the [user group](/api/get-user-groups) whose members are - allowed to create [reusable invitation - links](/help/invite-new-users#create-a-reusable-invitation-link) - to the organization. + allOf: + - $ref: "#/components/schemas/GroupSettingValue" + - description: | + A [group-setting value](/api/group-setting-values) defining the + set of users who are allowed to create [reusable invitation + links](/help/invite-new-users#create-a-reusable-invitation-link) + to the organization. - This setting can currently only be set to user groups that are - system groups, except for the system groups named - `"role:internet"` and `"role:owners"`. + **Changes**: Prior to Zulip 10.0 (feature level 314), this value used + to be of type integer and did not accept anonymous user groups. - **Changes**: New in Zulip 8.0 (feature level 209). + New in Zulip 8.0 (feature level 209). realm_inline_image_preview: type: boolean description: | @@ -16872,29 +16911,6 @@ paths: history of how message editing permissions work. [config-message-editing]: /help/restrict-message-editing-and-deletion - realm_edit_topic_policy: - type: integer - description: | - Present if `realm` is present in `fetch_event_types`. - - The [policy][permission-level] for which users can edit topics of any message. - - - 1 = Members only - - 2 = Admins only - - 3 = [Full members][calc-full-member] only - - 4 = Moderators only - - 5 = Everyone - - 6 = Nobody - - See [`PATCH /messages/{message_id}`](/api/update-message) for details and - history of how message editing permissions work. - - **Changes**: Nobody added as an option in Zulip 7.0 (feature level 159). - - New in Zulip 5.0 (feature level 75). - - [permission-level]: /api/roles-and-permissions#permission-levels - [calc-full-member]: /api/roles-and-permissions#determining-if-a-user-is-a-full-member realm_message_content_edit_limit_seconds: type: integer nullable: true @@ -17226,15 +17242,17 @@ paths: **Changes**: New in Zulip 8.0 (feature level 216). realm_can_access_all_users_group: - type: integer - description: | - The ID of the [user group](/api/get-user-groups) whose members - are allowed to access all users in the organization. + allOf: + - $ref: "#/components/schemas/GroupSettingValue" + - description: | + A [group-setting value](/api/group-setting-values) defining the + set of users who are allowed to access all users in the + organization. - This setting can currently only be set to `"role:members"` - and `"role:everyone"` system groups. + **Changes**: Prior to Zulip 10.0 (feature level 314), this value used + to be of type integer and did not accept anonymous user groups. - **Changes**: New in Zulip 8.0 (feature level 225). + New in Zulip 8.0 (feature level 225). zulip_plan_is_not_limited: type: boolean description: | @@ -19557,6 +19575,16 @@ paths: type: boolean default: true example: false + - name: exclude_archived + in: query + description: | + Whether to exclude archived streams from the results. + + **Changes**: New in Zulip 10.0 (feature level 315). + schema: + type: boolean + default: true + example: true - name: include_all_active in: query description: | @@ -19607,6 +19635,7 @@ paths: properties: stream_id: {} name: {} + is_archived: {} description: {} date_created: {} creator_id: @@ -19650,6 +19679,7 @@ paths: required: - stream_id - name + - is_archived - description - date_created - creator_id @@ -19678,6 +19708,7 @@ paths: "history_public_to_subscribers": false, "invite_only": true, "is_announcement_only": false, + "is_archived": false, "is_default": false, "is_web_public": false, "message_retention_days": null, @@ -19696,6 +19727,7 @@ paths: "history_public_to_subscribers": true, "invite_only": false, "is_announcement_only": false, + "is_archived": false, "is_default": true, "is_web_public": false, "message_retention_days": null, @@ -19763,6 +19795,7 @@ paths: "creator_id": null, "invite_only": false, "is_announcement_only": false, + "is_archived": false, "is_web_public": false, "message_retention_days": null, "name": "Denmark", @@ -20424,7 +20457,30 @@ paths: contentType: application/json responses: "200": - $ref: "#/components/responses/SimpleSuccess" + description: | + A success response containing the unique ID of the user group. + This field provides a straightforward way to reference the + newly created user group. + + **Changes**: New in Zulip 10.0 (feature level 317). + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/JsonSuccessBase" + - required: + - group_id + additionalProperties: false + properties: + result: {} + msg: {} + ignored_parameters_unsupported: {} + group_id: + type: integer + description: | + The unique ID of the created user group. + example: {"msg": "", "result": "success", "group_id": 123} + "400": description: Bad request. content: @@ -21550,6 +21606,7 @@ components: properties: stream_id: {} name: {} + is_archived: {} description: {} date_created: {} creator_id: @@ -21583,6 +21640,7 @@ components: required: - stream_id - name + - is_archived - description - date_created - creator_id @@ -21603,6 +21661,7 @@ components: properties: stream_id: {} name: {} + is_archived: {} description: {} date_created: {} creator_id: @@ -21621,6 +21680,7 @@ components: required: - stream_id - name + - is_archived - description - date_created - creator_id @@ -21646,6 +21706,13 @@ components: type: string description: | The name of the channel. + is_archived: + type: boolean + description: | + A boolean indicating whether the channel is [archived](/help/archive-a-channel). + + **Changes**: New in Zulip 10.0 (feature level 315). + Previously, this endpoint never returned archived channels. description: type: string description: | @@ -21923,6 +21990,36 @@ components: description: | `{config_key}`: Description/value of the configuration data key. type: string + WebhookConfigOption: + type: array + description: | + An array of configuration options where each option is an + object containing a unique identifier, a human-readable name, + and a validation function name hinting how to verify the + correct input format. + + This is an unstable API expected to be used only by the Zulip web + apps. Please discuss in chat.zulip.org before using it. + + **Changes**: New in Zulip 10.0 (feature level 318). + items: + type: object + additionalProperties: false + properties: + key: + type: string + description: | + A key for the configuration option to use in generated URLs. + label: + type: string + description: | + A human-readable label of the configuration option. + validator: + type: string + description: | + The name of the validator function for the configuration + option. Currently generated values are `check_bool` and + `check_str`. CustomProfileField: type: object additionalProperties: false @@ -22748,6 +22845,16 @@ components: was named `can_remove_subscribers_group_id`. New in Zulip 6.0 (feature level 142). + is_archived: + type: boolean + description: | + A boolean indicating whether the channel is [archived](/help/archive-a-channel). + + **Changes**: New in Zulip 10.0 (feature level 315). + Previously, subscriptions only included active + channels. Note that some endpoints will never return archived + channels unless the client declares explicit support for + them via the `archived_channels` client capability. DefaultChannelGroup: type: object description: | @@ -24465,6 +24572,67 @@ components: allOf: - $ref: "#/components/schemas/IgnoredParametersSuccess" + ################ + # Shared request bodies + ################ + requestBodies: + UpdateUser: + required: false + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + full_name: + description: | + The user's full name. + + **Changes**: Removed unnecessary JSON-encoding of this parameter in + Zulip 5.0 (feature level 106). + type: string + example: NewName + role: + description: | + New [role](/api/roles-and-permissions) for the user. Roles are encoded as: + + - Organization owner: 100 + - Organization administrator: 200 + - Organization moderator: 300 + - Member: 400 + - Guest: 600 + + Only organization owners can add or remove the owner role. + + The owner role cannot be removed from the only organization owner. + + **Changes**: New in Zulip 3.0 (feature level 8), replacing the previous + pair of `is_admin` and `is_guest` boolean parameters. Organization moderator + role added in Zulip 4.0 (feature level 60). + type: integer + example: 400 + profile_data: + description: | + A dictionary containing the updated custom profile field data for the user. + type: array + items: + type: object + example: + [{"id": 4, "value": "0"}, {"id": 5, "value": "1909-04-05"}] + new_email: + description: | + New email address for the user. Requires the user making the request + to be an organization owner and additionally have the `.can_change_user_emails` + special permission. + + **Changes**: New in Zulip 10.0 (feature level 285). + type: string + example: username@example.com + encoding: + role: + contentType: application/json + profile_data: + contentType: application/json + #################### # Shared parameters #################### diff --git a/zerver/tests/fixtures/mattermost_fixtures/direct_channel/export.json b/zerver/tests/fixtures/mattermost_fixtures/direct_channel/export.json index be785e3875..73762c80f1 100644 --- a/zerver/tests/fixtures/mattermost_fixtures/direct_channel/export.json +++ b/zerver/tests/fixtures/mattermost_fixtures/direct_channel/export.json @@ -17,7 +17,7 @@ {"type":"direct_post","direct_post":{"channel_members":["ron","harry"],"user":"ron","message":"hey harry","create_at":1566376137676,"flagged_by":null,"reactions":null,"replies":null,"attachments":[{"path":"20210622/teams/noteam/channels/mcrm7xee5bnpzn7u9ktsd91dwy/users/knq189b88fdxbdkeeasdynia4o/o3to4ezua3bajj31mzpkn96n5e/harry-ron.jpg"}]}} {"type":"direct_post","direct_post":{"channel_members":["ron","harry"],"user":"harry","message":"what's up","create_at":1566376318568,"flagged_by":null,"reactions":null,"replies":null,"attachments":null}} {"type":"direct_post","direct_post":{"channel_members":["ron","harry","ginny"],"user":"ginny","message":"Who is going to Hogsmeade this weekend?","create_at":1566376226493,"flagged_by":null,"reactions":null,"replies":null,"attachments":null}} -{"type":"direct_post","direct_post":{"channel_members":["ron","harry","ginny"],"user":"harry","message":"I am going.","create_at":1566376311350,"flagged_by":null,"reactions":null,"replies":null,"attachments":null}} +{"type":"direct_post","direct_post":{"channel_members":["ron","harry","ginny"],"user":"harry","message":"\u0000\u0001\u0001Hello How Are you\u0001\u0000\u0000\u0000\u0000","create_at":1566376311350,"flagged_by":null,"reactions":null,"replies":null,"attachments":null}} {"type":"direct_post","direct_post":{"channel_members":["ron","harry","ginny"],"user":"ron","message":"I am going as well","create_at":1566376286363,"flagged_by":null,"reactions":null,"replies":null,"attachments":null}} {"type":"direct_post","direct_post":{"channel_members":["harry","voldemort"],"user":"voldemort","message":"Hey Harry.","create_at":1566376318569,"flagged_by":null,"reactions":null,"replies":null,"attachments":null}} {"type":"direct_post","direct_post":{"channel_members":["harry","voldemort"],"user":"harry","message":"Ahh. Here we go again.","create_at":1566376318579,"flagged_by":null,"reactions":null,"replies":null,"attachments":null}} diff --git a/zerver/tests/fixtures/slack_message_conversion.json b/zerver/tests/fixtures/slack_message_conversion.json index d5a038a5c9..7725743998 100644 --- a/zerver/tests/fixtures/slack_message_conversion.json +++ b/zerver/tests/fixtures/slack_message_conversion.json @@ -8,7 +8,12 @@ { "name": "slack_link_with_pipe", "input": ">Google logo today:\n>\n>Kinda boring", - "conversion_output": ">Google logo today:\n>https://foo.com|foo\n>Kinda boring" + "conversion_output": ">Google logo today:\n>[foo](https://foo.com)\n>Kinda boring" + }, + { + "name": "slack_link_with_pipes", + "input": ">Google logo today:\n>\n>Kinda boring", + "conversion_output": ">Google logo today:\n>[foo|oof](https://foo.com)\n>Kinda boring" }, { "name": "slack_link2", diff --git a/zerver/tests/test_audit_log.py b/zerver/tests/test_audit_log.py index 9b2e1c40c8..ab846e52eb 100644 --- a/zerver/tests/test_audit_log.py +++ b/zerver/tests/test_audit_log.py @@ -31,6 +31,7 @@ from zerver.actions.realm_linkifiers import ( ) from zerver.actions.realm_playgrounds import check_add_realm_playground, do_remove_realm_playground from zerver.actions.realm_settings import ( + do_change_realm_permission_group_setting, do_deactivate_realm, do_reactivate_realm, do_set_realm_authentication_methods, @@ -89,7 +90,7 @@ from zerver.models.linkifiers import linkifiers_for_realm from zerver.models.realm_audit_logs import AuditLogEventType from zerver.models.realm_emoji import EmojiInfo, get_all_custom_emoji_for_realm from zerver.models.realm_playgrounds import get_realm_playgrounds -from zerver.models.realms import EditTopicPolicyEnum, RealmDomainDict, get_realm, get_realm_domains +from zerver.models.realms import RealmDomainDict, get_realm, get_realm_domains from zerver.models.streams import get_stream @@ -546,14 +547,24 @@ class TestRealmAuditLog(ZulipTestCase): 1, ) + administrators_system_group = NamedUserGroup.objects.get( + name=SystemGroups.ADMINISTRATORS, realm=realm, is_system_group=True + ) + everyone_system_group = NamedUserGroup.objects.get( + name=SystemGroups.EVERYONE, realm=realm, is_system_group=True + ) + value_expected = { - RealmAuditLog.OLD_VALUE: EditTopicPolicyEnum.EVERYONE, - RealmAuditLog.NEW_VALUE: EditTopicPolicyEnum.ADMINS_ONLY, - "property": "edit_topic_policy", + RealmAuditLog.OLD_VALUE: everyone_system_group.id, + RealmAuditLog.NEW_VALUE: administrators_system_group.id, + "property": "can_move_messages_between_topics_group", } - do_set_realm_property( - realm, "edit_topic_policy", EditTopicPolicyEnum.ADMINS_ONLY, acting_user=user + do_change_realm_permission_group_setting( + realm, + "can_move_messages_between_topics_group", + administrators_system_group, + acting_user=user, ) self.assertEqual( RealmAuditLog.objects.filter( diff --git a/zerver/tests/test_bots.py b/zerver/tests/test_bots.py index 876cd21d6a..eec79b8296 100644 --- a/zerver/tests/test_bots.py +++ b/zerver/tests/test_bots.py @@ -18,6 +18,7 @@ from zerver.lib.integrations import EMBEDDED_BOTS, WebhookIntegration from zerver.lib.test_classes import UploadSerializeMixin, ZulipTestCase from zerver.lib.test_helpers import avatar_disk_path, get_test_image_file from zerver.lib.utils import assert_is_not_none +from zerver.lib.webhooks.common import WebhookConfigOption from zerver.models import RealmUserDefault, Service, Subscription, UserProfile from zerver.models.bots import get_bot_services from zerver.models.realms import BotCreationPolicyEnum, get_realm @@ -37,7 +38,11 @@ stripe_sample_config_options = [ "stripe", ["financial"], display_name="Stripe", - config_options=[("Stripe API key", "stripe_api_key", _check_string)], + config_options=[ + WebhookConfigOption( + name="stripe_api_key", description="Stripe API key", validator=_check_string + ) + ], ), ] diff --git a/zerver/tests/test_decorators.py b/zerver/tests/test_decorators.py index 1bf6c022cd..ba44754ebd 100644 --- a/zerver/tests/test_decorators.py +++ b/zerver/tests/test_decorators.py @@ -856,7 +856,7 @@ class InactiveUserTest(ZulipTestCase): form = OurAuthenticationForm(request, payload) with self.settings(AUTHENTICATION_BACKENDS=("zproject.backends.EmailAuthBackend",)): self.assertFalse(form.is_valid()) - self.assertIn("Please enter a correct email", str(form.errors)) + self.assertIn("Incorrect email or password.", str(form.errors)) # Test a non-mirror-dummy deactivated user. user_profile.is_mirror_dummy = False diff --git a/zerver/tests/test_email_notifications.py b/zerver/tests/test_email_notifications.py index 68c59a0a47..9ce37b5352 100644 --- a/zerver/tests/test_email_notifications.py +++ b/zerver/tests/test_email_notifications.py @@ -11,6 +11,7 @@ from django.utils.timezone import now as timezone_now from django_auth_ldap.config import LDAPSearch from zerver.lib.email_notifications import ( + convert_html_to_markdown, enqueue_welcome_emails, get_onboarding_email_schedule, send_account_registered_email, @@ -671,3 +672,10 @@ class TestCustomWelcomeEmailSender(ZulipTestCase): email_data = orjson.loads(scheduled_emails[0].data) self.assertEqual(email_data["from_name"], name) self.assertEqual(email_data["from_address"], email) + + +class TestHtmlToMarkdown(ZulipTestCase): + def test_html_to_markdown_unicode(self) -> None: + self.assertEqual( + convert_html_to_markdown("a rose is not a rosé"), "a rose is not a rosé" + ) diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index 0eb68a205e..b90eabcb6d 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -305,6 +305,7 @@ class BaseAction(ZulipTestCase): user_list_incomplete: bool = False, client_is_old: bool = False, include_deactivated_groups: bool = False, + archived_channels: bool = False, ) -> Iterator[list[dict[str, Any]]]: """ Make sure we have a clean slate of client descriptors for these tests. @@ -354,6 +355,7 @@ class BaseAction(ZulipTestCase): linkifier_url_template=linkifier_url_template, user_list_incomplete=user_list_incomplete, include_deactivated_groups=include_deactivated_groups, + archived_channels=archived_channels, ) if client_is_old: @@ -395,6 +397,7 @@ class BaseAction(ZulipTestCase): linkifier_url_template=linkifier_url_template, user_list_incomplete=user_list_incomplete, include_deactivated_groups=include_deactivated_groups, + archived_channels=archived_channels, ) post_process_state(self.user_profile, hybrid_state, notification_settings_null) after = orjson.dumps(hybrid_state) @@ -425,6 +428,7 @@ class BaseAction(ZulipTestCase): linkifier_url_template=linkifier_url_template, user_list_incomplete=user_list_incomplete, include_deactivated_groups=include_deactivated_groups, + archived_channels=archived_channels, ) post_process_state(self.user_profile, normal_state, notification_settings_null) self.match_states(hybrid_state, normal_state, events) @@ -3355,6 +3359,23 @@ class NormalActionsTest(BaseAction): check_stream_delete("events[0]", events[0]) self.assertIsNone(events[0]["streams"][0]["stream_weekly_traffic"]) + def test_admin_deactivate_unsubscribed_stream(self) -> None: + self.set_up_db_for_testing_user_access() + stream = self.make_stream("test_stream") + iago = self.example_user("iago") + realm = iago.realm + self.user_profile = self.example_user("iago") + + self.subscribe(iago, stream.name) + self.assertCountEqual(self.users_subscribed_to_stream(stream.name, realm), [iago]) + + self.unsubscribe(iago, stream.name) + self.assertCountEqual(self.users_subscribed_to_stream(stream.name, realm), []) + + with self.verify_action(num_events=1, archived_channels=True) as events: + do_deactivate_stream(stream, acting_user=iago) + check_stream_delete("events[0]", events[0]) + def test_user_losing_access_on_deactivating_stream(self) -> None: self.set_up_db_for_testing_user_access() polonius = self.example_user("polonius") @@ -3367,11 +3388,9 @@ class NormalActionsTest(BaseAction): self.users_subscribed_to_stream(stream.name, realm), [hamlet, polonius] ) - with self.verify_action(num_events=2) as events: + with self.verify_action(num_events=2, archived_channels=True) as events: do_deactivate_stream(stream, acting_user=None) check_stream_delete("events[0]", events[0]) - check_realm_user_remove("events[1]", events[1]) - self.assertEqual(events[1]["person"]["user_id"], hamlet.id) # Test that if the subscribers of deactivated stream are involved in # DMs with guest, then the guest does not get "remove" event for them. @@ -3383,11 +3402,9 @@ class NormalActionsTest(BaseAction): self.users_subscribed_to_stream(stream.name, realm), [iago, polonius, shiva] ) - with self.verify_action(num_events=2) as events: + with self.verify_action(num_events=2, archived_channels=True) as events: do_deactivate_stream(stream, acting_user=None) check_stream_delete("events[0]", events[0]) - check_realm_user_remove("events[1]", events[1]) - self.assertEqual(events[1]["person"]["user_id"], iago.id) def test_subscribe_other_user_never_subscribed(self) -> None: for i, include_streams in enumerate([True, False]): @@ -3726,7 +3743,6 @@ class RealmPropertyActionTest(BaseAction): default_code_block_language=["python", "javascript"], message_content_delete_limit_seconds=[1000, 1100, 1200], invite_to_realm_policy=Realm.INVITE_TO_REALM_POLICY_TYPES, - edit_topic_policy=Realm.EDIT_TOPIC_POLICY_TYPES, message_content_edit_limit_seconds=[1000, 1100, 1200, None], move_messages_within_stream_limit_seconds=[1000, 1100, 1200], move_messages_between_streams_limit_seconds=[1000, 1100, 1200], @@ -3787,14 +3803,13 @@ class RealmPropertyActionTest(BaseAction): if name in [ "allow_message_editing", - "edit_topic_policy", "message_content_edit_limit_seconds", ]: check_realm_update_dict("events[0]", events[0]) else: check_realm_update("events[0]", events[0], name) - def do_set_realm_permission_group_setting_test(self, setting_name: str) -> None: + def do_test_allow_system_group(self, setting_name: str) -> None: all_system_user_groups = NamedUserGroup.objects.filter( realm=self.user_profile.realm, is_system_group=True, @@ -3808,22 +3823,6 @@ class RealmPropertyActionTest(BaseAction): now = timezone_now() - do_change_realm_permission_group_setting( - self.user_profile.realm, - setting_name, - default_group, - acting_user=self.user_profile, - ) - - self.assertEqual( - RealmAuditLog.objects.filter( - realm=self.user_profile.realm, - event_type=AuditLogEventType.REALM_PROPERTY_CHANGED, - event_time__gte=now, - acting_user=self.user_profile, - ).count(), - 1, - ) for user_group in all_system_user_groups: if user_group.name == default_group_name: continue @@ -4037,14 +4036,12 @@ class RealmPropertyActionTest(BaseAction): for prop in Realm.REALM_PERMISSION_GROUP_SETTINGS: with self.settings(SEND_DIGEST_EMAILS=True): - self.do_set_realm_permission_group_setting_test(prop) - - for prop in Realm.REALM_PERMISSION_GROUP_SETTINGS_WITH_NEW_API_FORMAT: + self.do_test_allow_system_group(prop) if Realm.REALM_PERMISSION_GROUP_SETTINGS[prop].require_system_group: # Anonymous system groups aren't relevant when # restricted to system groups. continue - with self.settings(SEND_DIGEST_EMAILs=True): + with self.settings(SEND_DIGEST_EMAILS=True): self.do_set_realm_permission_group_setting_to_anonymous_groups_test(prop) def do_set_realm_user_default_setting_test(self, name: str) -> None: diff --git a/zerver/tests/test_home.py b/zerver/tests/test_home.py index 031a9b872b..16313727cc 100644 --- a/zerver/tests/test_home.py +++ b/zerver/tests/test_home.py @@ -138,6 +138,7 @@ class HomeTest(ZulipTestCase): "realm_can_delete_own_message_group", "realm_can_manage_all_groups", "realm_can_move_messages_between_channels_group", + "realm_can_move_messages_between_topics_group", "realm_create_multiuse_invite_group", "realm_create_private_stream_policy", "realm_create_public_stream_policy", @@ -155,7 +156,6 @@ class HomeTest(ZulipTestCase): "realm_direct_message_permission_group", "realm_disallow_disposable_email_addresses", "realm_domains", - "realm_edit_topic_policy", "realm_email_auth_enabled", "realm_email_changes_disabled", "realm_emails_restricted_to_domains", diff --git a/zerver/tests/test_import_export.py b/zerver/tests/test_import_export.py index 5b38e877e4..cfda844d94 100644 --- a/zerver/tests/test_import_export.py +++ b/zerver/tests/test_import_export.py @@ -93,6 +93,7 @@ from zerver.models import ( ) from zerver.models.clients import get_client from zerver.models.groups import SystemGroups +from zerver.models.messages import ImageAttachment from zerver.models.presence import PresenceSequence from zerver.models.realm_audit_logs import AuditLogEventType from zerver.models.realms import get_realm @@ -335,6 +336,9 @@ class ExportFile(ZulipTestCase): class RealmImportExportTest(ExportFile): + def create_user_and_login(self, email: str, realm: Realm) -> None: + self.register(email, "test", subdomain=realm.subdomain) + def export_realm( self, realm: Realm, @@ -811,6 +815,32 @@ class RealmImportExportTest(ExportFile): ) self.assertEqual(realm_emoji.name, "hawaii") + # We want to set up some image data to verify image attachment thumbnailing works correctly + # in the import. + # We'll create a new user to use as the sender of the messages with such images, + # so that we can easily find them after importing - by fetching messages sent + # by the thumbnailing_test_user_email account. + thumbnailing_test_user_email = "thumbnailing_test@zulip.com" + self.create_user_and_login(thumbnailing_test_user_email, original_realm) + thumbnailing_test_user = get_user_by_delivery_email( + thumbnailing_test_user_email, original_realm + ) + + # Send a message with the image. After the import, we'll verify that this message + # and the associated ImageAttachment have been created correctly. + image_path_id = self.upload_and_thumbnail_image("img.png") + self.send_stream_message( + sender=thumbnailing_test_user, + stream_name="Verona", + content=f"An [image](/user_uploads/{image_path_id})", + ) + image_attachment = ImageAttachment.objects.get(path_id=image_path_id) + # Malform some ImageAttachment info. These shouldn't get exported (and certainly not imported!) + # anyway, so we can test that this misinformation doesn't make its way into the imported realm. + image_attachment.original_width_px = 9999 + image_attachment.original_height_px = 9999 + image_attachment.save() + # Deactivate a user to ensure such a case is covered. do_deactivate_user(self.example_user("aaron"), acting_user=None) @@ -1003,7 +1033,14 @@ class RealmImportExportTest(ExportFile): self.export_realm(original_realm, export_type=RealmExport.EXPORT_FULL_WITHOUT_CONSENT) - with self.settings(BILLING_ENABLED=False), self.assertLogs(level="INFO"): + with ( + self.settings(BILLING_ENABLED=False), + self.assertLogs(level="INFO"), + # With captureOnCommitCallbacks we ensure that tasks delegated to the queue workers + # are executed immediately. We use this to make thumbnailing runs in the import + # process in this test. + self.captureOnCommitCallbacks(execute=True), + ): do_import_realm(get_output_dir(), "test-zulip") # Make sure our export/import didn't somehow leak info into the @@ -1164,6 +1201,48 @@ class RealmImportExportTest(ExportFile): Message.objects.filter(realm=imported_realm).count(), ) + # Verify thumbnailing. + imported_thumbnailing_test_user = get_user_by_delivery_email( + thumbnailing_test_user_email, imported_realm + ) + imported_messages_with_thumbnail = Message.objects.filter( + sender=imported_thumbnailing_test_user, realm=imported_realm + ) + imported_message_with_thumbnail = imported_messages_with_thumbnail.latest("id") + attachment_with_thumbnail = Attachment.objects.get( + owner=imported_thumbnailing_test_user, messages=imported_message_with_thumbnail + ) + + path_id = attachment_with_thumbnail.path_id + # An ImageAttachment has been created in the import process. + imported_image_attachment = ImageAttachment.objects.get( + path_id=path_id, realm=imported_realm + ) + + # It figured out the dimensions correctly and didn't inherit the bad data in the + # original ImageAttachment. + self.assertEqual(imported_image_attachment.original_width_px, 128) + self.assertEqual(imported_image_attachment.original_height_px, 128) + # ImageAttachment.thumbnail_metadata contains information about thumbnails that actually + # got generated. By asserting it's not empty, we make sure thumbnailing ran for the image + # and that we didn't merely create the ImageAttachment row in the database. + self.assertNotEqual(len(imported_image_attachment.thumbnail_metadata), 0) + self.assertTrue(imported_image_attachment.thumbnail_metadata[0]) + + # Content and rendered_content got updated correctly, to point to the correct, new path_id + # and include the HTML for image preview using the thumbnail. + self.assertEqual( + imported_message_with_thumbnail.content, f"An [image](/user_uploads/{path_id})" + ) + expected_rendered_preview = ( + f'

      An image

      \n' + f'' + ) + self.assertEqual( + imported_message_with_thumbnail.rendered_content, expected_rendered_preview + ) + def test_import_message_edit_history(self) -> None: realm = get_realm("zulip") iago = self.example_user("iago") diff --git a/zerver/tests/test_invite.py b/zerver/tests/test_invite.py index 0071250cda..06d2035439 100644 --- a/zerver/tests/test_invite.py +++ b/zerver/tests/test_invite.py @@ -2834,19 +2834,46 @@ class MultiuseInviteTest(ZulipTestCase): self.login("iago") result = self.client_patch( "/json/realm", - {"create_multiuse_invite_group": orjson.dumps(full_members_system_group.id).decode()}, + { + "create_multiuse_invite_group": orjson.dumps( + {"new": full_members_system_group.id} + ).decode() + }, ) self.assert_json_error(result, "Must be an organization owner") self.login("desdemona") result = self.client_patch( "/json/realm", - {"create_multiuse_invite_group": orjson.dumps(full_members_system_group.id).decode()}, + { + "create_multiuse_invite_group": orjson.dumps( + {"new": full_members_system_group.id} + ).decode() + }, ) self.assert_json_success(result) realm = get_realm("zulip") self.assertEqual(realm.create_multiuse_invite_group_id, full_members_system_group.id) + # Test setting the value to an anonymous group. + iago = self.example_user("iago") + result = self.client_patch( + "/json/realm", + { + "create_multiuse_invite_group": orjson.dumps( + { + "new": { + "direct_members": [iago.id], + "direct_subgroups": [], + } + } + ).decode() + }, + ) + self.assert_json_success(result) + realm = get_realm("zulip") + self.assertCountEqual(realm.create_multiuse_invite_group.direct_members.all(), [iago]) + def test_multiuse_link_for_inviting_as_owner(self) -> None: self.login("iago") result = self.client_post( diff --git a/zerver/tests/test_management_commands.py b/zerver/tests/test_management_commands.py index c91bbd4133..8e37df70df 100644 --- a/zerver/tests/test_management_commands.py +++ b/zerver/tests/test_management_commands.py @@ -588,3 +588,17 @@ class TestSendCustomEmail(ZulipTestCase): call(" hamlet@zulip.com (zulip)"), ], ) + + +class TestSendZulipUpdateAnnouncements(ZulipTestCase): + COMMAND_NAME = "send_zulip_update_announcements" + + def test_reset_level(self) -> None: + realm = get_realm("zulip") + realm.zulip_update_announcements_level = 9 + realm.save() + + call_command(self.COMMAND_NAME, "--reset-level=5") + + realm.refresh_from_db() + self.assertEqual(realm.zulip_update_announcements_level, 5) diff --git a/zerver/tests/test_markdown.py b/zerver/tests/test_markdown.py index 74caf860a3..74684fe75b 100644 --- a/zerver/tests/test_markdown.py +++ b/zerver/tests/test_markdown.py @@ -3108,6 +3108,28 @@ class MarkdownStreamMentionTests(ZulipTestCase): ".

      ", ) + def test_message_id_multiple(self) -> None: + denmark = get_stream("Denmark", get_realm("zulip")) + sender_user_profile = self.example_user("othello") + msg = Message( + sender=sender_user_profile, + sending_client=get_client("test"), + realm=sender_user_profile.realm, + ) + content = "As mentioned in #**Denmark>danish@123** and #**Denmark>danish@456**." + self.assertEqual( + render_message_markdown(msg, content).rendered_content, + "

      As mentioned in " + f'' + f"#Denmark > danish @ 💬" + " and " + f'' + f"#Denmark > danish @ 💬" + ".

      ", + ) + def test_possible_stream_names(self) -> None: content = """#**test here** This mentions #**Denmark** too. diff --git a/zerver/tests/test_markdown_thumbnail.py b/zerver/tests/test_markdown_thumbnail.py index 07ac40d598..c2290a3028 100644 --- a/zerver/tests/test_markdown_thumbnail.py +++ b/zerver/tests/test_markdown_thumbnail.py @@ -2,6 +2,7 @@ import re from unittest.mock import patch import pyvips +from typing_extensions import override from zerver.actions.message_delete import do_delete_messages from zerver.actions.message_send import check_message, do_send_messages @@ -9,7 +10,7 @@ from zerver.lib.addressee import Addressee from zerver.lib.camo import get_camo_url from zerver.lib.markdown import render_message_markdown from zerver.lib.test_classes import ZulipTestCase -from zerver.lib.test_helpers import get_test_image_file, read_test_image_file +from zerver.lib.test_helpers import read_test_image_file from zerver.lib.thumbnail import ThumbnailFormat from zerver.lib.upload import upload_message_attachment from zerver.models import ( @@ -25,20 +26,10 @@ from zerver.worker.thumbnail import ensure_thumbnails class MarkdownThumbnailTest(ZulipTestCase): - def upload_image(self, image_name: str) -> str: + @override + def setUp(self) -> None: self.login("othello") - with get_test_image_file(image_name) as image_file: - response = self.assert_json_success( - self.client_post("/json/user_uploads", {"file": image_file}) - ) - return re.sub(r"/user_uploads/", "", response["url"]) - - def upload_and_thumbnail_image(self, image_name: str) -> str: - with self.captureOnCommitCallbacks(execute=True): - # Running captureOnCommitCallbacks includes inserting into - # the Rabbitmq queue, which in testing means we - # immediately run the worker for it, producing the thumbnails. - return self.upload_image(image_name) + super().setUp() def assert_message_content_is( self, message_id: int, rendered_content: str, user_name: str = "othello" diff --git a/zerver/tests/test_message_edit.py b/zerver/tests/test_message_edit.py index c6f92c3959..5a74bce349 100644 --- a/zerver/tests/test_message_edit.py +++ b/zerver/tests/test_message_edit.py @@ -6,7 +6,11 @@ import orjson from django.utils.timezone import now as timezone_now from zerver.actions.message_edit import get_mentions_for_message_updates -from zerver.actions.realm_settings import do_change_realm_plan_type, do_set_realm_property +from zerver.actions.realm_settings import ( + do_change_realm_permission_group_setting, + do_change_realm_plan_type, + do_set_realm_property, +) from zerver.actions.streams import do_deactivate_stream from zerver.actions.user_groups import add_subgroups_to_user_group, check_add_user_group from zerver.actions.user_topics import do_set_user_topic_visibility_policy @@ -18,7 +22,7 @@ from zerver.lib.topic import TOPIC_NAME from zerver.lib.utils import assert_is_not_none from zerver.models import Attachment, Message, NamedUserGroup, Realm, UserProfile, UserTopic from zerver.models.groups import SystemGroups -from zerver.models.realms import EditTopicPolicyEnum, WildcardMentionPolicyEnum, get_realm +from zerver.models.realms import WildcardMentionPolicyEnum, get_realm from zerver.models.streams import get_stream @@ -892,7 +896,7 @@ class EditMessageTest(ZulipTestCase): def set_message_editing_params( allow_message_editing: bool, message_content_edit_limit_seconds: int | str, - edit_topic_policy: int, + can_move_messages_between_topics_group: NamedUserGroup, ) -> None: result = self.client_patch( "/json/realm", @@ -901,7 +905,11 @@ class EditMessageTest(ZulipTestCase): "message_content_edit_limit_seconds": orjson.dumps( message_content_edit_limit_seconds ).decode(), - "edit_topic_policy": orjson.dumps(edit_topic_policy).decode(), + "can_move_messages_between_topics_group": orjson.dumps( + { + "new": can_move_messages_between_topics_group.id, + } + ).decode(), }, ) self.assert_json_success(result) @@ -949,38 +957,69 @@ class EditMessageTest(ZulipTestCase): message.date_sent -= timedelta(seconds=180) message.save() + administrators_system_group = NamedUserGroup.objects.get( + name=SystemGroups.ADMINISTRATORS, realm=get_realm("zulip"), is_system_group=True + ) + # test the various possible message editing settings # high enough time limit, all edits allowed - set_message_editing_params(True, 240, EditTopicPolicyEnum.ADMINS_ONLY) + set_message_editing_params(True, 240, administrators_system_group) do_edit_message_assert_success(id_, "A") # out of time, only topic editing allowed - set_message_editing_params(True, 120, EditTopicPolicyEnum.ADMINS_ONLY) + set_message_editing_params(True, 120, administrators_system_group) do_edit_message_assert_success(id_, "B", True) do_edit_message_assert_error(id_, "C", "The time limit for editing this message has passed") # infinite time, all edits allowed - set_message_editing_params(True, "unlimited", EditTopicPolicyEnum.ADMINS_ONLY) + set_message_editing_params(True, "unlimited", administrators_system_group) do_edit_message_assert_success(id_, "D") # without allow_message_editing, editing content is not allowed but # editing topic is allowed if topic-edit time limit has not passed # irrespective of content-edit time limit. - set_message_editing_params(False, 240, EditTopicPolicyEnum.ADMINS_ONLY) + set_message_editing_params(False, 240, administrators_system_group) do_edit_message_assert_success(id_, "B", True) - set_message_editing_params(False, 240, EditTopicPolicyEnum.ADMINS_ONLY) + set_message_editing_params(False, 240, administrators_system_group) do_edit_message_assert_success(id_, "E", True) - set_message_editing_params(False, 120, EditTopicPolicyEnum.ADMINS_ONLY) + set_message_editing_params(False, 120, administrators_system_group) do_edit_message_assert_success(id_, "F", True) - set_message_editing_params(False, "unlimited", EditTopicPolicyEnum.ADMINS_ONLY) + set_message_editing_params(False, "unlimited", administrators_system_group) do_edit_message_assert_success(id_, "G", True) - def test_edit_topic_policy(self) -> None: + def test_edit_message_in_archived_stream(self) -> None: + user = self.example_user("hamlet") + self.login("hamlet") + stream_name = "archived stream" + archived_stream = self.make_stream(stream_name) + self.subscribe(user, stream_name) + msg_id = self.send_stream_message( + user, "archived stream", topic_name="editing", content="before edit" + ) + result = self.client_patch( + f"/json/messages/{msg_id}", + { + "content": "content after edit", + }, + ) + self.assert_json_success(result) + + do_deactivate_stream(archived_stream, acting_user=None) + + result = self.client_patch( + f"/json/messages/{msg_id}", + { + "content": "editing second time", + }, + ) + self.assert_json_error(result, "Invalid message(s)") + + def test_can_move_messages_between_topics_group(self) -> None: def set_message_editing_params( allow_message_editing: bool, message_content_edit_limit_seconds: int | str, - edit_topic_policy: int, + can_move_messages_between_topics_group: NamedUserGroup, ) -> None: self.login("iago") result = self.client_patch( @@ -990,7 +1029,11 @@ class EditMessageTest(ZulipTestCase): "message_content_edit_limit_seconds": orjson.dumps( message_content_edit_limit_seconds ).decode(), - "edit_topic_policy": orjson.dumps(edit_topic_policy).decode(), + "can_move_messages_between_topics_group": orjson.dumps( + { + "new": can_move_messages_between_topics_group.id, + } + ).decode(), }, ) self.assert_json_success(result) @@ -1029,30 +1072,51 @@ class EditMessageTest(ZulipTestCase): # Guest user must be subscribed to the stream to access the message. polonius = self.example_user("polonius") + realm = polonius.realm self.subscribe(polonius, "Denmark") + administrators_system_group = NamedUserGroup.objects.get( + name=SystemGroups.ADMINISTRATORS, realm=realm, is_system_group=True + ) + full_members_system_group = NamedUserGroup.objects.get( + name=SystemGroups.FULL_MEMBERS, realm=realm, is_system_group=True + ) + members_system_group = NamedUserGroup.objects.get( + name=SystemGroups.MEMBERS, realm=realm, is_system_group=True + ) + moderators_system_group = NamedUserGroup.objects.get( + name=SystemGroups.MODERATORS, realm=realm, is_system_group=True + ) + everyone_system_group = NamedUserGroup.objects.get( + name=SystemGroups.EVERYONE, realm=realm, is_system_group=True + ) + nobody_system_group = NamedUserGroup.objects.get( + name=SystemGroups.NOBODY, realm=realm, is_system_group=True + ) + # any user can edit the topic of a message - set_message_editing_params(True, "unlimited", EditTopicPolicyEnum.EVERYONE) + set_message_editing_params(True, "unlimited", everyone_system_group) do_edit_message_assert_success(id_, "A", "polonius") # only members can edit topic of a message - set_message_editing_params(True, "unlimited", EditTopicPolicyEnum.MEMBERS_ONLY) + set_message_editing_params(True, "unlimited", members_system_group) do_edit_message_assert_error( id_, "B", "You don't have permission to edit this message", "polonius" ) do_edit_message_assert_success(id_, "B", "cordelia") # only full members can edit topic of a message - set_message_editing_params(True, "unlimited", EditTopicPolicyEnum.FULL_MEMBERS_ONLY) + set_message_editing_params(True, "unlimited", full_members_system_group) cordelia = self.example_user("cordelia") hamlet = self.example_user("hamlet") - do_set_realm_property(cordelia.realm, "waiting_period_threshold", 10, acting_user=None) cordelia.date_joined = timezone_now() - timedelta(days=9) cordelia.save() hamlet.date_joined = timezone_now() - timedelta(days=9) hamlet.save() + + do_set_realm_property(cordelia.realm, "waiting_period_threshold", 10, acting_user=None) do_edit_message_assert_error( id_, "C", "You don't have permission to edit this message", "cordelia" ) @@ -1062,15 +1126,12 @@ class EditMessageTest(ZulipTestCase): id_, "C", "You don't have permission to edit this message", "hamlet" ) - cordelia.date_joined = timezone_now() - timedelta(days=11) - cordelia.save() - hamlet.date_joined = timezone_now() - timedelta(days=11) - hamlet.save() + do_set_realm_property(cordelia.realm, "waiting_period_threshold", 8, acting_user=None) do_edit_message_assert_success(id_, "C", "cordelia") do_edit_message_assert_success(id_, "CD", "hamlet") # only moderators can edit topic of a message - set_message_editing_params(True, "unlimited", EditTopicPolicyEnum.MODERATORS_ONLY) + set_message_editing_params(True, "unlimited", moderators_system_group) do_edit_message_assert_error( id_, "D", "You don't have permission to edit this message", "cordelia" ) @@ -1081,14 +1142,14 @@ class EditMessageTest(ZulipTestCase): do_edit_message_assert_success(id_, "D", "shiva") # only admins can edit the topics of messages - set_message_editing_params(True, "unlimited", EditTopicPolicyEnum.ADMINS_ONLY) + set_message_editing_params(True, "unlimited", administrators_system_group) do_edit_message_assert_error( id_, "E", "You don't have permission to edit this message", "shiva" ) do_edit_message_assert_success(id_, "E", "iago") # even owners and admins cannot edit the topics of messages - set_message_editing_params(True, "unlimited", EditTopicPolicyEnum.NOBODY) + set_message_editing_params(True, "unlimited", nobody_system_group) do_edit_message_assert_error( id_, "H", "You don't have permission to edit this message", "desdemona" ) @@ -1097,14 +1158,14 @@ class EditMessageTest(ZulipTestCase): ) # users can edit topics even if allow_message_editing is False - set_message_editing_params(False, "unlimited", EditTopicPolicyEnum.EVERYONE) + set_message_editing_params(False, "unlimited", everyone_system_group) do_edit_message_assert_success(id_, "D", "cordelia") # non-admin users cannot edit topics sent > 1 week ago including # sender of the message. message.date_sent -= timedelta(seconds=604900) message.save() - set_message_editing_params(True, "unlimited", EditTopicPolicyEnum.EVERYONE) + set_message_editing_params(True, "unlimited", everyone_system_group) do_edit_message_assert_success(id_, "E", "iago") do_edit_message_assert_success(id_, "F", "shiva") do_edit_message_assert_error( @@ -1131,6 +1192,39 @@ class EditMessageTest(ZulipTestCase): do_edit_message_assert_success(id_, "G", "cordelia") do_edit_message_assert_success(id_, "H", "hamlet") + # Test for checking setting for non-system user group. + user_group = check_add_user_group( + realm, "new_group", [polonius, cordelia], acting_user=cordelia + ) + set_message_editing_params(True, "unlimited", user_group) + # Polonius and Cordelia are in the allowed user group, so can move messages. + do_edit_message_assert_success(id_, "I", "polonius") + do_edit_message_assert_success(id_, "J", "cordelia") + # Iago is not in the allowed user group, so cannot move messages. + do_edit_message_assert_error( + id_, "K", "You don't have permission to edit this message", "iago" + ) + + # Test for checking the setting for anonymous user group. + anonymous_user_group = self.create_or_update_anonymous_group_for_setting( + [cordelia], + [administrators_system_group], + ) + do_change_realm_permission_group_setting( + realm, + "can_move_messages_between_topics_group", + anonymous_user_group, + acting_user=None, + ) + # Cordelia is the direct member of the anonymous user group, so can move messages. + do_edit_message_assert_success(id_, "K", "cordelia") + # Iago is in the `administrators_system_group` subgroup, so can move messages. + do_edit_message_assert_success(id_, "L", "iago") + # Shiva is not in the anonymous user group, so cannot move messages. + do_edit_message_assert_error( + id_, "M", "You don't have permission to edit this message", "shiva" + ) + @mock.patch("zerver.actions.message_edit.send_event_on_commit") def test_topic_wildcard_mention_in_followed_topic( self, mock_send_event: mock.MagicMock diff --git a/zerver/tests/test_message_move_stream.py b/zerver/tests/test_message_move_stream.py index 3a2fcc425d..6b4fd86797 100644 --- a/zerver/tests/test_message_move_stream.py +++ b/zerver/tests/test_message_move_stream.py @@ -16,7 +16,7 @@ from zerver.lib.test_helpers import queries_captured from zerver.lib.url_encoding import near_stream_message_url from zerver.models import Message, NamedUserGroup, Stream, UserMessage, UserProfile from zerver.models.groups import SystemGroups -from zerver.models.realms import EditTopicPolicyEnum, get_realm +from zerver.models.realms import get_realm from zerver.models.streams import get_stream @@ -1130,10 +1130,19 @@ class MessageMoveStreamTest(ZulipTestCase): (user_profile, old_stream, new_stream, msg_id, msg_id_later) = self.prepare_move_topics( "othello", "old_stream_1", "new_stream_1", "test" ) - realm = user_profile.realm - realm.edit_topic_policy = EditTopicPolicyEnum.ADMINS_ONLY - realm.save() + + administrators_system_group = NamedUserGroup.objects.get( + name=SystemGroups.ADMINISTRATORS, realm=realm, is_system_group=True + ) + + do_change_realm_permission_group_setting( + realm, + "can_move_messages_between_topics_group", + administrators_system_group, + acting_user=None, + ) + self.login("cordelia") members_system_group = NamedUserGroup.objects.get( @@ -1175,7 +1184,7 @@ class MessageMoveStreamTest(ZulipTestCase): "iago", "test move stream", "new stream", "test" ) - with self.assert_database_query_count(53), self.assert_memcached_count(14): + with self.assert_database_query_count(55), self.assert_memcached_count(14): result = self.client_patch( f"/json/messages/{msg_id}", { diff --git a/zerver/tests/test_message_move_topic.py b/zerver/tests/test_message_move_topic.py index 2e1d31e060..8a9f745a19 100644 --- a/zerver/tests/test_message_move_topic.py +++ b/zerver/tests/test_message_move_topic.py @@ -266,7 +266,7 @@ class MessageMoveTopicTest(ZulipTestCase): # state + 1/user with a UserTopic row for the events data) # beyond what is typical were there not UserTopic records to # update. Ideally, we'd eliminate the per-user component. - with self.assert_database_query_count(25): + with self.assert_database_query_count(27): check_update_message( user_profile=hamlet, message_id=message_id, @@ -426,7 +426,7 @@ class MessageMoveTopicTest(ZulipTestCase): set_topic_visibility_policy(desdemona, muted_topics, UserTopic.VisibilityPolicy.MUTED) set_topic_visibility_policy(cordelia, muted_topics, UserTopic.VisibilityPolicy.MUTED) - with self.assert_database_query_count(29): + with self.assert_database_query_count(31): check_update_message( user_profile=desdemona, message_id=message_id, @@ -449,7 +449,7 @@ class MessageMoveTopicTest(ZulipTestCase): second_message_id = self.send_stream_message( hamlet, stream_name, topic_name="changed topic name", content="Second message" ) - with self.assert_database_query_count(23): + with self.assert_database_query_count(24): check_update_message( user_profile=desdemona, message_id=second_message_id, @@ -528,7 +528,7 @@ class MessageMoveTopicTest(ZulipTestCase): users_to_be_notified_via_muted_topics_event.append(user_topic.user_profile_id) change_all_topic_name = "Topic 1 edited" - with self.assert_database_query_count(30): + with self.assert_database_query_count(32): check_update_message( user_profile=hamlet, message_id=message_id, diff --git a/zerver/tests/test_message_send.py b/zerver/tests/test_message_send.py index 0fd0913177..9e40e6a4aa 100644 --- a/zerver/tests/test_message_send.py +++ b/zerver/tests/test_message_send.py @@ -30,7 +30,7 @@ from zerver.actions.realm_settings import ( do_change_realm_permission_group_setting, do_set_realm_property, ) -from zerver.actions.streams import do_change_stream_post_policy +from zerver.actions.streams import do_change_stream_post_policy, do_deactivate_stream from zerver.actions.user_groups import add_subgroups_to_user_group, check_add_user_group from zerver.actions.user_settings import do_change_user_setting from zerver.actions.users import do_change_can_forge_sender, do_deactivate_user @@ -1337,6 +1337,34 @@ class MessagePOSTTest(ZulipTestCase): msg = self.get_last_message() self.assertEqual(int(datetime_to_timestamp(msg.date_sent)), int(fake_timestamp)) + def test_send_message_in_archived_stream(self) -> None: + self.login("hamlet") + stream_name = "archived stream" + stream = self.make_stream(stream_name) + result = self.client_post( + "/json/messages", + { + "type": "channel", + "to": orjson.dumps([stream.id]).decode(), + "content": "Test message", + "topic": "Test topic", + }, + ) + self.assert_json_success(result) + + do_deactivate_stream(stream, acting_user=None) + + result = self.client_post( + "/json/messages", + { + "type": "channel", + "to": orjson.dumps([stream.id]).decode(), + "content": "Second Test message", + "topic": "Test topic", + }, + ) + self.assert_json_error(result, f"Not authorized to send to channel '{stream.name}'") + def test_unsubscribed_can_forge_sender(self) -> None: reset_email_visibility_to_everyone_in_zulip_realm() diff --git a/zerver/tests/test_outgoing_webhook_system.py b/zerver/tests/test_outgoing_webhook_system.py index 470c29c901..b0ca446964 100644 --- a/zerver/tests/test_outgoing_webhook_system.py +++ b/zerver/tests/test_outgoing_webhook_system.py @@ -648,7 +648,7 @@ class TestOutgoingWebhookMessaging(ZulipTestCase): prev_message = self.get_second_to_last_message() self.assertIn( - "tried to send a message to channel #**Denmark**, but that channel does not exist", + "Failure! Bot is unavailable", prev_message.content, ) diff --git a/zerver/tests/test_push_notifications.py b/zerver/tests/test_push_notifications.py index ccbf4be4c4..141be5d3af 100644 --- a/zerver/tests/test_push_notifications.py +++ b/zerver/tests/test_push_notifications.py @@ -14,7 +14,6 @@ import orjson import responses import time_machine from django.conf import settings -from django.db import transaction from django.db.models import F, Q from django.http.response import ResponseHeaders from django.test import override_settings @@ -2145,9 +2144,7 @@ class AnalyticsBouncerTest(BouncerTestCase): plan_type=RemoteRealm.PLAN_TYPE_SELF_MANAGED, ) - with transaction.atomic(), self.assertLogs("zulip.analytics", level="WARNING") as m: - # The usual atomic() wrapper to avoid IntegrityError breaking the test's - # transaction. + with self.assertLogs("zulip.analytics", level="WARNING") as m: send_server_data_to_push_bouncer() self.assertEqual(m.output, ["WARNING:zulip.analytics:Duplicate registration detected."]) @@ -2705,6 +2702,7 @@ class AnalyticsBouncerTest(BouncerTestCase): # Now we want to test the other side of this - bouncer's handling # of a deleted realm. with ( + self.captureOnCommitCallbacks(execute=True), self.assertLogs(logger, level="WARNING") as analytics_logger, mock.patch( "corporate.lib.stripe.RemoteRealmBillingSession.on_paid_plan", return_value=True diff --git a/zerver/tests/test_realm.py b/zerver/tests/test_realm.py index 4b7cd4d0b8..dcac8f603c 100644 --- a/zerver/tests/test_realm.py +++ b/zerver/tests/test_realm.py @@ -656,8 +656,7 @@ class RealmTest(ZulipTestCase): stats = merge_streams(realm, denmark, atlantis) self.assertEqual(stats, (1, 1, 1)) - with self.assertRaises(Stream.DoesNotExist): - get_stream("Atlantis", realm) + self.assertEqual(get_stream("Atlantis", realm).deactivated, True) stats = merge_streams(realm, denmark, new_stream_announcements_stream) self.assertEqual(stats, (2, new_stream_announcements_stream_messages_count, 10)) @@ -850,7 +849,6 @@ class RealmTest(ZulipTestCase): message_content_delete_limit_seconds=-10, wildcard_mention_policy=10, invite_to_realm_policy=10, - edit_topic_policy=10, message_content_edit_limit_seconds=0, move_messages_within_stream_limit_seconds=0, move_messages_between_streams_limit_seconds=0, @@ -1698,7 +1696,6 @@ class RealmAPITest(ZulipTestCase): ], message_content_delete_limit_seconds=[1000, 1100, 1200], invite_to_realm_policy=Realm.INVITE_TO_REALM_POLICY_TYPES, - edit_topic_policy=Realm.EDIT_TOPIC_POLICY_TYPES, message_content_edit_limit_seconds=[1000, 1100, 1200], move_messages_within_stream_limit_seconds=[1000, 1100, 1200], move_messages_between_streams_limit_seconds=[1000, 1100, 1200], @@ -1745,13 +1742,11 @@ class RealmAPITest(ZulipTestCase): self.assertEqual(getattr(realm, setting_name), default_group.usergroup_ptr) for user_group in all_system_user_groups: - value = orjson.dumps(user_group.id).decode() - if setting_name in Realm.REALM_PERMISSION_GROUP_SETTINGS_WITH_NEW_API_FORMAT: - value = orjson.dumps( - { - "new": user_group.id, - } - ).decode() + value = orjson.dumps( + { + "new": user_group.id, + } + ).decode() if ( ( @@ -1788,56 +1783,55 @@ class RealmAPITest(ZulipTestCase): if setting_permission_configuration.require_system_group: leadership_group = NamedUserGroup.objects.get(name="leadership", realm=realm) - value = orjson.dumps(leadership_group.id).decode() - if setting_name in Realm.REALM_PERMISSION_GROUP_SETTINGS_WITH_NEW_API_FORMAT: - value = orjson.dumps( - { - "new": leadership_group.id, - } - ).decode() + value = orjson.dumps( + { + "new": leadership_group.id, + } + ).decode() result = self.client_patch("/json/realm", {setting_name: value}) self.assert_json_error(result, f"'{setting_name}' must be a system user group.") - if setting_name in Realm.REALM_PERMISSION_GROUP_SETTINGS_WITH_NEW_API_FORMAT: - admins_group = NamedUserGroup.objects.get( - name=SystemGroups.ADMINISTRATORS, realm=realm - ) - moderators_group = NamedUserGroup.objects.get( - name=SystemGroups.MODERATORS, realm=realm - ) - value = orjson.dumps( - { - "new": { - "direct_members": [], - "direct_subgroups": [admins_group.id, leadership_group.id], - } + admins_group = NamedUserGroup.objects.get(name=SystemGroups.ADMINISTRATORS, realm=realm) + moderators_group = NamedUserGroup.objects.get(name=SystemGroups.MODERATORS, realm=realm) + value = orjson.dumps( + { + "new": { + "direct_members": [], + "direct_subgroups": [admins_group.id, leadership_group.id], } - ).decode() - result = self.client_patch("/json/realm", {setting_name: value}) - self.assert_json_error(result, f"'{setting_name}' must be a system user group.") + } + ).decode() + result = self.client_patch("/json/realm", {setting_name: value}) + self.assert_json_error(result, f"'{setting_name}' must be a system user group.") - value = orjson.dumps( - { - "new": { - "direct_members": [], - "direct_subgroups": [admins_group.id, moderators_group.id], - } + value = orjson.dumps( + { + "new": { + "direct_members": [], + "direct_subgroups": [admins_group.id, moderators_group.id], } - ).decode() - result = self.client_patch("/json/realm", {setting_name: value}) - self.assert_json_error(result, f"'{setting_name}' must be a system user group.") + } + ).decode() + result = self.client_patch("/json/realm", {setting_name: value}) + self.assert_json_error(result, f"'{setting_name}' must be a system user group.") - value = orjson.dumps( - { - "new": { - "direct_members": [], - "direct_subgroups": [admins_group.id], - } + group = admins_group + if setting_permission_configuration.allowed_system_groups: + group = NamedUserGroup.objects.get( + name=setting_permission_configuration.allowed_system_groups[0], realm=realm + ) + + value = orjson.dumps( + { + "new": { + "direct_members": [], + "direct_subgroups": [group.id], } - ).decode() - realm = self.update_with_api(setting_name, value) - self.assertEqual(getattr(realm, setting_name), admins_group.usergroup_ptr) + } + ).decode() + realm = self.update_with_api(setting_name, value) + self.assertEqual(getattr(realm, setting_name), group.usergroup_ptr) def do_test_realm_permission_group_setting_update_api_with_anonymous_groups( self, setting_name: str @@ -2031,13 +2025,10 @@ class RealmAPITest(ZulipTestCase): for prop in Realm.REALM_PERMISSION_GROUP_SETTINGS: with self.subTest(property=prop): self.do_test_realm_permission_group_setting_update_api(prop) - - for prop in Realm.REALM_PERMISSION_GROUP_SETTINGS_WITH_NEW_API_FORMAT: - if Realm.REALM_PERMISSION_GROUP_SETTINGS[prop].require_system_group: - # Anonymous system groups aren't relevant when - # restricted to system groups. - continue - with self.subTest(property=prop): + if Realm.REALM_PERMISSION_GROUP_SETTINGS[prop].require_system_group: + # Anonymous system groups aren't relevant when + # restricted to system groups. + continue self.do_test_realm_permission_group_setting_update_api_with_anonymous_groups(prop) # Not in Realm.property_types because org_type has @@ -2377,12 +2368,12 @@ class RealmAPITest(ZulipTestCase): self.login("iago") members_group = NamedUserGroup.objects.get(name="role:members", realm=realm) - req = {"can_access_all_users_group": orjson.dumps(members_group.id).decode()} + req = {"can_access_all_users_group": orjson.dumps({"new": members_group.id}).decode()} result = self.client_patch("/json/realm", req) self.assert_json_error(result, "Available on Zulip Cloud Plus. Upgrade to access.") do_change_realm_plan_type(realm, Realm.PLAN_TYPE_STANDARD, acting_user=None) - req = {"can_access_all_users_group": orjson.dumps(members_group.id).decode()} + req = {"can_access_all_users_group": orjson.dumps({"new": members_group.id}).decode()} result = self.client_patch("/json/realm", req) self.assert_json_error(result, "Available on Zulip Cloud Plus. Upgrade to access.") diff --git a/zerver/tests/test_signup.py b/zerver/tests/test_signup.py index 5fd42628ae..8ab0bfd07b 100644 --- a/zerver/tests/test_signup.py +++ b/zerver/tests/test_signup.py @@ -67,6 +67,7 @@ from zerver.models import ( CustomProfileFieldValue, DefaultStream, Message, + OnboardingStep, OnboardingUserMessage, PreregistrationRealm, PreregistrationUser, @@ -993,7 +994,7 @@ class LoginTest(ZulipTestCase): def test_login_nonexistent_user(self) -> None: result = self.login_with_return("xxx@zulip.com", "xxx") self.assertEqual(result.status_code, 200) - self.assert_in_response("Please enter a correct email and password", result) + self.assert_in_response("Incorrect email or password.", result) self.assert_logged_in_user_id(None) def test_login_wrong_subdomain(self) -> None: @@ -1009,9 +1010,7 @@ class LoginTest(ZulipTestCase): ], ) self.assertEqual(result.status_code, 200) - expected_error = ( - "Please enter a correct email and password. Note that both fields may be case-sensitive" - ) + expected_error = "Incorrect email or password." self.assert_in_response(expected_error, result) self.assert_logged_in_user_id(None) @@ -2828,6 +2827,9 @@ class UserSignUpTest(ZulipTestCase): hamlet_in_zulip.email_address_visibility = UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE hamlet_in_zulip.save() + OnboardingStep.objects.filter(user=hamlet_in_zulip).delete() + OnboardingStep.objects.create(user=hamlet_in_zulip, onboarding_step="intro_resolve_topic") + result = self.client_post("/accounts/home/", {"email": email}, subdomain=subdomain) self.assertEqual(result.status_code, 302) result = self.client_get(result["Location"], subdomain=subdomain) @@ -2846,6 +2848,12 @@ class UserSignUpTest(ZulipTestCase): self.assertEqual(hamlet.high_contrast_mode, False) self.assertEqual(hamlet.enable_stream_audible_notifications, False) self.assertEqual(hamlet.enter_sends, False) + # 'visibility_policy_banner' is marked as read in 'process_new_human_user'. + # 'copy_default_settings' is not executed as the user decided to NOT import + # settings from another realm, hence 'intro_resolve_topic' not marked as seen. + onboarding_steps = OnboardingStep.objects.filter(user=hamlet) + self.assertEqual(onboarding_steps.count(), 1) + self.assertEqual(onboarding_steps[0].onboarding_step, "visibility_policy_banner") def test_signup_with_user_settings_from_another_realm(self) -> None: hamlet_in_zulip = self.example_user("hamlet") @@ -2866,6 +2874,12 @@ class UserSignUpTest(ZulipTestCase): hamlet_in_zulip.email_address_visibility = UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE hamlet_in_zulip.save() + OnboardingStep.objects.filter(user=hamlet_in_zulip).delete() + OnboardingStep.objects.create(user=hamlet_in_zulip, onboarding_step="intro_resolve_topic") + OnboardingStep.objects.create( + user=hamlet_in_zulip, onboarding_step="visibility_policy_banner" + ) + # Now we'll be making requests to another subdomain, so we need to logout # to avoid polluting the session in the test environment by still being # logged in. @@ -2911,6 +2925,13 @@ class UserSignUpTest(ZulipTestCase): self.assertEqual( hamlet_in_lear.email_address_visibility, UserProfile.EMAIL_ADDRESS_VISIBILITY_NOBODY ) + # Verify that 'copy_default_settings' copies the onboarding steps. + onboarding_steps = OnboardingStep.objects.filter(user=hamlet_in_lear) + self.assertEqual(onboarding_steps.count(), 2) + self.assertEqual( + set(onboarding_steps.values_list("onboarding_step", flat=True)), + {"intro_resolve_topic", "visibility_policy_banner"}, + ) zulip_path_id = avatar_disk_path(hamlet_in_zulip) lear_path_id = avatar_disk_path(hamlet_in_lear) diff --git a/zerver/tests/test_slack_message_conversion.py b/zerver/tests/test_slack_message_conversion.py index f24dc857b8..40334327f3 100644 --- a/zerver/tests/test_slack_message_conversion.py +++ b/zerver/tests/test_slack_message_conversion.py @@ -113,6 +113,11 @@ class SlackMessageConversion(ZulipTestCase): self.assertEqual(text, "http://journals.plos.org/plosone/article") self.assertEqual(has_link, True) + message = "" + text, mentioned_users, has_link = convert_to_zulip_markdown(message, [], {}, slack_user_map) + self.assertEqual(text, "[Help logging in to CZO](http://chat.zulip.org/help/logging-in)") + self.assertEqual(has_link, True) + message = "" text, mentioned_users, has_link = convert_to_zulip_markdown(message, [], {}, slack_user_map) self.assertEqual(text, "mailto:foo@foo.com") diff --git a/zerver/tests/test_subs.py b/zerver/tests/test_subs.py index 2d27da6f49..46c8eb6ef9 100644 --- a/zerver/tests/test_subs.py +++ b/zerver/tests/test_subs.py @@ -9,6 +9,7 @@ from unittest import mock import orjson from django.conf import settings from django.core.exceptions import ValidationError +from django.db import transaction from django.http import HttpResponse from django.utils.timezone import now as timezone_now from typing_extensions import override @@ -94,7 +95,6 @@ from zerver.lib.test_helpers import ( cache_tries_captured, get_subscription, most_recent_message, - most_recent_usermessage, queries_captured, reset_email_visibility_to_everyone_in_zulip_realm, ) @@ -238,8 +238,8 @@ class TestMiscStuff(ZulipTestCase): """Verify that all the fields from `Stream.API_FIELDS` and `Subscription.API_FIELDS` present in `APIStreamDict` and `APISubscriptionDict`, respectively. """ - expected_fields = set(Stream.API_FIELDS) | {"stream_id"} - expected_fields -= {"id", "can_remove_subscribers_group_id"} + expected_fields = set(Stream.API_FIELDS) | {"stream_id", "is_archived"} + expected_fields -= {"id", "can_remove_subscribers_group_id", "deactivated"} expected_fields |= {"can_remove_subscribers_group"} stream_dict_fields = set(APIStreamDict.__annotations__.keys()) @@ -1424,7 +1424,7 @@ class StreamAdminTest(ZulipTestCase): ) .exists() ) - self.assertFalse(subscription_exists) + self.assertTrue(subscription_exists) def test_deactivate_stream_removes_default_stream(self) -> None: stream = self.make_stream("new_stream") @@ -1454,45 +1454,12 @@ class StreamAdminTest(ZulipTestCase): do_deactivate_stream(streams_to_remove[0], acting_user=None) self.assertEqual(get_streams(default_stream_groups[0]), streams_to_keep) - def test_deactivate_stream_marks_messages_as_read(self) -> None: - hamlet = self.example_user("hamlet") - cordelia = self.example_user("cordelia") - stream = self.make_stream("new_stream") - self.subscribe(hamlet, stream.name) - self.subscribe(cordelia, stream.name) - self.subscribe(hamlet, "Denmark") - self.subscribe(cordelia, "Denmark") - - self.send_stream_message(hamlet, stream.name) - new_stream_usermessage = most_recent_usermessage(cordelia) - - # We send a message to a different stream too, to verify that the - # deactivation of new_stream won't corrupt read state of UserMessage elsewhere. - self.send_stream_message(hamlet, "Denmark") - denmark_usermessage = most_recent_usermessage(cordelia) - - self.assertFalse(new_stream_usermessage.flags.read) - self.assertFalse(denmark_usermessage.flags.read) - - with self.captureOnCommitCallbacks(execute=True): - do_deactivate_stream(stream, acting_user=None) - new_stream_usermessage.refresh_from_db() - denmark_usermessage.refresh_from_db() - self.assertTrue(new_stream_usermessage.flags.read) - self.assertFalse(denmark_usermessage.flags.read) - def test_deactivated_streams_by_old_name(self) -> None: realm = get_realm("zulip") stream = self.make_stream("new_stream") do_deactivate_stream(stream, acting_user=None) self.assertEqual(set(deactivated_streams_by_old_name(realm, "new_stream")), {stream}) - second_stream = self.make_stream("new_stream") - do_deactivate_stream(second_stream, acting_user=None) - self.assertEqual( - set(deactivated_streams_by_old_name(realm, "new_stream")), {stream, second_stream} - ) - self.make_stream("!DEACTIVATED:old_style") # This is left active old_style = self.make_stream("old_style") do_deactivate_stream(old_style, acting_user=None) @@ -1512,6 +1479,31 @@ class StreamAdminTest(ZulipTestCase): with self.assertRaisesRegex(JsonableError, "Channel named existing already exists"): do_unarchive_stream(stream, new_name="existing", acting_user=None) + def test_unarchive_stream_private_with_no_subscribers(self) -> None: + stream = self.make_stream("private", invite_only=True) + do_deactivate_stream(stream, acting_user=None) + with self.assertRaisesRegex(JsonableError, "Channel is private and have no subscribers"): + do_unarchive_stream(stream, new_name="private", acting_user=None) + + def test_unarchive_stream_private_and_web_public(self) -> None: + hamlet = self.example_user("hamlet") + cordelia = self.example_user("cordelia") + + stream = self.make_stream("private", invite_only=True) + self.subscribe(hamlet, stream.name) + self.subscribe(cordelia, stream.name) + do_deactivate_stream(stream, acting_user=None) + stream = Stream.objects.get(id=stream.id) + # Previously, archiving a channel set invite_only=True without changing is_web_public. + # This led to archived channels potentially being in an invalid state. + stream.is_web_public = True + stream.save(update_fields=["is_web_public"]) + with self.capture_send_event_calls(expected_num_events=2): + do_unarchive_stream(stream, new_name="private", acting_user=None) + + stream = Stream.objects.get(id=stream.id) + self.assertFalse(stream.is_web_public) + def test_unarchive_stream(self) -> None: desdemona = self.example_user("desdemona") iago = self.example_user("iago") @@ -1519,49 +1511,30 @@ class StreamAdminTest(ZulipTestCase): cordelia = self.example_user("cordelia") stream = self.make_stream("new_stream", is_web_public=True) + was_invite_only = stream.invite_only + was_web_public = stream.is_web_public + was_history_public = stream.history_public_to_subscribers + self.subscribe(hamlet, stream.name) self.subscribe(cordelia, stream.name) do_deactivate_stream(stream, acting_user=None) - with self.capture_send_event_calls(expected_num_events=4) as events: + with self.capture_send_event_calls(expected_num_events=2) as events: do_unarchive_stream(stream, new_name="new_stream", acting_user=None) - # Tell all admins and owners that the stream exists + # Tell all subscribers and admins and owners that the stream exists self.assertEqual(events[0]["event"]["op"], "create") self.assertEqual(events[0]["event"]["streams"][0]["name"], "new_stream") self.assertEqual(events[0]["event"]["streams"][0]["stream_id"], stream.id) - self.assertEqual(set(events[0]["users"]), {iago.id, desdemona.id}) - - # Tell the owners that they're subscribed to it - self.assertEqual(events[1]["event"]["op"], "add") - self.assertEqual(events[1]["event"]["subscriptions"][0]["name"], "new_stream") - self.assertEqual(events[1]["event"]["subscriptions"][0]["stream_id"], stream.id) - self.assertEqual(events[1]["users"], [desdemona.id]) - - # iago (as an admin) gets to know that desdemona (the owner) is now subscribed. - self.assertEqual( - events[2], - { - "event": { - "op": "peer_add", - "stream_ids": [stream.id], - "type": "subscription", - "user_ids": [desdemona.id], - }, - "users": [iago.id], - }, - ) - - # Send a message there logging the reactivation - self.assertEqual(events[3]["event"]["type"], "message") + self.assertEqual(set(events[0]["users"]), {hamlet.id, cordelia.id, iago.id, desdemona.id}) stream = Stream.objects.get(id=stream.id) self.assertFalse(stream.deactivated) - self.assertTrue(stream.invite_only) - self.assertFalse(stream.is_web_public) - self.assertTrue(stream.history_public_to_subscribers) + self.assertEqual(stream.invite_only, was_invite_only) + self.assertEqual(stream.is_web_public, was_web_public) + self.assertEqual(stream.history_public_to_subscribers, was_history_public) self.assertEqual( - [desdemona.id], + [hamlet.id, cordelia.id], [ sub.user_profile_id for sub in get_active_subscriptions_for_stream_id( @@ -2479,12 +2452,7 @@ class StreamAdminTest(ZulipTestCase): realm = stream.realm stream_id = stream.id - # Simulate that a stream by the same name has already been - # deactivated, just to exercise our renaming logic: - # Since we do not know the id of these simulated stream we prepend the name with a random hashed_stream_id - ensure_stream(realm, "DB32B77!DEACTIVATED:" + active_name, acting_user=None) - - with self.capture_send_event_calls(expected_num_events=1) as events: + with self.capture_send_event_calls(expected_num_events=2) as events: result = self.client_delete("/json/streams/" + str(stream_id)) self.assert_json_success(result) @@ -2498,27 +2466,32 @@ class StreamAdminTest(ZulipTestCase): self.assertEqual(event["op"], "delete") self.assertEqual(event["streams"][0]["stream_id"], stream.id) - with self.assertRaises(Stream.DoesNotExist): - Stream.objects.get(realm=get_realm("zulip"), name=active_name) - - # A deleted stream's name is changed, is deactivated, is invite-only, - # and has no subscribers. hashed_stream_id = hashlib.sha512(str(stream_id).encode()).hexdigest()[0:7] - deactivated_stream_name = hashed_stream_id + "!DEACTIVATED:" + active_name + old_deactivated_stream_name = hashed_stream_id + "!DEACTIVATED:" + active_name + + with self.assertRaises(Stream.DoesNotExist): + Stream.objects.get(realm=get_realm("zulip"), name=old_deactivated_stream_name) + + # An archived stream is deactivated, but subscribers and + # permissions settings are not immediately changed. + deactivated_stream_name = active_name deactivated_stream = get_stream(deactivated_stream_name, realm) self.assertTrue(deactivated_stream.deactivated) - self.assertTrue(deactivated_stream.invite_only) self.assertEqual(deactivated_stream.name, deactivated_stream_name) - subscribers = self.users_subscribed_to_stream(deactivated_stream_name, realm) - self.assertEqual(subscribers, []) # It doesn't show up in the list of public streams anymore. result = self.client_get("/json/streams", {"include_subscribed": "false"}) public_streams = [s["name"] for s in self.assert_json_success(result)["streams"]] - self.assertNotIn(active_name, public_streams) self.assertNotIn(deactivated_stream_name, public_streams) - # Even if you could guess the new name, you can't subscribe to it. + # It shows up with `exclude_archived` parameter set to false. + result = self.client_get( + "/json/streams", {"exclude_archived": "false", "include_all_active": "true"} + ) + streams = [s["name"] for s in self.assert_json_success(result)["streams"]] + self.assertIn(deactivated_stream_name, streams) + + # You can't subscribe to archived stream. result = self.common_subscribe_to_streams( self.example_user("hamlet"), [deactivated_stream_name], allow_fail=True ) @@ -4044,7 +4017,9 @@ class SubscriptionRestApiTest(ZulipTestCase): def thunk2() -> HttpResponse: raise JsonableError("random failure") - with self.assertRaises(JsonableError): + with transaction.atomic(), self.assertRaises(JsonableError): + # The atomic() wrapper helps to avoid JsonableError breaking + # the test's transaction. compose_views([thunk1, thunk2]) user_profile = self.example_user("hamlet") @@ -5474,10 +5449,10 @@ class SubscriptionAPITest(ZulipTestCase): self.assert_length(result, 1) self.assertEqual(result[0]["stream_id"], stream1.id) - def test_gather_subscriptions_excludes_deactivated_streams(self) -> None: + def test_gather_subscriptions_deactivated_streams(self) -> None: """ - Check that gather_subscriptions_helper does not include deactivated streams in its - results. + Check that gather_subscriptions_helper does/doesn't include deactivated streams in its + results with `exclude_archived` parameter. """ realm = get_realm("zulip") admin_user = self.example_user("iago") @@ -5507,6 +5482,10 @@ class SubscriptionAPITest(ZulipTestCase): admin_after_delete = gather_subscriptions_helper(admin_user) non_admin_after_delete = gather_subscriptions_helper(non_admin_user) + admin_after_delete_include_archived = gather_subscriptions_helper( + admin_user, include_archived_channels=True + ) + # Compare results - should be 1 stream less self.assertTrue( len(admin_before_delete.subscriptions) == len(admin_after_delete.subscriptions) + 1, @@ -5518,6 +5497,14 @@ class SubscriptionAPITest(ZulipTestCase): "Expected exactly 1 less stream from gather_subscriptions_helper", ) + # Compare results - should be the same number of streams + self.assertTrue( + len(admin_before_delete.subscriptions) + len(admin_before_delete.unsubscribed) + == len(admin_after_delete_include_archived.subscriptions) + + len(admin_after_delete_include_archived.unsubscribed), + "Expected exact number of streams from gather_subscriptions_helper", + ) + def test_validate_user_access_to_subscribers_helper(self) -> None: """ Ensure the validate_user_access_to_subscribers_helper is properly raising @@ -5954,6 +5941,7 @@ class GetSubscribersTest(ZulipTestCase): def verify_sub_fields(self, sub_data: SubscriptionInfo) -> None: other_fields = { + "is_archived", "is_announcement_only", "in_home_view", "stream_id", @@ -5962,7 +5950,7 @@ class GetSubscribersTest(ZulipTestCase): } expected_fields = set(Stream.API_FIELDS) | set(Subscription.API_FIELDS) | other_fields - expected_fields -= {"id", "can_remove_subscribers_group_id"} + expected_fields -= {"id", "can_remove_subscribers_group_id", "deactivated"} expected_fields |= {"can_remove_subscribers_group"} for lst in [sub_data.subscriptions, sub_data.unsubscribed]: @@ -5970,6 +5958,7 @@ class GetSubscribersTest(ZulipTestCase): self.assertEqual(set(sub), expected_fields) other_fields = { + "is_archived", "is_announcement_only", "stream_id", "stream_weekly_traffic", @@ -5977,7 +5966,7 @@ class GetSubscribersTest(ZulipTestCase): } expected_fields = set(Stream.API_FIELDS) | other_fields - expected_fields -= {"id", "can_remove_subscribers_group_id"} + expected_fields -= {"id", "can_remove_subscribers_group_id", "deactivated"} expected_fields |= {"can_remove_subscribers_group"} for never_sub in sub_data.never_subscribed: diff --git a/zerver/tests/test_upload.py b/zerver/tests/test_upload.py index a6acaa5021..c1981ea875 100644 --- a/zerver/tests/test_upload.py +++ b/zerver/tests/test_upload.py @@ -26,7 +26,12 @@ from zerver.actions.realm_logo import do_change_logo_source from zerver.actions.realm_settings import do_change_realm_plan_type, do_set_realm_property from zerver.actions.user_settings import do_delete_avatar_image from zerver.lib.attachments import validate_attachment_request -from zerver.lib.avatar import avatar_url, get_avatar_field +from zerver.lib.avatar import ( + DEFAULT_AVATAR_FILE, + avatar_url, + get_avatar_field, + get_static_avatar_url, +) from zerver.lib.cache import cache_delete, cache_get, get_realm_used_upload_space_cache_key from zerver.lib.create_user import copy_default_settings from zerver.lib.initial_password import initial_password @@ -1270,6 +1275,14 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase): "Not logged in: API authentication or user session required", status_code=401, ) + # Disallow access by id for spectators with unauthenticated access + # when realm public streams is false. + response = self.client_get(f"/avatar/{cordelia.id}", {"foo": "bar"}) + self.assert_json_error( + response, + "Not logged in: API authentication or user session required", + status_code=401, + ) # Allow unauthenticated/spectator requests by ID. response = self.client_get(f"/avatar/{cordelia.id}", {"foo": "bar"}) @@ -1296,6 +1309,12 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase): redirect_url = response["Location"] self.assertTrue(redirect_url.endswith("images/unknown-user-avatar.png?foo=bar")) + invalid_user_id = 999 + response = self.client_get(f"/avatar/{invalid_user_id}", {"foo": "bar"}) + self.assertEqual(302, response.status_code) + redirect_url = response["Location"] + self.assertTrue(redirect_url.endswith("images/unknown-user-avatar.png?foo=bar")) + def test_get_user_avatar_medium(self) -> None: hamlet = self.example_user("hamlet") self.login_user(hamlet) @@ -1532,6 +1551,39 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase): result = self.client_post("/json/users/me/avatar", {"file": fp}) self.assert_json_error(result, "Uploaded file is larger than the allowed limit of 0 MiB") + def test_system_bot_avatars_url(self) -> None: + self.login("hamlet") + system_bot_emails = [ + settings.NOTIFICATION_BOT, + settings.WELCOME_BOT, + settings.EMAIL_GATEWAY_BOT, + ] + internal_realm = get_realm(settings.SYSTEM_BOT_REALM) + default_avatar = DEFAULT_AVATAR_FILE + + for email in system_bot_emails: + system_bot = get_system_bot(email, internal_realm.id) + response = self.client_get(f"/avatar/{email}") + redirect_url = response["Location"] + self.assertEqual(redirect_url, str(avatar_url(system_bot))) + + response = self.client_get(f"/avatar/{email}/medium") + redirect_url = response["Location"] + self.assertEqual(redirect_url, str(avatar_url(system_bot, medium=True))) + + with ( + self.settings(STATIC_ROOT="static/"), + patch("zerver.lib.avatar.staticfiles_storage.exists") as mock_exists, + ): + mock_exists.return_value = False + static_avatar_url = get_static_avatar_url("false-bot@zulip.com", False) + self.assertIn(default_avatar, static_avatar_url) + + with self.settings(DEBUG=True), self.assertRaises(AssertionError) as e: + get_static_avatar_url("false-bot@zulip.com", False) + expected_error_message = "Unknown avatar file for: false-bot@zulip.com" + self.assertEqual(str(e.exception), expected_error_message) + class RealmIconTest(UploadSerializeMixin, ZulipTestCase): def test_multiple_upload_failure(self) -> None: diff --git a/zerver/tests/test_users.py b/zerver/tests/test_users.py index cb51ea802d..d898093105 100644 --- a/zerver/tests/test_users.py +++ b/zerver/tests/test_users.py @@ -23,6 +23,7 @@ from zerver.actions.user_settings import bulk_regenerate_api_keys, do_change_use from zerver.actions.user_topics import do_set_user_topic_visibility_policy from zerver.actions.users import ( change_user_is_active, + do_change_can_change_user_emails, do_change_can_create_users, do_change_can_forge_sender, do_change_is_billing_admin, @@ -1120,6 +1121,113 @@ class BulkCreateUserTest(ZulipTestCase): ) +class UpdateUserByEmailEndpointTest(ZulipTestCase): + def test_update_user_by_email(self) -> None: + self.login("iago") + hamlet = self.example_user("hamlet") + do_change_user_setting( + hamlet, + "email_address_visibility", + UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE, + acting_user=None, + ) + + result = self.client_patch( + f"/json/users/{hamlet.delivery_email}", + dict(full_name="Newname"), + ) + self.assert_json_success(result) + hamlet.refresh_from_db() + self.assertEqual(hamlet.full_name, "Newname") + + do_change_user_setting( + hamlet, + "email_address_visibility", + UserProfile.EMAIL_ADDRESS_VISIBILITY_MEMBERS, + acting_user=None, + ) + result = self.client_patch( + f"/json/users/{hamlet.delivery_email}", + dict(full_name="Newname2"), + ) + self.assert_json_success(result) + hamlet.refresh_from_db() + self.assertEqual(hamlet.full_name, "Newname2") + + do_change_user_setting( + hamlet, + "email_address_visibility", + UserProfile.EMAIL_ADDRESS_VISIBILITY_NOBODY, + acting_user=None, + ) + result = self.client_patch( + f"/json/users/{hamlet.delivery_email}", + dict(full_name="Newname2"), + ) + self.assert_json_error(result, "No such user") + + # The dummy email can be used, when we don't have access + # to the target's email address. + dummy_email = hamlet.email + result = self.client_patch( + f"/json/users/{dummy_email}", + dict(full_name="Newname3"), + ) + self.assert_json_success(result) + hamlet.refresh_from_db() + self.assertEqual(hamlet.full_name, "Newname3") + + +class AdminChangeUserEmailTest(ZulipTestCase): + def test_change_user_email_backend(self) -> None: + cordelia = self.example_user("cordelia") + realm_admin = self.example_user("iago") + self.login_user(realm_admin) + + valid_params = dict(new_email="cordelia_new@zulip.com") + + self.assertEqual(realm_admin.can_change_user_emails, False) + result = self.client_patch(f"/json/users/{cordelia.id}", valid_params) + self.assert_json_error(result, "User not authorized to change user emails") + + do_change_can_change_user_emails(realm_admin, True) + # can_change_user_emails is insufficient without being a realm administrator: + do_change_user_role(realm_admin, UserProfile.ROLE_MEMBER, acting_user=None) + result = self.client_patch(f"/json/users/{cordelia.id}", valid_params) + self.assert_json_error(result, "Insufficient permission") + + do_change_user_role(realm_admin, UserProfile.ROLE_REALM_ADMINISTRATOR, acting_user=None) + result = self.client_patch( + f"/json/users/{cordelia.id}", + dict(new_email="invalid"), + ) + self.assert_json_error(result, "Invalid new email address.") + + result = self.client_patch( + f"/json/users/{UserProfile.objects.latest('id').id + 1}", + dict(new_email="new@zulip.com"), + ) + self.assert_json_error(result, "No such user") + + result = self.client_patch( + f"/json/users/{cordelia.id}", + dict(new_email=realm_admin.delivery_email), + ) + self.assert_json_error(result, "New email value error: Already has an account.") + + result = self.client_patch(f"/json/users/{cordelia.id}", valid_params) + self.assert_json_success(result) + + cordelia.refresh_from_db() + self.assertEqual(cordelia.delivery_email, "cordelia_new@zulip.com") + + last_realm_audit_log = RealmAuditLog.objects.last() + assert last_realm_audit_log is not None + self.assertEqual(last_realm_audit_log.event_type, AuditLogEventType.USER_EMAIL_CHANGED) + self.assertEqual(last_realm_audit_log.modified_user, cordelia) + self.assertEqual(last_realm_audit_log.acting_user, realm_admin) + + class AdminCreateUserTest(ZulipTestCase): def test_create_user_backend(self) -> None: # This test should give us complete coverage on diff --git a/zerver/tornado/django_api.py b/zerver/tornado/django_api.py index b9bc56b0cc..d7043401d8 100644 --- a/zerver/tornado/django_api.py +++ b/zerver/tornado/django_api.py @@ -91,6 +91,7 @@ def request_event_queue( linkifier_url_template: bool = False, user_list_incomplete: bool = False, include_deactivated_groups: bool = False, + archived_channels: bool = False, ) -> str | None: if not settings.USING_TORNADO: return None @@ -115,6 +116,7 @@ def request_event_queue( "linkifier_url_template": orjson.dumps(linkifier_url_template), "user_list_incomplete": orjson.dumps(user_list_incomplete), "include_deactivated_groups": orjson.dumps(include_deactivated_groups), + "archived_channels": orjson.dumps(archived_channels), } if event_types is not None: diff --git a/zerver/tornado/event_queue.py b/zerver/tornado/event_queue.py index 6b0f246424..84964eb044 100644 --- a/zerver/tornado/event_queue.py +++ b/zerver/tornado/event_queue.py @@ -79,6 +79,7 @@ class ClientDescriptor: linkifier_url_template: bool = False, user_list_incomplete: bool = False, include_deactivated_groups: bool = False, + archived_channels: bool = False, ) -> None: # TODO: We eventually want to upstream this code to the caller, but # serialization concerns make it a bit difficult. @@ -110,6 +111,7 @@ class ClientDescriptor: self.linkifier_url_template = linkifier_url_template self.user_list_incomplete = user_list_incomplete self.include_deactivated_groups = include_deactivated_groups + self.archived_channels = archived_channels # Default for lifespan_secs is DEFAULT_EVENT_QUEUE_TIMEOUT_SECS; # but users can set it as high as MAX_QUEUE_TIMEOUT_SECS. @@ -141,6 +143,7 @@ class ClientDescriptor: linkifier_url_template=self.linkifier_url_template, user_list_incomplete=self.user_list_incomplete, include_deactivated_groups=self.include_deactivated_groups, + archived_channels=self.archived_channels, ) @override @@ -178,6 +181,7 @@ class ClientDescriptor: d.get("linkifier_url_template", False), d.get("user_list_incomplete", False), d.get("include_deactivated_groups", False), + d.get("archived_channels", False), ) ret.last_connection_time = d["last_connection_time"] return ret diff --git a/zerver/tornado/views.py b/zerver/tornado/views.py index f2a9060bdd..3835cd8af3 100644 --- a/zerver/tornado/views.py +++ b/zerver/tornado/views.py @@ -210,6 +210,10 @@ def get_events_backend( Json[bool], ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED), ] = False, + archived_channels: Annotated[ + Json[bool], + ApiParamConfig(documentation_status=DocumentationStatus.INTENTIONALLY_UNDOCUMENTED), + ] = False, ) -> HttpResponse: if narrow is None: narrow = [] @@ -248,6 +252,7 @@ def get_events_backend( linkifier_url_template=linkifier_url_template, user_list_incomplete=user_list_incomplete, include_deactivated_groups=include_deactivated_groups, + archived_channels=archived_channels, ) result = in_tornado_thread(fetch_events)( diff --git a/zerver/transaction_tests/test_user_groups.py b/zerver/transaction_tests/test_user_groups.py index 3b5f6be325..1cdeff32ec 100644 --- a/zerver/transaction_tests/test_user_groups.py +++ b/zerver/transaction_tests/test_user_groups.py @@ -28,7 +28,6 @@ def dev_update_subgroups( assert BARRIER is not None try: with ( - transaction.atomic(), mock.patch("zerver.lib.user_groups.access_user_group_for_update") as m, ): diff --git a/zerver/views/message_edit.py b/zerver/views/message_edit.py index 838d40ff2b..18a43615f4 100644 --- a/zerver/views/message_edit.py +++ b/zerver/views/message_edit.py @@ -167,7 +167,7 @@ def validate_can_delete_message(user_profile: UserProfile, message: Message) -> return -@transaction.atomic +@transaction.atomic(durable=True) @typed_endpoint def delete_message_backend( request: HttpRequest, diff --git a/zerver/views/reactions.py b/zerver/views/reactions.py index c1b9c92597..55ad9c91bb 100644 --- a/zerver/views/reactions.py +++ b/zerver/views/reactions.py @@ -12,7 +12,7 @@ from zerver.models import Reaction, UserProfile # transaction.atomic is required since we use FOR UPDATE queries in access_message -@transaction.atomic +@transaction.atomic(durable=True) @typed_endpoint def add_reaction( request: HttpRequest, @@ -29,7 +29,7 @@ def add_reaction( # transaction.atomic is required since we use FOR UPDATE queries in access_message -@transaction.atomic +@transaction.atomic(durable=True) @typed_endpoint def remove_reaction( request: HttpRequest, diff --git a/zerver/views/realm.py b/zerver/views/realm.py index 2f012977cd..6aa7f5e4ed 100644 --- a/zerver/views/realm.py +++ b/zerver/views/realm.py @@ -52,7 +52,6 @@ from zerver.models.realms import ( BotCreationPolicyEnum, CommonPolicyEnum, DigestWeekdayEnum, - EditTopicPolicyEnum, InviteToRealmPolicyEnum, OrgTypeEnum, WildcardMentionPolicyEnum, @@ -101,9 +100,7 @@ def update_realm( disallow_disposable_email_addresses: Json[bool] | None = None, invite_required: Json[bool] | None = None, invite_to_realm_policy: Json[InviteToRealmPolicyEnum] | None = None, - create_multiuse_invite_group_id: Annotated[ - Json[int] | None, ApiParamConfig(whence="create_multiuse_invite_group") - ] = None, + create_multiuse_invite_group: Json[GroupSettingChangeRequest] | None = None, require_unique_names: Json[bool] | None = None, name_changes_disabled: Json[bool] | None = None, email_changes_disabled: Json[bool] | None = None, @@ -118,7 +115,6 @@ def update_realm( ApiParamConfig("message_content_delete_limit_seconds"), ] = None, allow_message_editing: Json[bool] | None = None, - edit_topic_policy: Json[EditTopicPolicyEnum] | None = None, mandatory_topics: Json[bool] | None = None, message_content_edit_limit_seconds_raw: Annotated[ Json[int | str] | None, ApiParamConfig("message_content_edit_limit_seconds") @@ -145,6 +141,7 @@ def update_realm( can_create_web_public_channel_group: Json[GroupSettingChangeRequest] | None = None, can_manage_all_groups: Json[GroupSettingChangeRequest] | None = None, can_move_messages_between_channels_group: Json[GroupSettingChangeRequest] | None = None, + can_move_messages_between_topics_group: Json[GroupSettingChangeRequest] | None = None, direct_message_initiator_group: Json[GroupSettingChangeRequest] | None = None, direct_message_permission_group: Json[GroupSettingChangeRequest] | None = None, invite_to_stream_policy: Json[CommonPolicyEnum] | None = None, @@ -174,9 +171,7 @@ def update_realm( ApiParamConfig("move_messages_between_streams_limit_seconds"), ] = None, enable_guest_user_indicator: Json[bool] | None = None, - can_access_all_users_group_id: Annotated[ - Json[int] | None, ApiParamConfig("can_access_all_users_group") - ] = None, + can_access_all_users_group: Json[GroupSettingChangeRequest] | None = None, ) -> HttpResponse: # Realm object is being refetched here to make sure that we # do not use stale object from cache which can happen when a @@ -226,7 +221,7 @@ def update_realm( if ( invite_to_realm_policy is not None or invite_required is not None - or create_multiuse_invite_group_id is not None + or create_multiuse_invite_group is not None or can_create_groups is not None or can_manage_all_groups is not None ) and not user_profile.is_realm_owner: @@ -243,7 +238,7 @@ def update_realm( if enable_spectator_access: realm.ensure_not_on_limited_plan() - if can_access_all_users_group_id is not None: + if can_access_all_users_group is not None: realm.can_enable_restricted_user_access_for_guests() data: dict[str, Any] = {} @@ -360,26 +355,18 @@ def update_realm( data[k] = v for setting_name, permission_configuration in Realm.REALM_PERMISSION_GROUP_SETTINGS.items(): - setting_group_id_name = permission_configuration.id_field_name - expected_current_setting_value = None - if setting_name in Realm.REALM_PERMISSION_GROUP_SETTINGS_WITH_NEW_API_FORMAT: - assert setting_name in req_group_setting_vars - if req_group_setting_vars[setting_name] is None: - continue + assert setting_name in req_group_setting_vars + if req_group_setting_vars[setting_name] is None: + continue - setting_value = req_group_setting_vars[setting_name] - new_setting_value = parse_group_setting_value(setting_value.new, setting_name) + setting_value = req_group_setting_vars[setting_name] + new_setting_value = parse_group_setting_value(setting_value.new, setting_name) - if setting_value.old is not None: - expected_current_setting_value = parse_group_setting_value( - setting_value.old, setting_name - ) - else: - assert setting_group_id_name in req_group_setting_vars - if req_group_setting_vars[setting_group_id_name] is None: - continue - new_setting_value = req_group_setting_vars[setting_group_id_name] + if setting_value.old is not None: + expected_current_setting_value = parse_group_setting_value( + setting_value.old, setting_name + ) current_value = getattr(realm, setting_name) current_setting_api_value = get_group_setting_value_for_api(current_value) diff --git a/zerver/views/streams.py b/zerver/views/streams.py index add9fe0880..6f0246cba5 100644 --- a/zerver/views/streams.py +++ b/zerver/views/streams.py @@ -468,7 +468,7 @@ def compose_views(thunks: list[Callable[[], HttpResponse]]) -> dict[str, Any]: """ json_dict: dict[str, Any] = {} - with transaction.atomic(): + with transaction.atomic(savepoint=False): for thunk in thunks: response = thunk() json_dict.update(orjson.loads(response.content)) @@ -847,6 +847,7 @@ def get_streams_backend( include_public: Json[bool] = True, include_web_public: Json[bool] = False, include_subscribed: Json[bool] = True, + exclude_archived: Json[bool] = True, include_all_active: Json[bool] = False, include_default: Json[bool] = False, include_owner_subscribed: Json[bool] = False, @@ -856,6 +857,7 @@ def get_streams_backend( include_public=include_public, include_web_public=include_web_public, include_subscribed=include_subscribed, + exclude_archived=exclude_archived, include_all_active=include_all_active, include_default=include_default, include_owner_subscribed=include_owner_subscribed, diff --git a/zerver/views/tusd.py b/zerver/views/tusd.py index cb13416a21..d053bf07ae 100644 --- a/zerver/views/tusd.py +++ b/zerver/views/tusd.py @@ -194,7 +194,7 @@ def handle_upload_pre_finish_hook( StorageClass=settings.S3_UPLOADS_STORAGE_CLASS, ) - with transaction.atomic(): + with transaction.atomic(durable=True): create_attachment( filename, path_id, diff --git a/zerver/views/user_groups.py b/zerver/views/user_groups.py index 04bae18a12..98e0d0bf64 100644 --- a/zerver/views/user_groups.py +++ b/zerver/views/user_groups.py @@ -101,8 +101,7 @@ def add_user_group( add_subgroups_to_user_group( context.supergroup, context.direct_subgroups, acting_user=user_profile ) - - return json_success(request) + return json_success(request, data={"group_id": user_group.id}) @require_member_or_admin @@ -208,7 +207,7 @@ def edit_user_group( @typed_endpoint -@transaction.atomic +@transaction.atomic(durable=True) def deactivate_user_group( request: HttpRequest, user_profile: UserProfile, @@ -469,6 +468,7 @@ def remove_subgroups_from_group_backend( @require_member_or_admin @typed_endpoint +@transaction.atomic(durable=True) def update_subgroups_of_user_group( request: HttpRequest, user_profile: UserProfile, diff --git a/zerver/views/user_settings.py b/zerver/views/user_settings.py index eef6dbdbfe..5c1319536a 100644 --- a/zerver/views/user_settings.py +++ b/zerver/views/user_settings.py @@ -100,7 +100,7 @@ def confirm_email_change(request: HttpRequest, confirmation_key: str) -> HttpRes assert isinstance(email_change_object, EmailChangeStatus) new_email = email_change_object.new_email old_email = email_change_object.old_email - with transaction.atomic(): + with transaction.atomic(durable=True): user_profile = UserProfile.objects.select_for_update().get( id=email_change_object.user_profile_id ) diff --git a/zerver/views/users.py b/zerver/views/users.py index ce69de8feb..b0082acbd4 100644 --- a/zerver/views/users.py +++ b/zerver/views/users.py @@ -4,6 +4,8 @@ from typing import Annotated, Any, TypeAlias from django.conf import settings from django.contrib.auth.models import AnonymousUser +from django.core import validators +from django.core.exceptions import ValidationError from django.core.files.uploadedfile import UploadedFile from django.db import transaction from django.http import HttpRequest, HttpResponse @@ -26,6 +28,7 @@ from zerver.actions.user_settings import ( check_change_bot_full_name, check_change_full_name, do_change_avatar_fields, + do_change_user_delivery_email, do_regenerate_api_key, ) from zerver.actions.users import ( @@ -39,7 +42,7 @@ from zerver.decorator import require_member_or_admin, require_realm_admin from zerver.forms import PASSWORD_TOO_WEAK_ERROR, CreateUserForm from zerver.lib.avatar import avatar_url, get_avatar_for_inaccessible_user, get_gravatar_url from zerver.lib.bot_config import set_bot_config -from zerver.lib.email_validation import email_allowed_for_realm +from zerver.lib.email_validation import email_allowed_for_realm, validate_email_not_already_in_realm from zerver.lib.exceptions import ( CannotDeactivateLastUserError, JsonableError, @@ -199,7 +202,7 @@ class ProfileDataElement(BaseModel): @typed_endpoint @transaction.atomic(durable=True) -def update_user_backend( +def update_user_by_id_api( request: HttpRequest, user_profile: UserProfile, *, @@ -207,10 +210,62 @@ def update_user_backend( full_name: str | None = None, role: Json[RoleParamType] | None = None, profile_data: Json[list[ProfileDataElement]] | None = None, + new_email: str | None = None, ) -> HttpResponse: target = access_user_by_id( user_profile, user_id, allow_deactivated=True, allow_bots=True, for_admin=True ) + return update_user_backend( + request, + user_profile, + target, + full_name=full_name, + role=role, + profile_data=profile_data, + new_email=new_email, + ) + + +@typed_endpoint +@transaction.atomic(durable=True) +def update_user_by_email_api( + request: HttpRequest, + user_profile: UserProfile, + *, + email: PathOnly[str], + full_name: str | None = None, + role: Json[RoleParamType] | None = None, + profile_data: Json[list[ProfileDataElement]] | None = None, + new_email: str | None = None, +) -> HttpResponse: + target = access_user_by_email( + user_profile, email, allow_deactivated=True, allow_bots=True, for_admin=True + ) + return update_user_backend( + request, + user_profile, + target, + full_name=full_name, + role=role, + profile_data=profile_data, + new_email=new_email, + ) + + +def update_user_backend( + request: HttpRequest, + user_profile: UserProfile, + target: UserProfile, + *, + full_name: str | None = None, + role: Json[RoleParamType] | None = None, + profile_data: Json[list[ProfileDataElement]] | None = None, + new_email: str | None = None, +) -> HttpResponse: + if new_email is not None and ( + not user_profile.can_change_user_emails or not user_profile.is_realm_admin + ): + raise JsonableError(_("User not authorized to change user emails")) if role is not None and target.role != role: # Require that the current user has permissions to @@ -261,21 +316,33 @@ def update_user_backend( ) do_update_user_custom_profile_data_if_changed(target, clean_profile_data) + if new_email is not None and target.delivery_email != new_email: + assert user_profile.can_change_user_emails and user_profile.is_realm_admin + try: + validators.validate_email(new_email) + except ValidationError: + raise JsonableError(_("Invalid new email address.")) + try: + validate_email_not_already_in_realm( + user_profile.realm, + new_email, + verbose=False, + ) + except ValidationError as e: + raise JsonableError(_("New email value error: {message}").format(message=e.message)) + + do_change_user_delivery_email(target, new_email, acting_user=user_profile) + return json_success(request) -def avatar( +def avatar_by_id( request: HttpRequest, maybe_user_profile: UserProfile | AnonymousUser, - email_or_id: str, + user_id: int, medium: bool = False, ) -> HttpResponse: - """Accepts an email address or user ID and returns the avatar""" - is_email = False - try: - int(email_or_id) - except ValueError: - is_email = True + """Accepts a user ID and returns the avatar""" if not maybe_user_profile.is_authenticated: # Allow anonymous access to avatars only if spectators are @@ -284,27 +351,14 @@ def avatar( if not realm.allow_web_public_streams_access(): raise MissingAuthenticationError - # We only allow the ID format for accessing a user's avatar - # for spectators. This is mainly for defense in depth, since - # email_address_visibility should mean spectators only - # interact with fake email addresses anyway. - if is_email: - raise MissingAuthenticationError - if settings.RATE_LIMITING: - unique_avatar_key = f"{realm.id}/{email_or_id}/{medium}" + unique_avatar_key = f"{realm.id}/{user_id}/{medium}" rate_limit_spectator_attachment_access_by_file(unique_avatar_key) else: realm = maybe_user_profile.realm try: - if is_email: - avatar_user_profile = get_user_including_cross_realm(email_or_id, realm) - else: - avatar_user_profile = get_user_by_id_in_realm_including_cross_realm( - int(email_or_id), realm - ) - + avatar_user_profile = get_user_by_id_in_realm_including_cross_realm(user_id, realm) url: str | None = None if maybe_user_profile.is_authenticated and not check_can_access_user( avatar_user_profile, maybe_user_profile @@ -314,9 +368,43 @@ def avatar( # If there is a valid user account passed in, use its avatar url = avatar_url(avatar_user_profile, medium=medium) assert url is not None + except UserProfile.DoesNotExist: + url = get_avatar_for_inaccessible_user() + + assert url is not None + if request.META["QUERY_STRING"]: + url = append_url_query_string(url, request.META["QUERY_STRING"]) + return redirect(url) + + +def avatar_by_email( + request: HttpRequest, + maybe_user_profile: UserProfile | AnonymousUser, + email: str, + medium: bool = False, +) -> HttpResponse: + """Accepts an email address and returns the avatar""" + + if not maybe_user_profile.is_authenticated: + # We only allow the ID format for accessing a user's avatar + # for spectators. This is mainly for defense in depth, since + # email_address_visibility should mean spectators only + # interact with fake email addresses anyway. + raise MissingAuthenticationError + + realm = maybe_user_profile.realm + + try: + avatar_user_profile = get_user_including_cross_realm(email, realm) + url: str | None = None + if not check_can_access_user(avatar_user_profile, maybe_user_profile): + url = get_avatar_for_inaccessible_user() + else: + # If there is a valid user account passed in, use its avatar + url = avatar_url(avatar_user_profile, medium=medium) + assert url is not None except UserProfile.DoesNotExist: # If there is no such user, treat it as a new gravatar - email = email_or_id avatar_version = 1 url = get_gravatar_url(email, avatar_version, medium) @@ -327,9 +415,16 @@ def avatar( def avatar_medium( - request: HttpRequest, maybe_user_profile: UserProfile | AnonymousUser, email_or_id: str + request: HttpRequest, + maybe_user_profile: UserProfile | AnonymousUser, + email: str | None = None, + user_id: int | None = None, ) -> HttpResponse: - return avatar(request, maybe_user_profile, email_or_id, medium=True) + if email: + return avatar_by_email(request, maybe_user_profile, email, medium=True) + else: + assert user_id is not None + return avatar_by_id(request, maybe_user_profile, user_id, medium=True) def get_stream_name(stream: Stream | None) -> str | None: diff --git a/zerver/webhooks/airbyte/__init__.py b/zerver/webhooks/airbyte/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/zerver/webhooks/airbyte/doc.md b/zerver/webhooks/airbyte/doc.md new file mode 100644 index 0000000000..488384a035 --- /dev/null +++ b/zerver/webhooks/airbyte/doc.md @@ -0,0 +1,28 @@ +# Zulip Airbyte integration + +Get Zulip notifications from Airbyte. + +{start_tabs} + +1. {!create-channel.md!} + +1. {!create-an-incoming-webhook.md!} + +1. {!generate-webhook-url-basic.md!} + +1. In Airbyte, go to your project settings. Click **Notifications**, + and toggle the **Webhook** button for the notifications you'd like + to receive. + +1. Enter the URL generated above in the **Webhook URL** field. Click the + **Save changes** button at the bottom of the page. + +{end_tabs} + +{!congrats.md!} + +![](/static/images/integrations/airbyte/001.png) + +### Related documentation + +{!webhooks-url-specification.md!} diff --git a/zerver/webhooks/airbyte/fixtures/airbyte_job_payload_failure.json b/zerver/webhooks/airbyte/fixtures/airbyte_job_payload_failure.json new file mode 100644 index 0000000000..bb8a76f9d7 --- /dev/null +++ b/zerver/webhooks/airbyte/fixtures/airbyte_job_payload_failure.json @@ -0,0 +1,83 @@ +{ + "text": "Your connection Google Sheets → Postgres from Google Sheets to Postgres failed\nThis happened with Checking source connection failed - please review this connection's configuration to prevent future syncs from failing\n\nYou can access its logs here: https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/connections/aa941643-07ea-48a2-9035-024575491720\n\nJob ID: 20441143", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Sync completed: " + } + }, + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*Source:*" + }, + { + "type": "mrkdwn", + "text": "" + }, + { + "type": "mrkdwn", + "text": "*Destination:*" + }, + { + "type": "mrkdwn", + "text": "" + }, + { + "type": "mrkdwn", + "text": "*Duration:*" + }, + { + "type": "mrkdwn", + "text": "1 min 23 sec" + } + ] + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Sync Summary:*\n1400 record(s) extracted / 1400 record(s) loaded\n281 kB extracted / 281 kB loaded\n" + } + } + ], + "data": { + "workspace": { + "id": "84d2dd6e-82aa-406e-91f3-bf8dbf176e69", + "name": "Zulip Airbyte Integration", + "url": "https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69" + }, + "connection": { + "id": "aa941643-07ea-48a2-9035-024575491720", + "name": "Google Sheets → Postgres", + "url": "https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/connections/aa941643-07ea-48a2-9035-024575491720" + }, + "source": { + "id": "363c0ea3-e989-4051-9f54-d41b794d6621", + "name": "Google Sheets", + "url": "https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/source/363c0ea3-e989-4051-9f54-d41b794d6621" + }, + "destination": { + "id": "b3a05072-e3c8-435a-8e6e-4a5c601039c6", + "name": "Postgres", + "url": "https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/destination/b3a05072-e3c8-435a-8e6e-4a5c601039c6" + }, + "jobId": 20441143, + "startedAt": "2024-10-22T20:27:59Z", + "finishedAt": "2024-10-22T20:29:22Z", + "bytesEmitted": 0, + "bytesCommitted": 0, + "recordsEmitted": 0, + "recordsCommitted": 0, + "errorMessage": "Checking source connection failed - please review this connection's configuration to prevent future syncs from failing", + "durationFormatted": "28 sec", + "bytesEmittedFormatted": "0 B", + "bytesCommittedFormatted": "0 B", + "success": false, + "durationInSeconds": 28 + } +} diff --git a/zerver/webhooks/airbyte/fixtures/airbyte_job_payload_success.json b/zerver/webhooks/airbyte/fixtures/airbyte_job_payload_success.json new file mode 100644 index 0000000000..d573f8b63d --- /dev/null +++ b/zerver/webhooks/airbyte/fixtures/airbyte_job_payload_success.json @@ -0,0 +1,83 @@ +{ + "text": "Your connection Google Sheets → Postgres from Google Sheets to Postgres succeeded\nThis was for null\n\nYou can access its logs here: https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/connections/aa941643-07ea-48a2-9035-024575491720\n\nJob ID: 20441143", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Sync completed: " + } + }, + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*Source:*" + }, + { + "type": "mrkdwn", + "text": "" + }, + { + "type": "mrkdwn", + "text": "*Destination:*" + }, + { + "type": "mrkdwn", + "text": "" + }, + { + "type": "mrkdwn", + "text": "*Duration:*" + }, + { + "type": "mrkdwn", + "text": "1 min 23 sec" + } + ] + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Sync Summary:*\n1400 record(s) extracted / 1400 record(s) loaded\n281 kB extracted / 281 kB loaded\n" + } + } + ], + "data": { + "workspace": { + "id": "84d2dd6e-82aa-406e-91f3-bf8dbf176e69", + "name": "Zulip Airbyte Integration", + "url": "https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69" + }, + "connection": { + "id": "aa941643-07ea-48a2-9035-024575491720", + "name": "Google Sheets → Postgres", + "url": "https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/connections/aa941643-07ea-48a2-9035-024575491720" + }, + "source": { + "id": "363c0ea3-e989-4051-9f54-d41b794d6621", + "name": "Google Sheets", + "url": "https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/source/363c0ea3-e989-4051-9f54-d41b794d6621" + }, + "destination": { + "id": "b3a05072-e3c8-435a-8e6e-4a5c601039c6", + "name": "Postgres", + "url": "https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/destination/b3a05072-e3c8-435a-8e6e-4a5c601039c6" + }, + "jobId": 20441143, + "startedAt": "2024-10-22T20:27:59Z", + "finishedAt": "2024-10-22T20:29:22Z", + "bytesEmitted": 288179, + "bytesCommitted": 288179, + "recordsEmitted": 1400, + "recordsCommitted": 1400, + "errorMessage": null, + "durationFormatted": "1 min 23 sec", + "bytesEmittedFormatted": "281 kB", + "bytesCommittedFormatted": "281 kB", + "success": true, + "durationInSeconds": 83 + } +} diff --git a/zerver/webhooks/airbyte/fixtures/test_airbyte_job_hello_world_failure.json b/zerver/webhooks/airbyte/fixtures/test_airbyte_job_hello_world_failure.json new file mode 100644 index 0000000000..5df5ad4aed --- /dev/null +++ b/zerver/webhooks/airbyte/fixtures/test_airbyte_job_hello_world_failure.json @@ -0,0 +1,3 @@ +{ + "text": "Hello World! This is a test from Airbyte to try slack notification settings for sync failures." +} diff --git a/zerver/webhooks/airbyte/fixtures/test_airbyte_job_hello_world_success.json b/zerver/webhooks/airbyte/fixtures/test_airbyte_job_hello_world_success.json new file mode 100644 index 0000000000..1323fd41c0 --- /dev/null +++ b/zerver/webhooks/airbyte/fixtures/test_airbyte_job_hello_world_success.json @@ -0,0 +1,4 @@ +{ + "text": "Hello World! This is a test from Airbyte to try slack notification settings for sync successes." +} + diff --git a/zerver/webhooks/airbyte/tests.py b/zerver/webhooks/airbyte/tests.py new file mode 100644 index 0000000000..1a997bc292 --- /dev/null +++ b/zerver/webhooks/airbyte/tests.py @@ -0,0 +1,70 @@ +from zerver.lib.test_classes import WebhookTestCase + + +class AirbyteHookTests(WebhookTestCase): + STREAM_NAME = "airbyte" + URL_TEMPLATE = "/api/v1/external/airbyte?api_key={api_key}&stream={stream}" + FIXTURE_DIR_NAME = "airbyte" + CHANNEL_NAME = "test" + WEBHOOK_DIR_NAME = "airbyte" + + def test_airbyte_job_success(self) -> None: + expected_topic = "Zulip Airbyte Integration - Google Sheets → Postgres" + + expected_message = """:green_circle: Airbyte sync **succeeded** for [Google Sheets → Postgres](https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/connections/aa941643-07ea-48a2-9035-024575491720). + + +* **Source:** [Google Sheets](https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/source/363c0ea3-e989-4051-9f54-d41b794d6621) +* **Destination:** [Postgres](https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/destination/b3a05072-e3c8-435a-8e6e-4a5c601039c6) +* **Records:** 1400 emitted, 1400 committed +* **Bytes:** 281 kB emitted, 281 kB committed +* **Duration:** 1 min 23 sec""" + + self.check_webhook( + "airbyte_job_payload_success", + expected_topic, + expected_message, + content_type="application/json", + ) + + def test_airbyte_job_failure(self) -> None: + expected_topic = "Zulip Airbyte Integration - Google Sheets → Postgres" + expected_message = """:red_circle: Airbyte sync **failed** for [Google Sheets → Postgres](https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/connections/aa941643-07ea-48a2-9035-024575491720). + + +* **Source:** [Google Sheets](https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/source/363c0ea3-e989-4051-9f54-d41b794d6621) +* **Destination:** [Postgres](https://cloud.airbyte.com/workspaces/84d2dd6e-82aa-406e-91f3-bf8dbf176e69/destination/b3a05072-e3c8-435a-8e6e-4a5c601039c6) +* **Records:** 0 emitted, 0 committed +* **Bytes:** 0 B emitted, 0 B committed +* **Duration:** 28 sec + +**Error message:** Checking source connection failed - please review this connection's configuration to prevent future syncs from failing""" + + self.check_webhook( + "airbyte_job_payload_failure", + expected_topic, + expected_message, + content_type="application/json", + ) + + def test_airbyte_job_hello_world_success(self) -> None: + expected_topic = "Airbyte notification" + expected_message = """Hello World! This is a test from Airbyte to try slack notification settings for sync successes.""" + + self.check_webhook( + "test_airbyte_job_hello_world_success", + expected_topic, + expected_message, + content_type="application/json", + ) + + def test_airbyte_job_hello_world_failure(self) -> None: + expected_topic = "Airbyte notification" + expected_message = """Hello World! This is a test from Airbyte to try slack notification settings for sync failures.""" + + self.check_webhook( + "test_airbyte_job_hello_world_failure", + expected_topic, + expected_message, + content_type="application/json", + ) diff --git a/zerver/webhooks/airbyte/view.py b/zerver/webhooks/airbyte/view.py new file mode 100644 index 0000000000..f422d0b144 --- /dev/null +++ b/zerver/webhooks/airbyte/view.py @@ -0,0 +1,97 @@ +# Webhooks for external integrations. + +from django.http import HttpRequest, HttpResponse + +from zerver.decorator import webhook_view +from zerver.lib.response import json_success +from zerver.lib.typed_endpoint import JsonBodyPayload, typed_endpoint +from zerver.lib.validator import WildValue, check_bool, check_int, check_string +from zerver.lib.webhooks.common import check_send_webhook_message +from zerver.models import UserProfile + +AIRBYTE_TOPIC_TEMPLATE = "{workspace} - {connection}" + +AIRBYTE_MESSAGE_TEMPLATE = """\ +{sync_status_emoji} Airbyte sync **{status}** for [{connection_name}]({connection_url}). + + +* **Source:** [{source_name}]({source_url}) +* **Destination:** [{destination_name}]({destination_url}) +* **Records:** {records_emitted} emitted, {records_committed} committed +* **Bytes:** {bytes_emitted} emitted, {bytes_committed} committed +* **Duration:** {duration} +""" + + +def extract_data_from_payload(payload_data: WildValue) -> dict[str, str | int | bool]: + data: dict[str, str | int | bool] = { + "workspace_name": payload_data["workspace"]["name"].tame(check_string), + "connection_name": payload_data["connection"]["name"].tame(check_string), + "source_name": payload_data["source"]["name"].tame(check_string), + "destination_name": payload_data["destination"]["name"].tame(check_string), + "connection_url": payload_data["connection"]["url"].tame(check_string), + "source_url": payload_data["source"]["url"].tame(check_string), + "destination_url": payload_data["destination"]["url"].tame(check_string), + "successful_sync": payload_data["success"].tame(check_bool), + "duration_formatted": payload_data["durationFormatted"].tame(check_string), + "records_emitted": payload_data["recordsEmitted"].tame(check_int), + "records_committed": payload_data["recordsCommitted"].tame(check_int), + "bytes_emitted_formatted": payload_data["bytesEmittedFormatted"].tame(check_string), + "bytes_committed_formatted": payload_data["bytesCommittedFormatted"].tame(check_string), + } + + if not data["successful_sync"]: + data["error_message"] = payload_data["errorMessage"].tame(check_string) + + return data + + +def format_message_from_data(data: dict[str, str | int | bool]) -> str: + content = AIRBYTE_MESSAGE_TEMPLATE.format( + sync_status_emoji=":green_circle:" if data["successful_sync"] else ":red_circle:", + status="succeeded" if data["successful_sync"] else "failed", + connection_name=data["connection_name"], + connection_url=data["connection_url"], + source_name=data["source_name"], + source_url=data["source_url"], + destination_name=data["destination_name"], + destination_url=data["destination_url"], + duration=data["duration_formatted"], + records_emitted=data["records_emitted"], + records_committed=data["records_committed"], + bytes_emitted=data["bytes_emitted_formatted"], + bytes_committed=data["bytes_committed_formatted"], + ) + + if not data["successful_sync"]: + error_message = data["error_message"] + content += f"\n**Error message:** {error_message}" + + return content + + +def create_topic_from_data(data: dict[str, str | int | bool]) -> str: + return AIRBYTE_TOPIC_TEMPLATE.format( + workspace=data["workspace_name"], + connection=data["connection_name"], + ) + + +@webhook_view("Airbyte") +@typed_endpoint +def api_airbyte_webhook( + request: HttpRequest, + user_profile: UserProfile, + *, + payload: JsonBodyPayload[WildValue], +) -> HttpResponse: + if "data" in payload: + data = extract_data_from_payload(payload["data"]) + content = format_message_from_data(data) + topic = create_topic_from_data(data) + else: + # Test Airbyte notification payloads only contain this field. + content = payload["text"].tame(check_string) + topic = "Airbyte notification" + check_send_webhook_message(request, user_profile, topic, content) + return json_success(request) diff --git a/zerver/webhooks/gitea/tests.py b/zerver/webhooks/gitea/tests.py index c6825f7124..20733b6790 100644 --- a/zerver/webhooks/gitea/tests.py +++ b/zerver/webhooks/gitea/tests.py @@ -53,7 +53,7 @@ class GiteaHookTests(WebhookTestCase): def test_pull_request_assigned(self) -> None: expected_topic_name = "test / PR #1906 test 2" - expected_message = """kostekIV assigned [PR #5](https://try.gitea.io/kostekIV/test/pulls/5) from `d` to `master` (assigned to kostekIV).""" + expected_message = """kostekIV assigned kostekIV to [PR #5](https://try.gitea.io/kostekIV/test/pulls/5) from `d` to `master` (assigned to kostekIV).""" self.check_webhook("pull_request__assigned", expected_topic_name, expected_message) def test_issues_opened(self) -> None: @@ -73,7 +73,7 @@ class GiteaHookTests(WebhookTestCase): def test_issues_assigned(self) -> None: expected_topic_name = "test / issue #3 Test issue" - expected_message = """kostekIV assigned [issue #3](https://try.gitea.io/kostekIV/test/issues/3) (assigned to kostekIV):\n\n~~~ quote\nTest body\n~~~""" + expected_message = """kostekIV assigned kostekIV to [issue #3](https://try.gitea.io/kostekIV/test/issues/3) (assigned to kostekIV):\n\n~~~ quote\nTest body\n~~~""" self.check_webhook("issues__assigned", expected_topic_name, expected_message) def test_issues_reopened(self) -> None: diff --git a/zerver/webhooks/gitea/view.py b/zerver/webhooks/gitea/view.py index 43c0b775b3..e7e937d206 100644 --- a/zerver/webhooks/gitea/view.py +++ b/zerver/webhooks/gitea/view.py @@ -46,6 +46,7 @@ def format_pull_request_event(payload: WildValue, include_title: bool = False) - base_branch=base_branch, title=title, assignee=stringified_assignee, + assignee_updated=stringified_assignee if action == "assigned" else None, ) diff --git a/zerver/webhooks/github/fixtures/pull_request__merged_private_repository.json b/zerver/webhooks/github/fixtures/pull_request__merged_private_repository.json new file mode 100644 index 0000000000..c09e075dd4 --- /dev/null +++ b/zerver/webhooks/github/fixtures/pull_request__merged_private_repository.json @@ -0,0 +1,412 @@ +{ + "action": "closed", + "number": 1, + "pull_request": { + "url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1", + "id": 34778301, + "html_url": "https://github.com/baxterthehacker/public-repo/pull/1", + "diff_url": "https://github.com/baxterthehacker/public-repo/pull/1.diff", + "patch_url": "https://github.com/baxterthehacker/public-repo/pull/1.patch", + "issue_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/1", + "number": 1, + "state": "merged", + "locked": false, + "title": "Update the README with new information", + "user": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "body": "This is a pretty simple change that we need to pull into master.", + "created_at": "2015-05-05T23:40:27Z", + "updated_at": "2015-05-05T23:40:27Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": null, + "assignee": null, + "milestone": null, + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1/commits", + "review_comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1/comments", + "review_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/1/comments", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "head": { + "label": "baxterthehacker:changes", + "ref": "changes", + "sha": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "user": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": true, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:12Z", + "pushed_at": "2015-05-05T23:40:26Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 1, + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "master" + } + }, + "base": { + "label": "baxterthehacker:master", + "ref": "master", + "sha": "9049f1265b7d61be4a8904a9a27120d2064dab3b", + "user": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:12Z", + "pushed_at": "2015-05-05T23:40:26Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 1, + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "master" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1" + }, + "html": { + "href": "https://github.com/baxterthehacker/public-repo/pull/1" + }, + "issue": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/issues/1" + }, + "comments": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/issues/1/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c" + } + }, + "merged": true, + "mergeable": null, + "mergeable_state": "unknown", + "merged_by": null, + "comments": 0, + "review_comments": 0, + "commits": 1, + "additions": 1, + "deletions": 1, + "changed_files": 1 + }, + "repository": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": true, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2015-05-05T23:40:12Z", + "pushed_at": "2015-05-05T23:40:26Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 1, + "forks": 0, + "open_issues": 1, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } + } diff --git a/zerver/webhooks/github/fixtures/push__1_commit_private_repository.json b/zerver/webhooks/github/fixtures/push__1_commit_private_repository.json new file mode 100644 index 0000000000..701be6fbff --- /dev/null +++ b/zerver/webhooks/github/fixtures/push__1_commit_private_repository.json @@ -0,0 +1,163 @@ +{ + "ref": "refs/heads/changes", + "before": "9049f1265b7d61be4a8904a9a27120d2064dab3b", + "after": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "created": false, + "deleted": false, + "forced": false, + "base_ref": null, + "compare": "https://github.com/baxterthehacker/private-repo/compare/9049f1265b7d...0d1a26e67d8f", + "commits": [ + { + "id": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "tree_id": "f9d2a07e9488b91af2641b26b9407fe22a451433", + "distinct": true, + "message": "Update README.md", + "timestamp": "2015-05-05T19:40:15-04:00", + "url": "https://github.com/baxterthehacker/private-repo/commit/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "author": { + "name": "baxterthehacker", + "email": "baxterthehacker@users.noreply.github.com", + "username": "baxterthehacker" + }, + "committer": { + "name": "baxterthehacker", + "email": "baxterthehacker@users.noreply.github.com", + "username": "baxterthehacker" + }, + "added": [ + + ], + "removed": [ + + ], + "modified": [ + "README.md" + ] + } + ], + "head_commit": { + "id": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "tree_id": "f9d2a07e9488b91af2641b26b9407fe22a451433", + "distinct": true, + "message": "Update README.md", + "timestamp": "2015-05-05T19:40:15-04:00", + "url": "https://github.com/baxterthehacker/private-repo/commit/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "author": { + "name": "baxterthehacker", + "email": "baxterthehacker@users.noreply.github.com", + "username": "baxterthehacker" + }, + "committer": { + "name": "baxterthehacker", + "email": "baxterthehacker@users.noreply.github.com", + "username": "baxterthehacker" + }, + "added": [ + + ], + "removed": [ + + ], + "modified": [ + "README.md" + ] + }, + "repository": { + "id": 35129377, + "name": "private-repo", + "full_name": "baxterthehacker/private-repo", + "owner": { + "name": "baxterthehacker", + "email": "baxterthehacker@users.noreply.github.com" + }, + "private": true, + "html_url": "https://github.com/baxterthehacker/private-repo", + "description": "", + "fork": false, + "url": "https://github.com/baxterthehacker/private-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/private-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/private-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/private-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/private-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/private-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/private-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/private-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/private-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/private-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/private-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/private-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/private-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/private-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/private-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/private-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/private-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/private-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/private-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/private-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/private-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/private-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/private-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/private-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/private-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/private-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/private-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/private-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/private-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/private-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/private-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/private-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/private-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/private-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/private-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/private-repo/releases{/id}", + "created_at": 1430869212, + "updated_at": "2015-05-05T23:40:12Z", + "pushed_at": 1430869217, + "git_url": "git://github.com/baxterthehacker/private-repo.git", + "ssh_url": "git@github.com:baxterthehacker/private-repo.git", + "clone_url": "https://github.com/baxterthehacker/private-repo.git", + "svn_url": "https://github.com/baxterthehacker/private-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master", + "stargazers": 0, + "master_branch": "master" + }, + "pusher": { + "name": "baxterthehacker", + "email": "baxterthehacker@users.noreply.github.com" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } + } diff --git a/zerver/webhooks/github/fixtures/push__merge_queue.json b/zerver/webhooks/github/fixtures/push__merge_queue.json index c19858b9bc..93fa05b26f 100644 --- a/zerver/webhooks/github/fixtures/push__merge_queue.json +++ b/zerver/webhooks/github/fixtures/push__merge_queue.json @@ -7,7 +7,7 @@ "node_id": "MDEwOJllG9zcXaRcvknzNYyTzM4OUQD=", "name": "infra-core", "full_name": "some-organization/infra-core", - "private": true, + "private": false, "owner": { "name": "some-organization", "email": null, diff --git a/zerver/webhooks/github/fixtures/repository.json b/zerver/webhooks/github/fixtures/repository.json index eed2dbaa4a..aa9bda847c 100644 --- a/zerver/webhooks/github/fixtures/repository.json +++ b/zerver/webhooks/github/fixtures/repository.json @@ -23,7 +23,7 @@ "type": "Organization", "site_admin": false }, - "private": true, + "private": false, "html_url": "https://github.com/baxterandthehackers/public-repo", "description": "", "fork": false, diff --git a/zerver/webhooks/github/fixtures/repository_private.json b/zerver/webhooks/github/fixtures/repository_private.json new file mode 100644 index 0000000000..eed2dbaa4a --- /dev/null +++ b/zerver/webhooks/github/fixtures/repository_private.json @@ -0,0 +1,119 @@ +{ + "action": "created", + "repository": { + "id": 27496774, + "name": "public-repo", + "full_name": "baxterandthehackers/public-repo", + "owner": { + "login": "baxterandthehackers", + "id": 7649605, + "avatar_url": "https://avatars.githubusercontent.com/u/7649605?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterandthehackers", + "html_url": "https://github.com/baxterandthehackers", + "followers_url": "https://api.github.com/users/baxterandthehackers/followers", + "following_url": "https://api.github.com/users/baxterandthehackers/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterandthehackers/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterandthehackers/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterandthehackers/subscriptions", + "organizations_url": "https://api.github.com/users/baxterandthehackers/orgs", + "repos_url": "https://api.github.com/users/baxterandthehackers/repos", + "events_url": "https://api.github.com/users/baxterandthehackers/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterandthehackers/received_events", + "type": "Organization", + "site_admin": false + }, + "private": true, + "html_url": "https://github.com/baxterandthehackers/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterandthehackers/public-repo", + "forks_url": "https://api.github.com/repos/baxterandthehackers/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterandthehackers/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterandthehackers/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterandthehackers/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterandthehackers/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterandthehackers/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterandthehackers/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterandthehackers/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterandthehackers/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterandthehackers/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterandthehackers/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterandthehackers/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterandthehackers/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterandthehackers/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterandthehackers/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterandthehackers/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterandthehackers/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterandthehackers/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterandthehackers/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterandthehackers/public-repo/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/baxterandthehackers/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterandthehackers/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterandthehackers/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterandthehackers/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterandthehackers/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterandthehackers/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterandthehackers/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterandthehackers/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterandthehackers/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterandthehackers/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterandthehackers/public-repo/releases{/id}", + "created_at": "2014-12-03T16:39:25Z", + "updated_at": "2014-12-03T16:39:25Z", + "pushed_at": "2014-12-03T16:39:25Z", + "git_url": "git://github.com/baxterandthehackers/public-repo.git", + "ssh_url": "git@github.com:baxterandthehackers/public-repo.git", + "clone_url": "https://github.com/baxterandthehackers/public-repo.git", + "svn_url": "https://github.com/baxterandthehackers/public-repo", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + }, + "organization": { + "login": "baxterandthehackers", + "id": 7649605, + "url": "https://api.github.com/orgs/baxterandthehackers", + "repos_url": "https://api.github.com/orgs/baxterandthehackers/repos", + "events_url": "https://api.github.com/orgs/baxterandthehackers/events", + "members_url": "https://api.github.com/orgs/baxterandthehackers/members{/member}", + "public_members_url": "https://api.github.com/orgs/baxterandthehackers/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/7649605?v=2" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=2", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/zerver/webhooks/github/tests.py b/zerver/webhooks/github/tests.py index 1fac82d5c1..88b385d8ee 100644 --- a/zerver/webhooks/github/tests.py +++ b/zerver/webhooks/github/tests.py @@ -61,6 +61,15 @@ class GitHubWebhookTest(WebhookTestCase): expected_message = "baxterthehacker [pushed](https://github.com/baxterthehacker/public-repo/compare/9049f1265b7d...0d1a26e67d8f) 1 commit to branch changes.\n\n* Update README.md ([0d1a26e67d8](https://github.com/baxterthehacker/public-repo/commit/0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c))" self.check_webhook("push__1_commit", TOPIC_BRANCH, expected_message) + def test_push_1_commit_private_repository_skipped(self) -> None: + self.url = self.build_webhook_url(ignore_private_repositories="true") + self.check_webhook( + fixture_name="push__1_commit_private_repository", + expected_topic_name=None, + expected_message=None, + expect_noop=True, + ) + def test_push_1_commit_without_username(self) -> None: expected_message = "eeshangarg [pushed](https://github.com/eeshangarg/public-repo/compare/0383613da871...2e8cf535fb38) 1 commit to branch changes. Commits by John Snow (1).\n\n* Update the README ([2e8cf535fb3](https://github.com/eeshangarg/public-repo/commit/2e8cf535fb38a3dab2476cdf856efda904ad4c94))" self.check_webhook("push__1_commit_without_username", TOPIC_BRANCH, expected_message) @@ -268,6 +277,15 @@ class GitHubWebhookTest(WebhookTestCase): ) self.check_webhook("pull_request__merged", TOPIC_PR, expected_message) + def test_pull_request_merged_msg_private_repository_skipped(self) -> None: + self.url = self.build_webhook_url(ignore_private_repositories="true") + self.check_webhook( + fixture_name="pull_request__merged_private_repository", + expected_topic_name=None, + expected_message=None, + expect_noop=True, + ) + def test_public_msg(self) -> None: expected_message = "baxterthehacker made the repository [baxterthehacker/public-repo](https://github.com/baxterthehacker/public-repo) public." self.check_webhook("public", TOPIC_REPO, expected_message) @@ -284,6 +302,19 @@ class GitHubWebhookTest(WebhookTestCase): expected_message = "baxterthehacker created the repository [baxterandthehackers/public-repo](https://github.com/baxterandthehackers/public-repo)." self.check_webhook("repository", TOPIC_REPO, expected_message) + def test_private_repository_msg(self) -> None: + expected_message = "baxterthehacker created the repository [baxterandthehackers/public-repo](https://github.com/baxterandthehackers/public-repo)." + self.check_webhook("repository", TOPIC_REPO, expected_message) + + def test_private_repository_skipped_msg(self) -> None: + self.url = self.build_webhook_url(ignore_private_repositories="true") + self.check_webhook( + fixture_name="repository_private", + expected_topic_name=None, + expected_message=None, + expect_noop=True, + ) + def test_team_add_msg(self) -> None: expected_message = "The repository [baxterandthehackers/public-repo](https://github.com/baxterandthehackers/public-repo) was added to team github." self.check_webhook("team_add", TOPIC_REPO, expected_message) @@ -386,14 +417,15 @@ class GitHubWebhookTest(WebhookTestCase): self.check_webhook("pull_request__assigned", expected_topic_name, expected_message) def test_pull_request_unassigned_msg(self) -> None: - expected_message = ( - "eeshangarg unassigned [PR #1](https://github.com/zulip-test-org/helloworld/pull/1)." - ) - self.check_webhook( - "pull_request__unassigned", - "helloworld / PR #1 Mention that Zulip rocks!", - expected_message, - ) + expected_message = "eeshangarg unassigned eeshangarg from [PR #1](https://github.com/zulip-test-org/helloworld/pull/1)." + expected_topic_name = "helloworld / PR #1 Mention that Zulip rocks!" + self.check_webhook("pull_request__unassigned", expected_topic_name, expected_message) + + def test_pull_request_unassigned_msg_with_custom_topic_in_url(self) -> None: + self.url = self.build_webhook_url(topic="notifications") + expected_topic_name = "notifications" + expected_message = "eeshangarg unassigned eeshangarg from [PR #1 Mention that Zulip rocks!](https://github.com/zulip-test-org/helloworld/pull/1)" + self.check_webhook("pull_request__unassigned", expected_topic_name, expected_message) def test_pull_request_ready_for_review_msg(self) -> None: expected_message = "**Hypro999** has marked [PR #2](https://github.com/Hypro999/temp-test-github-webhook/pull/2) as ready for review." diff --git a/zerver/webhooks/github/view.py b/zerver/webhooks/github/view.py index 17d40ba03f..abf724c318 100644 --- a/zerver/webhooks/github/view.py +++ b/zerver/webhooks/github/view.py @@ -3,6 +3,7 @@ from collections.abc import Callable from datetime import datetime, timezone from django.http import HttpRequest, HttpResponse +from pydantic import Json from zerver.decorator import log_unsupported_webhook_event, webhook_view from zerver.lib.exceptions import UnsupportedWebhookEventTypeError @@ -92,20 +93,16 @@ def get_assigned_or_unassigned_pull_request_body(helper: Helper) -> str: payload = helper.payload include_title = helper.include_title pull_request = payload["pull_request"] - assignee = pull_request.get("assignee") - if assignee: - stringified_assignee = assignee["login"].tame(check_string) + assignee = payload["assignee"]["login"].tame(check_string) - base_message = get_pull_request_event_message( + return get_pull_request_event_message( user_name=get_sender_name(payload), action=payload["action"].tame(check_string), url=pull_request["html_url"].tame(check_string), number=pull_request["number"].tame(check_int), title=pull_request["title"].tame(check_string) if include_title else None, + assignee_updated=assignee, ) - if assignee: - return base_message.replace("assigned", f"assigned {stringified_assignee} to", 1) - return base_message def get_closed_pull_request_body(helper: Helper) -> str: @@ -155,8 +152,7 @@ def get_issue_body(helper: Helper) -> str: include_title = helper.include_title action = payload["action"].tame(check_string) issue = payload["issue"] - has_assignee = "assignee" in payload - base_message = get_issue_event_message( + return get_issue_event_message( user_name=get_sender_name(payload), action=action, url=issue["html_url"].tame(check_string), @@ -167,17 +163,11 @@ def get_issue_body(helper: Helper) -> str: else issue["body"].tame(check_none_or(check_string)) ), title=issue["title"].tame(check_string) if include_title else None, + assignee_updated=payload["assignee"]["login"].tame(check_string) + if "assignee" in payload + else None, ) - if has_assignee: - stringified_assignee = payload["assignee"]["login"].tame(check_string) - if action == "assigned": - return base_message.replace("assigned", f"assigned {stringified_assignee} to", 1) - elif action == "unassigned": - return base_message.replace("unassigned", f"unassigned {stringified_assignee} from", 1) - - return base_message - def get_issue_comment_body(helper: Helper) -> str: payload = helper.payload @@ -910,6 +900,7 @@ def api_github_webhook( payload: JsonBodyPayload[WildValue], branches: str | None = None, user_specified_topic: OptionalUserSpecifiedTopicStr = None, + ignore_private_repositories: Json[bool] = False, ) -> HttpResponse: """ GitHub sends the event as an HTTP header. We have our @@ -919,6 +910,15 @@ def api_github_webhook( """ header_event = validate_extract_webhook_http_header(request, "X-GitHub-Event", "GitHub") + # Check if the repository is private and skip processing if ignore_private_repositories is True + if ( + "repository" in payload + and payload["repository"]["private"].tame(check_bool) + and ignore_private_repositories + ): + # Ignore private repository events + return json_success(request) + event = get_zulip_event_name(header_event, payload, branches) if event is None: # This is nothing to worry about--get_event() returns None diff --git a/zerver/webhooks/gogs/tests.py b/zerver/webhooks/gogs/tests.py index 3c98140e22..21c80059bf 100644 --- a/zerver/webhooks/gogs/tests.py +++ b/zerver/webhooks/gogs/tests.py @@ -94,7 +94,7 @@ class GogsHookTests(WebhookTestCase): def test_pull_request_assigned(self) -> None: expected_topic_name = "test / PR #1349 Test" - expected_message = """kostekIV assigned [PR #2](https://try.gogs.io/kostekIV/test/pulls/2) from `c` to `master`.""" + expected_message = """kostekIV assigned kostekIV to [PR #2](https://try.gogs.io/kostekIV/test/pulls/2) from `c` to `master`.""" self.check_webhook("pull_request__assigned", expected_topic_name, expected_message) def test_pull_request_synchronized(self) -> None: @@ -119,7 +119,7 @@ class GogsHookTests(WebhookTestCase): def test_issues_assignee(self) -> None: expected_topic_name = "test / issue #3 New test issue" - expected_message = """kostekIV assigned [issue #3](https://try.gogs.io/kostekIV/test/issues/3) (assigned to kostekIV):\n\n~~~ quote\nTest\n~~~""" + expected_message = """kostekIV assigned kostekIV to [issue #3](https://try.gogs.io/kostekIV/test/issues/3) (assigned to kostekIV):\n\n~~~ quote\nTest\n~~~""" self.check_webhook("issues__assigned", expected_topic_name, expected_message) def test_issues_closed(self) -> None: diff --git a/zerver/webhooks/gogs/view.py b/zerver/webhooks/gogs/view.py index eecfcd3e2e..7fe905a06f 100644 --- a/zerver/webhooks/gogs/view.py +++ b/zerver/webhooks/gogs/view.py @@ -86,6 +86,11 @@ def format_pull_request_event(payload: WildValue, include_title: bool = False) - target_branch = payload["pull_request"]["head_branch"].tame(check_string) base_branch = payload["pull_request"]["base_branch"].tame(check_string) title = payload["pull_request"]["title"].tame(check_string) if include_title else None + stringified_assignee = ( + payload["pull_request"]["assignee"]["login"].tame(check_string) + if payload["action"] and payload["pull_request"]["assignee"] + else None + ) return get_pull_request_event_message( user_name=user_name, @@ -95,20 +100,24 @@ def format_pull_request_event(payload: WildValue, include_title: bool = False) - target_branch=target_branch, base_branch=base_branch, title=title, + assignee_updated=stringified_assignee, ) def format_issues_event(payload: WildValue, include_title: bool = False) -> str: issue_nr = payload["issue"]["number"].tame(check_int) assignee = payload["issue"]["assignee"] + stringified_assignee = assignee["login"].tame(check_string) if assignee else None + action = payload["action"].tame(check_string) return get_issue_event_message( user_name=payload["sender"]["login"].tame(check_string), action=payload["action"].tame(check_string), url=get_issue_url(payload["repository"]["html_url"].tame(check_string), issue_nr), number=issue_nr, message=payload["issue"]["body"].tame(check_string), - assignee=assignee["login"].tame(check_string) if assignee else None, + assignee=stringified_assignee, title=payload["issue"]["title"].tame(check_string) if include_title else None, + assignee_updated=stringified_assignee if action == "assigned" else None, ) diff --git a/zerver/webhooks/slack_incoming/tests.py b/zerver/webhooks/slack_incoming/tests.py index cbf96e5e98..0b2ca20968 100644 --- a/zerver/webhooks/slack_incoming/tests.py +++ b/zerver/webhooks/slack_incoming/tests.py @@ -66,7 +66,8 @@ Hello, world. def test_message_without_payload(self) -> None: self.url = self.build_webhook_url() result = self.client_post(self.url) - self.assert_json_error(result, "Missing 'payload' argument") + self.assertEqual(result.json()["error"], "Missing 'payload' argument") + self.assertEqual(result.json()["ok"], False) def test_message_with_actions(self) -> None: expected_topic_name = "C1H9RESGL" diff --git a/zerver/webhooks/slack_incoming/view.py b/zerver/webhooks/slack_incoming/view.py index 79adaca429..3db74ac526 100644 --- a/zerver/webhooks/slack_incoming/view.py +++ b/zerver/webhooks/slack_incoming/view.py @@ -1,10 +1,13 @@ # Webhooks for external integrations. import re +from collections.abc import Callable +from functools import wraps from itertools import zip_longest from typing import Literal, TypedDict, cast -from django.http import HttpRequest, HttpResponse +from django.http import HttpRequest, HttpResponse, JsonResponse from django.utils.translation import gettext as _ +from typing_extensions import ParamSpec from zerver.decorator import webhook_view from zerver.lib.exceptions import JsonableError @@ -25,9 +28,31 @@ from zerver.lib.validator import ( from zerver.lib.webhooks.common import OptionalUserSpecifiedTopicStr, check_send_webhook_message from zerver.models import UserProfile +ParamT = ParamSpec("ParamT") + + +def slack_error_handler(view_func: Callable[..., HttpResponse]) -> Callable[..., HttpResponse]: + """ + A decorator that catches JsonableError exceptions and returns a + Slack-compatible error response in the format: + {ok: false, error: "error message"}. + """ + + @wraps(view_func) + def wrapped_view( + request: HttpRequest, *args: ParamT.args, **kwargs: ParamT.kwargs + ) -> HttpResponse: + try: + return view_func(request, *args, **kwargs) + except JsonableError as error: + return JsonResponse({"ok": False, "error": error.msg}, status=error.http_status_code) + + return wrapped_view + @webhook_view("SlackIncoming") @typed_endpoint +@slack_error_handler def api_slack_incoming_webhook( request: HttpRequest, user_profile: UserProfile, diff --git a/zerver/worker/missedmessage_emails.py b/zerver/worker/missedmessage_emails.py index 9f2b4c40f4..ef84ef23d5 100644 --- a/zerver/worker/missedmessage_emails.py +++ b/zerver/worker/missedmessage_emails.py @@ -216,7 +216,7 @@ class MissedMessageWorker(QueueProcessingWorker): def maybe_send_batched_emails(self) -> None: current_time = timezone_now() - with transaction.atomic(): + with transaction.atomic(durable=True): events_to_process = ScheduledMessageNotificationEmail.objects.filter( scheduled_timestamp__lte=current_time ).select_for_update() diff --git a/zilencer/views.py b/zilencer/views.py index 8bc6c96261..52083f1f76 100644 --- a/zilencer/views.py +++ b/zilencer/views.py @@ -50,7 +50,7 @@ from zerver.lib.push_notifications import ( send_apple_push_notification, send_test_push_notification_directly_to_devices, ) -from zerver.lib.queue import queue_json_publish +from zerver.lib.queue import queue_event_on_commit from zerver.lib.remote_server import ( InstallationCountDataForAnalytics, RealmAuditLogDataForAnalytics, @@ -211,7 +211,7 @@ def register_remote_server( _("A server with hostname {hostname} already exists").format(hostname=hostname) ) - with transaction.atomic(): + with transaction.atomic(durable=True): if remote_server is None: created = True remote_server = RemoteZulipServer.objects.create( @@ -1012,7 +1012,7 @@ def update_remote_realm_data_for_server( } for context in new_locally_deleted_remote_realms_on_paid_plan_contexts: email_dict["context"] = context - queue_json_publish("email_senders", email_dict) + queue_event_on_commit("email_senders", email_dict) def get_human_user_realm_uuids( @@ -1035,7 +1035,7 @@ def get_human_user_realm_uuids( return billable_realm_uuids -@transaction.atomic +@transaction.atomic(durable=True) def handle_customer_migration_from_server_to_realm( server: RemoteZulipServer, ) -> None: @@ -1156,7 +1156,7 @@ def handle_customer_migration_from_server_to_realm( @typed_endpoint -@transaction.atomic +@transaction.atomic(durable=True) def remote_server_post_analytics( request: HttpRequest, server: RemoteZulipServer, @@ -1267,7 +1267,7 @@ def remote_server_post_analytics( # 'last_audit_log_update' needs to be an atomic operation. # This helps to rely on 'last_audit_log_update' to assume # RemoteRealmAuditLog and LicenseLedger are up-to-date. - with transaction.atomic(): + with transaction.atomic(savepoint=False): # Important: Do not return early if we receive 0 rows; we must # updated last_audit_log_update even if there are no new rows, # to help identify server whose ability to connect to this diff --git a/zproject/computed_settings.py b/zproject/computed_settings.py index 407713c3ea..e66b949b08 100644 --- a/zproject/computed_settings.py +++ b/zproject/computed_settings.py @@ -615,7 +615,7 @@ LOCAL_FILES_DIR = os.path.join(LOCAL_UPLOADS_DIR, "files") if LOCAL_UPLOADS_DIR # ZulipStorage when not DEBUG. if not DEBUG: - STATICFILES_STORAGE = "zerver.lib.storage.ZulipStorage" + STORAGES = {"staticfiles": {"BACKEND": "zerver.lib.storage.ZulipStorage"}} if PRODUCTION: STATIC_ROOT = "/home/zulip/prod-static" else: diff --git a/zproject/default_settings.py b/zproject/default_settings.py index b4c7f6b691..6b62e88541 100644 --- a/zproject/default_settings.py +++ b/zproject/default_settings.py @@ -667,12 +667,6 @@ SIGNED_ACCESS_TOKEN_VALIDITY_IN_SECONDS = 60 CUSTOM_AUTHENTICATION_WRAPPER_FUNCTION: Callable[..., Any] | None = None -# Whether we allow settings to be set to a collection of users and -# groups as described in api_docs/group-setting-values.md. Set to -# False in production, as we can only handle named user groups in the -# web app settings UI. -ALLOW_GROUP_VALUED_SETTINGS = False - # Grace period during which we don't send a resolve/unresolve # notification to a stream and also delete the previous counter # notification. diff --git a/zproject/dev_settings.py b/zproject/dev_settings.py index c7def46bdb..20f7807a38 100644 --- a/zproject/dev_settings.py +++ b/zproject/dev_settings.py @@ -211,9 +211,6 @@ ZULIP_SERVICES_URL = f"http://push.{EXTERNAL_HOST}" ZULIP_SERVICE_PUSH_NOTIFICATIONS = True ZULIP_SERVICE_SUBMIT_USAGE_STATISTICS = True -# Breaks the UI if used, but enabled for development environment testing. -ALLOW_GROUP_VALUED_SETTINGS = True - # This value needs to be lower in development than usual to allow # for quicker testing of the feature. RESOLVE_TOPIC_UNDO_GRACE_PERIOD_SECONDS = 5 diff --git a/zproject/test_extra_settings.py b/zproject/test_extra_settings.py index a2c600b09d..f0951ef364 100644 --- a/zproject/test_extra_settings.py +++ b/zproject/test_extra_settings.py @@ -281,8 +281,6 @@ SCIM_CONFIG: dict[str, SCIMConfigDict] = { } } -ALLOW_GROUP_VALUED_SETTINGS = True - # This override disables the grace period for undoing resolving/unresolving # a topic in tests. # This allows tests to not worry about the special behavior during the grace period. diff --git a/zproject/urls.py b/zproject/urls.py index 49f1c9710b..e68e696a77 100644 --- a/zproject/urls.py +++ b/zproject/urls.py @@ -218,7 +218,8 @@ from zerver.views.user_settings import ( from zerver.views.user_topics import update_muted_topic, update_user_topic from zerver.views.users import ( add_bot_backend, - avatar, + avatar_by_email, + avatar_by_id, avatar_medium, create_user_backend, deactivate_bot_backend, @@ -232,7 +233,8 @@ from zerver.views.users import ( patch_bot_backend, reactivate_user_backend, regenerate_bot_api_key, - update_user_backend, + update_user_by_email_api, + update_user_by_id_api, ) from zerver.views.video_calls import ( complete_zoom_user, @@ -316,11 +318,11 @@ v1_api_and_json_patterns = [ rest_path( "users/", GET=get_members_backend, - PATCH=update_user_backend, + PATCH=update_user_by_id_api, DELETE=deactivate_user_backend, ), rest_path("users//subscriptions/", GET=get_subscription_backend), - rest_path("users/", GET=get_user_by_email), + rest_path("users/", GET=get_user_by_email, PATCH=update_user_by_email_api), rest_path("bots", GET=get_bots_backend, POST=add_bot_backend), rest_path("bots//api_key/regenerate", POST=regenerate_bot_api_key), rest_path("bots/", PATCH=patch_bot_backend, DELETE=deactivate_bot_backend), @@ -679,11 +681,22 @@ urls += [ # Avatars have the same constraint because their URLs are included # in API data structures used by both the mobile and web clients. rest_path( - "avatar/", - GET=(avatar, {"override_api_url_scheme", "allow_anonymous_user_web"}), + "avatar/", + GET=(avatar_by_id, {"override_api_url_scheme", "allow_anonymous_user_web"}), ), rest_path( - "avatar//medium", + "avatar/", + GET=(avatar_by_email, {"override_api_url_scheme", "allow_anonymous_user_web"}), + ), + rest_path( + "avatar//medium", + GET=( + avatar_medium, + {"override_api_url_scheme", "allow_anonymous_user_web"}, + ), + ), + rest_path( + "avatar//medium", GET=( avatar_medium, {"override_api_url_scheme", "allow_anonymous_user_web"},