mirror of https://github.com/zulip/zulip.git
templates: Prevent dangling end tags.
In cases where an opening tag is so long that we stretch it to 2+ lines of code, we should try to use block-style formatting in the template code. Unfortunately, we have lots of legacy code that violates this concept, so this is a timid fix. There are also legit use cases like textarea where we probably need to keep the ugly template syntax for things to render properly.
This commit is contained in:
parent
a6ee54d99d
commit
4792af5682
|
@ -68,7 +68,9 @@
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="button exit small rounded" data-dismiss="modal">{{t "Cancel" }}</button>
|
<button class="button exit small rounded" data-dismiss="modal">{{t "Cancel" }}</button>
|
||||||
<button id="submit-invitation" class="button small rounded sea-green" type="button"
|
<button id="submit-invitation" class="button small rounded sea-green" type="button"
|
||||||
data-loading-text="{{t 'Inviting...' }}">{{t "Invite" }}</button>
|
data-loading-text="{{t 'Inviting...' }}">
|
||||||
|
{{t "Invite" }}
|
||||||
|
</button>
|
||||||
<div class="alert" id="multiuse_invite_status"></div>
|
<div class="alert" id="multiuse_invite_status"></div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -21,7 +21,8 @@
|
||||||
<div id="search_arrows" class="pill-container input-append">
|
<div id="search_arrows" class="pill-container input-append">
|
||||||
<span class="search_icon search_open" ><i class="fa fa-search" aria-hidden="true"></i></span>
|
<span class="search_icon search_open" ><i class="fa fa-search" aria-hidden="true"></i></span>
|
||||||
<div contenteditable="true" class="input search-query input-block-level" id="search_query" type="text" placeholder="{{t 'Search' }}"
|
<div contenteditable="true" class="input search-query input-block-level" id="search_query" type="text" placeholder="{{t 'Search' }}"
|
||||||
autocomplete="off" aria-label="{{t 'Search' }}" title="{{t 'Search' }} (/)"> </div>
|
autocomplete="off" aria-label="{{t 'Search' }}" title="{{t 'Search' }} (/)">
|
||||||
|
</div>
|
||||||
<button class="btn search_button" type="button" id="search_exit" aria-label="{{t 'Exit search' }}"><i class="fa fa-remove" aria-hidden="true"></i></button>
|
<button class="btn search_button" type="button" id="search_exit" aria-label="{{t 'Exit search' }}"><i class="fa fa-remove" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
<div id="user-list">
|
<div id="user-list">
|
||||||
<div id="userlist-header">
|
<div id="userlist-header">
|
||||||
<h4 class='sidebar-title'
|
<h4 class='sidebar-title'
|
||||||
id='userlist-title' data-tippy-content="{{t 'Search people' }} (w)">{{t 'USERS' }}</h4>
|
id='userlist-title' data-tippy-content="{{t 'Search people' }} (w)">
|
||||||
|
{{t 'USERS' }}
|
||||||
|
</h4>
|
||||||
<i id="user_filter_icon" class="fa fa-search"
|
<i id="user_filter_icon" class="fa fa-search"
|
||||||
aria-hidden="true" aria-label="{{t 'Search people' }}"
|
aria-hidden="true" aria-label="{{t 'Search people' }}"
|
||||||
data-tippy-content="{{t 'Search people' }} (w)">
|
data-tippy-content="{{t 'Search people' }} (w)">
|
||||||
|
|
|
@ -12,7 +12,8 @@
|
||||||
<div class="add_subscribers_container">
|
<div class="add_subscribers_container">
|
||||||
<div class="pill-container person_picker">
|
<div class="pill-container person_picker">
|
||||||
<div class="input" contenteditable="true"
|
<div class="input" contenteditable="true"
|
||||||
data-placeholder="{{t 'Add subscribers. Use @usergroup or #streamname to bulk add subscribers.' }}"></div>
|
data-placeholder="{{t 'Add subscribers. Use @usergroup or #streamname to bulk add subscribers.' }}">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="add_subscriber_btn_wrapper inline-block">
|
<div class="add_subscriber_btn_wrapper inline-block">
|
||||||
<button type="submit" name="add_subscriber" class="button add-subscriber-button small rounded" tabindex="0">
|
<button type="submit" name="add_subscriber" class="button add-subscriber-button small rounded" tabindex="0">
|
||||||
|
|
|
@ -15,7 +15,8 @@
|
||||||
<div class="tab-data">
|
<div class="tab-data">
|
||||||
<div class="tabcontent active" id="profile-tab">
|
<div class="tabcontent active" id="profile-tab">
|
||||||
<div id="avatar" {{#if user_is_guest}} class="guest-avatar" {{/if}}
|
<div id="avatar" {{#if user_is_guest}} class="guest-avatar" {{/if}}
|
||||||
style="background-image: url('{{user_avatar}}');"></div>
|
style="background-image: url('{{user_avatar}}');">
|
||||||
|
</div>
|
||||||
<div id="default-section">
|
<div id="default-section">
|
||||||
{{#if show_email}}
|
{{#if show_email}}
|
||||||
<div id="email" class="default-field">
|
<div id="email" class="default-field">
|
||||||
|
|
|
@ -37,6 +37,8 @@
|
||||||
<p>{{ _("As a last resort, you can use a backup token:") }}</p>
|
<p>{{ _("As a last resort, you can use a backup token:") }}</p>
|
||||||
<p>
|
<p>
|
||||||
<button name="wizard_goto_step" type="submit" value="backup"
|
<button name="wizard_goto_step" type="submit" value="backup"
|
||||||
class="btn btn-default btn-block">{{ _("Use backup token") }}</button>
|
class="btn btn-default btn-block">
|
||||||
|
{{ _("Use backup token") }}
|
||||||
|
</button>
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -21,14 +21,18 @@ page can be easily identified in it's respective JavaScript file -->
|
||||||
<div class="group">
|
<div class="group">
|
||||||
{% if realm_web_public_access_enabled %}
|
{% if realm_web_public_access_enabled %}
|
||||||
<h2>{{_('Anonymous user') }}</h2>
|
<h2>{{_('Anonymous user') }}</h2>
|
||||||
<p><input type="submit" formaction="{{ current_realm.uri }}{{ url('login-local') }}"
|
<p>
|
||||||
name="prefers_web_public_view" class="btn-direct btn-admin" value="Anonymous login" /></p>
|
<input type="submit" formaction="{{ current_realm.uri }}{{ url('login-local') }}"
|
||||||
|
name="prefers_web_public_view" class="btn-direct btn-admin" value="Anonymous login" />
|
||||||
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h2>{{_('Owners') }}</h2>
|
<h2>{{_('Owners') }}</h2>
|
||||||
{% if direct_owners %}
|
{% if direct_owners %}
|
||||||
{% for direct_owner in direct_owners %}
|
{% for direct_owner in direct_owners %}
|
||||||
<p><input type="submit" formaction="{{ direct_owner.realm.uri }}{{ url('login-local') }}"
|
<p>
|
||||||
name="direct_email" class="btn-direct btn-admin" value="{{ direct_owner.delivery_email }}" /></p>
|
<input type="submit" formaction="{{ direct_owner.realm.uri }}{{ url('login-local') }}"
|
||||||
|
name="direct_email" class="btn-direct btn-admin" value="{{ direct_owner.delivery_email }}" />
|
||||||
|
</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>No owners found in this realm</p>
|
<p>No owners found in this realm</p>
|
||||||
|
@ -36,8 +40,10 @@ page can be easily identified in it's respective JavaScript file -->
|
||||||
<h2>{{ _('Administrators') }}</h2>
|
<h2>{{ _('Administrators') }}</h2>
|
||||||
{% if direct_admins %}
|
{% if direct_admins %}
|
||||||
{% for direct_admin in direct_admins %}
|
{% for direct_admin in direct_admins %}
|
||||||
<p><input type="submit" formaction="{{ direct_admin.realm.uri }}{{ url('login-local') }}"
|
<p>
|
||||||
name="direct_email" class="btn-direct btn-admin" value="{{ direct_admin.delivery_email }}" /></p>
|
<input type="submit" formaction="{{ direct_admin.realm.uri }}{{ url('login-local') }}"
|
||||||
|
name="direct_email" class="btn-direct btn-admin" value="{{ direct_admin.delivery_email }}" />
|
||||||
|
</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>No administrators found in this realm</p>
|
<p>No administrators found in this realm</p>
|
||||||
|
@ -45,8 +51,10 @@ page can be easily identified in it's respective JavaScript file -->
|
||||||
<h2>{{ _('Moderators') }}</h2>
|
<h2>{{ _('Moderators') }}</h2>
|
||||||
{% if direct_moderators %}
|
{% if direct_moderators %}
|
||||||
{% for direct_moderator in direct_moderators %}
|
{% for direct_moderator in direct_moderators %}
|
||||||
<p><input type="submit" formaction="{{ direct_moderator.realm.uri }}{{ url('login-local') }}"
|
<p>
|
||||||
name="direct_email" class="btn-direct btn-admin" value="{{ direct_moderator.delivery_email }}" /></p>
|
<input type="submit" formaction="{{ direct_moderator.realm.uri }}{{ url('login-local') }}"
|
||||||
|
name="direct_email" class="btn-direct btn-admin" value="{{ direct_moderator.delivery_email }}" />
|
||||||
|
</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>No moderators found in this realm</p>
|
<p>No moderators found in this realm</p>
|
||||||
|
@ -54,8 +62,10 @@ page can be easily identified in it's respective JavaScript file -->
|
||||||
<h2>{{ _('Guest users') }}</h2>
|
<h2>{{ _('Guest users') }}</h2>
|
||||||
{% if guest_users %}
|
{% if guest_users %}
|
||||||
{% for guest_user in guest_users %}
|
{% for guest_user in guest_users %}
|
||||||
<p><input type="submit" formaction="{{ guest_user.realm.uri }}{{ url('login-local') }}"
|
<p>
|
||||||
name="direct_email" class="btn-direct btn-admin" value="{{ guest_user.delivery_email }}" /></p>
|
<input type="submit" formaction="{{ guest_user.realm.uri }}{{ url('login-local') }}"
|
||||||
|
name="direct_email" class="btn-direct btn-admin" value="{{ guest_user.delivery_email }}" />
|
||||||
|
</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>No guest users found in this realm</p>
|
<p>No guest users found in this realm</p>
|
||||||
|
@ -66,8 +76,10 @@ page can be easily identified in it's respective JavaScript file -->
|
||||||
<h2>{{ _('Normal users') }}</h2>
|
<h2>{{ _('Normal users') }}</h2>
|
||||||
{% if direct_users %}
|
{% if direct_users %}
|
||||||
{% for direct_user in direct_users %}
|
{% for direct_user in direct_users %}
|
||||||
<p><input type="submit" formaction="{{ direct_user.realm.uri }}{{ url('login-local') }}"
|
<p>
|
||||||
name="direct_email" class="btn-direct btn-admin" value="{{ direct_user.delivery_email }}" /></p>
|
<input type="submit" formaction="{{ direct_user.realm.uri }}{{ url('login-local') }}"
|
||||||
|
name="direct_email" class="btn-direct btn-admin" value="{{ direct_user.delivery_email }}" />
|
||||||
|
</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>No normal users found in this realm</p>
|
<p>No normal users found in this realm</p>
|
||||||
|
|
|
@ -86,8 +86,7 @@
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<blockquote class="twitter-tweet" data-cards="hidden"><p
|
<blockquote class="twitter-tweet" data-cards="hidden"><p lang="en" dir="ltr">We just moved the Lichess team (~100 persons) to <a href="https://twitter.com/zulip?ref_src=twsrc%5Etfw">@zulip</a>, and I'm loving it. The topics in particular make it vastly superior to slack & discord, when it comes to dealing with many conversations.<br />Zulip is also open-source! <a href="https://t.co/lxHjf3YPMe">https://t.co/lxHjf3YPMe</a></p>— Thibault D (@ornicar) <a href="https://twitter.com/ornicar/status/1412672302601457664?ref_src=twsrc%5Etfw">July 7, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js"></script>
|
||||||
lang="en" dir="ltr">We just moved the Lichess team (~100 persons) to <a href="https://twitter.com/zulip?ref_src=twsrc%5Etfw">@zulip</a>, and I'm loving it. The topics in particular make it vastly superior to slack & discord, when it comes to dealing with many conversations.<br />Zulip is also open-source! <a href="https://t.co/lxHjf3YPMe">https://t.co/lxHjf3YPMe</a></p>— Thibault D (@ornicar) <a href="https://twitter.com/ornicar/status/1412672302601457664?ref_src=twsrc%5Etfw">July 7, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js"></script>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-half">
|
<div class="feature-half">
|
||||||
|
|
|
@ -250,6 +250,8 @@ def validate(
|
||||||
{e}"""
|
{e}"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
prevent_dangling_tags(fn, tokens)
|
||||||
|
|
||||||
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
|
||||||
|
@ -365,6 +367,51 @@ def validate(
|
||||||
raise TemplateParserException("Missing end tag")
|
raise TemplateParserException("Missing end tag")
|
||||||
|
|
||||||
|
|
||||||
|
def prevent_dangling_tags(fn: str, tokens: List[Token]) -> None:
|
||||||
|
"""
|
||||||
|
Prevent this kind of HTML:
|
||||||
|
|
||||||
|
<div attr attr
|
||||||
|
attr attr>Stuff</div>
|
||||||
|
|
||||||
|
We prefer:
|
||||||
|
<div attr attr
|
||||||
|
attr attr>
|
||||||
|
Stuff
|
||||||
|
</div>
|
||||||
|
|
||||||
|
We may eventually have the pretty_printer code do this
|
||||||
|
automatically, but there are some complications with
|
||||||
|
legacy code.
|
||||||
|
"""
|
||||||
|
min_row: Optional[int] = None
|
||||||
|
for token in tokens:
|
||||||
|
if token.kind in ("handlebars_singleton_end", "html_singleton_end"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# We only apply this validation for a couple tag types, because
|
||||||
|
# our existing templates may have some funny edge cases. We eventually
|
||||||
|
# want to be more aggressive here. We may need to be extra careful
|
||||||
|
# with tags like <pre> that have whitespace sensitivities.
|
||||||
|
if token.tag not in ("div", "button", "p"):
|
||||||
|
continue
|
||||||
|
if min_row and token.line < min_row:
|
||||||
|
raise TemplateParserException(
|
||||||
|
f"""
|
||||||
|
|
||||||
|
Please fix line {token.line} at {fn} (col {token.col})
|
||||||
|
by moving this tag so that it closes the block at the
|
||||||
|
same indentation level as its start tag:
|
||||||
|
|
||||||
|
{token.s}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
min_row = None
|
||||||
|
if token.line_span > 1:
|
||||||
|
min_row = token.line + token.line_span
|
||||||
|
|
||||||
|
|
||||||
def is_django_block_tag(tag: str) -> bool:
|
def is_django_block_tag(tag: str) -> bool:
|
||||||
return tag in [
|
return tag in [
|
||||||
"autoescape",
|
"autoescape",
|
||||||
|
|
Loading…
Reference in New Issue