Merge branch 'zulip:main' into add-script-color
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
<!-- Syntax: #**announce** -->
|
||||
<a class="stream" data-stream-id="9"
|
||||
href="/#narrow/channel/9-announce">
|
||||
#announce
|
||||
</a>
|
||||
|
||||
<!-- Syntax: #**announce>Zulip updates** -->
|
||||
<a class="stream-topic" data-stream-id="9"
|
||||
href="/#narrow/channel/9-announce/topic/Zulip.20updates">
|
||||
#announce > Zulip updates
|
||||
</a>
|
||||
|
||||
<!-- Syntax: #**announce>Zulip updates@214** -->
|
||||
<a class="message-link"
|
||||
href="/#narrow/channel/9-announce/topic/Zulip.20updates/near/214">
|
||||
#announce > Zulip updates @ 💬
|
||||
</a>
|
||||
```
|
||||
|
||||
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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 <greg@zulip.com>
|
||||
|
||||
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 <commit_id>` 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 <greg@zulip.com>
|
||||
- **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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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** <i class="fa fa-trash-o"></i> icon near the top right
|
||||
corner of the channel settings panel.
|
||||
1. Click the **archive** (<i class="zulip-icon zulip-icon-archive"></i>) 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** (<i class="zulip-icon zulip-icon-more-vertical"></i>), and
|
||||
select **Channel settings** to access the **trash**
|
||||
<i class="fa fa-trash-o"></i> 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)
|
||||
|
|
|
@ -1,19 +1,7 @@
|
|||
# Configure emoticon translations
|
||||
|
||||
You can configure whether emoticons like `:)` or `:(` will be automatically
|
||||
translated into emoji equivalents like
|
||||
<img
|
||||
src="/static/generated/emoji/images-google-64/1f642.png"
|
||||
alt="smile"
|
||||
class="emoji-small"
|
||||
/>
|
||||
or
|
||||
<img
|
||||
src="/static/generated/emoji/images-google-64/1f641.png"
|
||||
alt="slight_frown"
|
||||
class="emoji-small"
|
||||
/>
|
||||
by Zulip.
|
||||
translated into emoji equivalents like 🙂 or 🙁 by Zulip.
|
||||
|
||||
## Configure emoticon translations
|
||||
|
||||
|
|
|
@ -13,8 +13,8 @@ on how to use these views.
|
|||
|
||||
You can configure which view is set as your home view, and whether
|
||||
the <kbd>Esc</kbd> key navigates to the home view. Also, you can
|
||||
always reach the home view by using the <kbd>Ctrl</kbd> + <kbd>[</kbd>
|
||||
shortcut.
|
||||
always reach the home view by using the
|
||||
<kbd data-mac-key="Ctrl">Ctrl</kbd> + <kbd>[</kbd> 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
|
||||
(<kbd>Ctrl</kbd> + <kbd>[</kbd> or <kbd>Esc</kbd> if enabled).
|
||||
(<kbd data-mac-key="Ctrl">Ctrl</kbd> + <kbd>[</kbd> or <kbd>Esc</kbd>
|
||||
if enabled).
|
||||
|
||||
!!! tip ""
|
||||
|
||||
|
@ -62,8 +63,8 @@ designed to enhance the user experience in the app.
|
|||
By default, the <kbd>Esc</kbd> key shortcut will ultimately navigate to
|
||||
your home view. You can disable this key binding if you would prefer.
|
||||
This will not disable other <kbd>Esc</kbd> key shortcuts used in Zulip,
|
||||
and will not affect the behavior of the <kbd>Ctrl</kbd> + <kbd>[</kbd>
|
||||
shortcut.
|
||||
and will not affect the behavior of the
|
||||
<kbd data-mac-key="Ctrl">Ctrl</kbd> + <kbd>[</kbd> shortcut.
|
||||
|
||||
### Toggle whether <kbd>Esc</kbd> navigates to the home view
|
||||
|
||||
|
|
|
@ -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!}
|
||||
|
|
|
@ -47,13 +47,7 @@
|
|||
### Use an emoticon
|
||||
|
||||
You can configure Zulip to convert emoticons into emoji, so that, e.g., `:)`
|
||||
will be displayed as
|
||||
<img
|
||||
src="/static/generated/emoji/images-google-64/1f642.png"
|
||||
alt="smile"
|
||||
class="emoji-small"
|
||||
/>
|
||||
.
|
||||
will be displayed as 🙂 .
|
||||
|
||||
{start_tabs}
|
||||
|
||||
|
|
|
@ -32,9 +32,7 @@ message.
|
|||
|
||||
!!! keyboard_tip ""
|
||||
|
||||
You can react to the selected message with <img alt=":thumbs_up:"
|
||||
class="emoji" src="/static/generated/emoji/images/emoji/unicode/1f44d.png"
|
||||
title="thumbs up"/> by using the <kbd>+</kbd> shortcut.
|
||||
You can react to the selected message with 👍 by using the <kbd>+</kbd> shortcut.
|
||||
|
||||
{tab|mobile}
|
||||
|
||||
|
|
|
@ -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 <kbd>C</kbd> keyboard shortcut.
|
||||
1. Click the **plus** (<i class="zulip-icon zulip-icon-square-plus"></i>) 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** (<i class="zulip-icon zulip-icon-close"></i>) icon
|
||||
near the upper right of the compose box to erase the topic name.
|
||||
You can also use the <kbd>C</kbd> 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 <kbd>C</kbd> keyboard shortcut to start a new topic in
|
||||
the channel you're viewing.
|
||||
|
||||
{tab|mobile}
|
||||
|
||||
|
|
|
@ -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.
|
||||
```
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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** (<i class="zulip-icon zulip-icon-square-plus"></i>) 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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
|
@ -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).
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -32,9 +32,9 @@ in the Zulip app to add more to your repertoire as needed.
|
|||
<kbd>Ctrl</kbd> + <kbd>V</kbd>, and press <kbd>Ctrl</kbd> + <kbd>Z</kbd> to
|
||||
remove formatting.
|
||||
|
||||
* **Cancel compose and save draft**: <kbd>Esc</kbd> or <kbd>Ctrl</kbd> +
|
||||
<kbd>[</kbd> — Close the compose box and save the unsent message as a
|
||||
draft.
|
||||
* **Cancel compose and save draft**: <kbd>Esc</kbd> or
|
||||
<kbd data-mac-key="Ctrl">Ctrl</kbd> + <kbd>[</kbd> — Close the compose box
|
||||
and save the unsent message as a draft.
|
||||
|
||||
* **View drafts**: <kbd>D</kbd> — Use the arrow keys and <kbd>Enter</kbd>
|
||||
to restore a draft. Press <kbd>D</kbd> again to close.
|
||||
|
@ -54,8 +54,8 @@ in the Zulip app to add more to your repertoire as needed.
|
|||
|
||||
* **Toggle keyboard shortcuts view**: <kbd>?</kbd>
|
||||
|
||||
* **Go to your home view**: <kbd>Ctrl</kbd> + <kbd>[</kbd> (or
|
||||
<kbd>Esc</kbd>, [if enabled][disable-escape])
|
||||
* **Go to your home view**: <kbd data-mac-key="Ctrl">Ctrl</kbd> + <kbd>[</kbd>
|
||||
(or <kbd>Esc</kbd>, [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**: <kbd>Q</kbd>
|
||||
|
||||
* **Search people**: <kbd>W</kbd>
|
||||
* **Filter users**: <kbd>W</kbd>
|
||||
|
||||
## Scrolling
|
||||
|
||||
|
@ -158,8 +158,9 @@ in the Zulip app to add more to your repertoire as needed.
|
|||
|
||||
* **Toggle preview mode**: <kbd>Alt</kbd> + <kbd>P</kbd>
|
||||
|
||||
* **Cancel compose and save draft**: <kbd>Esc</kbd> or <kbd>Ctrl</kbd> +
|
||||
<kbd>[</kbd> — Close the compose box and save the unsent message as a draft.
|
||||
* **Cancel compose and save draft**: <kbd>Esc</kbd> or
|
||||
<kbd data-mac-key="Ctrl">Ctrl</kbd> + <kbd>[</kbd> — 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**: <kbd>Ctrl</kbd> + <kbd>S</kbd>
|
||||
|
||||
* **React with <img alt=":thumbs_up:" class="emoji"
|
||||
src="/static/generated/emoji/images/emoji/unicode/1f44d.png"
|
||||
title="thumbs up"/>**: <kbd>+</kbd>
|
||||
* **React with 👍**: <kbd>+</kbd>
|
||||
|
||||
* **Toggle first emoji reaction**: <kbd>=</kbd>
|
||||
|
||||
|
|
|
@ -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
|
||||
<kbd data-mac-following-key="⌥">Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>V</kbd>.
|
||||
|
||||
{end_tabs}
|
||||
|
||||
### Get a link to a specific topic
|
||||
|
|
|
@ -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!}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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}
|
|
@ -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)
|
||||
|
|
|
@ -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 (
|
||||
<img src="/static/generated/emoji/images-google-64/1f419.png" alt="octopus"
|
||||
class="emoji-small"/>).
|
||||
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)
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
{!user-groups-intro.md!}
|
||||
|
||||
{!user-groups-applications.md!}
|
||||
|
||||
## Browse and join user groups
|
||||
|
||||
{start_tabs}
|
||||
|
|
|
@ -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** (<i
|
||||
class="zulip-icon zulip-icon-triple-users"></i> ) icon in the upper right to
|
||||
show it.
|
||||
1. If the user list is hidden, click the **user list** (<i class="zulip-icon
|
||||
zulip-icon-triple-users"></i> ) icon in the upper right to show it.
|
||||
|
||||
1. Click the **search** (<i class="search_icon zulip-icon
|
||||
zulip-icon-search"></i>) 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 <kbd>W</kbd> keyboard shortcut to search people reveals the user list if
|
||||
The <kbd>W</kbd> keyboard shortcut to filter users reveals the user list if
|
||||
it is hidden.
|
||||
|
||||
{end_tabs}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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();
|
4068
pnpm-lock.yaml
|
@ -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,
|
||||
|
|
|
@ -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 -%>
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
1243
requirements/dev.txt
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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(
|
||||
"""
|
||||
|
|
Before Width: | Height: | Size: 157 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 156 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 79 KiB |
|
@ -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,
|
||||
|
|
|
@ -88,6 +88,8 @@
|
|||
<li><code>./manage.py mark_all_messages_unread</code>: Useful for testing reading messages.</li>
|
||||
<li><code>./manage.py create_realm</code>: Add a new realm. Useful for testing onboarding.</li>
|
||||
<li><code>./manage.py create_user</code>: Add a new user. Useful for testing onboarding.</li>
|
||||
<li><code>./manage.py send_zulip_update_announcements</code>: Send <a href="https://zulip.com/help/configure-automated-notices#zulip-update-announcements">Zulip
|
||||
update notices</a> drafted in `zerver/lib/zulip_update_announcements.py`.</li>
|
||||
<li><code>./manage.py add_mock_conversation</code>: 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.
|
||||
|
|
|
@ -38,12 +38,12 @@
|
|||
{% else %}
|
||||
<div class="find-account-form">
|
||||
<p>
|
||||
{% trans %}
|
||||
Enter your email address to receive an email with the URLs
|
||||
for all the Zulip Cloud organizations in which you have
|
||||
active accounts. If you have also forgotten your password,
|
||||
you can <a href="/help/change-your-password">reset it</a>.
|
||||
{% endtrans %}
|
||||
{% if corporate_enabled %}
|
||||
{% trans %}Enter your email address to receive an email with the URLs for all the Zulip Cloud organizations in which you have active accounts.{% endtrans %}
|
||||
{% else %}
|
||||
{% trans %}Enter your email address to receive an email with the URLs for all the Zulip organizations on this server in which you have active accounts.{% endtrans %}
|
||||
{% endif %}
|
||||
{% trans %}If you have also forgotten your password, you can <a href="/help/change-your-password">reset it</a>.{% endtrans %}
|
||||
</p>
|
||||
<form class="form-inline" id="find_account" name="email_form"
|
||||
action="{{ current_url }}" method="post">
|
||||
|
@ -55,7 +55,7 @@
|
|||
</div>
|
||||
<button type="submit">{{ _('Find accounts') }}</button>
|
||||
</div>
|
||||
<div><i>{{ form.emails.help_text }}</i></div>
|
||||
<div class="find-account-form-tip"><i>{{ form.emails.help_text }}</i></div>
|
||||
</form>
|
||||
<div id="errors"></div>
|
||||
{% if form.emails.errors %}
|
||||
|
|
|
@ -12,14 +12,15 @@
|
|||
<h1 class="get-started">{{ _('No organization found') }}</h1>
|
||||
</div>
|
||||
|
||||
<div class="app-main white-box">
|
||||
<div class="app-main find-account-page-container white-box">
|
||||
<p>
|
||||
{% trans %}There is no Zulip organization at <b>{{ current_url }}</b>.{% endtrans %}
|
||||
<br />
|
||||
</p>
|
||||
<p>
|
||||
{% if corporate_enabled %}
|
||||
{% trans %}Please try a different URL, or <a href="mailto:{{ support_email }}">contact Zulip support</a>.{% endtrans %}
|
||||
{% trans %}Please try a different URL, <a href="{{ root_domain_url }}/accounts/find/">get a list of your Zulip Cloud accounts</a>, or <a href="mailto:{{ support_email }}">contact Zulip support</a>.{% endtrans %}
|
||||
{% else %}
|
||||
{% trans %}Please try a different URL, or <a href="mailto:{{ support_email }}">contact this Zulip server's administrators</a>.{% endtrans %}
|
||||
{% trans %}Please try a different URL, <a href="{{ root_domain_url }}/accounts/find/">get a list of your accounts</a> on this server, or <a href="mailto:{{ support_email }}">contact this Zulip server's administrators</a>.{% endtrans %}
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -44,7 +44,9 @@
|
|||
</div>
|
||||
|
||||
<div class="bottom-text">
|
||||
{{ _("Need to get your group started on Zulip?") }} <a target="_blank" rel="noopener noreferrer" href="/new/">{{ _("Create a new organization.") }}</a>
|
||||
{% trans org_creation_link="/new/" %}
|
||||
<a target="_blank" rel="noopener noreferrer" href="{{ org_creation_link }}">Create a new organization</a> if you don't have one yet.
|
||||
{% endtrans %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -14,7 +14,7 @@ module.exports = {
|
|||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
corejs: "3.37",
|
||||
corejs: "3.39",
|
||||
include: ["transform-optional-chaining"],
|
||||
shippedProposals: true,
|
||||
useBuiltIns: "usage",
|
||||
|
|
|
@ -429,7 +429,7 @@ async function test_users_search(page: Page): Promise<void> {
|
|||
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");
|
||||
|
|
After Width: | Height: | Size: 566 B |
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<CombinedPill>({
|
||||
$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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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, {});
|
||||
|
|
|
@ -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>,
|
||||
): 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<number> {
|
||||
|
|
|
@ -77,9 +77,8 @@ type BuddyListRenderData = {
|
|||
pm_ids_set: Set<number>;
|
||||
total_human_subscribers_count: number;
|
||||
other_users_count: number;
|
||||
total_human_users: number;
|
||||
hide_headers: boolean;
|
||||
participant_ids_set: Set<number>;
|
||||
all_participant_ids: Set<number>;
|
||||
};
|
||||
|
||||
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) {
|
||||
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: matching_view_empty_list_message,
|
||||
empty_list_message: message,
|
||||
});
|
||||
$("#buddy-list-users-matching-view").html(empty_list_widget_html);
|
||||
$(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),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -77,12 +77,7 @@ export function append_custom_profile_fields(element_id: string, user_id: number
|
|||
}
|
||||
}
|
||||
|
||||
export function initialize_custom_user_type_fields(
|
||||
element_id: string,
|
||||
user_id: number,
|
||||
is_target_element_editable: boolean,
|
||||
pill_update_handler?: (
|
||||
field: {
|
||||
export type PillUpdateField = {
|
||||
type: number;
|
||||
field_data: string;
|
||||
hint: string;
|
||||
|
@ -91,9 +86,13 @@ export function initialize_custom_user_type_fields(
|
|||
order: number;
|
||||
required: boolean;
|
||||
display_in_profile_summary?: boolean | undefined;
|
||||
},
|
||||
pills: UserPillWidget,
|
||||
) => void,
|
||||
};
|
||||
|
||||
export function initialize_custom_user_type_fields(
|
||||
element_id: string,
|
||||
user_id: number,
|
||||
is_target_element_editable: boolean,
|
||||
pill_update_handler?: (field: PillUpdateField, pills: UserPillWidget) => void,
|
||||
): Map<number, UserPillWidget> {
|
||||
const field_types = realm.custom_profile_field_types;
|
||||
const user_pills = new Map<number, UserPillWidget>();
|
||||
|
|
|
@ -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 = $<HTMLInputElement>("input#new_subdomain").val()!.trim();
|
||||
const org_type = $<HTMLSelectOneElement>(
|
||||
"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;
|
||||
},
|
||||
};
|