mirror of
https://github.com/reddit-archive/reddit.git
synced 2026-01-28 16:28:01 -05:00
Implement a flair selection interface.
This commit is contained in:
@@ -35,7 +35,8 @@ from r2.lib.utils import timeago, tup, filter_links, levenshtein
|
||||
from r2.lib.pages import EnemyList, FriendList, ContributorList, ModList, \
|
||||
BannedList, BoringPage, FormPage, CssError, UploadedImage, ClickGadget, \
|
||||
UrlParser, WrappedUser
|
||||
from r2.lib.pages import FlairList, FlairCsv, FlairTemplateEditor
|
||||
from r2.lib.pages import FlairList, FlairCsv, FlairTemplateEditor, \
|
||||
FlairSelector
|
||||
from r2.lib.utils.trial_utils import indict, end_trial, trial_info
|
||||
from r2.lib.pages.things import wrap_links, default_thing_wrapper
|
||||
|
||||
@@ -2026,9 +2027,10 @@ class ApiController(RedditController):
|
||||
VModhash(),
|
||||
flair_template_id = nop('id'),
|
||||
text = VFlairText('text'),
|
||||
css_class = VFlairCss('css_class'))
|
||||
css_class = VFlairCss('css_class'),
|
||||
text_editable = VBoolean('text_editable'))
|
||||
def POST_flairtemplate(self, form, jquery, flair_template_id, text,
|
||||
css_class):
|
||||
css_class, text_editable):
|
||||
# Check validation.
|
||||
if form.has_errors('css_class', errors.BAD_CSS_NAME):
|
||||
form.set_html(".status:first", _('invalid css class'))
|
||||
@@ -2042,11 +2044,13 @@ class ApiController(RedditController):
|
||||
ft = FlairTemplate._byID(flair_template_id)
|
||||
ft.text = text
|
||||
ft.css_class = css_class
|
||||
ft.text_editable = text_editable
|
||||
ft._commit()
|
||||
new = False
|
||||
else:
|
||||
ft = FlairTemplateBySubredditIndex.create_template(
|
||||
c.site._id, text=text, css_class=css_class)
|
||||
c.site._id, text=text, css_class=css_class,
|
||||
text_editable=text_editable)
|
||||
new = True
|
||||
|
||||
# TODO(intortus): ...
|
||||
@@ -2063,6 +2067,43 @@ class ApiController(RedditController):
|
||||
jquery('input[name="css_class"]').data('saved', css_class)
|
||||
form.set_html('.status', _('saved'))
|
||||
|
||||
def POST_flairselector(self):
|
||||
return FlairSelector().render()
|
||||
|
||||
@validatedForm(VUser(),
|
||||
VModhash(),
|
||||
flair_template_id = nop("flair_template_id"))
|
||||
def POST_selectflair(self, form, jquery, flair_template_id):
|
||||
flair_template = FlairTemplateBySubredditIndex.get_template(
|
||||
c.site._id, flair_template_id)
|
||||
|
||||
if not flair_template:
|
||||
# TODO: serve error to client
|
||||
g.log.debug('invalid flair template (%s) for subreddit %s',
|
||||
flair_template_id, c.site._id)
|
||||
return
|
||||
|
||||
text = flair_template.text
|
||||
css_class = flair_template.css_class
|
||||
|
||||
c.site.add_flair(c.user)
|
||||
setattr(c.user, 'flair_%s_text' % c.site._id, text)
|
||||
setattr(c.user, 'flair_%s_css_class' % c.site._id, css_class)
|
||||
c.user._commit()
|
||||
|
||||
g.log.debug('committed flair: %r, %r', text, css_class)
|
||||
|
||||
#jquery('input[name="text"]').data('saved', text).value(text)
|
||||
#jquery('input[name="css_class"]').data('saved', css_class).value(
|
||||
#css_class)
|
||||
|
||||
u = WrappedUser(c.user, force_show_flair=True,
|
||||
flair_text_editable=flair_template.text_editable)
|
||||
flair = u.render(style='html')
|
||||
jquery('#tabbedpane-grant .id-%s'
|
||||
% c.user._fullname).parent().html(flair)
|
||||
jquery('.flairtoggle .id-%s' % c.user._fullname).parent().html(flair)
|
||||
|
||||
@validatedForm(VAdminOrAdminSecret("secret"),
|
||||
award = VByName("fullname"),
|
||||
description = VLength("description", max_length=1000),
|
||||
|
||||
@@ -493,9 +493,9 @@ class SubredditInfoBar(CachedTemplate):
|
||||
self.path = request.path
|
||||
|
||||
if c.user_is_loggedin:
|
||||
self.flair_selector = FlairSelector()
|
||||
self.flair_prefs = FlairPrefs()
|
||||
else:
|
||||
self.flair_selector = None
|
||||
self.flair_prefs = None
|
||||
|
||||
CachedTemplate.__init__(self)
|
||||
|
||||
@@ -2271,7 +2271,7 @@ class WrappedUser(CachedTemplate):
|
||||
|
||||
def __init__(self, user, attribs = [], context_thing = None, gray = False,
|
||||
subreddit = None, force_show_flair = None,
|
||||
flair_template = None):
|
||||
flair_template = None, flair_text_editable = False):
|
||||
attribs.sort()
|
||||
author_cls = 'author'
|
||||
|
||||
@@ -2289,9 +2289,12 @@ class WrappedUser(CachedTemplate):
|
||||
has_flair = bool(flair_text or flair_css_class)
|
||||
|
||||
if flair_template:
|
||||
flair_template_id = flair_template._id
|
||||
flair_text = flair_template.text
|
||||
flair_css_class = flair_template.css_class
|
||||
has_flair = True
|
||||
else:
|
||||
flair_template_id = None
|
||||
|
||||
if flair_css_class:
|
||||
# This is actually a list of CSS class *suffixes*. E.g., "a b c"
|
||||
@@ -2318,7 +2321,9 @@ class WrappedUser(CachedTemplate):
|
||||
flair_enabled = flair_enabled,
|
||||
flair_position = flair_position,
|
||||
flair_text = flair_text,
|
||||
flair_text_editable = flair_text_editable,
|
||||
flair_css_class = flair_css_class,
|
||||
flair_template_id = flair_template_id,
|
||||
author_cls = author_cls,
|
||||
author_title = author_title,
|
||||
attribs = attribs,
|
||||
@@ -2526,21 +2531,30 @@ class FlairTemplateEditor(Templated):
|
||||
class FlairTemplateSample(Templated):
|
||||
"""Like a read-only version of FlairTemplateEditor."""
|
||||
def __init__(self, flair_template):
|
||||
wrapped_user = WrappedUser(c.user, subreddit=c.site,
|
||||
force_show_flair=True,
|
||||
wrapped_user = WrappedUser(c.user, subreddit=c.site, force_show_flair=True,
|
||||
flair_template=flair_template)
|
||||
Templated.__init__(self, flair_template_id=flair_template._id,
|
||||
wrapped_user=wrapped_user)
|
||||
|
||||
class FlairPrefs(CachedTemplate):
|
||||
def __init__(self):
|
||||
sr_flair_enabled = getattr(c.site, 'flair_enabled', True)
|
||||
user_flair_enabled = getattr(c.user, 'flair_%s_enabled' % c.site._id,
|
||||
True)
|
||||
wrapped_user = WrappedUser(c.user, subreddit=c.site,
|
||||
force_show_flair=True)
|
||||
CachedTemplate.__init__(self, sr_flair_enabled=sr_flair_enabled,
|
||||
user_flair_enabled=user_flair_enabled,
|
||||
wrapped_user=wrapped_user)
|
||||
|
||||
class FlairSelector(CachedTemplate):
|
||||
"""Provide user with flair options according to subreddit settings."""
|
||||
def __init__(self):
|
||||
get_flair_attr = lambda a, default=None: getattr(
|
||||
c.user, 'flair_%s_%s' % (c.site._id, a), default)
|
||||
user_flair_enabled = get_flair_attr('enabled', default=True)
|
||||
text = get_flair_attr('text')
|
||||
css_class = get_flair_attr('css_class')
|
||||
sr_flair_enabled = getattr(c.site, 'flair_enabled', True)
|
||||
position = getattr(c.site, 'flair_position', 'right')
|
||||
|
||||
attr_pattern = 'flair_%s_%%s' % c.site._id
|
||||
text = getattr(c.user, attr_pattern % 'text')
|
||||
css_class = getattr(c.user, attr_pattern % 'css_class')
|
||||
|
||||
ids = FlairTemplateBySubredditIndex.get_template_ids(c.site._id)
|
||||
# TODO(intortus): Maintain sorting.
|
||||
@@ -2559,10 +2573,9 @@ class FlairSelector(CachedTemplate):
|
||||
wrapped_user = WrappedUser(c.user, subreddit=c.site,
|
||||
force_show_flair=True)
|
||||
|
||||
Templated.__init__(self, user_flair_enabled=user_flair_enabled,
|
||||
text=text, css_class=css_class,
|
||||
sr_flair_enabled=sr_flair_enabled,
|
||||
choices=choices, matching_template=matching_template,
|
||||
Templated.__init__(self, text=text, css_class=css_class,
|
||||
position=position, choices=choices,
|
||||
matching_template=matching_template,
|
||||
wrapped_user=wrapped_user)
|
||||
|
||||
|
||||
|
||||
@@ -172,6 +172,12 @@ class FlairTemplateBySubredditIndex(tdb_cassandra.Thing):
|
||||
except tdb_cassandra.NotFound:
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def get_template(cls, sr_id, ft_id):
|
||||
if ft_id not in cls.get_template_ids(sr_id):
|
||||
return None
|
||||
return FlairTemplate._byID(ft_id)
|
||||
|
||||
def _index_keys(self):
|
||||
keys = set(self._dirties.iterkeys())
|
||||
keys |= frozenset(self._orig.iterkeys())
|
||||
|
||||
@@ -644,6 +644,10 @@ ul.flat-vert {text-align: left;}
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.flair input {
|
||||
font-size: xx-small;
|
||||
}
|
||||
|
||||
.link .flair {
|
||||
margin-top: -1px;
|
||||
}
|
||||
@@ -671,6 +675,8 @@ ul.flat-vert {text-align: left;}
|
||||
margin-right: 4ex;
|
||||
}
|
||||
|
||||
.flaircell.narrow, .flairlist .header.narrow { width: 14ex; }
|
||||
|
||||
.flairsample-left { text-align: right !important; }
|
||||
.flairsample-right { text-align: left !important; }
|
||||
|
||||
@@ -680,6 +686,26 @@ ul.flat-vert {text-align: left;}
|
||||
.flairrow button { display: none; }
|
||||
.flairrow .edited button { display: inline-block; }
|
||||
|
||||
.flairselector { font-size: x-small; position: absolute; width: 400px; }
|
||||
|
||||
.flairselector.drop-choices.active {
|
||||
border: 1px solid gray;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.flairselector ul { float: left; width: 200px; }
|
||||
|
||||
.flairselector .selected, .flairselector.active li {
|
||||
display: block;
|
||||
font-weight: normal;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.flairselector li { border: 1px solid white; cursor: pointer; }
|
||||
.flairselector li:hover { background-color: #bbb; border: 1px solid #bbb; }
|
||||
.flairselector li a:hover { text-decoration: none; }
|
||||
.flairselector li.selected { border: dashed 1px black; }
|
||||
|
||||
.media-button .option { color: red; }
|
||||
.media-button .option.active {
|
||||
background: transparent none no-repeat scroll right center;
|
||||
|
||||
@@ -23,6 +23,62 @@ $(function() {
|
||||
return function() { return onSubmit.call(this, action); };
|
||||
}
|
||||
|
||||
function toggleFlairSelector() {
|
||||
open_menu(this);
|
||||
$(this).addClass("active");
|
||||
return false;
|
||||
}
|
||||
|
||||
function postFlairSelection(e) {
|
||||
$(this).parent().parent().siblings("input").val(this.id);
|
||||
post_form(this.parentNode.parentNode.parentNode, "selectflair");
|
||||
return false;
|
||||
}
|
||||
|
||||
function openFlairSelector() {
|
||||
var button = this;
|
||||
|
||||
function columnize(col) {
|
||||
var max_col_height = 10;
|
||||
var length = $(col).children().length;
|
||||
var num_cols = Math.ceil(length / max_col_height);
|
||||
var height = Math.ceil(length / num_cols);
|
||||
var num_short_cols = num_cols * height - length;
|
||||
|
||||
for (var i = 1; i < num_cols; i++) {
|
||||
var h = height;
|
||||
if (i <= num_short_cols) {
|
||||
h--;
|
||||
}
|
||||
var start = length - h;
|
||||
length -= h;
|
||||
var tail = $(col).children().slice(start).detach();
|
||||
$(col).after($("<ul>").append(tail));
|
||||
}
|
||||
return num_cols * 200;
|
||||
}
|
||||
|
||||
function handleResponse(r) {
|
||||
$(".flairselector").html(r);
|
||||
|
||||
var width = columnize($(".flairselector ul"));
|
||||
|
||||
$(".flairselector").width(width)
|
||||
.css("left",
|
||||
($(button).position().left + $(button).width() - width)
|
||||
+ "px");
|
||||
$(".flairselector li").click(postFlairSelection);
|
||||
}
|
||||
|
||||
$(".flairselector").html('<img src="/static/throbber.gif" />');
|
||||
$(".flairselector").addClass("active").width(18)
|
||||
.css("left",
|
||||
($(button).position().left + $(button).width() - 18) + "px")
|
||||
.css("top", $(button).position().top + "px");
|
||||
$.request("flairselector", {}, handleResponse, true, "html");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Attach event handlers to the various flair forms that may be on page.
|
||||
$(".flairlist").delegate(".flairtemplate form", "submit",
|
||||
makeOnSubmit('flairtemplate'));
|
||||
@@ -30,10 +86,15 @@ $(function() {
|
||||
makeOnSubmit('flair'));
|
||||
$(".flairlist").delegate(".flaircell input", "focus", onFocus);
|
||||
$(".flairlist").delegate(".flaircell input", "keyup", onEdit);
|
||||
$(".flairlist").delegate(".flaircell input", "change", onEdit);
|
||||
|
||||
// Event handlers for sidebar flair prefs.
|
||||
$(".flairtoggle").submit(function() {
|
||||
return post_form(this, 'setflairenabled');
|
||||
});
|
||||
$(".flairtoggle input").change(function() { $(this).parent().submit(); });
|
||||
|
||||
$(".flairselectbtn").click(openFlairSelector);
|
||||
|
||||
$(".flairselector .dropdown").click(toggleFlairSelector);
|
||||
});
|
||||
|
||||
37
r2/r2/templates/flairprefs.html
Normal file
37
r2/r2/templates/flairprefs.html
Normal file
@@ -0,0 +1,37 @@
|
||||
## 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 CondeNet, Inc.
|
||||
##
|
||||
## All portions of the code written by CondeNet are Copyright (c) 2006-2010
|
||||
## CondeNet, Inc. All Rights Reserved.
|
||||
################################################################################
|
||||
|
||||
%if thing.sr_flair_enabled:
|
||||
<form class="toggle flairtoggle">
|
||||
<input type="hidden" name="flair_template_id">
|
||||
<input id="flair_enabled" type="checkbox" name="flair_enabled"
|
||||
%if thing.user_flair_enabled:
|
||||
checked="checked"
|
||||
%endif
|
||||
>
|
||||
${_("Show my flair on this reddit. It looks like:")}
|
||||
<div>${thing.wrapped_user}</div>
|
||||
<a class="flairselectbtn" href="javascript://">select</a>
|
||||
<div class="flairselector drop-choices"></div>
|
||||
</form>
|
||||
%endif
|
||||
|
||||
@@ -20,25 +20,15 @@
|
||||
## CondeNet, Inc. All Rights Reserved.
|
||||
################################################################################
|
||||
|
||||
%if thing.sr_flair_enabled:
|
||||
<form class="toggle flairtoggle">
|
||||
<input id="flair_enabled" type="checkbox" name="flair_enabled"
|
||||
%if thing.user_flair_enabled:
|
||||
checked="checked"
|
||||
%endif
|
||||
>
|
||||
${_("Show my flair on this reddit. It looks like:")}
|
||||
<div class="flairselector">
|
||||
<input type="hidden" name="flair_template_id">
|
||||
<span class="dropdown selected">${thing.wrapped_user}</span>
|
||||
<ul class="drop-choices">
|
||||
%for choice in thing.choices:
|
||||
<li class="flairsample-${thing.position}"
|
||||
id="${choice.flair_template_id}">
|
||||
${unsafe(choice.render())}
|
||||
</li>
|
||||
%endfor
|
||||
</ul>
|
||||
</div>
|
||||
</form>
|
||||
%endif
|
||||
<ul>
|
||||
%for choice in thing.choices:
|
||||
<%
|
||||
c = 'flairsample-%s' % thing.position
|
||||
if choice.flair_template_id == thing.matching_template:
|
||||
c += ' selected'
|
||||
%>
|
||||
<li class="${c}" id="${choice.flair_template_id}">
|
||||
${choice}
|
||||
</li>
|
||||
%endfor
|
||||
</ul>
|
||||
|
||||
@@ -33,6 +33,10 @@
|
||||
${unsafe(thing.sample.render())}
|
||||
%endif
|
||||
</span>
|
||||
<span class="flaircell narrow">
|
||||
<input type="checkbox" name="text_editable"
|
||||
${'checked="checked"' if thing.text_editable else ''} />
|
||||
</span>
|
||||
<span class="flaircell">
|
||||
<input type="text" size="32" maxlength="64" name="text"
|
||||
value="${thing.text}" />
|
||||
|
||||
@@ -28,13 +28,17 @@
|
||||
empty_template._committed = True # to disable unnecessary warning
|
||||
%>
|
||||
|
||||
<div class="usertable">
|
||||
<div class="flairlist usertable">
|
||||
<span class="header"> </span>
|
||||
<span class="header narrow">${_('user can edit?')}</span>
|
||||
<span class="header">${_('flair text')}</span>
|
||||
<span class="header">${_('css class')}</span>
|
||||
<div class="flairtemplatelist flairlist pretty-form">
|
||||
%for flair_template in thing.templates:
|
||||
${unsafe(flair_template.render())}
|
||||
${flair_template}
|
||||
%endfor
|
||||
<div id="empty-flair-template">
|
||||
${unsafe(FlairTemplateEditor(empty_template).render())}
|
||||
${FlairTemplateEditor(empty_template)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,4 +20,4 @@
|
||||
## CondeNet, Inc. All Rights Reserved.
|
||||
################################################################################
|
||||
|
||||
${unsafe(thing.wrapped_user.render())}
|
||||
${thing.wrapped_user}
|
||||
|
||||
@@ -59,8 +59,8 @@
|
||||
_("you are no longer an approved submitter"))}
|
||||
%endif
|
||||
|
||||
%if thing.flair_selector:
|
||||
${unsafe(thing.flair_selector.render())}
|
||||
%if thing.flair_prefs:
|
||||
${thing.flair_prefs}
|
||||
%endif
|
||||
|
||||
%if thing.sr.description:
|
||||
|
||||
@@ -27,7 +27,14 @@
|
||||
<% enabled = user.flair_enabled %>
|
||||
%endif
|
||||
%if user.has_flair and enabled:
|
||||
<span class="flair ${user.flair_css_class}">${user.flair_text}</span>
|
||||
<span class="flair ${user.flair_css_class}">
|
||||
%if user.flair_text_editable:
|
||||
<input class="editable_flair_text" type="text"
|
||||
value="${user.flair_text}">
|
||||
%else:
|
||||
${user.flair_text}
|
||||
%endif
|
||||
</span>
|
||||
%endif
|
||||
</%def>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user