mirror of
https://github.com/zulip/zulip.git
synced 2025-10-22 20:42:14 +00:00
Compare commits
17 Commits
aac651fa39
...
90054890f3
Author | SHA1 | Date | |
---|---|---|---|
|
90054890f3 | ||
|
19e5c8b8c9 | ||
|
35aac76176 | ||
|
85e6cec1db | ||
|
bdb2c921ba | ||
|
dd92036550 | ||
|
9815db9811 | ||
|
5d7adcbc00 | ||
|
c2d008aadb | ||
|
f55c89a87f | ||
|
7185f2c236 | ||
|
b36f09c67f | ||
|
ad122af6f8 | ||
|
23740c97a4 | ||
|
f33ef8f206 | ||
|
e4ba536eae | ||
|
9ffe31e352 |
@@ -866,7 +866,7 @@ deactivated groups.
|
||||
|
||||
**Feature level 336**
|
||||
|
||||
* [Markdown message formatting](/api/message-formatting#image-previews): Added
|
||||
* [Markdown message formatting](/api/message-formatting#images): Added
|
||||
`data-original-content-type` attribute to convey the type of the original
|
||||
image, and optional `data-transcoded-image` attribute for images with formats
|
||||
which are not widely supported by browsers.
|
||||
@@ -1435,7 +1435,7 @@ deactivated groups.
|
||||
**Feature level 287**
|
||||
|
||||
* [Markdown message
|
||||
formatting](/api/message-formatting#image-previews): Added
|
||||
formatting](/api/message-formatting#images): Added
|
||||
`data-original-dimensions` attributes to placeholder images
|
||||
(`image-loading-placeholder`), containing the dimensions of the
|
||||
original image. This change was also backported to the Zulip 9.x
|
||||
@@ -1511,7 +1511,7 @@ releases.
|
||||
**Feature level 278**
|
||||
|
||||
* [Markdown message
|
||||
formatting](/api/message-formatting#image-previews): Added
|
||||
formatting](/api/message-formatting#images): Added
|
||||
`data-original-dimensions` attributes to placeholder images
|
||||
(`image-loading-placeholder`), containing the dimensions of the
|
||||
original image. Backported change from feature level 287.
|
||||
@@ -1524,7 +1524,7 @@ No changes; feature level used for Zulip 9.0 release.
|
||||
|
||||
**Feature level 276**
|
||||
|
||||
* [Markdown message formatting](/api/message-formatting#image-previews):
|
||||
* [Markdown message formatting](/api/message-formatting#images):
|
||||
Image preview elements not contain a `data-original-dimensions`
|
||||
attribute containing the dimensions of the original image.
|
||||
|
||||
|
@@ -121,31 +121,35 @@ the href for those is the default behavior of the link that also
|
||||
encodes the channel alongside the data-stream-id field, but clients
|
||||
can override that default based on `web_channel_default_view` setting.
|
||||
|
||||
## Image previews
|
||||
## Images
|
||||
|
||||
When a Zulip message is sent linking to an uploaded image, Zulip will
|
||||
generate an image preview element with the following format.
|
||||
generate an image preview element with the following format:
|
||||
|
||||
``` html
|
||||
<div class="message_inline_image">
|
||||
<a href="/user_uploads/path/to/image.png" title="image.png">
|
||||
<a href="/user_uploads/path/to/example.png" title="example.png">
|
||||
<img data-original-dimensions="1920x1080"
|
||||
data-original-content-type="image/png"
|
||||
src="/user_uploads/thumbnail/path/to/image.png/840x560.webp">
|
||||
src="/user_uploads/thumbnail/path/to/example.png/840x560.webp">
|
||||
</a>
|
||||
</div>
|
||||
```
|
||||
|
||||
If the server has not yet generated thumbnails for the image yet at
|
||||
the time the message is sent, the `img` element will be a temporary
|
||||
loading indicator image and have the `image-loading-placeholder`
|
||||
**Changes**: See [Changes to image formatting](#changes-to-image-formatting).
|
||||
|
||||
### Image-loading placeholders
|
||||
|
||||
If the server has yet to generate thumbnails for the image by
|
||||
the time the message is sent, the `img` element will temporarily
|
||||
reference a loading indicator image and have the `image-loading-placeholder`
|
||||
class, which clients can use to identify loading indicators and
|
||||
replace them with a more native loading indicator element if
|
||||
desired. For example:
|
||||
|
||||
``` html
|
||||
<div class="message_inline_image">
|
||||
<a href="/user_uploads/path/to/image.png" title="image.png">
|
||||
<a href="/user_uploads/path/to/example.png" title="example.png">
|
||||
<img class="image-loading-placeholder"
|
||||
data-original-dimensions="1920x1080"
|
||||
data-original-content-type="image/png"
|
||||
@@ -168,6 +172,31 @@ backlogged, an individual message containing multiple image previews
|
||||
may be re-rendered multiple times as each image finishes thumbnailing
|
||||
and triggers a message update.
|
||||
|
||||
### Transcoded images
|
||||
|
||||
Image elements whose formats are not widely supported by web browsers
|
||||
(e.g., HEIC and TIFF) may contain a `data-transcoded-image` attribute,
|
||||
which specifies a high-resolution thumbnail format that clients may
|
||||
opt to present instead of the original image. If the
|
||||
`data-transcoded-image` attribute is present, clients should use the
|
||||
`data-original-content-type` attribute to decide whether to display the
|
||||
original image or use the transcoded version.
|
||||
|
||||
Transcoded images are presented with this structure in image previews:
|
||||
|
||||
``` html
|
||||
<div class="message_inline_image">
|
||||
<a href="/user_uploads/path/to/example.heic" title="example.heic">
|
||||
<img data-original-dimensions="1920x1080"
|
||||
data-original-content-type="image/heic"
|
||||
data-transcoded-image="1920x1080.webp"
|
||||
src="/user_uploads/thumbnail/path/to/example.heic/840x560.webp">
|
||||
</a>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Recommended client processing of image previews
|
||||
|
||||
Clients are recommended to do the following when processing image
|
||||
previews:
|
||||
|
||||
@@ -217,16 +246,18 @@ previews:
|
||||
format match what they requested.
|
||||
- No other processing of the URLs is recommended.
|
||||
|
||||
**Changes**: In Zulip 10.0 (feature level 336), added
|
||||
### Changes to image formatting
|
||||
|
||||
**In Zulip 10.0** (feature level 336), added
|
||||
`data-original-content-type` attribute to convey the type of the
|
||||
original image, and optional `data-transcoded-image` attribute for
|
||||
images with formats which are not widely supported by browsers.
|
||||
|
||||
**Changes**: In Zulip 9.2 (feature levels 278-279, and 287+), added
|
||||
**In Zulip 9.2** (feature levels 278-279, and 287+), added
|
||||
`data-original-dimensions` to the `image-loading-placeholder` spinner
|
||||
images, containing the dimensions of the original image.
|
||||
|
||||
In Zulip 9.0 (feature level 276), added `data-original-dimensions`
|
||||
**In Zulip 9.0** (feature level 276), added `data-original-dimensions`
|
||||
attribute to images that have been thumbnailed, containing the
|
||||
dimensions of the full-size version of the image. Thumbnailing itself
|
||||
was reintroduced at feature level 275.
|
||||
|
@@ -587,6 +587,53 @@ analysis of recent application-level data changes.
|
||||
You may also want to adjust the [incremental backups][incremental]
|
||||
configuration.
|
||||
|
||||
### Restoring from wal-g backups
|
||||
|
||||
The following steps will restore the database from the latest
|
||||
backup. Note that this process will _delete your current database_.
|
||||
|
||||
1. As `root` on your database host, check your list of backups; the
|
||||
most recent will be listed at the bottom, and is what will be
|
||||
restored by the commands below.
|
||||
|
||||
```shell
|
||||
env-wal-g backup-list --pretty
|
||||
```
|
||||
|
||||
1. Stop Zulip, if it is running. On your application host (which may
|
||||
or may not be different from your database host, depending on your
|
||||
configuration):
|
||||
|
||||
```shell
|
||||
/home/zulip/deployments/current/scripts/stop-server
|
||||
```
|
||||
|
||||
1. As `root` on your database host, stop PostgreSQL:
|
||||
|
||||
```shell
|
||||
service postgresql stop
|
||||
```
|
||||
|
||||
1. As `root` on your database host, delete the current database, and
|
||||
restore from the backup. This may take some time, depending on the
|
||||
size of your database and your connection to your backup storage.
|
||||
|
||||
```shell
|
||||
pg_version=$(crudini --get /etc/zulip/zulip.conf postgresql version)
|
||||
rm -rf "/var/lib/postgresql/$pg_version/main"
|
||||
env-wal-g backup-fetch "/var/lib/postgresql/$pg_version/main" LATEST
|
||||
chown -R postgres.postgres "/var/lib/postgresql/$pg_version/main"
|
||||
touch "/var/lib/postgresql/$pg_version/main/recovery.signal"
|
||||
service postgresql start
|
||||
```
|
||||
|
||||
1. As `root` on your application host, flush caches and start Zulip:
|
||||
|
||||
```shell
|
||||
/home/zulip/deployments/current/scripts/setup/flush-memcached
|
||||
/home/zulip/deployments/current/scripts/start-server
|
||||
```
|
||||
|
||||
[wal]: https://www.postgresql.org/docs/current/wal-intro.html
|
||||
[archive-timeout]: https://www.postgresql.org/docs/current/runtime-config-wal.html#GUC-ARCHIVE-TIMEOUT
|
||||
[mobile-push]: ../production/mobile-push-notifications.md
|
||||
|
@@ -1,9 +1,5 @@
|
||||
| Zulip Server version | Supported versions of PostgreSQL |
|
||||
| -------------------- | -------------------------------- |
|
||||
| 3.x | 9.3, 9.5, 9.6, 10, 11, 12 |
|
||||
| 4.x | 9.3, 9.5, 9.6, 10, 11, 12, 13 |
|
||||
| 5.x | 10, 11, 12, 13, 14 |
|
||||
| 6.x | 11, 12, 13, 14 |
|
||||
| 7.x | 12, 13, 14, 15 |
|
||||
| 8.x | 12, 13, 14, 15, 16 |
|
||||
| 9.x | 12, 13, 14, 15, 16 |
|
||||
|
@@ -75,13 +75,13 @@ in order to export direct message data.
|
||||
1. [Create a new Slack app](https://api.slack.com/apps). Choose the "From
|
||||
scratch" creation option.
|
||||
1. [Create a
|
||||
bot user](https://api.slack.com/authentication/basics#scopes),
|
||||
bot user](https://docs.slack.dev/app-management/quickstart-app-settings/#creating),
|
||||
following the instructions to add the following OAuth scopes to your bot:
|
||||
* `emoji:read`
|
||||
* `users:read`
|
||||
* `users:read.email`
|
||||
* `team:read`
|
||||
1. [Install your new app](https://api.slack.com/authentication/basics#installing)
|
||||
1. [Install your new app](https://docs.slack.dev/app-management/quickstart-app-settings/#installing)
|
||||
to your Slack workspace.
|
||||
1. You will immediately see a **Bot User OAuth Token**, which is a long
|
||||
string of numbers and characters starting with `xoxb-`. Copy this token. It
|
||||
|
@@ -983,6 +983,11 @@ export class MessageListView {
|
||||
update_group_date(second_group, curr_msg_container.msg, prev_msg_container?.msg);
|
||||
// We could add an action to update the date row, but for now rerender the group.
|
||||
message_actions.rerender_groups.push(second_group);
|
||||
} else if (second_group.bookend_top) {
|
||||
// We know there was no bookend_top before since we
|
||||
// are adding messages to the top.
|
||||
const rendered_bookend_html = render_bookend(second_group);
|
||||
this.$list.prepend($(rendered_bookend_html));
|
||||
}
|
||||
message_actions.prepend_groups = new_message_groups;
|
||||
this._message_groups = [...new_message_groups, ...this._message_groups];
|
||||
|
@@ -90,23 +90,16 @@ function get_users_typing_for_narrow(): number[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
const terms = narrow_state.search_terms();
|
||||
if (terms[0] === undefined) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const first_term = terms[0];
|
||||
if (first_term.operator === "dm") {
|
||||
// Narrow has a filter with either "dm:" or "is:dm".
|
||||
const current_filter = narrow_state.filter()!;
|
||||
if (current_filter.has_operator("dm")) {
|
||||
// Get list of users typing in this conversation
|
||||
const narrow_emails_string = first_term.operand;
|
||||
// TODO: Create people.emails_strings_to_user_ids.
|
||||
const narrow_user_ids_string = people.reply_to_to_user_ids_string(narrow_emails_string);
|
||||
if (!narrow_user_ids_string) {
|
||||
const narrow_emails_string = current_filter.operands("dm")[0]!;
|
||||
if (!people.is_valid_bulk_emails_for_compose(narrow_emails_string.split(","))) {
|
||||
// Narrowed to an invalid direct message recipient.
|
||||
return [];
|
||||
}
|
||||
const narrow_user_ids = narrow_user_ids_string
|
||||
.split(",")
|
||||
.map((user_id_string) => Number.parseInt(user_id_string, 10));
|
||||
const narrow_user_ids = people.emails_string_to_user_ids(narrow_emails_string);
|
||||
const group = [...narrow_user_ids, current_user.user_id];
|
||||
return typing_data.get_group_typists(group);
|
||||
}
|
||||
|
@@ -174,7 +174,6 @@ class RegistrationForm(RealmDetailsForm):
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
# Since the superclass doesn't except random extra kwargs, we
|
||||
# remove it from the kwargs dict before initializing.
|
||||
self.realm_creation = kwargs["realm_creation"]
|
||||
self.realm = kwargs.pop("realm", None)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -390,6 +389,7 @@ class CaptchaRealmCreationForm(RealmCreationForm):
|
||||
|
||||
@override
|
||||
def clean(self) -> None:
|
||||
super().clean()
|
||||
if not self.data.get("captcha"):
|
||||
self.add_error("captcha", _("Validation failed, please try again."))
|
||||
|
||||
|
@@ -259,7 +259,7 @@ class PythonAPIIntegration(Integration):
|
||||
|
||||
|
||||
class WebhookIntegration(Integration):
|
||||
DEFAULT_FUNCTION_PATH = "zerver.webhooks.{name}.view.api_{name}_webhook"
|
||||
DEFAULT_FUNCTION_PATH = "zerver.webhooks.{dir_name}.view.api_{dir_name}_webhook"
|
||||
DEFAULT_URL = "api/v1/external/{name}"
|
||||
DEFAULT_CLIENT_NAME = "Zulip{name}Webhook"
|
||||
DEFAULT_DOC_PATH = "{name}/doc.md"
|
||||
@@ -296,8 +296,12 @@ class WebhookIntegration(Integration):
|
||||
url_options=url_options,
|
||||
)
|
||||
|
||||
if dir_name is None:
|
||||
dir_name = self.name
|
||||
self.dir_name = dir_name
|
||||
|
||||
if function is None:
|
||||
function = self.DEFAULT_FUNCTION_PATH.format(name=name)
|
||||
function = self.DEFAULT_FUNCTION_PATH.format(dir_name=dir_name)
|
||||
self.function_name = function
|
||||
|
||||
if url is None:
|
||||
@@ -308,10 +312,6 @@ class WebhookIntegration(Integration):
|
||||
doc = self.DEFAULT_DOC_PATH.format(name=name)
|
||||
self.doc = doc
|
||||
|
||||
if dir_name is None:
|
||||
dir_name = self.name
|
||||
self.dir_name = dir_name
|
||||
|
||||
def get_function(self) -> Callable[[HttpRequest], HttpResponseBase]:
|
||||
return import_string(self.function_name)
|
||||
|
||||
@@ -497,8 +497,6 @@ WEBHOOK_INTEGRATIONS: list[WebhookIntegration] = [
|
||||
"github",
|
||||
["version-control"],
|
||||
display_name="GitHub",
|
||||
function="zerver.webhooks.github.view.api_github_webhook",
|
||||
stream_name="github",
|
||||
url_options=[
|
||||
WebhookUrlOption.build_preset_config(PresetUrlOption.BRANCHES),
|
||||
WebhookUrlOption.build_preset_config(PresetUrlOption.IGNORE_PRIVATE_REPOSITORIES),
|
||||
@@ -510,7 +508,6 @@ WEBHOOK_INTEGRATIONS: list[WebhookIntegration] = [
|
||||
display_name="GitHub Sponsors",
|
||||
logo="images/integrations/logos/github.svg",
|
||||
dir_name="github",
|
||||
function="zerver.webhooks.github.view.api_github_webhook",
|
||||
doc="github/githubsponsors.md",
|
||||
stream_name="github",
|
||||
),
|
||||
@@ -536,12 +533,7 @@ WEBHOOK_INTEGRATIONS: list[WebhookIntegration] = [
|
||||
WebhookIntegration("helloworld", ["misc"], display_name="Hello World"),
|
||||
WebhookIntegration("heroku", ["deployment"]),
|
||||
WebhookIntegration("homeassistant", ["misc"], display_name="Home Assistant"),
|
||||
WebhookIntegration(
|
||||
"ifttt",
|
||||
["meta-integration"],
|
||||
function="zerver.webhooks.ifttt.view.api_iftt_app_webhook",
|
||||
display_name="IFTTT",
|
||||
),
|
||||
WebhookIntegration("ifttt", ["meta-integration"], display_name="IFTTT"),
|
||||
WebhookIntegration("insping", ["monitoring"]),
|
||||
WebhookIntegration("intercom", ["customer-support"]),
|
||||
# Avoid collision with jira-plugin's doc "jira/doc.md".
|
||||
|
@@ -27,7 +27,8 @@ def assert_is_local_storage_path(type: Literal["avatars", "files"], full_path: s
|
||||
defense in depth.
|
||||
"""
|
||||
assert settings.LOCAL_UPLOADS_DIR is not None
|
||||
type_path = os.path.join(settings.LOCAL_UPLOADS_DIR, type)
|
||||
type_path = os.path.normpath(os.path.join(settings.LOCAL_UPLOADS_DIR, type))
|
||||
full_path = os.path.normpath(full_path)
|
||||
assert os.path.commonpath([type_path, full_path]) == type_path
|
||||
|
||||
|
||||
|
@@ -15,7 +15,7 @@ def remove_google_blob(apps: StateApps, schema_editor: BaseDatabaseSchemaEditor)
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("zerver", "0752_remove_stream_is_in_zephyr_realm"),
|
||||
("zerver", "0751_externalauthid_zerver_user_externalauth_uniq"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
12
zerver/migrations/0754_merge_20251014_1855.py
Normal file
12
zerver/migrations/0754_merge_20251014_1855.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# Generated by Django 5.2.6 on 2025-10-14 18:55
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("zerver", "0752_remove_stream_is_in_zephyr_realm"),
|
||||
("zerver", "0753_remove_google_blob_emojiset"),
|
||||
]
|
||||
|
||||
operations = []
|
@@ -2073,11 +2073,14 @@ class RealmCreationTest(ZulipTestCase):
|
||||
email="<foo", realm_subdomain="custom-test", realm_name="Zulip test"
|
||||
)
|
||||
self.assert_in_response("Please use your real email address.", result)
|
||||
self.assert_in_response("Enter a valid email address.", result)
|
||||
|
||||
result = self.submit_realm_creation_form(
|
||||
email="foo\x00bar", realm_subdomain="custom-test", realm_name="Zulip test"
|
||||
)
|
||||
self.assert_in_response("Please use your real email address.", result)
|
||||
self.assert_in_response("Null characters are not allowed.", result)
|
||||
self.assert_in_response("Enter a valid email address.", result)
|
||||
|
||||
@override_settings(OPEN_REALM_CREATION=True)
|
||||
def test_mailinator_signup(self) -> None:
|
||||
|
@@ -5,7 +5,7 @@ class IFTTTHookTests(WebhookTestCase):
|
||||
CHANNEL_NAME = "ifttt"
|
||||
URL_TEMPLATE = "/api/v1/external/ifttt?stream={stream}&api_key={api_key}"
|
||||
WEBHOOK_DIR_NAME = "ifttt"
|
||||
VIEW_FUNCTION_NAME = "api_iftt_app_webhook"
|
||||
VIEW_FUNCTION_NAME = "api_ifttt_webhook"
|
||||
|
||||
def test_ifttt_when_subject_and_body_are_correct(self) -> None:
|
||||
expected_topic_name = "Email sent from email@email.com"
|
||||
|
@@ -13,7 +13,7 @@ from zerver.models import UserProfile
|
||||
|
||||
@webhook_view("IFTTT")
|
||||
@typed_endpoint
|
||||
def api_iftt_app_webhook(
|
||||
def api_ifttt_webhook(
|
||||
request: HttpRequest,
|
||||
user_profile: UserProfile,
|
||||
*,
|
||||
|
Reference in New Issue
Block a user