Implement a flair selection interface.

This commit is contained in:
Logan Hanks
2011-08-18 11:24:14 -07:00
parent 87c52ce751
commit a43cef3b2f
12 changed files with 237 additions and 48 deletions

View File

@@ -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),

View File

@@ -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)

View File

@@ -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())

View File

@@ -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;

View File

@@ -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);
});

View 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

View File

@@ -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>

View File

@@ -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}" />

View File

@@ -28,13 +28,17 @@
empty_template._committed = True # to disable unnecessary warning
%>
<div class="usertable">
<div class="flairlist usertable">
<span class="header">&nbsp;</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>

View File

@@ -20,4 +20,4 @@
## CondeNet, Inc. All Rights Reserved.
################################################################################
${unsafe(thing.wrapped_user.render())}
${thing.wrapped_user}

View File

@@ -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:

View File

@@ -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>