mirror of
https://github.com/reddit-archive/reddit.git
synced 2026-04-27 03:00:12 -04:00
Timeouts: Add modal for forbidden actions.
Works similarly to the 'login-required' modal. Links that perform actions that are forbidden when 'in timeout' are given the 'access-required' class, which triggers the modal if the current user is in timeout. Some links additionally needed to have their default click handlers edited to exit early in this case (e.g. the ynbutton's toggle function).
This commit is contained in:
@@ -493,6 +493,7 @@ module["reddit"] = LocalizedModule("reddit.js",
|
||||
"popup.js",
|
||||
"login.js",
|
||||
"locked.js",
|
||||
"timeouts.js",
|
||||
"newsletter.js",
|
||||
"flair.js",
|
||||
"interestbar.js",
|
||||
|
||||
@@ -398,6 +398,16 @@ class Reddit(Templated):
|
||||
|
||||
self.toolbars = self.build_toolbars()
|
||||
|
||||
if c.user_is_loggedin and c.user.in_timeout:
|
||||
hook = hooks.get_hook('timeouts.fetch_expiration')
|
||||
expires = hook.call_until_return(user=c.user)
|
||||
if expires:
|
||||
now = datetime.datetime.now(g.tz)
|
||||
expire_date = expires - now
|
||||
self.timeout_days_remaining = expire_date.days + 1
|
||||
else:
|
||||
self.timeout_days_remaining = 0
|
||||
|
||||
has_style_override = (c.user.pref_default_theme_sr and
|
||||
feature.is_enabled('stylesheets_everywhere') and
|
||||
c.user.pref_enable_default_themes)
|
||||
@@ -442,24 +452,24 @@ class Reddit(Templated):
|
||||
buttons = []
|
||||
|
||||
buttons.append(NamedButton("wikirecentrevisions",
|
||||
css_class="wikiaction-revisions",
|
||||
css_class="wikiaction-revisions access-required",
|
||||
dest="/wiki/revisions"))
|
||||
|
||||
buttons.append(NamedButton("wikipageslist",
|
||||
css_class="wikiaction-pages",
|
||||
css_class="wikiaction-pages access-required",
|
||||
dest="/wiki/pages"))
|
||||
if moderator:
|
||||
buttons += [NamedButton('wikibanned', css_class='reddit-ban',
|
||||
buttons += [NamedButton('wikibanned', css_class='reddit-ban access-required',
|
||||
dest='/about/wikibanned'),
|
||||
NamedButton('wikicontributors',
|
||||
css_class='reddit-contributors',
|
||||
css_class='reddit-contributors access-required',
|
||||
dest='/about/wikicontributors')
|
||||
]
|
||||
|
||||
return SideContentBox(_('wiki tools'),
|
||||
[NavMenu(buttons,
|
||||
type="flat_vert",
|
||||
css_class="icon-menu",
|
||||
css_class="icon-menu access-required",
|
||||
separator="")],
|
||||
_id="wikiactions",
|
||||
collapsible=True)
|
||||
@@ -473,43 +483,43 @@ class Reddit(Templated):
|
||||
|
||||
if is_single_subreddit and is_moderator_with_perms('config'):
|
||||
buttons.append(NavButton(menu.community_settings,
|
||||
css_class="reddit-edit",
|
||||
css_class="reddit-edit access-required",
|
||||
dest="edit"))
|
||||
buttons.append(NavButton(menu.edit_stylesheet,
|
||||
css_class="edit-stylesheet",
|
||||
css_class="edit-stylesheet access-required",
|
||||
dest="stylesheet"))
|
||||
|
||||
if is_moderator_with_perms('mail'):
|
||||
buttons.append(NamedButton("modmail",
|
||||
dest="message/inbox",
|
||||
css_class="moderator-mail"))
|
||||
css_class="moderator-mail access-required"))
|
||||
|
||||
if is_single_subreddit:
|
||||
if is_moderator_with_perms('access'):
|
||||
buttons.append(NamedButton("moderators",
|
||||
css_class="reddit-moderators"))
|
||||
css_class="reddit-moderators access-required"))
|
||||
|
||||
if not c.site.hide_contributors:
|
||||
buttons.append(NavButton(
|
||||
menu.contributors,
|
||||
"contributors",
|
||||
css_class="reddit-contributors"))
|
||||
css_class="reddit-contributors access-required"))
|
||||
|
||||
buttons.append(NamedButton("traffic", css_class="reddit-traffic"))
|
||||
buttons.append(NamedButton("traffic", css_class="reddit-traffic access-required"))
|
||||
|
||||
if is_moderator_with_perms('posts'):
|
||||
buttons += [NamedButton("modqueue", css_class="reddit-modqueue"),
|
||||
NamedButton("reports", css_class="reddit-reported"),
|
||||
NamedButton("spam", css_class="reddit-spam"),
|
||||
NamedButton("edited", css_class="reddit-edited")]
|
||||
buttons += [NamedButton("modqueue", css_class="reddit-modqueue access-required"),
|
||||
NamedButton("reports", css_class="reddit-reported access-required"),
|
||||
NamedButton("spam", css_class="reddit-spam access-required"),
|
||||
NamedButton("edited", css_class="reddit-edited access-required")]
|
||||
|
||||
if is_single_subreddit:
|
||||
if is_moderator_with_perms('access'):
|
||||
buttons.append(NamedButton("banned", css_class="reddit-ban"))
|
||||
buttons.append(NamedButton("banned", css_class="reddit-ban access-required"))
|
||||
if is_moderator_with_perms('access', 'mail'):
|
||||
buttons.append(NamedButton("muted", css_class="reddit-mute"))
|
||||
buttons.append(NamedButton("muted", css_class="reddit-mute access-required"))
|
||||
if is_moderator_with_perms('flair'):
|
||||
buttons.append(NamedButton("flair", css_class="reddit-flair"))
|
||||
buttons.append(NamedButton("flair", css_class="reddit-flair access-required"))
|
||||
|
||||
if is_single_subreddit and is_moderator_with_perms('config'):
|
||||
# append automod button if they have an AutoMod configuration
|
||||
@@ -518,15 +528,15 @@ class Reddit(Templated):
|
||||
buttons.append(NamedButton(
|
||||
"automod",
|
||||
dest="../wiki/edit/config/automoderator",
|
||||
css_class="reddit-automod",
|
||||
css_class="reddit-automod access-required",
|
||||
))
|
||||
except tdb_cassandra.NotFound:
|
||||
pass
|
||||
|
||||
buttons.append(NamedButton("log", css_class="reddit-moderationlog"))
|
||||
buttons.append(NamedButton("log", css_class="reddit-moderationlog access-required"))
|
||||
if is_moderator_with_perms('posts'):
|
||||
buttons.append(
|
||||
NamedButton("unmoderated", css_class="reddit-unmoderated"))
|
||||
NamedButton("unmoderated", css_class="reddit-unmoderated access-required"))
|
||||
|
||||
return SideContentBox(_('moderation tools'),
|
||||
[NavMenu(buttons,
|
||||
@@ -729,12 +739,14 @@ class Reddit(Templated):
|
||||
more_text = _("about moderation team")
|
||||
mod_href = c.site.path + 'about/moderators'
|
||||
|
||||
if '/r/%s' % c.site.name == g.admin_message_acct:
|
||||
is_admin_sr = '/r/%s' % c.site.name == g.admin_message_acct
|
||||
|
||||
if is_admin_sr:
|
||||
label = _('message the admins')
|
||||
else:
|
||||
label = _('message the moderators')
|
||||
helplink = ("/message/compose?to=%%2Fr%%2F%s" % c.site.name,
|
||||
label)
|
||||
label, is_admin_sr)
|
||||
ps.append(SideContentBox(_('moderators'),
|
||||
moderators[:sidebar_list_length],
|
||||
helplink = helplink,
|
||||
@@ -2541,10 +2553,17 @@ class BannedInterstitial(Interstitial):
|
||||
|
||||
|
||||
class BannedUserInterstitial(BannedInterstitial):
|
||||
"""The banned user message."""
|
||||
"""The message shown when viewing a banned user profile."""
|
||||
pass
|
||||
|
||||
|
||||
class InTimeoutInterstitial(BannedInterstitial):
|
||||
"""The message shown to a user in timeout."""
|
||||
def __init__(self, timeout_days_remaining=0):
|
||||
self.timeout_days_remaining = timeout_days_remaining
|
||||
BannedInterstitial.__init__(self)
|
||||
|
||||
|
||||
class PrivateInterstitial(Interstitial):
|
||||
"""The interstitial shown on private subreddits."""
|
||||
pass
|
||||
|
||||
@@ -143,6 +143,7 @@ def header_url(url):
|
||||
def js_config(extra_config=None):
|
||||
logged = c.user_is_loggedin and c.user.name
|
||||
user_id = c.user_is_loggedin and c.user._id
|
||||
user_in_timeout = c.user_is_loggedin and c.user.in_timeout
|
||||
gold = bool(logged and c.user.gold)
|
||||
controller_name = request.environ['pylons.routes_dict']['controller']
|
||||
action_name = request.environ['pylons.routes_dict']['action']
|
||||
@@ -178,6 +179,8 @@ def js_config(extra_config=None):
|
||||
"logged": logged,
|
||||
# logged in user's id
|
||||
"user_id": user_id,
|
||||
# is user in timeout?
|
||||
"user_in_timeout": user_in_timeout,
|
||||
# the subreddit's name (for posts)
|
||||
"post_site": cur_subreddit,
|
||||
# the user's voting hash
|
||||
|
||||
@@ -865,6 +865,12 @@ class Subreddit(Thing, Printable, BaseSite):
|
||||
else:
|
||||
return self.is_allowed_to_view(user)
|
||||
|
||||
def is_banned(self, user):
|
||||
if user and user.in_timeout:
|
||||
return True
|
||||
else:
|
||||
return super(Subreddit, self).is_banned(user)
|
||||
|
||||
def is_allowed_to_view(self, user):
|
||||
"""Returns whether user can view based on permissions and settings"""
|
||||
if self.type in ('public', 'restricted',
|
||||
|
||||
@@ -16,6 +16,11 @@ r.actionForm = {
|
||||
toggleActionForm: function(e) {
|
||||
var el = e.target;
|
||||
var $el = $(el);
|
||||
|
||||
if (r.timeouts.isLinkRestricted(el)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var $thing = $el.thing();
|
||||
var $thingForm = $thing.find('> .entry .action-form');
|
||||
var formSelector = $el.data('action-form');
|
||||
|
||||
@@ -128,6 +128,7 @@ $(function() {
|
||||
r.newsletter.ui.init()
|
||||
r.cachePoisoning.init()
|
||||
r.locked.init();
|
||||
r.timeouts.init();
|
||||
} catch (err) {
|
||||
r.sendError('Error during base.js init', err.toString());
|
||||
}
|
||||
|
||||
@@ -83,6 +83,10 @@ $(function() {
|
||||
}
|
||||
|
||||
function openFlairSelector(e) {
|
||||
if (r.config.user_in_timeout) {
|
||||
return false;
|
||||
}
|
||||
|
||||
close_menus(e);
|
||||
|
||||
var button = this;
|
||||
|
||||
@@ -75,6 +75,10 @@ r.gold = {
|
||||
},
|
||||
|
||||
_toggleThingGoldForm: function (e) {
|
||||
if (r.config.user_in_timeout) {
|
||||
return;
|
||||
}
|
||||
|
||||
var $link = $(e.target);
|
||||
var $thing = $link.thing();
|
||||
var thingFullname = $link.thing_id();
|
||||
|
||||
@@ -248,6 +248,10 @@ function toggle_label (elem, callback, cancelback) {
|
||||
}
|
||||
|
||||
function toggle(elem, callback, cancelback) {
|
||||
if (r.config.user_in_timeout) {
|
||||
return false;
|
||||
}
|
||||
|
||||
r.analytics.breadcrumbs.storeLastClick(elem)
|
||||
|
||||
var self = $(elem).parent().addBack().filter(".option");
|
||||
@@ -845,6 +849,10 @@ function cancel_usertext(elem) {
|
||||
}
|
||||
|
||||
function reply(elem) {
|
||||
if (r.config.user_in_timeout) {
|
||||
return;
|
||||
}
|
||||
|
||||
var form = comment_reply_for_elem(elem);
|
||||
|
||||
// quote any selected text and put it in the textarea if it's empty
|
||||
|
||||
50
r2/r2/public/static/js/timeouts.js
Normal file
50
r2/r2/public/static/js/timeouts.js
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
If the current user is 'in timeout', show a modal on restricted actions.
|
||||
|
||||
requires r.config (base.js)
|
||||
requires r.ui.Popup (popup.js)
|
||||
*/
|
||||
!function(r) {
|
||||
// initialized early so click handlers can be bound on declaration
|
||||
r.timeouts = {};
|
||||
|
||||
_.extend(r.timeouts, {
|
||||
init: function() {
|
||||
if (!r.config.user_in_timeout) { return; }
|
||||
|
||||
$('body').on('click', '.access-required', this._handleClick);
|
||||
},
|
||||
|
||||
getPopup: function() {
|
||||
// gets the cached popup instance if available, otherwise creates it.
|
||||
if (this._popup) { return this._popup; }
|
||||
|
||||
var content = $('#access-popup').html();
|
||||
var popup = new r.ui.Popup({
|
||||
size: 'large',
|
||||
content: content,
|
||||
className: 'access-denied-modal',
|
||||
});
|
||||
|
||||
popup.$.on('click', '.interstitial .c-btn', this._handleModalClick);
|
||||
this._popup = popup;
|
||||
return popup;
|
||||
},
|
||||
|
||||
_handleClick: function onClick(e) {
|
||||
this.getPopup()
|
||||
.show();
|
||||
return false;
|
||||
}.bind(r.timeouts),
|
||||
|
||||
_handleModalClick: function onClick(e) {
|
||||
this.getPopup()
|
||||
.hide();
|
||||
return false;
|
||||
}.bind(r.timeouts),
|
||||
|
||||
isLinkRestricted: function(el) {
|
||||
return $(el).hasClass('access-required') && r.config.user_in_timeout;
|
||||
},
|
||||
});
|
||||
}(r);
|
||||
@@ -36,7 +36,7 @@
|
||||
$(document.body).on('click', '.arrow', function vote(e) {
|
||||
var $el = $(this);
|
||||
|
||||
if (!r.config.logged) {
|
||||
if (!r.config.logged || r.config.user_in_timeout) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
56
r2/r2/templates/intimeoutinterstitial.html
Normal file
56
r2/r2/templates/intimeoutinterstitial.html
Normal file
@@ -0,0 +1,56 @@
|
||||
## The contents of this file are subject to the Common Public Attribution
|
||||
## License Version 1.0. (the "License"); you may not use this file except in
|
||||
## compliance with the License. You may obtain a copy of the License at
|
||||
## http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
|
||||
## License Version 1.1, but Sections 14 and 15 have been added to cover use of
|
||||
## software over a computer network and provide for limited attribution for the
|
||||
## Original Developer. In addition, Exhibit A has been modified to be
|
||||
## consistent with Exhibit B.
|
||||
##
|
||||
## Software distributed under the License is distributed on an "AS IS" basis,
|
||||
## WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
||||
## the specific language governing rights and limitations under the License.
|
||||
##
|
||||
## The Original Code is reddit.
|
||||
##
|
||||
## The Original Developer is the Initial Developer. The Initial Developer of
|
||||
## the Original Code is reddit Inc.
|
||||
##
|
||||
## All portions of the code written by reddit are Copyright (c) 2006-2015
|
||||
## reddit Inc. All Rights Reserved.
|
||||
###############################################################################
|
||||
|
||||
<%!
|
||||
from r2.lib.template_helpers import static
|
||||
%>
|
||||
|
||||
<%namespace file="utils.html" import="_mdf"/>
|
||||
|
||||
<%inherit file="interstitial.html"/>
|
||||
|
||||
<%def name="interstitial_image_attrs()">
|
||||
src="${static('interstitial-image-banned.png')}"
|
||||
alt="${_('banned')}"
|
||||
</%def>
|
||||
|
||||
<%def name="interstitial_title()">
|
||||
%if thing.timeout_days_remaining:
|
||||
${_("You have been temporarily banned from Reddit")}
|
||||
%else:
|
||||
${_("You have been banned from Reddit")}
|
||||
%endif
|
||||
</%def>
|
||||
|
||||
<%def name="interstitial_message()">
|
||||
%if thing.message:
|
||||
${parent.interstitial_message()}
|
||||
%elif thing.timeout_days_remaining:
|
||||
<%
|
||||
days = ungettext('day', 'days', thing.timeout_days_remaining)
|
||||
%>
|
||||
${_mdf("You may not participate for %(num)s %(days)s.",
|
||||
num=thing.timeout_days_remaining, days=days)}
|
||||
%else:
|
||||
${_("You may not participate.")}
|
||||
%endif
|
||||
</%def>
|
||||
@@ -215,8 +215,9 @@ ${self.RenderPrintable()}
|
||||
arrow_class = arrow_type + ("mod" if mod else "")
|
||||
login_class = "login-required" if not g.read_only_mode else ""
|
||||
archived_class = "archived" if not getattr(thing, 'votable', True) else ""
|
||||
access_class = "access-required"
|
||||
%>
|
||||
<div ${classes("arrow", arrow_class, login_class, archived_class)}
|
||||
<div ${classes("arrow", arrow_class, login_class, archived_class, access_class)}
|
||||
%if not g.read_only_mode:
|
||||
role="button"
|
||||
% if dir > 0:
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
|
||||
%if thing.show_report:
|
||||
<li class="report-button">
|
||||
<a href="javascript:void(0)" class="action-thing" data-action-form="#report-action-form">
|
||||
<a href="javascript:void(0)" class="action-thing access-required" data-action-form="#report-action-form">
|
||||
${_("report")}
|
||||
</a>
|
||||
</li>
|
||||
@@ -139,7 +139,7 @@
|
||||
<li class="give-gold-button">
|
||||
<a href="/gold?goldtype=gift&months=1&thing=${thing.thing._fullname}"
|
||||
title="${_("give reddit gold in appreciation of this post.")}"
|
||||
class="give-gold login-required"
|
||||
class="give-gold login-required access-required"
|
||||
>${_("give gold")}</a>
|
||||
</li>
|
||||
% endif
|
||||
@@ -412,7 +412,7 @@
|
||||
%if not getattr(thing, "suppress_reply_buttons", False) and (thing.can_reply or thing.locked):
|
||||
<li class="reply-button">
|
||||
%if thing.can_reply:
|
||||
${self.simple_button(_("reply {verb}"), "reply")}
|
||||
${self.simple_button(_("reply {verb}"), "reply", "access-required")}
|
||||
%else:
|
||||
${self.simple_button(_("reply {verb}"), "void", css_class="locked-error")}
|
||||
%endif
|
||||
@@ -491,7 +491,7 @@
|
||||
%endif
|
||||
%if thing.can_reply:
|
||||
<li>
|
||||
${self.simple_button(_("reply {verb}"), "reply")}
|
||||
${self.simple_button(_("reply {verb}"), "reply", "access-required")}
|
||||
</li>
|
||||
%endif
|
||||
</%def>
|
||||
@@ -561,7 +561,7 @@
|
||||
<%
|
||||
if question is None:
|
||||
question = _("are you sure?")
|
||||
link = ('<a href="#" class="togglebutton" onclick="return toggle(this)">'
|
||||
link = ('<a href="#" class="togglebutton access-required" onclick="return toggle(this)">'
|
||||
+ conditional_websafe(title) + '</a>')
|
||||
link = conditional_websafe(format) % {format_arg : link}
|
||||
link = unsafe(link.replace(" <", " <").replace("> ", "> "))
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
|
||||
%if hasattr(thing, "goldlink"):
|
||||
<div class="giftgold">
|
||||
<a href="${thing.goldlink}">
|
||||
<a href="${thing.goldlink}" class="access-required">
|
||||
${thing.giftmsg}
|
||||
</a>
|
||||
</div>
|
||||
@@ -168,7 +168,9 @@
|
||||
%if not thing.viewing_self:
|
||||
<img src="${static('mailgray.png')}"/>
|
||||
 
|
||||
${plain_link(_("send a private message"), "/message/compose/?to=%s" % thing.user.name)}
|
||||
<a href="${"/message/compose/?to=%s" % thing.user.name}" class="access-required">
|
||||
${_("send a private message")}
|
||||
</a>
|
||||
%endif
|
||||
|
||||
<span class="age">
|
||||
|
||||
@@ -24,7 +24,14 @@
|
||||
from r2.config import feature
|
||||
from r2.lib.template_helpers import add_sr, static, join_urls, class_dict, get_domain
|
||||
from r2.lib.filters import unsafe, scriptsafe_dumps
|
||||
from r2.lib.pages import SearchForm, ClickGadget, SideContentBox, Login, ListingChooser
|
||||
from r2.lib.pages import (
|
||||
SearchForm,
|
||||
ClickGadget,
|
||||
SideContentBox,
|
||||
Login,
|
||||
ListingChooser,
|
||||
InTimeoutInterstitial,
|
||||
)
|
||||
from r2.lib import tracking
|
||||
from pylons import request
|
||||
from r2.lib.strings import strings
|
||||
@@ -174,6 +181,11 @@
|
||||
<%include file="prefoptions.html" />
|
||||
</script>
|
||||
%endif
|
||||
%if c.user_is_loggedin and c.user.in_timeout:
|
||||
<script id="access-popup" type="text/template">
|
||||
${InTimeoutInterstitial(timeout_days_remaining=thing.timeout_days_remaining)}
|
||||
</script>
|
||||
%endif
|
||||
%endif
|
||||
% if c.secure:
|
||||
## Pixel to pick up HSTS policies from the base domain
|
||||
|
||||
@@ -125,10 +125,12 @@
|
||||
mail_img_class = 'nohavemail'
|
||||
mail_img_title = "no new mod mail"
|
||||
mail_path = "/message/moderator/"
|
||||
|
||||
css_class = "%s access-required" % mail_img_class
|
||||
%>
|
||||
${plain_link(_("mod messages"), path=mail_path,
|
||||
title = mail_img_title, _sr_path = False,
|
||||
_id = "modmail", _class=mail_img_class)}
|
||||
_id = "modmail", _class=css_class)}
|
||||
${separator("|")}
|
||||
%endif
|
||||
%endif
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<div class="sidebox ${thing.css_class}${' disabled' if thing.disabled else ''}">
|
||||
<div class="morelink">
|
||||
${plain_link(thing.title, thing.link, _sr_path=thing.sr_path,
|
||||
_class='login-required' if thing.show_cover else None, nocname=thing.nocname,
|
||||
_class='login-required access-required' if thing.show_cover else None, nocname=thing.nocname,
|
||||
target=thing.target)}
|
||||
<div class="nub"> </div>
|
||||
</div>
|
||||
@@ -34,7 +34,7 @@
|
||||
%if thing.show_icon:
|
||||
<div class="spacer">
|
||||
${plain_link('', thing.link, _sr_path=thing.sr_path,
|
||||
_class='login-required' if thing.show_cover else None, nocname=thing.nocname)}
|
||||
_class='login-required access-required' if thing.show_cover else None, nocname=thing.nocname)}
|
||||
%else:
|
||||
<div class="spacer no-icon">
|
||||
%endif
|
||||
|
||||
@@ -23,7 +23,10 @@
|
||||
<%namespace file="utils.html" import="tags"/>
|
||||
<div class="sidecontentbox ${thing.extra_class or ''} ${'collapsible' if thing.collapsible else ''}" ${tags(_id=thing._id)}>
|
||||
%if thing.helplink:
|
||||
<a class="helplink" href="${thing.helplink[0]}">
|
||||
<%
|
||||
access_required = len(thing.helplink) < 3 or not thing.helplink[2]
|
||||
%>
|
||||
<a class="helplink ${'access-required' if access_required else ''}" href="${thing.helplink[0]}">
|
||||
${thing.helplink[1]}
|
||||
</a>
|
||||
%endif
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
${flair(thing, enabled=thing.force_show_flair)}
|
||||
%endif
|
||||
%if thing.include_flair_selector:
|
||||
(<a class="flairselectbtn" data-name="${thing.name}" href="javascript://void(0)">${_('edit')}</a>)
|
||||
(<a class="flairselectbtn access-required" data-name="${thing.name}" href="javascript://void(0)">${_('edit')}</a>)
|
||||
<div class="flairselector drop-choices"></div>
|
||||
%endif
|
||||
<span class="userattrs">
|
||||
|
||||
Reference in New Issue
Block a user