templates: Mark all void tags as self-closing.

This reverses the policy that was set, but incompletely enforced, by
commit 951514dd7d.  The self-closing tag
syntax is clearer, more consistent, simpler to parse, compatible with
XML, preferred by Prettier, and (most importantly now) required by
FormatJS.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2021-04-20 15:46:14 -07:00 committed by Tim Abbott
parent 7177529107
commit dd3fa4ac52
84 changed files with 345 additions and 346 deletions

View File

@ -584,8 +584,8 @@ class TestSupportEndpoint(ZulipTestCase):
'<span class="label">user</span>\n', '<span class="label">user</span>\n',
f"<h3>{full_name}</h3>", f"<h3>{full_name}</h3>",
f"<b>Email</b>: {email}", f"<b>Email</b>: {email}",
"<b>Is active</b>: True<br>", "<b>Is active</b>: True<br />",
f"<b>Role</b>: {role}<br>", f"<b>Role</b>: {role}<br />",
], ],
html_response, html_response,
) )

View File

@ -1,11 +1,11 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<title>Zulip - 500 internal server error</title> <title>Zulip - 500 internal server error</title>
<base href="/static/webpack-bundles/"> <base href="/static/webpack-bundles/" />
<meta http-equiv="refresh" content="60;URL='/'"> <meta http-equiv="refresh" content="60;URL='/'" />
</head> </head>

View File

