diff --git a/r2/r2/controllers/api.py b/r2/r2/controllers/api.py index f4b8028b4..0d7dfb072 100644 --- a/r2/r2/controllers/api.py +++ b/r2/r2/controllers/api.py @@ -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), diff --git a/r2/r2/lib/pages/pages.py b/r2/r2/lib/pages/pages.py index 987577383..0099b3e60 100644 --- a/r2/r2/lib/pages/pages.py +++ b/r2/r2/lib/pages/pages.py @@ -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) diff --git a/r2/r2/models/flair.py b/r2/r2/models/flair.py index d8b85c351..a5c7db328 100644 --- a/r2/r2/models/flair.py +++ b/r2/r2/models/flair.py @@ -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()) diff --git a/r2/r2/public/static/css/reddit.css b/r2/r2/public/static/css/reddit.css index afc32d629..1a2d727dc 100644 --- a/r2/r2/public/static/css/reddit.css +++ b/r2/r2/public/static/css/reddit.css @@ -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; diff --git a/r2/r2/public/static/js/flair.js b/r2/r2/public/static/js/flair.js index 1931b5508..a002e3ed0 100644 --- a/r2/r2/public/static/js/flair.js +++ b/r2/r2/public/static/js/flair.js @@ -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($("
');
+ $(".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);
});
diff --git a/r2/r2/templates/flairprefs.html b/r2/r2/templates/flairprefs.html
new file mode 100644
index 000000000..71df07072
--- /dev/null
+++ b/r2/r2/templates/flairprefs.html
@@ -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:
+