@ -39,7 +39,7 @@
<div class="input-group"> <div class="input-group">
<label for="profile_field_choices_edit">{{t "Field choices" }}</label> <label for="profile_field_choices_edit">{{t "Field choices" }}</label>
<div class="profile-field-choices" name="profile_field_choices_edit"> <div class="profile-field-choices" name="profile_field_choices_edit">
<hr> <hr />
<div class="edit_profile_field_choices_container"> <div class="edit_profile_field_choices_container">
{{#each choices}} {{#each choices}}
{{> settings/profile_field_choice }} {{> settings/profile_field_choice }}

View File

@ -5,9 +5,9 @@
{{/if}} {{/if}}
</span> </span>
{{#if second_line}} {{#if second_line}}
<br><span class="tooltip_inner_content">{{second_line}}</span> <br /><span class="tooltip_inner_content">{{second_line}}</span>
{{/if}} {{/if}}
{{#if third_line}} {{#if third_line}}
<br><span class="tooltip_inner_content">{{third_line}}</span> <br /><span class="tooltip_inner_content">{{third_line}}</span>
{{/if}} {{/if}}
</div> </div>

View File

@ -8,7 +8,7 @@
<a role="button" class="compose_control_button fa fa-smile-o" aria-label="{{t 'Add emoji' }}" id="emoji_map" tabindex=0 title="{{t 'Add emoji' }}"></a> <a role="button" class="compose_control_button fa fa-smile-o" aria-label="{{t 'Add emoji' }}" id="emoji_map" tabindex=0 title="{{t 'Add emoji' }}"></a>
{{#if giphy_api_available }} {{#if giphy_api_available }}
<a role="button" class="compose_control_button" aria-label="{{t 'Add GIF' }}" id="compose_box_giphy_grid" title="{{t 'Add GIF' }}"> <a role="button" class="compose_control_button" aria-label="{{t 'Add GIF' }}" id="compose_box_giphy_grid" title="{{t 'Add GIF' }}">
<img class="compose_giphy_logo" tabindex=0 src="/static/images/GIPHY_logo.png"> <img class="compose_giphy_logo" tabindex=0 src="/static/images/GIPHY_logo.png" />
</a> </a>
{{/if}} {{/if}}
<a class="message-control-link drafts-link" href="#drafts" title="{{t 'Drafts' }} (d)">{{t 'Drafts' }}</a> <a class="message-control-link drafts-link" href="#drafts" title="{{t 'Drafts' }} (d)">{{t 'Drafts' }}</a>

View File

@ -2,7 +2,7 @@
<div class="arrow"></div> <div class="arrow"></div>
<div class="popover-inner"> <div class="popover-inner">
<div class="search-box"> <div class="search-box">
<input type="text" tabindex=0 id="giphy-search-query" class="search-query" placeholder="{{t 'Search GIFs' }}"> <input type="text" tabindex=0 id="giphy-search-query" class="search-query" placeholder="{{t 'Search GIFs' }}" />
<button type="button" class="btn clear_search_button" id="giphy_search_clear"> <button type="button" class="btn clear_search_button" id="giphy_search_clear">
<i class="fa fa-remove" aria-hidden="true"></i> <i class="fa fa-remove" aria-hidden="true"></i>
</button> </button>
@ -15,7 +15,7 @@
<div class="giphy-content"></div> <div class="giphy-content"></div>
</div> </div>
<div class="popover-footer"> <div class="popover-footer">
<img src="/static/images/GIPHY_attribution.png" alt="{{t 'GIPHY attribution' }}"> <img src="/static/images/GIPHY_attribution.png" alt="{{t 'GIPHY attribution' }}" />
</div> </div>
</div> </div>
</div> </div>

View File

@ -7,7 +7,7 @@
<p class="hotspot-description">{{description}}</p> <p class="hotspot-description">{{description}}</p>
</div> </div>
<div class="hotspot-popover-bottom"> <div class="hotspot-popover-bottom">
<img class="hotspot-img" alt=_("hotspot illustration") src="{{img}}"> <img class="hotspot-img" alt=_("hotspot illustration") src="{{img}}" />
<button class="hotspot-confirm">{{t 'Got it!' }}</button> <button class="hotspot-confirm">{{t 'Got it!' }}</button>
</div> </div>
</div> </div>

View File

@ -1,7 +1,7 @@
<div class="hotspot-inline trailing_bookend bookend hotspot-message" id="hotspot_intro_reply_icon"> <div class="hotspot-inline trailing_bookend bookend hotspot-message" id="hotspot_intro_reply_icon">
<div class="hotspot-inline-top"><h1 class="hotspot-title">Send a reply</h1></div> <div class="hotspot-inline-top"><h1 class="hotspot-title">Send a reply</h1></div>
<div class="hotspot-inline-left"> <div class="hotspot-inline-left">
<img class="hotspot-img" alt="" src="/static/images/hotspots/whale.svg"> <img class="hotspot-img" alt="" src="/static/images/hotspots/whale.svg" />
</div> </div>
<div class="hotspot-inline-right"> <div class="hotspot-inline-right">
<p>{{t 'Click anywhere on a message to reply.' }}</p> <p>{{t 'Click anywhere on a message to reply.' }}</p>

View File

@ -24,7 +24,7 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<hr> <hr />
<a href="/help/format-your-message-using-markdown" target="_blank" rel="noopener noreferrer">{{t "Detailed message formatting documentation" }}</a> <a href="/help/format-your-message-using-markdown" target="_blank" rel="noopener noreferrer">{{t "Detailed message formatting documentation" }}</a>
</div> </div>
</div> </div>

View File

@ -20,13 +20,13 @@
<input type="text" placeholder="{{topic}}" value="{{topic}}" class="message_edit_topic" id="message_edit_topic" /> <input type="text" placeholder="{{topic}}" value="{{topic}}" class="message_edit_topic" id="message_edit_topic" />
<div class="message_edit_breadcrumb_messages" style='display:none;'> <div class="message_edit_breadcrumb_messages" style='display:none;'>
<label class="checkbox"> <label class="checkbox">
<input class="send_notification_to_new_thread" name="send_notification_to_new_thread" type="checkbox" {{#if notify_new_thread}}checked="checked"{{/if}}> <input class="send_notification_to_new_thread" name="send_notification_to_new_thread" type="checkbox" {{#if notify_new_thread}}checked="checked"{{/if}} />
<span></span> <span></span>
</label> </label>
<label for="send_notification_to_new_thread">{{t "Send notification to new topic" }}</label> <label for="send_notification_to_new_thread">{{t "Send notification to new topic" }}</label>
<div class="break-row"></div> <!-- break --> <div class="break-row"></div> <!-- break -->
<label class="checkbox"> <label class="checkbox">
<input class="send_notification_to_old_thread" name="send_notification_to_old_thread" type="checkbox" {{#if notify_old_thread}}checked="checked"{{/if}}> <input class="send_notification_to_old_thread" name="send_notification_to_old_thread" type="checkbox" {{#if notify_old_thread}}checked="checked"{{/if}} />
<span></span> <span></span>
</label> </label>
<label for="send_notification_to_old_thread">{{t "Send notification to old topic" }}</label> <label for="send_notification_to_old_thread">{{t "Send notification to old topic" }}</label>
@ -70,7 +70,7 @@
<a role="button" tabindex=0 class="compose_control_button fa fa-smile-o" aria-label="{{t 'Add emoji' }}" id="emoji_map" data-message-id="{{message_id}}" title="{{t 'Add emoji' }}"></a> <a role="button" tabindex=0 class="compose_control_button fa fa-smile-o" aria-label="{{t 'Add emoji' }}" id="emoji_map" data-message-id="{{message_id}}" title="{{t 'Add emoji' }}"></a>
{{#if giphy_api_available }} {{#if giphy_api_available }}
<a role="button" class="compose_control_button" aria-label="{{t 'Add GIF' }}" id="compose_box_giphy_grid" title="{{t 'Add GIF' }}"> <a role="button" class="compose_control_button" aria-label="{{t 'Add GIF' }}" id="compose_box_giphy_grid" title="{{t 'Add GIF' }}">
<img class="compose_giphy_logo" tabindex=0 src="/static/images/GIPHY_logo.png" data-message-id="{{message_id}}"> <img class="compose_giphy_logo" tabindex=0 src="/static/images/GIPHY_logo.png" data-message-id="{{message_id}}" />
</a> </a>
{{/if}} {{/if}}
<a role="button" tabindex=0 class="message-control-link" data-overlay-trigger="message-formatting" >{{t 'Help' }}</a> <a role="button" tabindex=0 class="message-control-link" data-overlay-trigger="message-formatting" >{{t 'Help' }}</a>

View File

@ -14,5 +14,5 @@
{{/if}} {{/if}}
<div class="message_author"><div class="author_details">{{ posted_or_edited }} {{ edited_by }}</div></div> <div class="message_author"><div class="author_details">{{ posted_or_edited }} {{ edited_by }}</div></div>
</div> </div>
<hr> <hr />
{{/each}} {{/each}}

View File

@ -20,17 +20,17 @@
{{/each}} {{/each}}
</select> </select>
<i class="fa fa-angle-right" aria-hidden="true"></i> <i class="fa fa-angle-right" aria-hidden="true"></i>
<input name="new_topic_name" type="text" class="inline_topic_edit" value="{{topic_name}}"> <input name="new_topic_name" type="text" class="inline_topic_edit" value="{{topic_name}}" />
<input name="old_topic_name" type="hidden" class="inline_topic_edit" value="{{topic_name}}"> <input name="old_topic_name" type="hidden" class="inline_topic_edit" value="{{topic_name}}" />
<input name="current_stream_id" type="hidden" value="{{current_stream_id}}"> <input name="current_stream_id" type="hidden" value="{{current_stream_id}}" />
<div class="topic_move_breadcrumb_messages new-style"> <div class="topic_move_breadcrumb_messages new-style">
<label class="checkbox"> <label class="checkbox">
<input class="send_notification_to_new_thread" name="send_notification_to_new_thread" type="checkbox" {{#if notify_new_thread}}checked="checked"{{/if}}> <input class="send_notification_to_new_thread" name="send_notification_to_new_thread" type="checkbox" {{#if notify_new_thread}}checked="checked"{{/if}} />
<span></span> <span></span>
</label> </label>
<label for="send_notification_to_new_thread">{{t "Send notification to new topic" }}</label> <label for="send_notification_to_new_thread">{{t "Send notification to new topic" }}</label>
<label class="checkbox"> <label class="checkbox">
<input class="send_notification_to_old_thread" name="send_notification_to_old_thread" type="checkbox" {{#if notify_old_thread}}checked="checked"{{/if}}> <input class="send_notification_to_old_thread" name="send_notification_to_old_thread" type="checkbox" {{#if notify_old_thread}}checked="checked"{{/if}} />
<span></span> <span></span>
</label> </label>
<label for="send_notification_to_old_thread">{{t "Send notification to old topic" }}</label> <label for="send_notification_to_old_thread">{{t "Send notification to old topic" }}</label>

View File

@ -17,7 +17,7 @@
{{/each}} {{/each}}
</div> </div>
<br> <br />
<input class="add-user-list-filter" name="user_list_filter" type="text" <input class="add-user-list-filter" name="user_list_filter" type="text"
autocomplete="off" placeholder="{{t "Filter" }}" /> autocomplete="off" placeholder="{{t "Filter" }}" />

View File

@ -3,7 +3,7 @@
{{> recent_topics_filters}} {{> recent_topics_filters}}
</div> </div>
<div class="search_group" role="group"> <div class="search_group" role="group">
<input type="text" id="recent_topics_search" value="{{ search_val }}" placeholder="{{t 'Filter topics (t)' }}"> <input type="text" id="recent_topics_search" value="{{ search_val }}" placeholder="{{t 'Filter topics (t)' }}" />
<button type="button" class="btn clear_search_button" id="recent_topics_search_clear"> <button type="button" class="btn clear_search_button" id="recent_topics_search_clear">
<i class="fa fa-remove" aria-hidden="true"></i> <i class="fa fa-remove" aria-hidden="true"></i>
</button> </button>

View File

@ -184,7 +184,7 @@
</div> </div>
</div> </div>
<hr class="settings_separator"> <hr class="settings_separator" />
<div class="form-horizontal" id="api_key_button_box"> <div class="form-horizontal" id="api_key_button_box">
<h3>{{t "API key" }}</h3> <h3>{{t "API key" }}</h3>

View File

@ -11,7 +11,7 @@
<div class="settings-section-title">{{t "Add new default stream" }}</div> <div class="settings-section-title">{{t "Add new default stream" }}</div>
<div class="inline-block" id="default_stream_inputs"> <div class="inline-block" id="default_stream_inputs">
<label for="default_stream_name">{{t "Stream name" }}</label> <label for="default_stream_name">{{t "Stream name" }}</label>
<input class="create_default_stream" type="text" placeholder="{{t "Stream name" }}" name="stream_name" autocomplete="off" aria-label="{{t "Stream name" }}"> <input class="create_default_stream" type="text" placeholder="{{t "Stream name" }}" name="stream_name" autocomplete="off" aria-label="{{t "Stream name" }}" />
</div> </div>
<div class="inline-block"> <div class="inline-block">
<button type="submit" id="do_submit_stream" class="button rounded sea-green">{{t "Add stream" }}</button> <button type="submit" id="do_submit_stream" class="button rounded sea-green">{{t "Add stream" }}</button>

View File

@ -11,7 +11,7 @@
</p> </p>
</li> </li>
<hr> <hr />
{{! tabindex="0" Makes anchor tag focusable. Needed for keyboard support. }} {{! tabindex="0" Makes anchor tag focusable. Needed for keyboard support. }}
<li> <li>

View File

@ -4,7 +4,7 @@
{{/if}} {{/if}}
<a data-user-id="{{user_id}}" class="view_user_profile">{{full_name}}</a>{{#unless @last}},{{else}}.{{/unless}} <a data-user-id="{{user_id}}" class="view_user_profile">{{full_name}}</a>{{#unless @last}},{{else}}.{{/unless}}
{{/each}} {{/each}}
<br> <br />
{{#each already_subscribed_users}} {{#each already_subscribed_users}}
{{#if @first}} {{#if @first}}
{{t "Already subscribed users:" }} {{t "Already subscribed users:" }}

View File

@ -6,7 +6,7 @@
</p> </p>
</li> </li>
<hr> <hr />
{{#if can_mute_topic}} {{#if can_mute_topic}}
<li> <li>

View File

@ -5,7 +5,7 @@
{{group_description}} {{group_description}}
</div> </div>
</div> </div>
<hr> <hr />
<ul class="nav nav-list member-list" data-simplebar data-simplebar-auto-hide="false"> <ul class="nav nav-list member-list" data-simplebar data-simplebar-auto-hide="false">
{{#each members}} {{#each members}}
<li> <li>
@ -20,7 +20,7 @@
</li> </li>
{{/each}} {{/each}}
</ul> </ul>
<hr> <hr />
<ul class="nav nav-list manage-group"> <ul class="nav nav-list manage-group">
<li> <li>
<a href="#organization/user-groups-admin"> <a href="#organization/user-groups-admin">

View File

@ -52,7 +52,7 @@
</div> </div>
{{#if is_me}} {{#if is_me}}
<hr> <hr />
{{#if can_set_away}} {{#if can_set_away}}
<li> <li>
<a tabindex="0" class="set_away_status"> <a tabindex="0" class="set_away_status">
@ -80,7 +80,7 @@
{{/if}} {{/if}}
{{#if status_text}} {{#if status_text}}
{{#unless is_me}}<hr>{{/unless}} {{#unless is_me}}<hr />{{/unless}}
<li class="user_info_status_text"> <li class="user_info_status_text">
<span id="status_message"> <span id="status_message">
{{status_text}} {{status_text}}
@ -89,7 +89,7 @@
</li> </li>
{{/if}} {{/if}}
<hr> <hr />
{{#if show_user_profile}} {{#if show_user_profile}}
<li> <li>
<a tabindex="0" class="view_full_user_profile"> <a tabindex="0" class="view_full_user_profile">
@ -134,7 +134,7 @@
</a> </a>
</li> </li>
{{/if}} {{/if}}
<hr> <hr />
<li> <li>
<a href="{{ pm_with_uri }}" class="narrow_to_private_messages"> <a href="{{ pm_with_uri }}" class="narrow_to_private_messages">
<i class="fa fa-lock" aria-hidden="true"></i> <i class="fa fa-lock" aria-hidden="true"></i>

View File

@ -14,7 +14,7 @@
</a> </a>
{{/if}} {{/if}}
</div> </div>
<br> <br />
{{#if show_email}} {{#if show_email}}
<div id="email" class="default-field"> <div id="email" class="default-field">
<span class="name">{{#tr}}Email{{/tr}}</span> <span class="name">{{#tr}}Email{{/tr}}</span>
@ -40,7 +40,7 @@
</div> </div>
{{/if}} {{/if}}
</div> </div>
<hr> <hr />
<div id="content"> <div id="content">
{{#each profile_data}} {{#each profile_data}}
<div data-type="{{this.type}}" class="field-section custom_user_field" data-field-id="{{this.id}}"> <div data-type="{{this.type}}" class="field-section custom_user_field" data-field-id="{{this.id}}">

View File

@ -18,5 +18,5 @@
<input type="text" class="poll-option" placeholder="{{t 'New choice'}}" /> <input type="text" class="poll-option" placeholder="{{t 'New choice'}}" />
<button class="poll-option">{{t "Add choice" }}</button> <button class="poll-option">{{t "Add choice" }}</button>
</div> </div>
<br> <br />
</div> </div>

View File

@ -1,4 +1,4 @@
<br> <br />
{{#each pending_tasks}} {{#each pending_tasks}}
<li> <li>
<button class="task" data-key="{{ key }}"> <button class="task" data-key="{{ key }}">

View File

@ -2,7 +2,7 @@
{% block customhead %} {% block customhead %}
{{ super() }} {{ super() }}
<meta http-equiv="refresh" content="60;URL='/'"> <meta http-equiv="refresh" content="60;URL='/'" />
{% endblock %} {% endblock %}
{% block portico_class_name %}error{% endblock %} {% block portico_class_name %}error{% endblock %}

View File

@ -12,13 +12,13 @@
{% if not is_home %} {% if not is_home %}
<a class="show-all" href="/activity">Home</a> <a class="show-all" href="/activity">Home</a>
<br> <br />
{% endif %} {% endif %}
<h4>{{ title }}</h4> <h4>{{ title }}</h4>
{% if realm_link %} {% if realm_link %}
<a href="{{ realm_link }}">Graph</a><br> <a href="{{ realm_link }}">Graph</a><br />
{% endif %} {% endif %}
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">

View File

@ -1,9 +1,9 @@
<span class="label">realm</span> <span class="label">realm</span>
<h3><img src="{{ realm_icon_url(realm) }}" class="support-realm-icon"> {{ realm.name }}</h3> <h3><img src="{{ realm_icon_url(realm) }}" class="support-realm-icon" /> {{ realm.name }}</h3>
<b>URL</b>: <a target="_blank" rel="noopener noreferrer" href="{{ realm.uri }}">{{ realm.uri }}</a> | <b>URL</b>: <a target="_blank" rel="noopener noreferrer" href="{{ realm.uri }}">{{ realm.uri }}</a> |
<a target="_blank" rel="noopener noreferrer" href="/stats/realm/{{ realm.string_id }}/">stats</a> | <a target="_blank" rel="noopener noreferrer" href="/stats/realm/{{ realm.string_id }}/">stats</a> |
<a target="_blank" rel="noopener noreferrer" href="/realm_activity/{{ realm.string_id }}/">activity</a><br> <a target="_blank" rel="noopener noreferrer" href="/realm_activity/{{ realm.string_id }}/">activity</a><br />
<b>Date created</b>: {{ realm.date_created|timesince }} ago<br> <b>Date created</b>: {{ realm.date_created|timesince }} ago<br />
{% set owner_emails_string = get_realm_owner_emails_as_string(realm) %} {% set owner_emails_string = get_realm_owner_emails_as_string(realm) %}
<b>Owners</b>: {{ owner_emails_string }} <b>Owners</b>: {{ owner_emails_string }}
{% if owner_emails_string %} {% if owner_emails_string %}
@ -11,7 +11,7 @@
<i class="fa fa-copy"></i> <i class="fa fa-copy"></i>
</a> </a>
{% endif %} {% endif %}
<br> <br />
{% set admin_emails_string = get_realm_admin_emails_as_string(realm) %} {% set admin_emails_string = get_realm_admin_emails_as_string(realm) %}
<b>Admins</b>: {{ admin_emails_string }} <b>Admins</b>: {{ admin_emails_string }}
{% if admin_emails_string %} {% if admin_emails_string %}
@ -19,7 +19,7 @@
<i class="fa fa-copy"></i> <i class="fa fa-copy"></i>
</a> </a>
{% endif %} {% endif %}
<br> <br />
{% set first_human_user = realm.get_first_human_user() %} {% set first_human_user = realm.get_first_human_user() %}
{% if first_human_user %} {% if first_human_user %}
<b>First human user</b>: {{ first_human_user.delivery_email }} <b>First human user</b>: {{ first_human_user.delivery_email }}
@ -31,7 +31,7 @@
{% endif %} {% endif %}
<form method="POST" class="support-realm-status-form"> <form method="POST" class="support-realm-status-form">
<b>Status</b>:<br> <b>Status</b>:<br />
{{ csrf_input }} {{ csrf_input }}
<input type="hidden" name="realm_id" value="{{ realm.id }}" /> <input type="hidden" name="realm_id" value="{{ realm.id }}" />
<select name="status"> <select name="status">
@ -41,14 +41,14 @@
<button type="submit" class="button rounded small support-submit-button">Update</button> <button type="submit" class="button rounded small support-submit-button">Update</button>
</form> </form>
<form method="POST"> <form method="POST">
<b>New subdomain</b>:<br> <b>New subdomain</b>:<br />
{{ csrf_input }} {{ csrf_input }}
<input type="hidden" name="realm_id" value="{{ realm.id }}" /> <input type="hidden" name="realm_id" value="{{ realm.id }}" />
<input type="text" name="new_subdomain" required> <input type="text" name="new_subdomain" required />
<button type="submit" class="button rounded small support-submit-button">Update</button> <button type="submit" class="button rounded small support-submit-button">Update</button>
</form> </form>
<form method="POST" class="support-plan-type-form"> <form method="POST" class="support-plan-type-form">
<b>Plan type</b>:<br> <b>Plan type</b>:<br />
{{ csrf_input }} {{ csrf_input }}
<input type="hidden" name="realm_id" value="{{ realm.id }}" /> <input type="hidden" name="realm_id" value="{{ realm.id }}" />
<select name="plan_type"> <select name="plan_type">
@ -60,7 +60,7 @@
<button type="submit" class="button rounded small support-submit-button">Update</button> <button type="submit" class="button rounded small support-submit-button">Update</button>
</form> </form>
<form method="POST" class="sponsorship-pending-form"> <form method="POST" class="sponsorship-pending-form">
<b>Sponsorship pending</b>:<br> <b>Sponsorship pending</b>:<br />
{{ csrf_input }} {{ csrf_input }}
<input type="hidden" name="realm_id" value="{{ realm.id }}" /> <input type="hidden" name="realm_id" value="{{ realm.id }}" />
<select name="sponsorship_pending"> <select name="sponsorship_pending">
@ -83,35 +83,35 @@
{% endif %} {% endif %}
<form method="POST" class="support-discount-form"> <form method="POST" class="support-discount-form">
<b>Discount (use 85 for nonprofits)</b>:<br> <b>Discount (use 85 for nonprofits)</b>:<br />
{{ csrf_input }} {{ csrf_input }}
<input type="hidden" name="realm_id" value="{{ realm.id }}" /> <input type="hidden" name="realm_id" value="{{ realm.id }}" />
{% if realm.current_plan and realm.current_plan.fixed_price %} {% if realm.current_plan and realm.current_plan.fixed_price %}
<input type="number" name="discount" value="{{ get_discount_for_realm(realm) }}" disabled> <input type="number" name="discount" value="{{ get_discount_for_realm(realm) }}" disabled />
<button type="submit" class="button rounded small support-submit-button" disabled>Update</button> <button type="submit" class="button rounded small support-submit-button" disabled>Update</button>
{% else %} {% else %}
<input type="number" name="discount" value="{{ get_discount_for_realm(realm) }}" required> <input type="number" name="discount" value="{{ get_discount_for_realm(realm) }}" required />
<button type="submit" class="button rounded small support-submit-button">Update</button> <button type="submit" class="button rounded small support-submit-button">Update</button>
{% endif %} {% endif %}
</form> </form>
{% if realm.current_plan %} {% if realm.current_plan %}
<div class="current-plan-details"> <div class="current-plan-details">
<h3>📅 Current plan</h3> <h3>📅 Current plan</h3>
<b>Name</b>: {{ realm.current_plan.name }}<br> <b>Name</b>: {{ realm.current_plan.name }}<br />
<b>Status</b>: {{realm.current_plan.get_plan_status_as_text()}}<br> <b>Status</b>: {{realm.current_plan.get_plan_status_as_text()}}<br />
<b>Billing schedule</b>: {% if realm.current_plan.billing_schedule == realm.current_plan.ANNUAL %}Annual{% else %}Monthly{% endif %}<br> <b>Billing schedule</b>: {% if realm.current_plan.billing_schedule == realm.current_plan.ANNUAL %}Annual{% else %}Monthly{% endif %}<br />
<b>Licenses</b>: {{ realm.current_plan.licenses_used }}/{{ realm.current_plan.licenses }} ({% if realm.current_plan.automanage_licenses %}Automatic{% else %}Manual{% endif %})<br> <b>Licenses</b>: {{ realm.current_plan.licenses_used }}/{{ realm.current_plan.licenses }} ({% if realm.current_plan.automanage_licenses %}Automatic{% else %}Manual{% endif %})<br />
{% if realm.current_plan.price_per_license %} {% if realm.current_plan.price_per_license %}
<b>Price per license</b>: ${{ realm.current_plan.price_per_license/100 }}<br> <b>Price per license</b>: ${{ realm.current_plan.price_per_license/100 }}<br />
{% else %} {% else %}
<b>Fixed price</b>: ${{ realm.current_plan.fixed_price/100 }}<br> <b>Fixed price</b>: ${{ realm.current_plan.fixed_price/100 }}<br />
{% endif %} {% endif %}
<b>Next invoice date</b>: {{ realm.current_plan.next_invoice_date.strftime('%d %B %Y') }}<br> <b>Next invoice date</b>: {{ realm.current_plan.next_invoice_date.strftime('%d %B %Y') }}<br />
</div> </div>
<form method="POST" class="billing-method-form"> <form method="POST" class="billing-method-form">
<br> <br />
<b>Billing method</b><br> <b>Billing method</b><br />
{{ csrf_input }} {{ csrf_input }}
<input type="hidden" name="realm_id" value="{{ realm.id }}" /> <input type="hidden" name="realm_id" value="{{ realm.id }}" />
<select name="billing_method" class="billing-method-select" required> <select name="billing_method" class="billing-method-select" required>
@ -122,8 +122,8 @@
</form> </form>
<form method="POST" class="downgrade-plan-form"> <form method="POST" class="downgrade-plan-form">
<br> <br />
<b>Downgrade plan</b><br> <b>Downgrade plan</b><br />
{{ csrf_input }} {{ csrf_input }}
<input type="hidden" name="realm_id" value="{{ realm.id }}" /> <input type="hidden" name="realm_id" value="{{ realm.id }}" />
<select name="downgrade_method" class="downgrade-plan-method-select" required> <select name="downgrade_method" class="downgrade-plan-method-select" required>

View File

@ -125,7 +125,7 @@
<span class="last_update_tooltip" data-tippy-content="{% trans %}A full update of all the graphs happens once a day. The “messages sent over time” graph is updated once an hour.{% endtrans %}"> <span class="last_update_tooltip" data-tippy-content="{% trans %}A full update of all the graphs happens once a day. The “messages sent over time” graph is updated once an hour.{% endtrans %}">
<span class="fa fa-info-circle" id="id_last_update_question_sign"></span> <span class="fa fa-info-circle" id="id_last_update_question_sign"></span>
</span> </span>
<br> <br />
<span class="docs_link"><a href="/help/analytics">{{ _("Analytics documentation") }}</a></span> <span class="docs_link"><a href="/help/analytics">{{ _("Analytics documentation") }}</a></span>
</div> </div>
</div> </div>

View File

@ -9,10 +9,10 @@
{% block content %} {% block content %}
<div class="container"> <div class="container">
<br> <br />
<form class="new-style"> <form class="new-style">
<center> <center>
<input type="text" name="q" class="input-xxlarge search-query" placeholder="full names, emails, string_ids, organization URLs separated by commas" value="{{ request.GET.get('q', '') }}" autofocus> <input type="text" name="q" class="input-xxlarge search-query" placeholder="full names, emails, string_ids, organization URLs separated by commas" value="{{ request.GET.get('q', '') }}" autofocus />
<button type="submit" class="button small support-search-button">Search</button> <button type="submit" class="button small support-search-button">Search</button>
</center> </center>
</form> </form>
@ -37,15 +37,15 @@
<div class="support-query-result new-style"> <div class="support-query-result new-style">
<span class="label">user</span> <span class="label">user</span>
<h3>{{ user.full_name }}</h3> <h3>{{ user.full_name }}</h3>
<b>Email</b>: {{ user.delivery_email }}<br> <b>Email</b>: {{ user.delivery_email }}<br />
<b>Date joined</b>: {{ user.date_joined|timesince }} ago<br> <b>Date joined</b>: {{ user.date_joined|timesince }} ago<br />
<b>Is active</b>: {{ user.is_active }}<br> <b>Is active</b>: {{ user.is_active }}<br />
<b>Role</b>: {{ user.get_role_name() }}<br> <b>Role</b>: {{ user.get_role_name() }}<br />
<hr> <hr />
<div> <div>
{% include "analytics/realm_details.html" %} {% include "analytics/realm_details.html" %}
</div> </div>
<hr> <hr />
</div> </div>
{% endfor %} {% endfor %}
@ -83,32 +83,32 @@
{% set realm = object %} {% set realm = object %}
{% set show_realm_details = False %} {% set show_realm_details = False %}
{% endif %} {% endif %}
<br> <br />
<br> <br />
{% if email %} {% if email %}
<b>Email</b>: {{ email }}<br> <b>Email</b>: {{ email }}<br />
{% endif %} {% endif %}
<b>Link</b>: {{ confirmation.url }} <b>Link</b>: {{ confirmation.url }}
<a title="Copy link" class="copy-button" data-copytext="{{ confirmation.url }}"> <a title="Copy link" class="copy-button" data-copytext="{{ confirmation.url }}">
<i class="fa fa-copy"></i> <i class="fa fa-copy"></i>
</a><br> </a><br />
<b>Expires in</b>: {{ confirmation.expires_in }}<br> <b>Expires in</b>: {{ confirmation.expires_in }}<br />
{% if confirmation.link_status %} {% if confirmation.link_status %}
<b>Status</b>: {{ confirmation.link_status }} <b>Status</b>: {{ confirmation.link_status }}
{% endif %} {% endif %}
<br> <br />
{% if show_realm_details %} {% if show_realm_details %}
<hr> <hr />
<div> <div>
{% include "analytics/realm_details.html" %} {% include "analytics/realm_details.html" %}
</div> </div>
{% elif realm %} {% elif realm %}
<b>Realm</b>: {{ realm.string_id }} <b>Realm</b>: {{ realm.string_id }}
<br> <br />
{% endif %} {% endif %}
<br> <br />
</div> </div>
<br> <br />
{% endfor %} {% endfor %}
</div> </div>
</div> </div>

View File

@ -2,7 +2,7 @@
{% set entrypoint = "billing" %} {% set entrypoint = "billing" %}
{% block customhead %} {% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://checkout.stripe.com/checkout.js"></script> <script src="https://checkout.stripe.com/checkout.js"></script>
{% endblock %} {% endblock %}
@ -26,7 +26,7 @@
<li><a data-toggle="tab" href="#settings">Settings</a></li> <li><a data-toggle="tab" href="#settings">Settings</a></li>
</ul> </ul>
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}"> <input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}" />
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane active" id="overview"> <div class="tab-pane active" id="overview">
{% if free_trial %} {% if free_trial %}
@ -133,13 +133,13 @@
</div> </div>
<div id="goto-zulip-organization-link"> <div id="goto-zulip-organization-link">
{% if onboarding %} {% if onboarding %}
<br> <br />
<h3> <h3>
<b><a href="/">Go to your Zulip organization</a></b> <b><a href="/">Go to your Zulip organization</a></b>
</h3> </h3>
{% endif %} {% endif %}
</div> </div>
<hr> <hr />
<div class="support-link"> <div class="support-link">
<p> <p>
Contact <a href="mailto:support@zulip.com">support@zulip.com</a> Contact <a href="mailto:support@zulip.com">support@zulip.com</a>

View File

@ -6,7 +6,7 @@
{% endblock %} {% endblock %}
{% block customhead %} {% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% endblock %} {% endblock %}
{% block portico_content %} {% block portico_content %}
@ -176,8 +176,8 @@
</li> </li>
</ul> </ul>
<br> <br />
<hr> <hr />
<h2 id="fullstack">Senior Full Stack Engineer</h2> <h2 id="fullstack">Senior Full Stack Engineer</h2>
<p> <p>
@ -245,8 +245,8 @@
</li> </li>
</ul> </ul>
<br> <br />
<hr> <hr />
<h2>How to apply for a job</h2> <h2>How to apply for a job</h2>
<p> <p>

View File

@ -2,7 +2,7 @@
{% set entrypoint = "upgrade" %} {% set entrypoint = "upgrade" %}
{% block customhead %} {% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://checkout.stripe.com/checkout.js"></script> <script src="https://checkout.stripe.com/checkout.js"></script>
{% endblock %} {% endblock %}
@ -37,18 +37,18 @@
<li><a data-toggle="tab" href="#sponsorship">💚 Request sponsorship</a></li> <li><a data-toggle="tab" href="#sponsorship">💚 Request sponsorship</a></li>
</ul> </ul>
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}"> <input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}" />
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane active" id="autopay"> <div class="tab-pane active" id="autopay">
<div id="autopay-input-section"> <div id="autopay-input-section">
<form id="autopay-form" data-key="{{ publishable_key }}" data-email="{{ email }}" method="post"> <form id="autopay-form" data-key="{{ publishable_key }}" data-email="{{ email }}" method="post">
<input type="hidden" name="seat_count" value="{{ seat_count }}"> <input type="hidden" name="seat_count" value="{{ seat_count }}" />
<input type="hidden" name="signed_seat_count" value="{{ signed_seat_count }}"> <input type="hidden" name="signed_seat_count" value="{{ signed_seat_count }}" />
<input type="hidden" name="salt" value="{{ salt }}"> <input type="hidden" name="salt" value="{{ salt }}" />
<input type="hidden" name="billing_modality" value="charge_automatically"> <input type="hidden" name="billing_modality" value="charge_automatically" />
{% if onboarding and free_trial_days %} {% if onboarding and free_trial_days %}
<p><b>Not ready to start your trial?</b> <a href="/">Continue with the Zulip Cloud Free plan</a>.</p> <p><b>Not ready to start your trial?</b> <a href="/">Continue with the Zulip Cloud Free plan</a>.</p>
<hr> <hr />
<h2>Zulip Standard free trial</h2> <h2>Zulip Standard free trial</h2>
{% endif %} {% endif %}
<div class="payment-schedule"> <div class="payment-schedule">
@ -104,7 +104,7 @@
{% if free_trial_days %} {% if free_trial_days %}
After the Free Trial, you&rsquo;ll be charged After the Free Trial, you&rsquo;ll be charged
<b>$<span id="charged_amount"></span></b> for <b>{{ seat_count }}</b> <b>$<span id="charged_amount"></span></b> for <b>{{ seat_count }}</b>
users (or more if you later add more users).<br> users (or more if you later add more users).<br />
We'll automatically charge you for additional licenses as users We'll automatically charge you for additional licenses as users
are added, and remove licenses not in use at the end of each billing are added, and remove licenses not in use at the end of each billing
@ -112,31 +112,31 @@
{% else %} {% else %}
You&rsquo;ll initially be charged You&rsquo;ll initially be charged
<b>$<span id="charged_amount"></span></b> for <b>{{ seat_count }}</b> <b>$<span id="charged_amount"></span></b> for <b>{{ seat_count }}</b>
users.<br> users.<br />
We'll automatically charge you for additional licenses as users We'll automatically charge you for additional licenses as users
are added, and remove licenses not in use at the end of each billing are added, and remove licenses not in use at the end of each billing
period. period.
{% endif %} {% endif %}
</p> </p>
<input type="hidden" name="licenses" id="automatic_license_count" value="{{ seat_count }}"> <input type="hidden" name="licenses" id="automatic_license_count" value="{{ seat_count }}" />
</div> </div>
<div id="license-manual-section"> <div id="license-manual-section">
<p> <p>
{% if free_trial_days %} {% if free_trial_days %}
Enter the number of users you would like to pay for after the Free Trial.<br> Enter the number of users you would like to pay for after the Free Trial.<br />
You'll need to manually add licenses to add or invite You'll need to manually add licenses to add or invite
additional users. additional users.
{% else %} {% else %}
Enter the number of users you would like to pay for.<br> Enter the number of users you would like to pay for.<br />
You'll need to manually add licenses to add or invite You'll need to manually add licenses to add or invite
additional users. additional users.
{% endif %} {% endif %}
</p> </p>
<h4>Number of licenses (minimum {{ seat_count }})</h4> <h4>Number of licenses (minimum {{ seat_count }})</h4>
<input type="number" name="licenses" min="{{ seat_count }}" autocomplete="off" id="manual_license_count" required/><br> <input type="number" name="licenses" min="{{ seat_count }}" autocomplete="off" id="manual_license_count" required/><br />
</div> </div>
<button id="add-card-button" class="stripe-button-el"> <button id="add-card-button" class="stripe-button-el">
@ -164,12 +164,12 @@
<form id="invoice-form" method="post"> <form id="invoice-form" method="post">
{% if onboarding and free_trial_days %} {% if onboarding and free_trial_days %}
<p><b>Not ready to start your trial?</b> <a href="/">Continue with the Zulip Cloud Free plan</a>.</p> <p><b>Not ready to start your trial?</b> <a href="/">Continue with the Zulip Cloud Free plan</a>.</p>
<hr> <hr />
<h2>Zulip Standard free trial</h2> <h2>Zulip Standard free trial</h2>
{% endif %} {% endif %}
<input type="hidden" name="signed_seat_count" value="{{ signed_seat_count }}"> <input type="hidden" name="signed_seat_count" value="{{ signed_seat_count }}" />
<input type="hidden" name="salt" value="{{ salt }}"> <input type="hidden" name="salt" value="{{ salt }}" />
<input type="hidden" name="billing_modality" value="send_invoice"> <input type="hidden" name="billing_modality" value="send_invoice" />
<div class="payment-schedule"> <div class="payment-schedule">
<h3>{{ _("Payment schedule") }}</h3> <h3>{{ _("Payment schedule") }}</h3>
<label> <label>
@ -187,18 +187,18 @@
</div> </div>
<p> <p>
{% if free_trial_days %} {% if free_trial_days %}
Enter the number of users you would like to pay for.<br> Enter the number of users you would like to pay for.<br />
We'll email you an invoice after the free trial. We'll email you an invoice after the free trial.
Invoices can be paid by ACH transfer or credit card. Invoices can be paid by ACH transfer or credit card.
{% else %} {% else %}
Enter the number of users you would like to pay for.<br> Enter the number of users you would like to pay for.<br />
We'll email you an invoice in 1-2 hours. Invoices can be paid by We'll email you an invoice in 1-2 hours. Invoices can be paid by
ACH transfer or credit card. ACH transfer or credit card.
{% endif %} {% endif %}
</p> </p>
<h4>Number of licenses (minimum {{ min_invoiced_licenses }})</h4> <h4>Number of licenses (minimum {{ min_invoiced_licenses }})</h4>
<input type="number" min="{{ min_invoiced_licenses }}" autocomplete="off" <input type="number" min="{{ min_invoiced_licenses }}" autocomplete="off"
id="invoiced_licenses" name="licenses" required/><br> id="invoiced_licenses" name="licenses" required/><br />
<button type="submit" id="invoice-button" class="stripe-button-el invoice-button">Buy Standard</button> <button type="submit" id="invoice-button" class="stripe-button-el invoice-button">Buy Standard</button>
</form> </form>
</div> </div>
@ -232,7 +232,7 @@
<option value="event">{{_('Event (hackathons, conferences, etc.)')}}</option> <option value="event">{{_('Event (hackathons, conferences, etc.)')}}</option>
<option value="other">{{_('Other')}}</option> <option value="other">{{_('Other')}}</option>
</select> </select>
<br> <br />
<label> <label>
<h4>Organization website</h4> <h4>Organization website</h4>
</label> </label>
@ -241,7 +241,7 @@
<h4>Describe your organization briefly</h4> <h4>Describe your organization briefly</h4>
</label> </label>
<textarea name="description" style="width: 100%;" cols="100" rows="5" required></textarea> <textarea name="description" style="width: 100%;" cols="100" rows="5" required></textarea>
<br> <br />
<p id="sponsorship-discount-details"></p> <p id="sponsorship-discount-details"></p>
<button type="submit" id="sponsorship-button" class="stripe-button-el invoice-button">Submit</button> <button type="submit" id="sponsorship-button" class="stripe-button-el invoice-button">Submit</button>
</form> </form>

View File

@ -6,7 +6,7 @@
{% endblock %} {% endblock %}
{% block customhead %} {% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% endblock %} {% endblock %}
{% block portico_content %} {% block portico_content %}
@ -38,7 +38,7 @@
<li><p>Get your Zulip API key from the Zulip "Settings" panel and put it in a file in your <li><p>Get your Zulip API key from the Zulip "Settings" panel and put it in a file in your
Athena home directory called <code>~/Private/.zulip-api-key</code>.</p></li> Athena home directory called <code>~/Private/.zulip-api-key</code>.</p></li>
<li><p>Run the following command to copy over all of your subscriptions:<br> <li><p>Run the following command to copy over all of your subscriptions:<br />
<code>/mit/tabbott/zulip/zephyr_mirror.py --sync-subscriptions</code></p> <code>/mit/tabbott/zulip/zephyr_mirror.py --sync-subscriptions</code></p>
<p> <strong>NOTE</strong>: Zulip supports several ways to control what messages you want to read <p> <strong>NOTE</strong>: Zulip supports several ways to control what messages you want to read

View File

@ -48,10 +48,10 @@ the registration flow has its own (nearly identical) copy of the fields below in
{% endfor %} {% endfor %}
{% endif %} {% endif %}
</div> </div>
<br> <br />
<div class="control-group"> <div class="control-group">
<div class="controls"> <div class="controls">
<input type="submit" class="btn btn-primary" value="{{ _('Enter') }}" /><br> <input type="submit" class="btn btn-primary" value="{{ _('Enter') }}" /><br />
<input type="hidden" name="next" value="{{ next }}" /> <input type="hidden" name="next" value="{{ next }}" />
</div> </div>
</div> </div>

View File

@ -29,7 +29,7 @@ page can be easily identified in it's respective JavaScript file -->
</div> </div>
<div class="invite-required"> <div class="invite-required">
<hr> <hr />
<i class="fa fa-lock"></i>{{ _("You need an invitation to join this organization.") }} <i class="fa fa-lock"></i>{{ _("You need an invitation to join this organization.") }}
</div> </div>
</div> </div>

View File

@ -159,6 +159,6 @@
<div id="compose" {% if embedded %}data-embedded{% endif %}> <div id="compose" {% if embedded %}data-embedded{% endif %}>
</div> </div>
<audio id="notification-sound-audio"> <audio id="notification-sound-audio">
<source id="notification-sound-source-ogg" type="audio/ogg"> <source id="notification-sound-source-ogg" type="audio/ogg" />
<source id="notification-sound-source-mp3" type="audio/mpeg"> <source id="notification-sound-source-mp3" type="audio/mpeg" />
</audio> </audio>

View File

@ -4,12 +4,12 @@
{# Includes some other templates as tabs. #} {# Includes some other templates as tabs. #}
{% block meta_viewport %} {% block meta_viewport %}
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
{% endblock %} {% endblock %}
{% block customhead %} {% block customhead %}
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes" />
<link href="/static/images/logo/apple-touch-icon-precomposed.png" rel="apple-touch-icon-precomposed"> <link href="/static/images/logo/apple-touch-icon-precomposed.png" rel="apple-touch-icon-precomposed" />
<style> <style>
#app-loading { #app-loading {
background-color: hsl(0, 0%, 100%); background-color: hsl(0, 0%, 100%);

View File

@ -328,7 +328,7 @@
</tr> </tr>
</table> </table>
</div> </div>
<hr> <hr />
<a href="/help/keyboard-shortcuts" target="_blank" rel="noopener noreferrer">{% trans %}Detailed keyboard shortcuts documentation{% endtrans %}</a> <a href="/help/keyboard-shortcuts" target="_blank" rel="noopener noreferrer">{% trans %}Detailed keyboard shortcuts documentation{% endtrans %}</a>
</div> </div>
</div> </div>

View File

@ -112,7 +112,7 @@
<span class="operator_value">{{ placeholder_keyword }}</span>. <span class="operator_value">{{ placeholder_keyword }}</span>.
{% endtrans %} {% endtrans %}
</p> </p>
<hr> <hr />
<a href="/help/search-for-messages#search-operators" target="_blank" rel="noopener noreferrer">{% trans %}Detailed search operators documentation{% endtrans %}</a> <a href="/help/search-for-messages#search-operators" target="_blank" rel="noopener noreferrer">{% trans %}Detailed search operators documentation{% endtrans %}</a>
</div> </div>
</div> </div>

View File

@ -2,7 +2,7 @@
{% set entrypoint = "landing-page" %} {% set entrypoint = "landing-page" %}
{% block customhead %} {% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style media="screen"> <style media="screen">
.app.portico-page { padding-bottom: 0px; } .app.portico-page { padding-bottom: 0px; }
</style> </style>

View File

@ -4,7 +4,7 @@
{# Base template for the whole site. #} {# Base template for the whole site. #}
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
{% block title %} {% block title %}
{% if user_profile and user_profile.realm.name %} {% if user_profile and user_profile.realm.name %}
<title>{{user_profile.realm.name}} - Zulip</title> <title>{{user_profile.realm.name}} - Zulip</title>
@ -16,10 +16,10 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}
<link id="favicon" rel="icon" href="/static/images/favicon.svg?v=4"> <link id="favicon" rel="icon" href="/static/images/favicon.svg?v=4" />
<link rel="alternate icon" href="/static/images/favicon.png?v=4"> <link rel="alternate icon" href="/static/images/favicon.png?v=4" />
{% block meta_viewport %} {% block meta_viewport %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% endblock %} {% endblock %}
{% if not user_profile %} {% if not user_profile %}
{% include 'zerver/meta_tags.html' %} {% include 'zerver/meta_tags.html' %}

View File

@ -9,7 +9,7 @@
<div class="errorbox config-error"> <div class="errorbox config-error">
<div class="errorcontent"> <div class="errorcontent">
<h1 class="lead">Configuration error</h1> <h1 class="lead">Configuration error</h1>
<br> <br />
{% if error_name == "ldap_error_realm_is_none" %} {% if error_name == "ldap_error_realm_is_none" %}
{% trans %} {% trans %}
You are trying to log in using LDAP without creating an You are trying to log in using LDAP without creating an

View File

@ -26,14 +26,14 @@
{{ _("Log in with another account") }} {{ _("Log in with another account") }}
</button> </button>
</form> </form>
<br> <br />
<form class="form-inline" id="send_confirm" name="send_confirm" <form class="form-inline" id="send_confirm" name="send_confirm"
action="{{ continue_link }}" method="get"> action="{{ continue_link }}" method="get">
<button class="outline"> <button class="outline">
{{ _("Continue to registration") }} {{ _("Continue to registration") }}
</button> </button>
{% if full_name %} {% if full_name %}
<input type="hidden" name="full_name" value="{{ full_name }}"> <input type="hidden" name="full_name" value="{{ full_name }}" />
{% endif %} {% endif %}
</form> </form>
</div> </div>

View File

@ -2,7 +2,7 @@
{% block customhead %} {% block customhead %}
{{ super() }} {{ super() }}
<meta http-equiv="refresh" content="60;URL='/'"> <meta http-equiv="refresh" content="60;URL='/'" />
{% endblock %} {% endblock %}
{% block portico_content %} {% block portico_content %}

View File

@ -4,7 +4,7 @@
{% block content %} {% block content %}
<div class="flex new-style"> <div class="flex new-style">
<div class="desktop-redirect-box white-box"> <div class="desktop-redirect-box white-box">
<img class="avatar desktop-redirect-image" src="{{ realm_icon_url }}" alt=""/><br> <img class="avatar desktop-redirect-image" src="{{ realm_icon_url }}" alt=""/><br />
<p class="copy-token-info">{% trans %}Copy this login token and return to your Zulip app to finish logging in:{% endtrans %}</p> <p class="copy-token-info">{% trans %}Copy this login token and return to your Zulip app to finish logging in:{% endtrans %}</p>
<p> <p>
<span class="input-box"> <span class="input-box">

View File

@ -41,14 +41,14 @@
</tr> </tr>
<tr> <tr>
<td><a href="/emails">/emails</a></td> <td><a href="/emails">/emails</a></td>
<td><code>./scripts/setup/inline_email_css.py</code><br> <td><code>./scripts/setup/inline_email_css.py</code><br />
Run the command if you made changes to source.html email templates. Run the command if you made changes to source.html email templates.
</td> </td>
<td>View outgoing and example emails.</td> <td>View outgoing and example emails.</td>
</tr> </tr>
<tr> <tr>
<td><a href="/stats/realm/analytics/">/stats/realm/analytics/</a></td> <td><a href="/stats/realm/analytics/">/stats/realm/analytics/</a></td>
<td><code>./manage.py populate_analytics_db</code><br> <td><code>./manage.py populate_analytics_db</code><br />
Run the command after changing analytics data population logic. Run the command after changing analytics data population logic.
</td> </td>
<td>View the /stats page with some pre-populated data</td> <td>View the /stats page with some pre-populated data</td>

View File

@ -46,7 +46,7 @@
<label for="forward_address"><strong>Address to which emails should be forwarded</strong></label> <label for="forward_address"><strong>Address to which emails should be forwarded</strong></label>
<input type="text" id="address" name="forward_address" placeholder="eg: your-email@example.com" value="{{forward_address}}"/> <input type="text" id="address" name="forward_address" placeholder="eg: your-email@example.com" value="{{forward_address}}"/>
</div> </div>
<br> <br />
<div class="alert alert-info"> <div class="alert alert-info">
You must set up SMTP as described You must set up SMTP as described
<a target="_blank" rel="noopener noreferrer" href="https://zulip.readthedocs.io/en/latest/subsystems/email.html#development-and-testing"> <a target="_blank" rel="noopener noreferrer" href="https://zulip.readthedocs.io/en/latest/subsystems/email.html#development-and-testing">

View File

@ -3,7 +3,7 @@
{% block customhead %} {% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% endblock %} {% endblock %}
@ -67,21 +67,21 @@
</div> </div>
</div> </div>
<br> <br />
<div class="row2"> <div class="row2">
<label for="URL"><b>URL</b> (Automatically Generated)</label> <label for="URL"><b>URL</b> (Automatically Generated)</label>
<input id="URL" type="text" /> <input id="URL" type="text" />
</div> </div>
<br> <br />
<div class="row3"> <div class="row3">
<label for="custom_http_headers"><b>Custom HTTP Headers</b></label> <label for="custom_http_headers"><b>Custom HTTP Headers</b></label>
<textarea id="custom_http_headers"></textarea> <textarea id="custom_http_headers"></textarea>
</div> </div>
<br> <br />
<div class="row4"> <div class="row4">
<div class="col1"> <div class="col1">
@ -104,6 +104,6 @@
</div> </div>
<div class="pad-small"></div> <div class="pad-small"></div>
<input id="csrftoken" type="hidden" value="{{ csrf_token }}"> <input id="csrftoken" type="hidden" value="{{ csrf_token }}" />
{% endblock %} {% endblock %}

View File

@ -25,7 +25,7 @@
{{ render_markdown_path(article, api_uri_context) }} {{ render_markdown_path(article, api_uri_context) }}
<div id="footer" class="documentation-footer"> <div id="footer" class="documentation-footer">
<hr> <hr />
<p>We're here to help! Email us at <a href="mailto:{{ support_email }}">{{ support_email }}</a> with questions, feedback, or feature requests.</p> <p>We're here to help! Email us at <a href="mailto:{{ support_email }}">{{ support_email }}</a> with questions, feedback, or feature requests.</p>
</div> </div>
</div> </div>

View File

@ -23,4 +23,4 @@
<div class="email-text" style="display: none;"> <div class="email-text" style="display: none;">
<pre>{{ body }}</pre> <pre>{{ body }}</pre>
</div> </div>
<hr> <hr />

View File

@ -29,7 +29,7 @@
<p>{{ new_streams.html|display_list(1000)|safe }}.</p> <p>{{ new_streams.html|display_list(1000)|safe }}.</p>
{% endif %} {% endif %}
<br> <br />
<p><a href="{{ realm_uri }}">{% trans %}Click here to log in to Zulip and catch up.{% endtrans %}</a></p> <p><a href="{{ realm_uri }}">{% trans %}Click here to log in to Zulip and catch up.{% endtrans %}</a></p>
{% endblock %} {% endblock %}

View File

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<title>Zulip</title> <title>Zulip</title>
</head> </head>
{% if has_preheader %} {% if has_preheader %}

View File

@ -17,15 +17,15 @@
<p> <p>
{{ _('Your account details:') }} {{ _('Your account details:') }}
<li>{% trans organization_url=macros.link_tag(realm_uri) %}Organization URL: {{ organization_url }}{% endtrans %}<br></li> <li>{% trans organization_url=macros.link_tag(realm_uri) %}Organization URL: {{ organization_url }}{% endtrans %}<br /></li>
{% if ldap %} {% if ldap %}
{% if ldap_username %} {% if ldap_username %}
<li>{% trans %}Username: {{ ldap_username }}{% endtrans %}<br></li> <li>{% trans %}Username: {{ ldap_username }}{% endtrans %}<br /></li>
{% else %} {% else %}
<li>{{ _('Use your LDAP account to log in') }}<br></li> <li>{{ _('Use your LDAP account to log in') }}<br /></li>
{% endif %} {% endif %}
{% else %} {% else %}
<li>{% trans email=macros.email_tag(email) %}Email: {{ email }}{% endtrans %}<br></li> <li>{% trans email=macros.email_tag(email) %}Email: {{ email }}{% endtrans %}<br /></li>
{% endif %} {% endif %}
{% trans apps_page_link="https://zulip.com/apps" %}(you'll need these to sign in to the <a href="{{ apps_page_link }}">mobile and desktop</a> apps){% endtrans %} {% trans apps_page_link="https://zulip.com/apps" %}(you'll need these to sign in to the <a href="{{ apps_page_link }}">mobile and desktop</a> apps){% endtrans %}
</p> </p>
@ -39,7 +39,7 @@
</p> </p>
<p> <p>
{{ _("Cheers,") }}<br> {{ _("Cheers,") }}<br />
{{ _("Team Zulip") }} {{ _("Team Zulip") }}
</p> </p>

View File

@ -24,7 +24,7 @@
<p><a href="{{ realm_uri }}">{{ _("Take it for a spin now.") }}</a></p> <p><a href="{{ realm_uri }}">{{ _("Take it for a spin now.") }}</a></p>
<p>{{ _("Thanks,") }}<br>{{ _("Zulip") }}</p> <p>{{ _("Thanks,") }}<br />{{ _("Zulip") }}</p>
{% endblock %} {% endblock %}
{% block manage_preferences %} {% block manage_preferences %}

View File

@ -24,19 +24,19 @@
{% block manage_preferences %} {% block manage_preferences %}
<div class="email-preferences"> <div class="email-preferences">
&mdash;<br> &mdash;<br />
{% if mention %} {% if mention %}
{% trans %}You are receiving this because you were mentioned in {{ realm_name }}.{% endtrans %}<br> {% trans %}You are receiving this because you were mentioned in {{ realm_name }}.{% endtrans %}<br />
{% elif stream_email_notify %} {% elif stream_email_notify %}
{% trans %}You are receiving this because you have email notifications enabled for this stream.{% endtrans %}<br> {% trans %}You are receiving this because you have email notifications enabled for this stream.{% endtrans %}<br />
{% endif %} {% endif %}
{% if reply_to_zulip %} {% if reply_to_zulip %}
{% trans notif_url=realm_uri + "/#settings/notifications" %}Reply to this email directly, <a href="{{ narrow_url }}">view it in Zulip</a>, or <a href="{{ notif_url }}">manage email preferences</a>.{% endtrans %} {% trans notif_url=realm_uri + "/#settings/notifications" %}Reply to this email directly, <a href="{{ narrow_url }}">view it in Zulip</a>, or <a href="{{ notif_url }}">manage email preferences</a>.{% endtrans %}
{% elif not show_message_content %} {% elif not show_message_content %}
{% trans notif_url=realm_uri + "/#settings/notifications" %}<a href="{{ narrow_url }}">View or reply in Zulip</a>, or <a href="{{ notif_url }}">manage email preferences</a>.{% endtrans %} <br> {% trans notif_url=realm_uri + "/#settings/notifications" %}<a href="{{ narrow_url }}">View or reply in Zulip</a>, or <a href="{{ notif_url }}">manage email preferences</a>.{% endtrans %} <br />
{% else %} {% else %}
{% trans notif_url=realm_uri + "/#settings/notifications" %}<a href="{{ narrow_url }}">Reply in Zulip</a>, or <a href="{{ notif_url }}">manage email preferences</a>.{% endtrans %} <br> {% trans notif_url=realm_uri + "/#settings/notifications" %}<a href="{{ narrow_url }}">Reply in Zulip</a>, or <a href="{{ notif_url }}">manage email preferences</a>.{% endtrans %} <br />
<br> <br />
{% trans url="https://zulip.readthedocs.io/en/latest/production/email-gateway.html" %} {% trans url="https://zulip.readthedocs.io/en/latest/production/email-gateway.html" %}
Do not reply to this email. This Zulip server is not configured to accept incoming emails (<a href="{{ url }}">help</a>). Do not reply to this email. This Zulip server is not configured to accept incoming emails (<a href="{{ url }}">help</a>).
{% endtrans %} {% endtrans %}

View File

@ -10,7 +10,7 @@
{% trans new_email=macros.email_tag(new_email), support_email=macros.email_tag(support_email) %}The email associated with your Zulip account was recently changed to {{ new_email }}. If you did not request this change, please contact us immediately at {{ support_email }}.{% endtrans %} {% trans new_email=macros.email_tag(new_email), support_email=macros.email_tag(support_email) %}The email associated with your Zulip account was recently changed to {{ new_email }}. If you did not request this change, please contact us immediately at {{ support_email }}.{% endtrans %}
</p> </p>
<p> <p>
{{ _("Best,") }}<br> {{ _("Best,") }}<br />
{{ _("Team Zulip") }} {{ _("Team Zulip") }}
</p> </p>
{% endblock %} {% endblock %}

View File

@ -37,7 +37,7 @@
</p> </p>
<p> <p>
{{ _("Thanks,") }}<br> {{ _("Thanks,") }}<br />
{{ _("Zulip Security") }} {{ _("Zulip Security") }}
</p> </p>
{% endblock %} {% endblock %}

View File

@ -3,21 +3,21 @@
{% block content %} {% block content %}
<b>Support URL</b>: <a href="{{ support_url }}">{{ support_url }}</a> <b>Support URL</b>: <a href="{{ support_url }}">{{ support_url }}</a>
<br><br> <br /><br />
<b>Website</b>: <a href="{{ website }}">{{ website }}</a> <b>Website</b>: <a href="{{ website }}">{{ website }}</a>
<br><br> <br /><br />
<b>Organization type</b>: {{ organization_type }} <b>Organization type</b>: {{ organization_type }}
<br><br> <br /><br />
<b>Description</b>: <b>Description</b>:
<br> <br />
{{ description }} {{ description }}
<br><br> <br /><br />
<b>Requested by</b>: {{ requested_by }} ({{ user_role }}) <b>Requested by</b>: {{ requested_by }} ({{ user_role }})

View File

@ -6,7 +6,7 @@
{% endblock %} {% endblock %}
{% block customhead %} {% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% endblock %} {% endblock %}
{% block portico_content %} {% block portico_content %}

View File

@ -9,7 +9,7 @@
{% endblock %} {% endblock %}
{% block customhead %} {% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% endblock %} {% endblock %}
{% block portico_content %} {% block portico_content %}
@ -104,7 +104,7 @@
</div> </div>
</div> </div>
<hr> <hr />
<div class="company-container"> <div class="company-container">
<header> <header>

View File

@ -11,7 +11,7 @@ conversations and focus work.' %}
{% endblock %} {% endblock %}
{% block customhead %} {% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% endblock %} {% endblock %}
{% block portico_content %} {% block portico_content %}

View File

@ -6,7 +6,7 @@
{% endblock %} {% endblock %}
{% block customhead %} {% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% endblock %} {% endblock %}
{% block portico_content %} {% block portico_content %}

View File

@ -2,7 +2,7 @@
{% set entrypoint = "landing-page" %} {% set entrypoint = "landing-page" %}
{% block customhead %} {% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style> <style>
.portico-page { .portico-page {
@ -30,7 +30,7 @@
<h1>Chat for distributed teams.</h1> <h1>Chat for distributed teams.</h1>
<p> <p>
Zulip combines the immediacy of real-time chat with an email Zulip combines the immediacy of real-time chat with an email
threading model. <br class="line-break-desktop">With Zulip, you can catch threading model. <br class="line-break-desktop" />With Zulip, you can catch
up on important conversations while ignoring up on important conversations while ignoring
irrelevant ones. irrelevant ones.
</p> </p>
@ -44,7 +44,7 @@
<div class="item-inner"> <div class="item-inner">
<button data-target="#tour-carousel" data-slide="next" type="button" name="button" class="start-button">Take the tour</button> <button data-target="#tour-carousel" data-slide="next" type="button" name="button" class="start-button">Take the tour</button>
<img src="/static/images/story-tutorial/zulip-topic-blurred.png" alt="" class="start-image"> <img src="/static/images/story-tutorial/zulip-topic-blurred.png" alt="" class="start-image" />
</div> </div>
</div> </div>
<div class="item"> <div class="item">
@ -53,11 +53,11 @@
<div class="zulip-slack-comparison"> <div class="zulip-slack-comparison">
<div class="comparison-zulip"> <div class="comparison-zulip">
<div class="caption">Zulip</div> <div class="caption">Zulip</div>
<img src="/static/images/story-tutorial/zulip-streams.png" class="zulip-streams" alt="{{ _('Streams in Zulip') }}"> <img src="/static/images/story-tutorial/zulip-streams.png" class="zulip-streams" alt="{{ _('Streams in Zulip') }}" />
</div> </div>
<div class="comparison-slack"> <div class="comparison-slack">
<div class="caption">Other team chat</div> <div class="caption">Other team chat</div>
<img src="/static/images/story-tutorial/slack-streams.png" class="slack-streams" alt="{{ _('Streams in Slack') }}"> <img src="/static/images/story-tutorial/slack-streams.png" class="slack-streams" alt="{{ _('Streams in Slack') }}" />
</div> </div>
</div> </div>
</div> </div>
@ -68,11 +68,11 @@
<div class="zulip-slack-comparison"> <div class="zulip-slack-comparison">
<div class="comparison-zulip"> <div class="comparison-zulip">
<div class="caption">Zulip</div> <div class="caption">Zulip</div>
<img src="/static/images/story-tutorial/zulip-streams-selected.png" class="zulip-streams-selected" alt="{{ _('Topics in Zulip') }}"> <img src="/static/images/story-tutorial/zulip-streams-selected.png" class="zulip-streams-selected" alt="{{ _('Topics in Zulip') }}" />
</div> </div>
<div class="comparison-slack"> <div class="comparison-slack">
<div class="caption">Other team chat</div> <div class="caption">Other team chat</div>
<img src="/static/images/story-tutorial/slack-streams-selected.png" class="slack-streams-selected" alt="{{ _('Streams in Slack') }}"> <img src="/static/images/story-tutorial/slack-streams-selected.png" class="slack-streams-selected" alt="{{ _('Streams in Slack') }}" />
</div> </div>
</div> </div>
</div> </div>
@ -83,11 +83,11 @@
<div class="zulip-slack-comparison"> <div class="zulip-slack-comparison">
<div class="comparison-zulip"> <div class="comparison-zulip">
<div class="caption comparison-2-caption-zulip">Zulip</div> <div class="caption comparison-2-caption-zulip">Zulip</div>
<img src="/static/images/story-tutorial/zulip-streams-unreads-arrows.png" alt="{{ _('Stream topics in Zulip') }}" class="zulip-unreads-arrows"> <img src="/static/images/story-tutorial/zulip-streams-unreads-arrows.png" alt="{{ _('Stream topics in Zulip') }}" class="zulip-unreads-arrows" />
</div> </div>
<div class="comparison-slack"> <div class="comparison-slack">
<div class="caption comparison-2-caption-slack">Other team chat</div> <div class="caption comparison-2-caption-slack">Other team chat</div>
<img src="/static/images/story-tutorial/slack-streams-unreads.png" class="slack-stream-unreads" alt="{{ _('Streams in Slack') }}"> <img src="/static/images/story-tutorial/slack-streams-unreads.png" class="slack-stream-unreads" alt="{{ _('Streams in Slack') }}" />
</div> </div>
</div> </div>
</div> </div>
@ -95,27 +95,27 @@
<div class="item"> <div class="item">
<div class="item-inner"> <div class="item-inner">
<p class="tour-item-header">Let&rsquo;s click on &ldquo;Tuesday night catering.&rdquo;</p> <p class="tour-item-header">Let&rsquo;s click on &ldquo;Tuesday night catering.&rdquo;</p>
<img src="/static/images/story-tutorial/zulip-topic.png" alt="{{ _('The Tuesday night catering topic in Zulip') }}" class="zulip-topic mobile-hide"> <img src="/static/images/story-tutorial/zulip-topic.png" alt="{{ _('The Tuesday night catering topic in Zulip') }}" class="zulip-topic mobile-hide" />
<img src="/static/images/story-tutorial/zulip-topic-crop.png" alt="{{ _('The Tuesday night catering topic in Zulip') }}" class="centered-image zulip-topic mobile-show"> <img src="/static/images/story-tutorial/zulip-topic-crop.png" alt="{{ _('The Tuesday night catering topic in Zulip') }}" class="centered-image zulip-topic mobile-show" />
</div> </div>
</div> </div>
<div class="item"> <div class="item">
<div class="item-inner"> <div class="item-inner">
<p class="tour-item-header">Messages in Zulip retain their context even if they&rsquo;re sent hours after the conversation started:</p> <p class="tour-item-header">Messages in Zulip retain their context even if they&rsquo;re sent hours after the conversation started:</p>
<img src="/static/images/story-tutorial/zulip-streams-easy.png" alt="{{ _('The Tuesday night catering topic in Zulip') }}" class="zulip-easy mobile-hide"> <img src="/static/images/story-tutorial/zulip-streams-easy.png" alt="{{ _('The Tuesday night catering topic in Zulip') }}" class="zulip-easy mobile-hide" />
<div class="mobile-show"> <div class="mobile-show">
<img src="/static/images/story-tutorial/zulip-streams-easy-mobile-top.png" class="centered-image" alt="{{ _('The Tuesday night catering topic in Zulip') }}"> <img src="/static/images/story-tutorial/zulip-streams-easy-mobile-top.png" class="centered-image" alt="{{ _('The Tuesday night catering topic in Zulip') }}" />
<p class="tour-explanation">Messages sent hours apart are linked in the same topic.</p> <p class="tour-explanation">Messages sent hours apart are linked in the same topic.</p>
<img src="/static/images/story-tutorial/zulip-streams-easy-mobile-bottom.png" class="centered-image" alt="{{ _('The Tuesday night catering topic in Zulip - compose box') }}"> <img src="/static/images/story-tutorial/zulip-streams-easy-mobile-bottom.png" class="centered-image" alt="{{ _('The Tuesday night catering topic in Zulip - compose box') }}" />
</div> </div>
</div> </div>
</div> </div>
<div class="item"> <div class="item">
<div class="item-inner"> <div class="item-inner">
<p class="tour-item-header">Without topics, it&rsquo;s hard to catch up efficiently, and hard to participate in conversations that started while you were away.</p> <p class="tour-item-header">Without topics, it&rsquo;s hard to catch up efficiently, and hard to participate in conversations that started while you were away.</p>
<img src="/static/images/story-tutorial/slack-overwhelming.png" alt="{{ _('The Tuesday night catering topic in Slack') }}" class="slack-overwhelming mobile-hide"> <img src="/static/images/story-tutorial/slack-overwhelming.png" alt="{{ _('The Tuesday night catering topic in Slack') }}" class="slack-overwhelming mobile-hide" />
<div class="mobile-show"> <div class="mobile-show">
<img src="/static/images/story-tutorial/slack-overwhelming-mobile.png" class="centered-image" alt="{{ _('The Tuesday night catering topic in Slack') }}"> <img src="/static/images/story-tutorial/slack-overwhelming-mobile.png" class="centered-image" alt="{{ _('The Tuesday night catering topic in Slack') }}" />
<p class="tour-explanation">The last message about Tuesday night catering is hidden 56 messages ago. Meanwhile, you just see a mix of unrelated messages.</p> <p class="tour-explanation">The last message about Tuesday night catering is hidden 56 messages ago. Meanwhile, you just see a mix of unrelated messages.</p>
</div> </div>
</div> </div>
@ -128,11 +128,11 @@
</a> </a>
<div class="other-resources"> <div class="other-resources">
<div class="other-resources-section"> <div class="other-resources-section">
<a href="/why-zulip"><img src="/static/images/landing-page/organised.svg" alt=""></a> <a href="/why-zulip"><img src="/static/images/landing-page/organised.svg" alt="" /></a>
<p><a href="/why-zulip">Zulip vs Slack &rarr;</a></p> <p><a href="/why-zulip">Zulip vs Slack &rarr;</a></p>
</div> </div>
<div class="other-resources-section"> <div class="other-resources-section">
<a href="/features"><img src="/static/images/landing-page/featured.svg" alt=""></a> <a href="/features"><img src="/static/images/landing-page/featured.svg" alt="" /></a>
<p><a href="/features">See all features &rarr;</a></p> <p><a href="/features">See all features &rarr;</a></p>
</div> </div>
</div> </div>
@ -169,7 +169,7 @@
Zulip has modern apps for every major platform, Zulip has modern apps for every major platform,
powered by Electron and React Native. powered by Electron and React Native.
</p> </p>
<br> <br />
</div> </div>
<div class="platform-icons"> <div class="platform-icons">
@ -389,49 +389,49 @@
<div class="integration-icons"> <div class="integration-icons">
<a href="/integrations/doc/travis"> <a href="/integrations/doc/travis">
<div class="group"> <div class="group">
<img class="integration-logo" src="/static/images/integrations/logos/travis.svg" alt="{{ _('Travis logo') }}"> <img class="integration-logo" src="/static/images/integrations/logos/travis.svg" alt="{{ _('Travis logo') }}" />
<h3 class="integration-name">Travis CI</h3> <h3 class="integration-name">Travis CI</h3>
<p class="integration-description">See build results immediately</p> <p class="integration-description">See build results immediately</p>
</div> </div>
</a> </a>
<a href="/integrations/doc/github"> <a href="/integrations/doc/github">
<div class="group"> <div class="group">
<img class="integration-logo" src="/static/images/integrations/logos/github.svg" alt="{{ _('GitHub logo') }}"> <img class="integration-logo" src="/static/images/integrations/logos/github.svg" alt="{{ _('GitHub logo') }}" />
<h3 class="integration-name">GitHub</h3> <h3 class="integration-name">GitHub</h3>
<p class="integration-description">Track issues and pull requests</p> <p class="integration-description">Track issues and pull requests</p>
</div> </div>
</a> </a>
<a href="/integrations/doc/heroku"> <a href="/integrations/doc/heroku">
<div class="group"> <div class="group">
<img class="integration-logo" src="/static/images/integrations/logos/heroku.svg" alt="{{ _('Heroku logo') }}"> <img class="integration-logo" src="/static/images/integrations/logos/heroku.svg" alt="{{ _('Heroku logo') }}" />
<h3 class="integration-name">Heroku</h3> <h3 class="integration-name">Heroku</h3>
<p class="integration-description">Keep up with deployments</p> <p class="integration-description">Keep up with deployments</p>
</div> </div>
</a> </a>
<a href="/integrations/doc/zendesk"> <a href="/integrations/doc/zendesk">
<div class="group"> <div class="group">
<img class="integration-logo" src="/static/images/integrations/logos/zendesk.svg" alt="{{ _('Zendesk logo') }}"> <img class="integration-logo" src="/static/images/integrations/logos/zendesk.svg" alt="{{ _('Zendesk logo') }}" />
<h3 class="integration-name">Zendesk</h3> <h3 class="integration-name">Zendesk</h3>
<p class="integration-description">Receive support tickets and updates</p> <p class="integration-description">Receive support tickets and updates</p>
</div> </div>
</a> </a>
<a href="/integrations/doc/jira"> <a href="/integrations/doc/jira">
<div class="group"> <div class="group">
<img class="integration-logo" src="/static/images/integrations/logos/jira.svg" alt="{{ _('JIRA logo') }}"> <img class="integration-logo" src="/static/images/integrations/logos/jira.svg" alt="{{ _('JIRA logo') }}" />
<h3 class="integration-name">JIRA</h3> <h3 class="integration-name">JIRA</h3>
<p class="integration-description">Monitor project bugs and issues</p> <p class="integration-description">Monitor project bugs and issues</p>
</div> </div>
</a> </a>
<a href="/integrations/doc/sentry"> <a href="/integrations/doc/sentry">
<div class="group"> <div class="group">
<img class="integration-logo" src="/static/images/integrations/logos/sentry.svg" alt="{{ _('Sentry logo') }}"> <img class="integration-logo" src="/static/images/integrations/logos/sentry.svg" alt="{{ _('Sentry logo') }}" />
<h3 class="integration-name">Sentry</h3> <h3 class="integration-name">Sentry</h3>
<p class="integration-description">See real-time error tracking</p> <p class="integration-description">See real-time error tracking</p>
</div> </div>
</a> </a>
<a href="/integrations/doc/pagerduty" class="hide-1"> <a href="/integrations/doc/pagerduty" class="hide-1">
<div class="group"> <div class="group">
<img class="integration-logo" src="/static/images/integrations/logos/pagerduty.svg" alt="{{ _('Pagerduty logo') }}"> <img class="integration-logo" src="/static/images/integrations/logos/pagerduty.svg" alt="{{ _('Pagerduty logo') }}" />
<h3 class="integration-name">Pagerduty</h3> <h3 class="integration-name">Pagerduty</h3>
<p class="integration-description">Connect to your monitoring systems</p> <p class="integration-description">Connect to your monitoring systems</p>
</div> </div>
@ -501,7 +501,7 @@
</div> </div>
</div> </div>
<hr> <hr />
<div class="company-container"> <div class="company-container">
<header> <header>

View File

@ -18,7 +18,7 @@ Markdown
HTML HTML
``` ```
<a href="https://chat.zulip.org"><img src="https://img.shields.io/badge/zulip-join_chat-brightgreen.svg"></a> <a href="https://chat.zulip.org"><img src="https://img.shields.io/badge/zulip-join_chat-brightgreen.svg" /></a>
``` ```
## Link to a stream or topic ## Link to a stream or topic

View File

@ -6,7 +6,7 @@
{% endblock %} {% endblock %}
{% block customhead %} {% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% endblock %} {% endblock %}
{% block portico_content %} {% block portico_content %}

View File

@ -2,7 +2,7 @@
{% set entrypoint = "integrations" %} {% set entrypoint = "integrations" %}
{% block customhead %} {% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% endblock %} {% endblock %}
{% block hello_page_container %} hello-main{% endblock %} {% block hello_page_container %} hello-main{% endblock %}
@ -85,7 +85,7 @@
</h4> </h4>
</a> </a>
{% endfor %} {% endfor %}
<hr> <hr />
<h3>{% trans %}Custom integrations{% endtrans %}</h3> <h3>{% trans %}Custom integrations{% endtrans %}</h3>
<a href="/api/incoming-webhooks-overview"> <a href="/api/incoming-webhooks-overview">
<h4>{% trans %}Incoming webhooks{% endtrans %}</h4> <h4>{% trans %}Incoming webhooks{% endtrans %}</h4>
@ -125,7 +125,7 @@
</a> </a>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<hr> <hr />
<div class="integration-request center"> <div class="integration-request center">
<p>Don't see an integration you need? We'd love to help.</p> <p>Don't see an integration you need? We'd love to help.</p>
<a href="/api/integrations-overview" class="button green"> <a href="/api/integrations-overview" class="button green">

View File

@ -9,7 +9,7 @@
<div class="app-main white-box"> <div class="app-main white-box">
{{ _('Hi there! Thank you for your interest in Zulip.') }} {{ _('Hi there! Thank you for your interest in Zulip.') }}
<br> <br />
{% trans %}There is no Zulip organization hosted at this subdomain.{% endtrans %} {% trans %}There is no Zulip organization hosted at this subdomain.{% endtrans %}
</div> </div>
</div> </div>

View File

@ -42,7 +42,7 @@ page can be easily identified in it's respective JavaScript file. -->
{% if password_auth_enabled %} {% if password_auth_enabled %}
<form name="login_form" id="login_form" method="post" class="login-form" <form name="login_form" id="login_form" method="post" class="login-form"
action="{{ url('login') }}"> action="{{ url('login') }}">
<input type="hidden" name="next" value="{{ next }}"> <input type="hidden" name="next" value="{{ next }}" />
{% if two_factor_authentication_enabled %} {% if two_factor_authentication_enabled %}
{{ wizard.management_form }} {{ wizard.management_form }}
@ -112,7 +112,7 @@ page can be easily identified in it's respective JavaScript file. -->
{% for backend in page_params.external_authentication_methods %} {% for backend in page_params.external_authentication_methods %}
<div class="login-social"> <div class="login-social">
<form class="social_login_form form-inline" action="{{ backend.login_url }}" method="get"> <form class="social_login_form form-inline" action="{{ backend.login_url }}" method="get">
<input type="hidden" name="next" value="{{ next }}"> <input type="hidden" name="next" value="{{ next }}" />
<button id="login_{{ backend.button_id_suffix }}" class="login-social-button" <button id="login_{{ backend.button_id_suffix }}" class="login-social-button"
{% if backend.display_icon %} style="background-image:url({{ backend.display_icon }})" {% endif %}> {{ _('Log in with %(identity_provider)s', identity_provider=backend.display_name) }} {% if backend.display_icon %} style="background-image:url({{ backend.display_icon }})" {% endif %}> {{ _('Log in with %(identity_provider)s', identity_provider=backend.display_name) }}
</button> </button>

View File

@ -1,28 +1,28 @@
<!-- Google / Search Engine Tags --> <!-- Google / Search Engine Tags -->
{% if allow_search_engine_indexing %} {% if allow_search_engine_indexing %}
{% if OPEN_GRAPH_DESCRIPTION %} {% if OPEN_GRAPH_DESCRIPTION %}
<meta name="description" content="{{ OPEN_GRAPH_DESCRIPTION }}"> <meta name="description" content="{{ OPEN_GRAPH_DESCRIPTION }}" />
{% else %} {% else %}
<meta name="description" content="Zulip combines the immediacy of real-time chat with an email threading model. With Zulip, you can catch up on important conversations while ignoring irrelevant ones."> <meta name="description" content="Zulip combines the immediacy of real-time chat with an email threading model. With Zulip, you can catch up on important conversations while ignoring irrelevant ones." />
{% endif %} {% endif %}
{% else %} {% else %}
<meta name="robots" content="noindex,nofollow"> <meta name="robots" content="noindex,nofollow" />
{% endif %} {% endif %}
<!-- Open Graph / Facebook / Twitter Meta Tags --> <!-- Open Graph / Facebook / Twitter Meta Tags -->
<meta property="og:url" content="{{ OPEN_GRAPH_URL }}"> <meta property="og:url" content="{{ OPEN_GRAPH_URL }}" />
<meta property="og:type" content="website"> <meta property="og:type" content="website" />
<meta property="og:site_name" content="Zulip"> <meta property="og:site_name" content="Zulip" />
{% if OPEN_GRAPH_TITLE %} {% if OPEN_GRAPH_TITLE %}
<meta property="og:title" content="{{ OPEN_GRAPH_TITLE }}"> <meta property="og:title" content="{{ OPEN_GRAPH_TITLE }}" />
<meta property="og:description" content="{{ OPEN_GRAPH_DESCRIPTION }}"> <meta property="og:description" content="{{ OPEN_GRAPH_DESCRIPTION }}" />
{% else %} {% else %}
<meta property="og:title" content="Chat for distributed teams"> <meta property="og:title" content="Chat for distributed teams" />
<meta property="og:description" content="Zulip combines the immediacy of real-time chat with an email threading model. With Zulip, you can catch up on important conversations while ignoring irrelevant ones."> <meta property="og:description" content="Zulip combines the immediacy of real-time chat with an email threading model. With Zulip, you can catch up on important conversations while ignoring irrelevant ones." />
{% endif %} {% endif %}
{% if OPEN_GRAPH_IMAGE %} {% if OPEN_GRAPH_IMAGE %}
<meta property="og:image" content="{{ OPEN_GRAPH_IMAGE }}"> <meta property="og:image" content="{{ OPEN_GRAPH_IMAGE }}" />
{% else %} {% else %}
<meta property="og:image" content="{{ realm_uri }}/static/images/logo/zulip-icon-128x128.png"> <meta property="og:image" content="{{ realm_uri }}/static/images/logo/zulip-icon-128x128.png" />
{% endif %} {% endif %}
<meta name="twitter:card" content="summary"> <meta name="twitter:card" content="summary" />

View File

@ -2,7 +2,7 @@
{% set entrypoint = "landing-page" %} {% set entrypoint = "landing-page" %}
{% block customhead %} {% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% endblock %} {% endblock %}
{% block portico_content %} {% block portico_content %}
@ -28,7 +28,7 @@
<div class="description"> <div class="description">
Try Zulip for an unlimited period of time. Try Zulip for an unlimited period of time.
</div> </div>
<hr> <hr />
<ul class="feature-list"> <ul class="feature-list">
<li>10,000 messages of search history</li> <li>10,000 messages of search history</li>
<li>File storage up to 5 GB total</li> <li>File storage up to 5 GB total</li>
@ -61,7 +61,7 @@
<div class="description"> <div class="description">
Make Zulip your home. Make Zulip your home.
</div> </div>
<hr> <hr />
<ul class="feature-list"> <ul class="feature-list">
<li>Full search history</li> <li>Full search history</li>
<li>File storage up to 10 GB per user</li> <li>File storage up to 10 GB per user</li>
@ -77,7 +77,7 @@
<div class="price">6<span class="price-cents">.67</span></div> <div class="price">6<span class="price-cents">.67</span></div>
<div class="details"> <div class="details">
per user per month per user per month
<br> <br />
$8/month billed monthly $8/month billed monthly
</div> </div>
</div> </div>
@ -122,7 +122,7 @@
<div class="description"> <div class="description">
Install your own Zulip server. Install your own Zulip server.
</div> </div>
<hr> <hr />
<ul class="feature-list"> <ul class="feature-list">
<li>Easy self-service installation</li> <li>Easy self-service installation</li>
<li>Free and open source forever under Apache 2.0 license</li> <li>Free and open source forever under Apache 2.0 license</li>
@ -148,7 +148,7 @@
<div class="description"> <div class="description">
For mission-critical installations. For mission-critical installations.
</div> </div>
<hr> <hr />
<ul class="feature-list"> <ul class="feature-list">
<li>Easy self-service installation</li> <li>Easy self-service installation</li>
<li>Support response SLAs</li> <li>Support response SLAs</li>

View File

@ -6,7 +6,7 @@
{% endblock %} {% endblock %}
{% block customhead %} {% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% endblock %} {% endblock %}
{% block portico_content %} {% block portico_content %}

View File

@ -2,7 +2,7 @@
{% set entrypoint = "landing-page" %} {% set entrypoint = "landing-page" %}
{% block customhead %} {% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% endblock %} {% endblock %}
{% block portico_content %} {% block portico_content %}

View File

@ -180,7 +180,7 @@ Form is validated both client-side using jquery-validate (see signup.js) and ser
{% endif %} {% endif %}
</fieldset> </fieldset>
{% if default_stream_groups %} {% if default_stream_groups %}
<hr> <hr />
<div class="default-stream-groups"> <div class="default-stream-groups">
<p class="margin">{{ _('What are you interested in?') }}</p> <p class="margin">{{ _('What are you interested in?') }}</p>
{% for default_stream_group in default_stream_groups %} {% for default_stream_group in default_stream_groups %}
@ -206,7 +206,7 @@ Form is validated both client-side using jquery-validate (see signup.js) and ser
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<hr> <hr />
{% endif %} {% endif %}
<div class="input-group margin terms-of-service"> <div class="input-group margin terms-of-service">

View File

@ -6,7 +6,7 @@
{% endblock %} {% endblock %}
{% block customhead %} {% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% endblock %} {% endblock %}
{% block portico_content %} {% block portico_content %}

View File

@ -86,28 +86,28 @@
</p> </p>
<div class="contributors-list"> <div class="contributors-list">
<input id="total" type="radio" name="tabs" checked> <input id="total" type="radio" name="tabs" checked />
<label for="total"><i class="fa fa-globe" aria-hidden="true"></i>&nbsp; Total</label> <label for="total"><i class="fa fa-globe" aria-hidden="true"></i>&nbsp; Total</label>
<input id="server" type="radio" name="tabs"> <input id="server" type="radio" name="tabs" />
<label for="server"><i class="fa fa-server" aria-hidden="true"></i>&nbsp; Server</label> <label for="server"><i class="fa fa-server" aria-hidden="true"></i>&nbsp; Server</label>
<input id="desktop" type="radio" name="tabs"> <input id="desktop" type="radio" name="tabs" />
<label for="desktop"><i class="fa fa-desktop" aria-hidden="true"></i>&nbsp; Desktop</label> <label for="desktop"><i class="fa fa-desktop" aria-hidden="true"></i>&nbsp; Desktop</label>
<input id="mobile" type="radio" name="tabs"> <input id="mobile" type="radio" name="tabs" />
<label for="mobile"><i class="fa fa-mobile" aria-hidden="true"></i>&nbsp; Mobile</label> <label for="mobile"><i class="fa fa-mobile" aria-hidden="true"></i>&nbsp; Mobile</label>
<input id="python-zulip-api" type="radio" name="tabs"> <input id="python-zulip-api" type="radio" name="tabs" />
<label for="python-zulip-api"><i class="fa fa-code" aria-hidden="true"></i>&nbsp; Python API</label> <label for="python-zulip-api"><i class="fa fa-code" aria-hidden="true"></i>&nbsp; Python API</label>
<input id="zulip-js" type="radio" name="tabs"> <input id="zulip-js" type="radio" name="tabs" />
<label for="zulip-js"><i class="fa fa-code" aria-hidden="true"></i>&nbsp; JS API</label> <label for="zulip-js"><i class="fa fa-code" aria-hidden="true"></i>&nbsp; JS API</label>
<input id="zulipbot" type="radio" name="tabs"> <input id="zulipbot" type="radio" name="tabs" />
<label for="zulipbot"><i class="fa fa-at" aria-hidden="true"></i>&nbsp; Zulipbot</label> <label for="zulipbot"><i class="fa fa-at" aria-hidden="true"></i>&nbsp; Zulipbot</label>
<input id="terminal" type="radio" name="tabs"> <input id="terminal" type="radio" name="tabs" />
<label for="terminal"><i class="fa fa-terminal" aria-hidden="true"></i>&nbsp; Terminal</label> <label for="terminal"><i class="fa fa-terminal" aria-hidden="true"></i>&nbsp; Terminal</label>
<div id="tab-total" class="contributors"></div> <div id="tab-total" class="contributors"></div>
@ -128,7 +128,7 @@
<img class="avatar_img" src="<%= avatar %>" alt="{{ _('Avatar') }}" /> <img class="avatar_img" src="<%= avatar %>" alt="{{ _('Avatar') }}" />
</div> </div>
<div class='info'> <div class='info'>
<b><%= name %></b><br> <b><%= name %></b><br />
<%= commits %> <%= commits === 1 ? 'commit' : 'commits' %> <%= commits %> <%= commits === 1 ? 'commit' : 'commits' %>
</div> </div>
</a> </a>

View File

@ -4,7 +4,7 @@
{# Terms of Service. #} {# Terms of Service. #}
{% block customhead %} {% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% endblock %} {% endblock %}
{% block portico_content %} {% block portico_content %}

View File

@ -9,7 +9,7 @@
{% endblock %} {% endblock %}
{% block customhead %} {% block customhead %}
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% endblock %} {% endblock %}
{% block portico_content %} {% block portico_content %}

View File

@ -8,7 +8,7 @@ Mattermost, Discord, Spark, and others.
Anyone who wakes up to this frequently can tell you it is not fun. Anyone who wakes up to this frequently can tell you it is not fun.
<img src="/static/images/why-zulip/slack-unreads.png" class="slack-image" alt="Slack unreads"> <img src="/static/images/why-zulip/slack-unreads.png" class="slack-image" alt="Slack unreads" />
The lack of organization and context in Slack channels means that anyone The lack of organization and context in Slack channels means that anyone
using Slack heavily has to manually scan through hundreds of messages a day using Slack heavily has to manually scan through hundreds of messages a day
@ -124,13 +124,13 @@ effective threading model: Every channel message has a topic, just
like every message in email has a subject line. (Channels are called like every message in email has a subject line. (Channels are called
streams in Zulip.) streams in Zulip.)
<img src="/static/images/why-zulip/zulip-topics.png" class="zulip-topics-image" alt="Zulip topics"> <img src="/static/images/why-zulip/zulip-topics.png" class="zulip-topics-image" alt="Zulip topics" />
Topics hold Zulip conversations together, just like subject lines hold email Topics hold Zulip conversations together, just like subject lines hold email
conversations together. They allow you to efficiently catch up on messages conversations together. They allow you to efficiently catch up on messages
and reply in context, even to conversations that started hours or days ago. and reply in context, even to conversations that started hours or days ago.
<img src="/static/images/why-zulip/zulip-reply-later.png" class="zulip-reply-later-image" alt="Zulip reply later"> <img src="/static/images/why-zulip/zulip-reply-later.png" class="zulip-reply-later-image" alt="Zulip reply later" />
## Zulip changes how you can operate. ## Zulip changes how you can operate.

View File

@ -120,9 +120,9 @@ def tokenize(text: str) -> List[Token]:
tag = tag_parts[0] tag = tag_parts[0]
if is_special_html_tag(s, tag): if tag == "!DOCTYPE":
kind = "html_special" kind = "html_doctype"
elif is_self_closing_html_tag(s, tag): elif s.endswith("/>"):
kind = "html_singleton" kind = "html_singleton"
else: else:
kind = "html_start" kind = "html_start"
@ -203,6 +203,26 @@ def tokenize(text: str) -> List[Token]:
return tokens return tokens
HTML_VOID_TAGS = {
"area",
"base",
"br",
"col",
"command",
"embed",
"hr",
"img",
"input",
"keygen",
"link",
"meta",
"param",
"source",
"track",
"wbr",
}
def validate( def validate(
fn: Optional[str] = None, text: Optional[str] = None, check_indent: bool = True fn: Optional[str] = None, text: Optional[str] = None, check_indent: bool = True
) -> None: ) -> None:
@ -227,6 +247,7 @@ def validate(
class State: class State:
def __init__(self, func: Callable[[Token], None]) -> None: def __init__(self, func: Callable[[Token], None]) -> None:
self.depth = 0 self.depth = 0
self.foreign = False
self.matcher = func self.matcher = func
def no_start_tag(token: Token) -> None: def no_start_tag(token: Token) -> None:
@ -249,6 +270,10 @@ def validate(
start_col = start_token.col start_col = start_token.col
old_matcher = state.matcher old_matcher = state.matcher
old_foreign = state.foreign
if start_tag in ["math", "svg"]:
state.foreign = True
def f(end_token: Token) -> None: def f(end_token: Token) -> None:
@ -283,6 +308,7 @@ def validate(
""" """
) )
state.matcher = old_matcher state.matcher = old_matcher
state.foreign = old_foreign
state.depth -= 1 state.depth -= 1
state.matcher = f state.matcher = f
@ -292,7 +318,16 @@ def validate(
tag = token.tag tag = token.tag
if kind == "html_start": if kind == "html_start":
if not state.foreign and tag in HTML_VOID_TAGS:
raise TemplateParserException(
f"Tag must be self-closing: {tag} at {fn} line {token.line}, col {token.col}"
)
start_tag_matcher(token) start_tag_matcher(token)
elif kind == "html_singleton":
if not state.foreign and tag not in HTML_VOID_TAGS:
raise TemplateParserException(
f"Tag must not be self-closing: {tag} at {fn} line {token.line}, col {token.col}"
)
elif kind == "html_end": elif kind == "html_end":
state.matcher(token) state.matcher(token)
@ -315,45 +350,6 @@ def validate(
raise TemplateParserException("Missing end tag") raise TemplateParserException("Missing end tag")
def is_special_html_tag(s: str, tag: str) -> bool:
return tag in ["link", "meta", "!DOCTYPE"]
OPTIONAL_CLOSING_TAGS = [
"br",
"circle",
"img",
"input",
"path",
"polygon",
"stop",
]
def is_self_closing_html_tag(s: str, tag: str) -> bool:
if s.endswith("/>"):
if tag in OPTIONAL_CLOSING_TAGS:
return True
raise TokenizationException("Singleton tag not allowed", tag)
self_closing_tag = tag in [
"area",
"base",
"br",
"col",
"embed",
"hr",
"img",
"input",
"param",
"source",
"track",
"wbr",
]
if self_closing_tag:
return True
return False
def is_django_block_tag(tag: str) -> bool: def is_django_block_tag(tag: str) -> bool:
return tag in [ return tag in [
"autoescape", "autoescape",

View File

@ -3,6 +3,7 @@ import unittest
import tools.lib.template_parser import tools.lib.template_parser
from tools.lib.html_branches import ( from tools.lib.html_branches import (
Node,
build_id_dict, build_id_dict,
get_tag_info, get_tag_info,
html_branches, html_branches,
@ -49,35 +50,40 @@ class TestHtmlBranches(unittest.TestCase):
tree = html_tag_tree(html) tree = html_tag_tree(html)
assert tree.children[0].token is not None def serialize(node: Node) -> object:
self.assertEqual(tree.children[0].token.kind, "html_start") return (
self.assertEqual(tree.children[0].token.tag, "html") node.token and (node.token.kind, node.token.tag),
[serialize(child) for child in node.children],
)
assert tree.children[0].children[0].token is not None expected = (
self.assertEqual(tree.children[0].children[0].token.kind, "html_start") None,
self.assertEqual(tree.children[0].children[0].token.tag, "head") [
(
assert tree.children[0].children[0].children[0].token is not None ("html_start", "html"),
self.assertEqual(tree.children[0].children[0].children[0].token.kind, "html_start") [
self.assertEqual(tree.children[0].children[0].children[0].token.tag, "title") (
("html_start", "head"),
assert tree.children[0].children[1].token is not None [
self.assertEqual(tree.children[0].children[1].token.kind, "html_start") (("html_start", "title"), []),
self.assertEqual(tree.children[0].children[1].token.tag, "body") (("html_singleton", "meta"), []),
(("html_singleton", "link"), []),
assert tree.children[0].children[1].children[0].token is not None ],
self.assertEqual(tree.children[0].children[1].children[0].token.kind, "html_start") ),
self.assertEqual(tree.children[0].children[1].children[0].token.tag, "p") (
("html_start", "body"),
assert tree.children[0].children[1].children[0].children[0].token is not None [
self.assertEqual( (
tree.children[0].children[1].children[0].children[0].token.kind, "html_singleton" ("html_start", "p"),
[(("html_start", "br"), []), (("html_start", "p"), [])],
)
],
),
],
)
],
) )
self.assertEqual(tree.children[0].children[1].children[0].children[0].token.tag, "br") self.assertEqual(serialize(tree), expected)
assert tree.children[0].children[1].children[1].token is not None
self.assertEqual(tree.children[0].children[1].children[1].token.kind, "html_start")
self.assertEqual(tree.children[0].children[1].children[1].token.tag, "p")
def test_html_branches(self) -> None: def test_html_branches(self) -> None:
html = """ html = """
@ -99,19 +105,16 @@ class TestHtmlBranches(unittest.TestCase):
""" """
branches = html_branches(html) branches = html_branches(html)
self.assertEqual(branches[0].text(), "html head title")
self.assertEqual(branches[1].text(), "html body p br")
self.assertEqual(branches[2].text(), "html body p")
self.assertEqual( self.assertEqual(
branches[0].staircase_text(), "\n html\n head\n title\n" [(branch.text(), branch.staircase_text()) for branch in branches],
[
("html head title", "\n html\n head\n title\n"),
("html head meta", "\n html\n head\n meta\n"),
("html head link", "\n html\n head\n link\n"),
("html body p br", "\n html\n body\n p\n br\n"),
("html body p p", "\n html\n body\n p\n p\n"),
],
) )
self.assertEqual(
branches[1].staircase_text(),
"\n html\n body\n p\n br\n",
)
self.assertEqual(branches[2].staircase_text(), "\n html\n body\n p\n")
def test_build_id_dict(self) -> None: def test_build_id_dict(self) -> None:
templates = ["test_template1.html", "test_template2.html"] templates = ["test_template1.html", "test_template2.html"]

View File

@ -20,7 +20,7 @@ BAD_HTML = """
<link rel="stylesheet" href="style.css" /> <link rel="stylesheet" href="style.css" />
</head> </head>
<body> <body>
<div><p>Hello<br>world!</p></div> <div><p>Hello<br />world!</p></div>
<p>Goodbye<!-- test -->world!</p> <p>Goodbye<!-- test -->world!</p>
<table> <table>
<tr> <tr>
@ -52,7 +52,7 @@ GOOD_HTML = """
<link rel="stylesheet" href="style.css" /> <link rel="stylesheet" href="style.css" />
</head> </head>
<body> <body>
<div><p>Hello<br>world!</p></div> <div><p>Hello<br />world!</p></div>
<p>Goodbye<!-- test -->world!</p> <p>Goodbye<!-- test -->world!</p>
<table> <table>
<tr> <tr>
@ -338,7 +338,7 @@ BAD_HTML13 = """
{{#if this.is_realm_emoji}} {{#if this.is_realm_emoji}}
<img src="{{this.url}}" class="emoji" /> <img src="{{this.url}}" class="emoji" />
{{else}} {{else}}
<br> <br />
{{/if}} {{/if}}
{{/if}} {{/if}}
<div>{{this.count}}</div> <div>{{this.count}}</div>
@ -353,7 +353,7 @@ GOOD_HTML13 = """
{{#if this.is_realm_emoji}} {{#if this.is_realm_emoji}}
<img src="{{this.url}}" class="emoji" /> <img src="{{this.url}}" class="emoji" />
{{else}} {{else}}
<br> <br />
{{/if}} {{/if}}
{{/if}} {{/if}}
<div>{{this.count}}</div> <div>{{this.count}}</div>

View File

@ -260,23 +260,23 @@ class ParserTest(unittest.TestCase):
validate(text=my_html) validate(text=my_html)
def test_tokenize(self) -> None: def test_tokenize(self) -> None:
tag = "<meta whatever>bla" tag = "<!DOCTYPE html>"
token = tokenize(tag)[0] token = tokenize(tag)[0]
self.assertEqual(token.kind, "html_special") self.assertEqual(token.kind, "html_doctype")
tag = "<a>bla" tag = "<a>bla"
token = tokenize(tag)[0] token = tokenize(tag)[0]
self.assertEqual(token.kind, "html_start") self.assertEqual(token.kind, "html_start")
self.assertEqual(token.tag, "a") self.assertEqual(token.tag, "a")
tag = "<br>bla" tag = "<br />bla"
token = tokenize(tag)[0] token = tokenize(tag)[0]
self.assertEqual(token.kind, "html_singleton") self.assertEqual(token.kind, "html_singleton")
self.assertEqual(token.tag, "br") self.assertEqual(token.tag, "br")
tag = "<input>bla" tag = "<input>bla"
token = tokenize(tag)[0] token = tokenize(tag)[0]
self.assertEqual(token.kind, "html_singleton") self.assertEqual(token.kind, "html_start") # We later mark this an error.
self.assertEqual(token.tag, "input") self.assertEqual(token.tag, "input")
tag = "<input />bla" tag = "<input />bla"

View File

@ -45,4 +45,4 @@ API_FEATURE_LEVEL = 57
# historical commits sharing the same major version, in which case a # historical commits sharing the same major version, in which case a
# minor version bump suffices. # minor version bump suffices.
PROVISION_VERSION = "141.1" PROVISION_VERSION = "141.2"

View File

@ -54,7 +54,7 @@ class DocPageTest(ZulipTestCase):
self.assertIn(s, str(result.content)) self.assertIn(s, str(result.content))
if not doc_html_str: if not doc_html_str:
self.assert_in_success_response( self.assert_in_success_response(
['<meta name="robots" content="noindex,nofollow">'], result ['<meta name="robots" content="noindex,nofollow" />'], result
) )
# Test the URL on the root subdomain # Test the URL on the root subdomain
@ -65,7 +65,7 @@ class DocPageTest(ZulipTestCase):
self.assertIn(expected_content, str(result.content)) self.assertIn(expected_content, str(result.content))
if not doc_html_str: if not doc_html_str:
self.assert_in_success_response( self.assert_in_success_response(
['<meta name="robots" content="noindex,nofollow">'], result ['<meta name="robots" content="noindex,nofollow" />'], result
) )
for s in extra_strings: for s in extra_strings:
@ -88,7 +88,7 @@ class DocPageTest(ZulipTestCase):
# Every page has a meta-description # Every page has a meta-description
self.assert_in_success_response(['<meta name="description" content="'], result) self.assert_in_success_response(['<meta name="description" content="'], result)
self.assert_not_in_success_response( self.assert_not_in_success_response(
['<meta name="robots" content="noindex,nofollow">'], result ['<meta name="robots" content="noindex,nofollow" />'], result
) )
# Test the URL on the "zephyr" subdomain with the landing page setting # Test the URL on the "zephyr" subdomain with the landing page setting
@ -101,7 +101,7 @@ class DocPageTest(ZulipTestCase):
self.assertIn(s, str(result.content)) self.assertIn(s, str(result.content))
if not doc_html_str: if not doc_html_str:
self.assert_in_success_response( self.assert_in_success_response(
['<meta name="robots" content="noindex,nofollow">'], result ['<meta name="robots" content="noindex,nofollow" />'], result
) )
def test_api_doc_endpoints(self) -> None: def test_api_doc_endpoints(self) -> None:
@ -172,14 +172,14 @@ class DocPageTest(ZulipTestCase):
def test_portico_pages_open_graph_metadata(self) -> None: def test_portico_pages_open_graph_metadata(self) -> None:
# Why Zulip # Why Zulip
url = "/why-zulip/" url = "/why-zulip/"
title = '<meta property="og:title" content="Team chat with first-class threading">' title = '<meta property="og:title" content="Team chat with first-class threading" />'
description = '<meta property="og:description" content="Most team chats are overwhelming' description = '<meta property="og:description" content="Most team chats are overwhelming'
self._test(url, title, doc_html_str=True) self._test(url, title, doc_html_str=True)
self._test(url, description, doc_html_str=True) self._test(url, description, doc_html_str=True)
# Features # Features
url = "/features/" url = "/features/"
title = '<meta property="og:title" content="Zulip Features">' title = '<meta property="og:title" content="Zulip Features" />'
description = '<meta property="og:description" content="First class threading' description = '<meta property="og:description" content="First class threading'
self._test(url, title, doc_html_str=True) self._test(url, title, doc_html_str=True)
self._test(url, description, doc_html_str=True) self._test(url, description, doc_html_str=True)
@ -202,21 +202,21 @@ class DocPageTest(ZulipTestCase):
def test_integration_pages_open_graph_metadata(self) -> None: def test_integration_pages_open_graph_metadata(self) -> None:
url = "/integrations/doc/github" url = "/integrations/doc/github"
title = '<meta property="og:title" content="Connect GitHub to Zulip">' title = '<meta property="og:title" content="Connect GitHub to Zulip" />'
description = '<meta property="og:description" content="Zulip comes with over' description = '<meta property="og:description" content="Zulip comes with over'
self._test(url, title, doc_html_str=True) self._test(url, title, doc_html_str=True)
self._test(url, description, doc_html_str=True) self._test(url, description, doc_html_str=True)
# Test category pages # Test category pages
url = "/integrations/communication" url = "/integrations/communication"
title = '<meta property="og:title" content="Connect your Communication tools to Zulip">' title = '<meta property="og:title" content="Connect your Communication tools to Zulip" />'
description = '<meta property="og:description" content="Zulip comes with over' description = '<meta property="og:description" content="Zulip comes with over'
self._test(url, title, doc_html_str=True) self._test(url, title, doc_html_str=True)
self._test(url, description, doc_html_str=True) self._test(url, description, doc_html_str=True)
# Test integrations page # Test integrations page
url = "/integrations/" url = "/integrations/"
title = '<meta property="og:title" content="Connect the tools you use to Zulip">' title = '<meta property="og:title" content="Connect the tools you use to Zulip" />'
description = '<meta property="og:description" content="Zulip comes with over' description = '<meta property="og:description" content="Zulip comes with over'
self._test(url, title, doc_html_str=True) self._test(url, title, doc_html_str=True)
self._test(url, description, doc_html_str=True) self._test(url, description, doc_html_str=True)