A new submit page.

The ability to submit to any subreddit from the regular submit page.
The ability to add text to a self-post.
Improved comments pages.
Support for HD Youtube videos.
Numerous bug-fixes.
This commit is contained in:
spez
2009-06-18 10:55:14 -07:00
parent e713c0e561
commit 77e51a304d
71 changed files with 1949 additions and 1112 deletions

View File

@@ -61,9 +61,9 @@ def load_environment(global_conf={}, app_conf={}):
#tmpl_options['myghty.escapes'] = dict(l=webhelpers.auto_link, s=webhelpers.simple_format)
tmpl_options = config['buffet.template_options']
tmpl_options['mako.default_filters'] = ["websafe"]
tmpl_options['mako.default_filters'] = ["mako_websafe"]
tmpl_options['mako.imports'] = \
["from r2.lib.filters import websafe, unsafe",
["from r2.lib.filters import websafe, unsafe, mako_websafe",
"from pylons import c, g, request",
"from pylons.i18n import _, ungettext"]

View File

@@ -43,11 +43,12 @@ from r2.lib.menus import CommentSortMenu
from r2.lib.normalized_hot import expire_hot
from r2.lib.captcha import get_iden
from r2.lib.strings import strings
from r2.lib.filters import _force_unicode, websafe_json, spaceCompress
from r2.lib.filters import _force_unicode, websafe_json, websafe, spaceCompress
from r2.lib.db import queries
from r2.lib.media import force_thumbnail, thumbnail_url
from r2.lib.comment_tree import add_comment, delete_comment
from r2.lib import tracking, sup, cssfilter, emailer
from r2.lib.subreddit_search import search_reddits
from simplejson import dumps
@@ -89,28 +90,24 @@ class ApiController(RedditController):
@validatedForm(VCaptcha(),
name=VRequired('name', errors.NO_NAME),
email=VRequired('email', errors.NO_EMAIL),
replyto = ValidEmails("replyto", num = 1),
email=ValidEmails('email', num = 1),
reason = VOneOf('reason', ('ad_inq', 'feedback')),
message=VRequired('message', errors.NO_MESSAGE),
message=VRequired('text', errors.NO_TEXT),
)
def POST_feedback(self, form, jquery, name, email,
replyto, reason, message):
def POST_feedback(self, form, jquery, name, email, reason, message):
if not (form.has_errors('name', errors.NO_NAME) or
form.has_errors('email', errors.NO_EMAIL) or
(request.POST.get("replyto") and replyto is None and
form.has_errors("replyto", errors.BAD_EMAILS)) or
form.has_errors('personal', errors.NO_MESSAGE) or
form.chk_captcha(errors.BAD_CAPTCHA)):
form.has_errors('email', errors.BAD_EMAILS) or
form.has_errors('text', errors.NO_TEXT) or
form.has_errors('captcha', errors.BAD_CAPTCHA)):
if reason != 'ad_inq':
emailer.feedback_email(email, message, name = name or '',
reply_to = replyto or '')
emailer.feedback_email(email, message, name, reply_to = '')
else:
emailer.ad_inq_email(email, message, name = name or '',
reply_to = replyto or '')
emailer.ad_inq_email(email, message, name, reply_to = '')
form.set_html(".status", _("thanks for your message! "
"you should hear back from us shortly."))
form.set_inputs(personal = "", captcha = "")
form.set_inputs(text = "", captcha = "")
POST_ad_inq = POST_feedback
@@ -121,7 +118,7 @@ class ApiController(RedditController):
ip = ValidIP(),
to = VExistingUname('to'),
subject = VRequired('subject', errors.NO_SUBJECT),
body = VMessage('message'))
body = VMessage(['text', 'message']))
def POST_compose(self, form, jquery, to, subject, body, ip):
"""
handles message composition under /message/compose.
@@ -129,16 +126,15 @@ class ApiController(RedditController):
if not (form.has_errors("to", errors.USER_DOESNT_EXIST,
errors.NO_USER) or
form.has_errors("subject", errors.NO_SUBJECT) or
form.has_errors("message", errors.NO_MSG_BODY,
errors.COMMENT_TOO_LONG) or
form.chk_captcha(errors.BAD_CAPTCHA)):
form.has_errors("text", errors.NO_TEXT, errors.TOO_LONG) or
form.has_errors("captcha", errors.BAD_CAPTCHA)):
spam = (c.user._spam or
errors.BANNED_IP in c.errors or
errors.BANNED_DOMAIN in c.errors)
m, inbox_rel = Message._new(c.user, to, subject, body, ip, spam)
form.set_html(".success", _("your message has been delivered"))
form.set_inputs(to = "", subject = "", message = "")
form.set_html(".status", _("your message has been delivered"))
form.set_inputs(to = "", subject = "", text = "", captcha="")
if g.write_query_queue:
queries.new_message(m, inbox_rel)
@@ -155,39 +151,57 @@ class ApiController(RedditController):
url = VUrl(['url', 'sr']),
title = VTitle('title'),
save = VBoolean('save'),
then = VOneOf('then', ('tb', 'comments'), default='comments')
)
def POST_submit(self, form, jquery, url, title, save, sr, ip, then):
selftext = VSelfText('text'),
kind = VOneOf('kind', ['link', 'self', 'poll']),
then = VOneOf('then', ('tb', 'comments'), default='comments'))
def POST_submit(self, form, jquery, url, selftext, kind, title, save,
sr, ip, then):
#backwards compatability
if url == 'self':
kind = 'self'
if isinstance(url, (unicode, str)):
# VUrl may have replaced 'url' by adding 'http://'
form.set_inputs(url = url)
should_ratelimit = sr.should_ratelimit(c.user, 'link')
#remove the ratelimit error if the user's karma is high
if not should_ratelimit:
c.errors.remove(errors.RATELIMIT)
if not kind:
# this should only happen if somebody is trying to post
# links in some automated manner outside of the regular
# submission page, and hasn't updated their script
return
# check for no url, or clear that error field on return
if form.has_errors("url", errors.NO_URL, errors.BAD_URL):
pass
elif form.has_errors("url", errors.ALREADY_SUB):
form.redirect(url[0].already_submitted_link)
# check for title, otherwise look it up and return it
elif form.has_errors("title", errors.NO_TITLE):
# try to fetch the title
title = get_title(url)
if title:
# note: focus first, since it clears the form
form.set_inputs(title = title)
# wipe out the no title error
form.clear_errors(errors.NO_TITLE)
return
if form.has_errors('sr', errors.SUBREDDIT_NOEXIST,
errors.SUBREDDIT_REQUIRED):
# checking to get the error set in the form, but we can't
# check for rate-limiting if there's no subreddit
return
else:
should_ratelimit = sr.should_ratelimit(c.user, 'link')
#remove the ratelimit error if the user's karma is high
if not should_ratelimit:
c.errors.remove((errors.RATELIMIT, 'ratelimit'))
elif (form.has_errors("title", errors.TITLE_TOO_LONG) or
form.chk_captcha(errors.BAD_CAPTCHA, errors.RATELIMIT)):
if kind == 'link':
# check for no url, or clear that error field on return
if form.has_errors("url", errors.NO_URL, errors.BAD_URL):
pass
elif form.has_errors("url", errors.ALREADY_SUB):
form.redirect(url[0].already_submitted_link)
# check for title, otherwise look it up and return it
elif form.has_errors("title", errors.NO_TEXT):
pass
elif kind == 'self' and form.has_errors('text', errors.TOO_LONG):
pass
if form.has_error() or not title: return
if form.has_errors("title", errors.TOO_LONG, errors.NO_TEXT):
pass
if form.has_errors('ratelimit', errors.RATELIMIT):
pass
if form.has_error() or not title:
return
# check whether this is spam:
spam = (c.user._spam or
@@ -195,12 +209,17 @@ class ApiController(RedditController):
errors.BANNED_DOMAIN in c.errors)
# well, nothing left to do but submit it
l = Link._submit(request.post.title, url, c.user, sr, ip, spam)
if url.lower() == 'self':
l = Link._submit(request.post.title, url if kind == 'link' else 'self',
c.user, sr, ip, spam)
if kind == 'self':
l.url = l.make_permalink_slow()
l.is_self = True
l.selftext = selftext
l._commit()
l.set_url_cache()
v = Vote.vote(c.user, l, True, ip, spam)
if save:
r = l._save(c.user)
@@ -240,6 +259,18 @@ class ApiController(RedditController):
form.redirect(path)
@validatedForm(VUser(),
url = VSanitizedUrl(['url']))
def POST_fetch_title(self, form, jquery, url):
if url:
title = get_title(url)
if title:
form.set_inputs(title = title)
form.set_html(".title-status", "");
else:
form.set_html(".title-status", _("no title found"))
def _login(self, form, user, dest='', rem = None):
"""
AJAX login handler, used by both login and register to set the
@@ -282,7 +313,9 @@ class ApiController(RedditController):
form.has_errors("email", errors.BAD_EMAILS) or
form.has_errors("passwd", errors.BAD_PASSWORD) or
form.has_errors("passwd2", errors.BAD_PASSWORD_MATCH) or
form.chk_captcha(errors.BAD_CAPTCHA,errors.RATELIMIT)):
form.has_errors('ratelimit', errors.RATELIMIT) or
form.has_errors('captcha', errors.BAD_CAPTCHA)):
user = register(name, password)
VRatelimit.ratelimit(rate_ip = True, prefix = "rate_register_")
@@ -310,7 +343,6 @@ class ApiController(RedditController):
self._subscribe(sr, sub)
self._login(form, user, dest, rem)
@noresponse(VUser(),
VModhash(),
@@ -429,7 +461,6 @@ class ApiController(RedditController):
"""
# password is required to proceed
if form.has_errors("curpass", errors.WRONG_PASSWORD):
form.set_input(curpass = "")
return
# check if the email is valid. If one is given and it is
@@ -444,7 +475,7 @@ class ApiController(RedditController):
updated = True
# change password
if (password and
if (password and
not (form.has_errors("newpass", errors.BAD_PASSWORD) or
form.has_errors("verpass", errors.BAD_PASSWORD_MATCH))):
change_password(c.user, password)
@@ -454,6 +485,7 @@ class ApiController(RedditController):
else:
form.set_html('.status',
_('your password has been updated'))
form.set_inputs(curpass = "", newpass = "", verpass = "")
# the password has changed, so the user's cookie has been
# invalidated. drop a new cookie.
self.login(c.user)
@@ -509,32 +541,41 @@ class ApiController(RedditController):
Report.new(c.user, thing)
@validatedForm(VUser(), VModhash(),
comment = VByNameIfAuthor('parent'),
body = VComment('comment'))
def POST_editcomment(self, form, jquery, comment, body):
if not form.has_errors("comment",
errors.BAD_COMMENT, errors.COMMENT_TOO_LONG,
@validatedForm(VUser(),
VModhash(),
item = VByNameIfAuthor('thing_id'),
text = VComment('text'))
def POST_editusertext(self, form, jquery, item, text):
if not form.has_errors("text",
errors.NO_TEXT, errors.TOO_LONG,
errors.NOT_AUTHOR):
comment.body = body
comment.editted = True
comment._commit()
jquery.replace_things(comment, True, True)
if isinstance(item, Comment):
kind = 'comment'
item.body = text
elif isinstance(item, Link):
kind = 'link'
item.selftext = text
# flag search indexer that something has changed
tc.changed(comment)
item.editted = True
item._commit()
tc.changed(item)
if kind == 'link':
set_last_modified(item, 'comments')
wrapper = make_wrapper(ListingController.builder_wrapper,
expand_children = True)
jquery(".content").replace_things(item, True, True, wrap = wrapper)
@validatedForm(VUser(),
VModhash(),
VRatelimit(rate_user = True, rate_ip = True,
prefix = "rate_comment_"),
ip = ValidIP(),
parent = VSubmitParent('parent'),
comment = VComment('comment'))
VModhash(),
VRatelimit(rate_user = True, rate_ip = True,
prefix = "rate_comment_"),
ip = ValidIP(),
parent = VSubmitParent(['thing_id', 'parent']),
comment = VComment(['text', 'comment']))
def POST_comment(self, commentform, jquery, parent, comment, ip):
should_ratelimit = True
#check the parent type here cause we need that for the
@@ -559,14 +600,14 @@ class ApiController(RedditController):
if not should_ratelimit:
c.errors.remove(errors.RATELIMIT)
if (not commentform.has_errors("comment",
errors.BAD_COMMENT,
errors.COMMENT_TOO_LONG,
if (not commentform.has_errors("text",
errors.NO_TEXT,
errors.TOO_LONG,
errors.RATELIMIT) and
not commentform.has_errors("parent",
errors.DELETED_COMMENT)):
spam = (c.user._spam or errors.BANNED_IP in c.errors)
if is_message:
to = Account._byID(parent.author_id)
subject = parent.subject
@@ -607,7 +648,6 @@ class ApiController(RedditController):
# remove any null listings that may be present
jquery("#noresults").hide()
#update the queries
if g.write_query_queue:
if is_message:
@@ -627,10 +667,10 @@ class ApiController(RedditController):
VCaptcha(),
VRatelimit(rate_user = True, rate_ip = True,
prefix = "rate_share_"),
share_from = VLength('share_from', length = 100),
share_from = VLength('share_from', max_length = 100),
emails = ValidEmails("share_to"),
reply_to = ValidEmails("replyto", num = 1),
message = VLength("message", length = 1000),
message = VLength("message", max_length = 1000),
thing = VByName('parent'))
def POST_share(self, shareform, jquery, emails, thing, share_from, reply_to,
message):
@@ -641,14 +681,11 @@ class ApiController(RedditController):
if not should_ratelimit:
c.errors.remove(errors.RATELIMIT)
if emails and (errors.NO_EMAILS in c.errors):
c.errors.remove(errors.NO_EMAILS)
# share_from and messages share a comment_too_long error.
# share_from and messages share a too_long error.
# finding an error on one necessitates hiding the other error
if shareform.has_errors("share_from", errors.COMMENT_TOO_LONG):
if shareform.has_errors("share_from", errors.TOO_LONG):
shareform.find(".message-errors").children().hide()
elif shareform.has_errors("message", errors.COMMENT_TOO_LONG):
elif shareform.has_errors("message", errors.TOO_LONG):
shareform.find(".share-form-errors").children().hide()
# reply_to and share_to also share errors...
elif shareform.has_errors("share_to", errors.BAD_EMAILS,
@@ -656,11 +693,12 @@ class ApiController(RedditController):
errors.TOO_MANY_EMAILS):
shareform.find(".reply-to-errors").children().hide()
elif shareform.has_errors("replyto", errors.BAD_EMAILS,
errors.NO_EMAILS,
errors.TOO_MANY_EMAILS):
shareform.find(".share-to-errors").children().hide()
# lastly, check the captcha.
elif shareform.chk_captcha(errors.BAD_CAPTCHA, errors.RATELIMIT):
elif shareform.has_errors("captcha", errors.BAD_CAPTCHA):
pass
elif shareform.has_errors("ratelimit", errors.RATELIMIT):
pass
else:
c.user.add_share_emails(emails)
@@ -845,7 +883,7 @@ class ApiController(RedditController):
@validate(VSrModerator(),
VModhash(),
file = VLength('file', length=1024*500),
file = VLength('file', max_length=1024*500),
name = VCssName("name"),
header = nop('header'))
def POST_upload_sr_img(self, file, header, name):
@@ -910,9 +948,9 @@ class ApiController(RedditController):
prefix = 'create_reddit_'),
sr = VByName('sr'),
name = VSubredditName("name"),
title = VSubredditTitle("title"),
title = VLength("title", max_length = 100),
domain = VCnameDomain("domain"),
description = VSubredditDesc("description"),
description = VLength("description", max_length = 500),
lang = VLang("lang"),
over_18 = VBoolean('over_18'),
show_media = VBoolean('show_media'),
@@ -946,12 +984,12 @@ class ApiController(RedditController):
elif not sr and form.has_errors("name", errors.SUBREDDIT_EXISTS,
errors.BAD_SR_NAME):
form.find('#example_name').hide()
elif form.has_errors('title', errors.NO_TITLE, errors.TITLE_TOO_LONG):
elif form.has_errors('title', errors.NO_TEXT, errors.TOO_LONG):
form.find('#example_title').hide()
elif form.has_errors('domain', errors.BAD_CNAME, errors.USED_CNAME):
form.find('#example_domain').hide()
elif (form.has_errors(None, errors.INVALID_OPTION) or
form.has_errors('description', errors.DESC_TOO_LONG)):
form.has_errors('description', errors.TOO_LONG)):
pass
#creating a new reddit
elif not sr:
@@ -1141,8 +1179,11 @@ class ApiController(RedditController):
@validatedForm(user = VUserWithEmail('name'))
def POST_password(self, form, jquery, user):
if not form.has_errors('name', errors.USER_DOESNT_EXIST,
errors.NO_EMAIL_FOR_USER):
if form.has_errors('name', errors.USER_DOESNT_EXIST):
return
elif form.has_errors('name', errors.NO_EMAIL_FOR_USER):
return
else:
emailer.password_email(user)
form.set_html(".status", _("an email will be sent to that account's address shortly"))
@@ -1150,16 +1191,17 @@ class ApiController(RedditController):
@validatedForm(cache_evt = VCacheKey('reset', ('key', 'name')),
password = VPassword(['passwd', 'passwd2']))
def POST_resetpassword(self, form, jquery, cache_evt, password):
if errors.BAD_USERNAME in c.errors:
# clear reset event -- the user failed to know their user name
if form.has_errors('name', errors.EXPIRED):
cache_evt.clear()
return form.redirect('/password')
elif (not form.has_errors('passwd', errors.BAD_PASSWORD) and
not form.has_errors('passwd2', errors.BAD_PASSWORD_MATCH) and
cache_evt.user):
form.redirect('/password')
elif form.has_errors('passwd', errors.BAD_PASSWORD):
pass
elif form.has_errors('passwd2', errors.BAD_PASSWORD_MATCH):
pass
elif cache_evt.user:
# successfully entered user name and valid new password
change_password(cache_evt.user, password)
self._login(jquery, cache_evt.user, '/resetpassword')
self._login(jquery, cache_evt.user, '/')
cache_evt.clear()
@@ -1239,7 +1281,7 @@ class ApiController(RedditController):
l.num_margin = 0
l.mid_margin = 0
jquery.replace_things(l, stubs = True)
jquery(".content").replace_things(l, stubs = True)
if show:
jquery('.organic-listing .link:visible').hide()
@@ -1289,7 +1331,7 @@ class ApiController(RedditController):
# we're allowing mutliple submissions, so we really just
# want the URL
url = url[0].url
if form.has_errors('title', errors.NO_TITLE):
if form.has_errors('title', errors.NO_TEXT, errors.TOO_LONG):
pass
elif form.has_errors('url', errors.NO_URL, errors.BAD_URL):
pass
@@ -1345,7 +1387,7 @@ class ApiController(RedditController):
@validate(VSponsor(),
link = VByName('link_id'),
file = VLength('file',500*1024))
file = VLength('file', 500*1024))
def POST_link_thumb(self, link=None, file=None):
errors = dict(BAD_CSS_NAME = "", IMAGE_ERROR = "")
try:
@@ -1407,3 +1449,18 @@ class ApiController(RedditController):
ip = request.ip)
)
@json_validate(query = nop('query'))
def POST_search_reddit_names(self, query):
names = []
if query:
names = search_reddits(query)
return {'names': names}
@validate(link = VByName('link_id', thing_cls = Link))
def POST_expando(self, link):
if not link:
abort(404, 'not found')
wrapped = IDBuilder([link._fullname]).get_items()[0][0]
return spaceCompress(websafe(wrapped.link_child.content()))

View File

@@ -25,17 +25,13 @@ from copy import copy
error_list = dict((
('USER_REQUIRED', _("please login to do that")),
('NO_URL', _('url required')),
('NO_URL', _('a url is required')),
('BAD_URL', _('you should check that url')),
('NO_TITLE', _('title required')),
('TITLE_TOO_LONG', _('you can be more succinct than that')),
('COMMENT_TOO_LONG', _('you can be more succinct than that')),
('BAD_CAPTCHA', _('your letters stink')),
('BAD_CAPTCHA', _('care to try these again?')),
('BAD_USERNAME', _('invalid user name')),
('USERNAME_TAKEN', _('that username is already taken')),
('NO_THING_ID', _('id not specified')),
('NOT_AUTHOR', _("you can't do that")),
('BAD_COMMENT', _('please enter a comment')),
('DELETED_COMMENT', _('that comment has been deleted')),
('DELETED_THING', _('that element has been deleted.')),
('BAD_PASSWORD', _('invalid password')),
@@ -44,9 +40,7 @@ error_list = dict((
('NO_NAME', _('please enter a name')),
('NO_EMAIL', _('please enter an email address')),
('NO_EMAIL_FOR_USER', _('no email address for that user')),
('NO_MESSAGE', _('please enter a message')),
('NO_TO_ADDRESS', _('send it to whom?')),
('NO_MSG_BODY', _('please enter a message')),
('NO_SUBJECT', _('please enter a subject')),
('USER_DOESNT_EXIST', _("that user doesn't exist")),
('NO_USER', _('please enter a username')),
@@ -55,6 +49,7 @@ error_list = dict((
('ALREADY_SUB', _("that link has already been submitted")),
('SUBREDDIT_EXISTS', _('that reddit already exists')),
('SUBREDDIT_NOEXIST', _('that reddit doesn\'t exist')),
('SUBREDDIT_REQUIRED', _('you must specify a reddit')),
('BAD_SR_NAME', _('that name isn\'t going to work')),
('RATELIMIT', _('you are trying to submit too fast. try again in %(time)s.')),
('EXPIRED', _('your session has expired')),
@@ -64,11 +59,13 @@ error_list = dict((
('BAD_CNAME', "that domain isn't going to work"),
('USED_CNAME', "that domain is already in use"),
('INVALID_OPTION', _('that option is not valid')),
('DESC_TOO_LONG', _('description is too long')),
('CHEATER', 'what do you think you\'re doing there?'),
('BAD_EMAILS', _('the following emails are invalid: %(emails)s')),
('NO_EMAILS', _('please enter at least one email address')),
('TOO_MANY_EMAILS', _('please only share to %(num)s emails at a time.')),
('TOO_LONG', _("this is too long (max: %(max_length)s)")),
('NO_TEXT', _('we need something here')),
))
errors = Storage([(e, e) for e in error_list.keys()])
@@ -97,8 +94,10 @@ class ErrorSet(object):
def __init__(self):
self.errors = {}
def __contains__(self, error_name):
return self.errors.has_key(error_name)
def __contains__(self, pair):
"""Expectes an (error_name, field_name) tuple and checks to
see if it's in the errors list."""
return self.errors.has_key(pair)
def __getitem__(self, name):
return self.errors[name]
@@ -110,16 +109,16 @@ class ErrorSet(object):
for x in self.errors:
yield x
def _add(self, error_name, msg, msg_params = {}, field = None):
self.errors[error_name] = Error(error_name, msg, msg_params,
field = field)
def add(self, error_name, msg_params = {}, field = None):
msg = error_list[error_name]
self._add(error_name, msg, msg_params = msg_params, field = field)
for field_name in tup(field):
e = Error(error_name, msg, msg_params, field = field_name)
self.errors[(error_name, field_name)] = e
def remove(self, error_name):
if self.errors.has_key(error_name):
del self.errors[error_name]
def remove(self, pair):
"""Expectes an (error_name, field_name) tuple and removes it
from the errors list."""
if self.errors.has_key(pair):
del self.errors[pair]
class UserRequiredException(Exception): pass

View File

@@ -26,29 +26,16 @@ from r2.lib.pages import FormPage, Feedback, Captcha
class FeedbackController(RedditController):
def _feedback(self, name = '', email = '', message='',
replyto='', action=''):
title = _("inquire about advertising on reddit") if action else ''
captcha = Captcha() if not c.user_is_loggedin \
or c.user.needs_captcha() else None
if request.get.has_key("done"):
success = _("thanks for your message! you should hear back from us shortly.")
else:
success = ''
return FormPage(_("advertise") if action == 'ad_inq' \
else _("feedback"),
content = Feedback(captcha=captcha,
message=message,
replyto=replyto,
email=email, name=name,
success=success,
action=action,
title=title),
def GET_ad_inq(self):
title = _("inquire about advertising on reddit")
return FormPage('advertise',
content = Feedback(title=title,
action='ad_inq'),
loginbox = False).render()
def GET_ad_inq(self):
return self._feedback(action='ad_inq')
def GET_feedback(self):
return self._feedback()
title = _("send reddit feedback")
return FormPage('feedback',
content = Feedback(title=title,
action='feedback'),
loginbox = False).render()

View File

@@ -109,7 +109,7 @@ class FrontController(RedditController):
if not key and request.referer:
referer_path = request.referer.split(g.domain)[-1]
done = referer_path.startswith(request.fullpath)
elif not cache_evt.user:
elif not getattr(cache_evt, "user", None):
return self.abort404()
return BoringPage(_("reset password"),
content=ResetPassword(key=key, done=done)).render()
@@ -174,12 +174,11 @@ class FrontController(RedditController):
# insert reply box only for logged in user
if c.user_is_loggedin and article.subreddit_slow.can_comment(c.user):
#no comment box for permalinks
if not comment:
displayPane.append(CommentReplyBox(link_name =
article._fullname))
else:
displayPane.append(CommentReplyBox())
displayPane.append(UserText(item = article, creating = True,
post_form = 'comment',
display = not bool(comment),
cloneable = True))
# finally add the comment listing
displayPane.append(listing.listing())
@@ -498,7 +497,9 @@ class FrontController(RedditController):
return res
captcha = Captcha() if c.user.needs_captcha() else None
sr_names = Subreddit.submit_sr_names(c.user) if c.default_sr else ()
sr_names = (Subreddit.submit_sr_names(c.user) or
Subreddit.submit_sr_names(None))
return FormPage(_("submit"),
content=NewLink(url=url or '',

View File

@@ -48,12 +48,15 @@ class Validator(object):
self.default = default
self.post, self.get, self.url = post, get, url
def set_error(self, error, msg_params = {}):
def set_error(self, error, msg_params = {}, field = False):
"""
Adds the provided error to c.errors and flags that it is come
from the validator's param
"""
c.errors.add(error, msg_params = msg_params, field = self.param)
if field is False:
field = self.param
c.errors.add(error, msg_params = msg_params, field = field)
def __call__(self, url):
a = []
@@ -135,7 +138,7 @@ def api_validate(response_function):
simple_vals, param_vals, *a, **kw)
except UserRequiredException:
responder.send_failure(errors.USER_REQUIRED)
return self.response_func(dict(iter(responder)))
return self.response_func(responder.make_response())
return newfn
return val
return _api_validate
@@ -146,6 +149,10 @@ def noresponse(self, self_method, responder, simple_vals, param_vals, *a, **kw):
self_method(self, *a, **kw)
return self.response_func({})
@api_validate
def json_validate(self, self_method, responder, simple_vals, param_vals, *a, **kw):
r = self_method(self, *a, **kw)
return self.response_func(r)
@api_validate
def validatedForm(self, self_method, responder, simple_vals, param_vals,
@@ -156,16 +163,19 @@ def validatedForm(self, self_method, responder, simple_vals, param_vals,
# clear out the status line as a courtesy
form.set_html(".status", "")
# do the actual work
val = self_method(self, form, responder, *a, **kw)
# auto-refresh the captcha if there are errors.
if (c.errors.errors and
any(isinstance(v, VCaptcha) for v in simple_vals)):
form.has_errors('captcha', errors.BAD_CAPTCHA)
form.new_captcha()
if val: return val
return self.response_func(dict(iter(responder)))
# do the actual work
val = self_method(self, form, responder, *a, **kw)
if val:
return val
else:
return self.response_func(responder.make_response())
@@ -266,46 +276,41 @@ def chksrname(x):
class VLength(Validator):
def __init__(self, item, length = 10000,
empty_error = errors.BAD_COMMENT,
length_error = errors.COMMENT_TOO_LONG, **kw):
Validator.__init__(self, item, **kw)
self.length = length
self.len_error = length_error
self.emp_error = empty_error
only_whitespace = re.compile(r"^\s*$", re.UNICODE)
def run(self, title):
if not title:
self.set_error(self.emp_error)
elif len(title) > self.length:
self.set_error(self.len_error)
def __init__(self, param, max_length,
empty_error = errors.NO_TEXT,
length_error = errors.TOO_LONG,
**kw):
Validator.__init__(self, param, **kw)
self.max_length = max_length
self.length_error = length_error
self.empty_error = empty_error
def run(self, text, text2 = ''):
text = text or text2
if self.empty_error and (not text or self.only_whitespace.match(text)):
self.set_error(self.empty_error)
elif len(text) > self.max_length:
self.set_error(self.length_error, {'max_length': self.max_length})
else:
return title
return text
class VTitle(VLength):
only_whitespace = re.compile(r"^\s*$", re.UNICODE)
def __init__(self, item, length = 300, **kw):
VLength.__init__(self, item, length = length,
empty_error = errors.NO_TITLE,
length_error = errors.TITLE_TOO_LONG, **kw)
def run(self, title):
title = VLength.run(self, title)
if title and self.only_whitespace.match(title):
self.set_error(errors.NO_TITLE)
else:
return title
def __init__(self, param, max_length = 300, **kw):
VLength.__init__(self, param, max_length, **kw)
class VComment(VLength):
def __init__(self, item, length = 10000, **kw):
VLength.__init__(self, item, length = length, **kw)
def __init__(self, param, max_length = 10000, **kw):
VLength.__init__(self, param, max_length, **kw)
class VSelfText(VLength):
def __init__(self, param, max_length = 10000, **kw):
VLength.__init__(self, param, max_length, **kw)
class VMessage(VLength):
def __init__(self, item, length = 10000, **kw):
VLength.__init__(self, item, length = length,
empty_error = errors.NO_MSG_BODY, **kw)
def __init__(self, param, max_length = 10000, **kw):
VLength.__init__(self, param, max_length, **kw)
class VSubredditName(VRequired):
@@ -388,7 +393,7 @@ class VByNameIfAuthor(VByName):
if not thing._loaded: thing._load()
if c.user_is_loggedin and thing.author_id == c.user._id:
return thing
return self.error(errors.NOT_AUTHOR)
return self.set_error(errors.NOT_AUTHOR)
class VCaptcha(Validator):
default_param = ('iden', 'captcha')
@@ -466,7 +471,9 @@ class VSRSubmitPage(Validator):
abort(403, "forbidden")
class VSubmitParent(VByName):
def run(self, fullname):
def run(self, fullname, fullname2):
#for backwards compatability (with iphone app)
fullname = fullname or fullname2
if fullname:
parent = VByName.run(self, fullname)
if parent and parent._deleted:
@@ -482,30 +489,34 @@ class VSubmitParent(VByName):
class VSubmitSR(Validator):
def run(self, sr_name):
if not sr_name:
self.set_error(errors.SUBREDDIT_REQUIRED)
return None
try:
sr = Subreddit._by_name(sr_name)
except (NotFound, AttributeError):
self.set_error(errors.SUBREDDIT_NOEXIST)
sr = None
return None
if sr and not (c.user_is_loggedin and sr.can_submit(c.user)):
abort(403, "forbidden")
else:
return sr
pass_rx = re.compile(r".{3,20}")
pass_rx = re.compile(r"^.{3,20}$")
def chkpass(x):
return x if x and pass_rx.match(x) else None
class VPassword(VRequired):
def __init__(self, item, *a, **kw):
VRequired.__init__(self, item, errors.BAD_PASSWORD, *a, **kw)
class VPassword(Validator):
def run(self, password, verify):
if not chkpass(password):
return self.error()
self.set_error(errors.BAD_PASSWORD)
return
elif verify != password:
return self.error(errors.BAD_PASSWORD_MATCH)
self.set_error(errors.BAD_PASSWORD_MATCH)
return password
else:
return password
@@ -695,11 +706,11 @@ class VRatelimit(Validator):
# when errors have associated field parameters, we'll need
# to add that here
if self.error == errors.RATELIMIT:
self.set_error(errors.RATELIMIT, {'time': time})
self.set_error(errors.RATELIMIT, {'time': time},
field = 'ratelimit')
else:
self.set_error(self.error)
@classmethod
def ratelimit(self, rate_user = False, rate_ip = False, prefix = "rate_",
seconds = None):
@@ -736,16 +747,13 @@ class VCacheKey(Validator):
self.key = key
if key:
uid = g.cache.get(str(self.cache_prefix + "_" + self.key))
try:
a = Account._byID(uid, data = True)
if name and a.name.lower() != name.lower():
self.set_error(errors.BAD_USERNAME)
else:
self.user = a
except NotFound:
self.set_error(errors.BAD_USERNAME)
if uid:
try:
self.user = Account._byID(uid, data = True)
except NotFound:
return
#found everything we need
return self
self.set_error(errors.EXPIRED)
class VOneOf(Validator):

View File

@@ -51,6 +51,7 @@ def simple_email(to, fr, subj, body):
def password_email(user):
key = passhash(random.randint(0, 1000), user.email)
passlink = 'http://' + g.domain + '/resetpassword/' + key
print passlink
cache.set("reset_%s" %key, user._id, time=1800)
simple_email(user.email, 'reddit@reddit.com',
'reddit.com password reset',

View File

@@ -88,13 +88,19 @@ def unsafe(text=''):
def websafe_json(text=""):
return c_websafe_json(_force_unicode(text))
def websafe(text=''):
def mako_websafe(text = ''):
if text.__class__ == _Unsafe:
return text
elif text.__class__ != unicode:
text = _force_unicode(text)
return c_websafe(text)
def websafe(text=''):
if text.__class__ != unicode:
text = _force_unicode(text)
#wrap the response in _Unsafe so make_websafe doesn't unescape it
return _Unsafe(c_websafe(text))
from mako.filters import url_escape
def edit_comment_filter(text = ''):
try:

View File

@@ -26,11 +26,17 @@ from r2.lib.filters import websafe_json
from r2.lib.template_helpers import replace_render
from r2.lib.jsontemplates import get_api_subtype
from r2.lib.base import BaseController
from r2.models import IDBuilder, Listing
import simplejson
from pylons import c
from pylons import c, g
def json_respond(x):
return websafe_json(simplejson.dumps(x or ''))
if g.debug:
return websafe_json(simplejson.dumps(x or '',
sort_keys=True, indent=4))
else:
return websafe_json(simplejson.dumps(x or ''))
class JsonResponse(object):
"""
@@ -42,14 +48,14 @@ class JsonResponse(object):
self._clear()
def _clear(self):
self._has_errors = set([])
self._errors = set()
self._new_captcha = False
self._data = {}
def send_failure(self, error):
c.errors.add(error)
self._clear()
self._has_errors.add(error)
self._errors.add((error, None))
def __call__(self, *a):
return self
@@ -57,58 +63,39 @@ class JsonResponse(object):
def __getattr__(self, key):
return self
def __iter__(self):
def make_response(self):
res = {}
if self._data:
res['data'] = self._data
res['errors'] = [(e, c.errors[e].message) for e in self._has_errors]
yield ("json", res)
def _mark_error(self, e):
pass
def _unmark_error(self, e):
pass
res['errors'] = [(e[0], c.errors[e].message) for e in self._errors]
return {"json": res}
def set_error(self, error_name, field_name):
self._errors.add((error_name, field_name))
def has_error(self):
return bool(self._has_errors)
return bool(self._errors)
def has_errors(self, input, *errors, **kw):
rval = False
for e in errors:
if e in c.errors:
# get list of params checked to generate this error
# if they exist, make sure they match input checked
fields = c.errors[e].fields
if not input or not fields or input in fields:
self._has_errors.add(e)
rval = True
self._mark_error(e)
else:
self._unmark_error(e)
if rval and input:
self.focus_input(input)
return rval
def clear_errors(self, *errors):
for e in errors:
if e in self._has_errors:
self._has_errors.remove(e)
self._unmark_error(e)
def has_errors(self, field_name, *errors, **kw):
have_error = False
for error_name in errors:
if (error_name, field_name) in c.errors:
self.set_error(error_name, field_name)
have_error = True
return have_error
def _things(self, things, action, *a, **kw):
"""
function for inserting/replacing things in listings.
"""
from r2.models import IDBuilder, Listing
listing = None
if isinstance(things, Listing):
listing = things.listing()
things = listing.things
things = tup(things)
if not all(isinstance(t, Wrapped) for t in things):
b = IDBuilder([t._fullname for t in things])
wrap = kw.pop('wrap', Wrapped)
b = IDBuilder([t._fullname for t in things], wrap)
things = b.get_items()[0]
data = [replace_render(listing, t) for t in things]
@@ -162,7 +149,9 @@ class JQueryResponse(JsonResponse):
JsonResponse._clear(self)
def send_failure(self, error):
JsonResponse.send_failure(self, error)
c.errors.add(error)
self._clear()
self._errors.add((self, error, None))
self.refresh()
def __call__(self, *a):
@@ -178,8 +167,25 @@ class JQueryResponse(JsonResponse):
self.ops.append([self.objs[obj], newi, op, args])
return new
def __iter__(self):
yield ("jquery", self.ops)
def set_error(self, error_name, field_name):
#self is the form that had the error checked, but we need to
#add this error to the top_node of this response and give it a
#reference to the form.
self.top_node._errors.add((self, error_name, field_name))
def has_error(self):
return bool(self.top_node._errors)
def make_response(self):
#add the error messages
for (form, error_name, field_name) in self._errors:
selector = ".error." + error_name
if field_name:
selector += ".field-" + field_name
message = c.errors[(error_name, field_name)].message
form.find(selector).show().html(message).end()
return {"jquery": self.ops}
# thing methods
#--------------
@@ -196,22 +202,17 @@ class JQueryResponse(JsonResponse):
# convenience methods:
# --------------------
def _mark_error(self, e):
self.find("." + e).show().html(c.errors[e].message).end()
def _unmark_error(self, e):
self.find("." + e).html("").end()
#def _mark_error(self, e, field):
# self.find("." + e).show().html(c.errors[e].message).end()
#
#def _unmark_error(self, e):
# self.find("." + e).html("").end()
def new_captcha(self):
if not self._new_captcha:
self.captcha(get_iden())
self._new_captcha = True
def chk_captcha(self, *errors):
if self.has_errors(None, *errors):
self.new_captcha()
return True
def get_input(self, name):
return self.find("*[name=%s]" % name)

View File

@@ -42,10 +42,6 @@ def make_typename(typ):
def make_fullname(typ, _id):
return '%s_%s' % (make_typename(typ), to36(_id))
def mass_part_render(thing, **kw):
return dict([(k, spaceCompress(thing.part_render(v)).strip(' ')) \
for k, v in kw.iteritems()])
class JsonTemplate(Template):
def __init__(self): pass
@@ -253,8 +249,8 @@ class CommentJsonTemplate(ThingJsonTemplate):
else:
parent_id = make_fullname(Comment, parent_id)
d = ThingJsonTemplate.rendered_data(self, wrapped)
d.update(mass_part_render(wrapped, contentHTML = 'commentBody',
contentTxt = 'commentText'))
d['contentText'] = self.thing_attr(wrapped, 'body')
d['contentHTML'] = self.thing_attr(wrapped, 'body_html')
d['parent'] = parent_id
d['link'] = make_fullname(Link, wrapped.link_id)
return d
@@ -268,6 +264,9 @@ class MoreCommentJsonTemplate(CommentJsonTemplate):
def kind(self, wrapped):
return "more"
def rendered_data(self, wrapped):
return ThingJsonTemplate.rendered_data(self, wrapped)
class MessageJsonTemplate(ThingJsonTemplate):
_data_attrs_ = ThingJsonTemplate.data_attrs(new = "new",
subject = "subject",

View File

@@ -20,7 +20,7 @@
# All portions of the code written by CondeNet are Copyright (c) 2006-2009
# CondeNet, Inc. All Rights Reserved.
################################################################################
from wrapped import Wrapped
from wrapped import Wrapped, Styled
from pylons import c, request, g
from utils import query_string, timeago
from strings import StringHandler, plurals
@@ -150,30 +150,6 @@ menu = MenuHandler(hot = _('hot'),
current_promos = _('promoted links'),
)
class Styled(Wrapped):
"""Rather than creating a separate template for every possible
menu/button style we might want to use, this class overrides the
render function to render only the <%def> in the template whose
name matches 'style'.
Additionally, when rendering, the '_id' and 'css_class' attributes
are intended to be used in the outermost container's id and class
tag.
"""
def __init__(self, style, _id = '', css_class = '', **kw):
self._id = _id
self.css_class = css_class
self.style = style
Wrapped.__init__(self, **kw)
def render(self, **kw):
"""Using the canonical template file, only renders the <%def>
in the template whose name is given by self.style"""
style = kw.get('style', c.render_style or 'html')
return Wrapped.part_render(self, self.style, style = style, **kw)
def menu_style(type):
"""Simple manager function for the styled menus. Returns a
(style, css_class) pair given a 'type', defaulting to style =
@@ -185,12 +161,11 @@ def menu_style(type):
srdrop = ('dropdown', 'srdrop'),
flatlist = ('flatlist', 'flat-list'),
tabmenu = ('tabmenu', ''),
formtab = ('tabmenu', 'formtab'),
flat_vert = ('flatlist', 'flat-vert'),
)
return d.get(type, default)
class NavMenu(Styled):
"""generates a navigation menu. The intention here is that the
'style' parameter sets what template/layout to use to differentiate, say,
@@ -337,7 +312,7 @@ class JsButton(NavButton):
"""A button which fires a JS event and thus has no path and cannot
be in the 'selected' state"""
def __init__(self, title, style = 'js', **kw):
NavButton.__init__(self, title, '', style = style, **kw)
NavButton.__init__(self, title, '#', style = style, **kw)
def build(self, *a, **kw):
self.path = 'javascript:void(0)'
@@ -391,7 +366,7 @@ class SortMenu(SimpleGetMenu):
options = ('hot', 'new', 'top', 'old', 'controversial')
def __init__(self, **kw):
kw['title'] = _("sort by")
kw['title'] = _("sorted by")
SimpleGetMenu.__init__(self, **kw)
@classmethod
@@ -521,6 +496,11 @@ class SubredditMenu(NavMenu):
"""Always return False so the title is always displayed"""
return None
class JsNavMenu(NavMenu):
def find_selected(self):
"""Always return the first element."""
return self.options[0]
# --------------------
# TODO: move to admin area
class AdminReporterMenu(SortMenu):

View File

@@ -19,8 +19,11 @@
# All portions of the code written by CondeNet are Copyright (c) 2006-2009
# CondeNet, Inc. All Rights Reserved.
################################################################################
from r2.lib.wrapped import Wrapped, NoTemplateFound
from r2.models import IDBuilder, LinkListing, Account, Default, FakeSubreddit, Subreddit, Friends, All, Sub, NotFound, DomainSR
from r2.lib.wrapped import Wrapped, NoTemplateFound, Styled
from r2.models import IDBuilder, LinkListing, Account, Default
from r2.models import FakeSubreddit, Subreddit
from r2.models import Friends, All, Sub, NotFound, DomainSR
from r2.models import make_wrapper
from r2.config import cache
from r2.lib.jsonresponse import json_respond
from r2.lib.jsontemplates import is_api
@@ -32,13 +35,16 @@ from r2.lib.traffic import load_traffic, load_summary
from r2.lib.captcha import get_iden
from r2.lib.filters import spaceCompress, _force_unicode, _force_utf8
from r2.lib.menus import NavButton, NamedButton, NavMenu, PageNameNav, JsButton
from r2.lib.menus import SubredditButton, SubredditMenu, OffsiteButton, menu
from r2.lib.menus import SubredditButton, SubredditMenu
from r2.lib.menus import OffsiteButton, menu, JsNavMenu
from r2.lib.strings import plurals, rand_strings, strings, Score
from r2.lib.utils import title_to_url, query_string, UrlParser, to_js, vote_hash
from r2.lib.template_helpers import add_sr, get_domain
from r2.lib.subreddit_search import popular_searches
import sys, random, datetime, locale, calendar, re
import sys, random, datetime, locale, calendar, simplejson, re
import graph
from itertools import chain
from urllib import quote
datefmt = _force_utf8(_('%d %b %Y'))
@@ -274,7 +280,6 @@ class Reddit(Wrapped):
if c.user_is_loggedin:
more_buttons.append(NamedButton('saved', False))
more_buttons.append(NamedButton('recommended', False))
if c.user_is_admin:
more_buttons.append(NamedButton('admin'))
@@ -287,6 +292,11 @@ class Reddit(Wrapped):
if c.user_is_admin:
more_buttons.append(NamedButton('traffic'))
#if there's only one button in the dropdown, get rid of the dropdown
if len(more_buttons) == 1:
main_buttons.append(more_buttons[0])
more_buttons = []
toolbar = [NavMenu(main_buttons, type='tabmenu')]
if more_buttons:
toolbar.append(NavMenu(more_buttons, title=menu.more, type='tabdrop'))
@@ -300,13 +310,13 @@ class Reddit(Wrapped):
return "<Reddit>"
@staticmethod
def content_stack(*a):
def content_stack(panes, css_class = None):
"""Helper method for reordering the content stack."""
return PaneStack(filter(None, a))
return PaneStack(filter(None, panes), css_class = css_class)
def content(self):
"""returns a Wrapped (or renderable) item for the main content div."""
return self.content_stack(self.infobar, self.nav_menu, self._content)
return self.content_stack((self.infobar, self.nav_menu, self._content))
class ClickGadget(Wrapped):
def __init__(self, links, *a, **kw):
@@ -417,11 +427,15 @@ class MessagePage(Reddit):
if not kw.has_key('show_sidebar'):
kw['show_sidebar'] = False
Reddit.__init__(self, *a, **kw)
self.replybox = CommentReplyBox()
self.replybox = UserText(item = None, creating = True,
post_form = 'comment', display = False,
cloneable = True)
def content(self):
return self.content_stack(self.replybox, self.infobar,
self.nav_menu, self._content)
return self.content_stack((self.replybox,
self.infobar,
self.nav_menu,
self._content))
def build_toolbars(self):
buttons = [NamedButton('compose'),
@@ -486,8 +500,11 @@ class LoginPage(BoringPage):
class Login(Wrapped):
"""The two-unit login and register form."""
def __init__(self, user_reg = '', user_login = '', dest=''):
Wrapped.__init__(self, user_reg = user_reg, user_login = user_login,
dest = dest)
Wrapped.__init__(self,
user_reg = user_reg,
user_login = user_login,
dest = dest,
captcha = Captcha())
class SearchPage(BoringPage):
@@ -501,8 +518,8 @@ class SearchPage(BoringPage):
BoringPage.__init__(self, pagename, robots='noindex', *a, **kw)
def content(self):
return self.content_stack(self.searchbar, self.infobar,
self.nav_menu, self._content)
return self.content_stack((self.searchbar, self.infobar,
self.nav_menu, self._content))
class CommentsPanel(Wrapped):
"""the side-panel on the reddit toolbar frame that shows the top
@@ -530,10 +547,11 @@ class LinkInfoPage(Reddit):
def __init__(self, link = None, comment = None,
link_title = '', *a, **kw):
# TODO: temp hack until we find place for builder_wrapper
from r2.controllers.listingcontroller import ListingController
wrapper = make_wrapper(ListingController.builder_wrapper,
expand_children = True)
link_builder = IDBuilder(link._fullname,
wrap = ListingController.builder_wrapper)
wrap = wrapper)
# link_listing will be the one-element listing at the top
self.link_listing = LinkListing(link_builder, nextprev=False).listing()
@@ -553,6 +571,7 @@ class LinkInfoPage(Reddit):
else:
params = {'title':_force_unicode(link_title), 'site' : c.site.name}
title = strings.link_info_title % params
Reddit.__init__(self, title = title, *a, **kw)
def build_toolbars(self):
@@ -579,8 +598,11 @@ class LinkInfoPage(Reddit):
return toolbar
def content(self):
return self.content_stack(self.infobar, self.link_listing,
self.nav_menu, self._content)
return self.content_stack((self.infobar, self.link_listing,
PaneStack([PaneStack((self.nav_menu,
self._content))],
title = _("comments"),
css_class = "commentarea")))
def rightbox(self):
rb = Reddit.rightbox(self)
@@ -612,8 +634,6 @@ class EditReddit(Reddit):
else:
return []
class SubredditsPage(Reddit):
"""container for rendering a list of reddits. The corner
searchbox is hidden and its functionality subsumed by an in page
@@ -651,8 +671,8 @@ class SubredditsPage(Reddit):
NavMenu(buttons, base_path = '/reddits', type="tabmenu")]
def content(self):
return self.content_stack(self.searchbar, self.nav_menu,
self.sr_infobar, self._content)
return self.content_stack((self.searchbar, self.nav_menu,
self.sr_infobar, self._content))
def rightbox(self):
ps = Reddit.rightbox(self)
@@ -663,7 +683,7 @@ class MySubredditsPage(SubredditsPage):
"""Same functionality as SubredditsPage, without the search box."""
def content(self):
return self.content_stack(self.nav_menu, self.infobar, self._content)
return self.content_stack((self.nav_menu, self.infobar, self._content))
def votes_visible(user):
@@ -868,15 +888,6 @@ class Captcha(Wrapped):
self.iden = get_captcha()
Wrapped.__init__(self)
class CommentReplyBox(Wrapped):
"""Used on LinkInfoPage to render the comment reply form at the
top of the comment listing as well as the template for the forms
which are JS inserted when clicking on 'reply' in either a comment
or message listing."""
def __init__(self, link_name='', captcha=None, action = 'comment'):
Wrapped.__init__(self, link_name = link_name, captcha = captcha,
action = action)
class PermalinkMessage(Wrapped):
"""renders the box on comment pages that state 'you are viewing a
single comment's thread'"""
@@ -886,12 +897,14 @@ class PermalinkMessage(Wrapped):
class PaneStack(Wrapped):
"""Utility class for storing and rendering a list of block elements."""
def __init__(self, panes=[], div_id = None, css_class=None, div=False):
def __init__(self, panes=[], div_id = None, css_class=None, div=False,
title=""):
div = div or div_id or css_class or False
self.div_id = div_id
self.css_class = css_class
self.div = div
self.stack = list(panes)
self.title = title
Wrapped.__init__(self)
def append(self, item):
@@ -952,7 +965,6 @@ class Frame(Wrapped):
dorks_re = re.compile(r"https?://?([-\w.]*\.)?digg\.com/\w+\.\w+(/|$)")
class FrameToolbar(Wrapped):
"""The reddit voting toolbar used together with Frame."""
extension_handling = False
def __init__(self, link = None, title = None, url = None, expanded = False, **kw):
self.title = title
self.url = url
@@ -998,11 +1010,41 @@ class FrameToolbar(Wrapped):
Wrapped.__init__(self, **kw)
extension_handling = False
class NewLink(Wrapped):
"""Render the link submission form"""
def __init__(self, captcha = None, url = '', title= '', subreddits = (),
then = 'comments'):
tabs = (('link', ('link-desc', 'url-field')),
('text', ('text-desc', 'text-field')))
all_fields = set(chain(*(parts for (tab, parts) in tabs)))
buttons = []
self.default_tabs = tabs[0][1]
self.default_tab = tabs[0][0]
for tab_name, parts in tabs:
to_show = ','.join('#' + p for p in parts)
to_hide = ','.join('#' + p for p in all_fields if p not in parts)
onclick = "return select_form_tab(this, '%s', '%s');"
onclick = onclick % (to_show, to_hide)
if tab_name == self.default_tab:
self.default_show = to_show
self.default_hide = to_hide
buttons.append(JsButton(tab_name, onclick=onclick, css_class=tab_name))
self.formtabs_menu = JsNavMenu(buttons, type = 'formtab')
self.default_tabs = tabs[0][1]
self.sr_searches = simplejson.dumps(popular_searches())
if isinstance(c.site, FakeSubreddit):
self.default_sr = subreddits[0] if subreddits else g.default_sr
else:
self.default_sr = c.site.name
Wrapped.__init__(self, captcha = captcha, url = url,
title = title, subreddits = subreddits,
then = then)
@@ -1082,11 +1124,22 @@ class ButtonDemoPanel(Wrapped):
class Feedback(Wrapped):
"""The feedback and ad inquery form(s)"""
def __init__(self, captcha=None, title=None, action='/feedback',
message='', name='', email='', replyto='', success = False):
Wrapped.__init__(self, captcha = captcha, title = title, action = action,
message = message, name = name, email = email, replyto = replyto,
success = success)
def __init__(self, title, action):
email = name = ''
if c.user_is_loggedin:
email = getattr(c.user, "email", "")
name = c.user.name
captcha = None
if not c.user_is_loggedin or c.user.needs_captcha():
captcha = Captcha()
Wrapped.__init__(self,
captcha = captcha,
title = title,
action = action,
email = email,
name = name)
class WidgetDemoPanel(Wrapped):
@@ -1260,7 +1313,7 @@ class DetailsPage(LinkInfoPage):
def content(self):
# TODO: a better way?
from admin_pages import Details
return self.content_stack(self.link_listing, Details(link = self.link))
return self.content_stack((self.link_listing, Details(link = self.link)))
class Cnameframe(Wrapped):
"""The frame page."""
@@ -1289,8 +1342,7 @@ class PromotePage(Reddit):
buttons = [NamedButton('current_promos', dest = ''),
NamedButton('new_promo')]
menu = NavMenu(buttons, title='show', base_path = '/promote',
type='flatlist')
menu = NavMenu(buttons, base_path = '/promote', type='flatlist')
if nav_menus:
nav_menus.insert(0, menu)
@@ -1330,6 +1382,83 @@ class PromoteLinkForm(Wrapped):
listing = listing,
*a, **kw)
class TabbedPane(Wrapped):
def __init__(self, tabs):
"""Renders as tabbed area where you can choose which tab to
render. Tabs is a list of tuples (tab_name, tab_pane)."""
buttons = []
for tab_name, title, pane in tabs:
buttons.append(JsButton(title, onclick="return select_tab_menu(this, '%s');" % tab_name))
self.tabmenu = JsNavMenu(buttons, type = 'tabpane')
self.tabs = tabs
Wrapped.__init__(self)
class LinkChild(Wrapped):
def __init__(self, link, load = False, expand = False, nofollow = False):
self.link = link
self.expand = expand
self.load = load or expand
self.nofollow = nofollow
Wrapped.__init__(self)
def content(self):
return ''
class MediaChild(LinkChild):
css_style = "video"
def content(self):
return self.link.media_object
class SelfTextChild(LinkChild):
css_style = "selftext"
def content(self):
u = UserText(self.link, self.link.selftext,
editable = c.user == self.link.author,
nofollow = self.nofollow)
#have to force the render style to html for now cause of some
#c.render_style weirdness
return u.render(style = 'html')
class SelfText(Wrapped):
def __init__(self, link):
Wrapped.__init__(self, link = link)
class UserText(Wrapped):
def __init__(self,
item,
text = '',
have_form = True,
editable = False,
creating = False,
nofollow = False,
display = True,
post_form = 'editusertext',
cloneable = False,
extra_css = ''):
css_class = "usertext"
if cloneable:
css_class += " cloneable"
if extra_css:
css_class += " " + extra_css
Wrapped.__init__(self,
item = item,
text = text,
have_form = have_form,
editable = editable,
creating = creating,
nofollow = nofollow,
display = display,
post_form = post_form,
cloneable = cloneable,
css_class = css_class)
def button(self):
pass
class Traffic(Wrapped):
@staticmethod
def slice_traffic(traffic, *indices):
@@ -1491,3 +1620,4 @@ class RedditTraffic(Traffic):
class InnerToolbarFrame(Wrapped):
def __init__(self, link, expanded = False):
Wrapped.__init__(self, link = link, expanded = expanded)

View File

@@ -281,7 +281,7 @@ def make_scraper(url):
#Youtube
class YoutubeScraper(MediaScraper):
media_template = '<object width="425" height="350"><param name="movie" value="http://www.youtube.com/v/$video_id"></param><param name="wmode" value="transparent"></param><embed src="http://www.youtube.com/v/$video_id" type="application/x-shockwave-flash" wmode="transparent" width="425" height="350"></embed></object>'
media_template = '<object width="480" height="295"><param name="movie" value="http://www.youtube-nocookie.com/v/$video_id"></param><param name="wmode" value="transparent"></param><embed src="http://www.youtube-nocookie.com/v/$video_id" type="application/x-shockwave-flash" wmode="transparent" width="480" height="295"></embed></object>'
thumbnail_template = 'http://img.youtube.com/vi/$video_id/default.jpg'
video_id_rx = re.compile('.*v=([A-Za-z0-9-_]+).*')

View File

@@ -121,6 +121,9 @@ string_dict = dict(
also get there by clicking the link's title
(in the middle of the toolbar, to the right of the comments button).
"""),
submit_link = _("""You are submitting a link. The key to a successful submission is interesting content and a deceptive title."""),
submit_text = _("""You are submitting a text-based post. Speak your mind. A title is required, but expanding further in the text field is not. Beginning your title with "vote up if" is violation of intergalactic law."""),
)
class StringHandler(object):

View File

@@ -0,0 +1,46 @@
from r2.models import *
from r2.lib import utils
from pylons import g
sr_prefix = 'sr_search_'
def load_all_reddits():
query_cache = {}
q = Subreddit._query(Subreddit.c.type == 'public',
Subreddit.c._downs > 1,
sort = (desc('_downs'), desc('_ups')),
data = True)
for sr in utils.fetch_things2(q):
name = sr.name.lower()
for i in xrange(len(name)):
prefix = name[:i + 1]
names = query_cache.setdefault(prefix, [])
if len(names) < 10:
names.append(sr.name)
g.rendercache.set_multi(query_cache, prefix = sr_prefix)
def search_reddits_cached(query):
return g.rendercache.get(sr_prefix + query) or []
def search_reddits(query):
return search_reddits_cached(str(query.lower()))
@memoize('popular_searches', time = 3600)
def popular_searches():
top_reddits = Subreddit._query(Subreddit.c.type == 'public',
sort = desc('_downs'),
limit = 100,
data = True)
top_searches = {}
for sr in top_reddits:
name = sr.name.lower()
for i in xrange(min(len(name), 3)):
query = name[:i + 1]
r = search_reddits(query)
top_searches[query] = r
return top_searches

View File

@@ -20,6 +20,7 @@
# CondeNet, Inc. All Rights Reserved.
################################################################################
from r2.models import *
from r2.lib.jsontemplates import is_api
from filters import unsafe, websafe
from r2.lib.utils import vote_hash, UrlParser
@@ -102,8 +103,11 @@ def replace_render(listing, item, style = None, display = True):
pass
return rendered_item
child_txt = ( hasattr(item, "child") and item.child )\
and item.child.render(style = style) or ""
if is_api():
child_txt = ""
else:
child_txt = ( hasattr(item, "child") and item.child )\
and item.child.render(style = style) or ""
# handle API calls differently from normal request: dicts not strings are passed around
if isinstance(rendered_item, dict):
@@ -239,7 +243,7 @@ def add_sr(path, sr_path = True, nocname=False, force_hostname = False):
path to include c.site.path.
"""
# don't do anything if it is just an anchor
if path.startswith('#'):
if path.startswith('#') or path.startswith('javascript:'):
return path
u = UrlParser(path)

View File

@@ -24,6 +24,7 @@ from utils import storage
from itertools import chain
import sys
sys.setrecursionlimit(500)
class NoTemplateFound(Exception): pass
@@ -111,3 +112,26 @@ def SimpleWrapped(**kw):
kw.update(kw1)
Wrapped.__init__(self, *a, **kw)
return _SimpleWrapped
class Styled(Wrapped):
"""Rather than creating a separate template for every possible
menu/button style we might want to use, this class overrides the
render function to render only the <%def> in the template whose
name matches 'style'.
Additionally, when rendering, the '_id' and 'css_class' attributes
are intended to be used in the outermost container's id and class
tag.
"""
def __init__(self, style, _id = '', css_class = '', **kw):
self._id = _id
self.css_class = css_class
self.style = style
Wrapped.__init__(self, **kw)
def render(self, **kw):
"""Using the canonical template file, only renders the <%def>
in the template whose name is given by self.style"""
from pylons import c
style = kw.get('style', c.render_style or 'html')
return Wrapped.part_render(self, self.style, style = style, **kw)

View File

@@ -596,6 +596,14 @@ class CommentBuilder(Builder):
return final
def make_wrapper(parent_wrapper = Wrapped, **params):
def wrapper_fn(thing):
w = parent_wrapper(thing)
for k, v in params.iteritems():
setattr(w, k, v)
return w
return wrapper_fn
class TopCommentBuilder(CommentBuilder):
"""A comment builder to fetch only the top-level, non-spam,
non-deleted comments"""

View File

@@ -52,6 +52,7 @@ class Link(Thing, Printable):
promote_until = None,
promoted_by = None,
disable_comments = False,
selftext = '',
ip = '0.0.0.0')
def __init__(self, *a, **kw):
@@ -204,6 +205,7 @@ class Link(Thing, Printable):
if c.user_is_admin:
return False
link_child = wrapped.link_child
s = (str(i) for i in (wrapped._fullname,
bool(c.user_is_sponsor),
bool(c.user_is_loggedin),
@@ -224,7 +226,12 @@ class Link(Thing, Printable):
wrapped.show_reports,
wrapped.can_ban,
wrapped.thumbnail,
wrapped.moderator_banned))
wrapped.moderator_banned,
#link child stuff
bool(link_child),
bool(link_child) and link_child.load,
bool(link_child) and link_child.expand
))
# htmllite depends on other get params
s = ''.join(s)
if c.render_style == "htmllite":
@@ -284,7 +291,8 @@ class Link(Thing, Printable):
item.thumbnail = thumbnail_url(item)
else:
item.thumbnail = g.default_thumb
item.score = max(0, item.score)
item.domain = (domain(item.url) if not item.is_self
@@ -329,6 +337,20 @@ class Link(Thing, Printable):
item.domain_path = "/domain/%s" % item.domain
if item.is_self:
item.domain_path = item.subreddit_path
#this is wrong, but won't be so wrong when we move this
#whole chunk of code into pages.py
from r2.lib.pages import MediaChild, SelfTextChild
item.link_child = None
item.editable = False
if item.media_object:
item.link_child = MediaChild(item, load = True)
elif item.selftext:
expand = getattr(item, 'expand_children', False)
item.link_child = SelfTextChild(item, expand = expand,
nofollow = item.nofollow)
#draw the edit button if the contents are pre-expanded
item.editable = expand and item.author == c.user
item.tblink = "http://%s/tb/%s" % (
get_domain(cname = c.cname, subreddit=False),
@@ -549,9 +571,18 @@ class Comment(Thing, Printable):
item.author != c.user and
not item.show_spam)))
if item._deleted and not c.user_is_admin:
item.author = DeletedUser()
item.body = '[deleted]'
extra_css = ''
if item._deleted:
if c.user_is_admin:
extra_css += "grayed"
else:
item.author = DeletedUser()
item.body = '[deleted]'
if c.focal_comment == item._id36:
extra_css += 'border'
# don't collapse for admins, on profile pages, or if deleted
item.collapsed = ((item.score < min_score) and
@@ -570,6 +601,12 @@ class Comment(Thing, Printable):
item.score_fmt = Score.points
item.permalink = item.make_permalink(item.link, item.subreddit)
#will seem less horrible when add_props is in pages.py
from r2.lib.pages import UserText
item.usertext = UserText(item, item.body,
editable = item.author == c.user,
nofollow = item.nofollow,
extra_css = extra_css)
class StarkComment(Comment):
"""Render class for the comments in the top-comments display in
the reddit toolbar"""

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

View File

@@ -23,6 +23,10 @@ body {
z-index: 1;
}
textarea { font: normal small verdana, arial, helvetica, sans-serif; }
/*html,body { height: 100%; }*/
/* IE dumbness patch. hidden input in a hidden block that is
* subsequently shown leads to the input to "show" and generate undesired
* )padding. This makes it go away. */
@@ -69,7 +73,6 @@ input[type=checkbox], input[type=radio] { margin-top: .4em; }
/* forms */
.iform th { text-align: right; color: black; font-weight: normal; text-transform: lowercase; }
.wrong {color: red; font-weight: normal}
.subform input.text { width: 25em }
@@ -112,13 +115,15 @@ ul.flat-vert {text-align: left;}
.pref { font-weight: bold; }
#header {
border-bottom: 1px solid #5f99cf;
position: relative;
background-color: #cee3f8;
z-index: 99;
}
#header-img {margin-top: 2px;}
#header-img {margin-top: 2px; margin-right: 5px;}
#header-top {
position: absolute;
@@ -147,6 +152,7 @@ ul.flat-vert {text-align: left;}
font-weight: bold;
margin-right: 1ex;
font-variant: small-caps;
line-height: 100%;
font-size: 1.2em;
}
.pagename a {color: black; }
@@ -234,19 +240,20 @@ ul.flat-vert {text-align: left;}
white-space: nowrap;
display: inline;
}
.tabmenu li {
display: inline;
font-weight: bold;
margin: 0px 3px;
padding: 2px 6px 0px 6px;
background-color: #eff7ff;
}
.tabmenu li.selected { padding: 0; }
.tabmenu li a {
padding: 2px 6px 0 6px;
background-color: #eff7ff;
}
.tabmenu li.selected a{
color: orangered;
padding: 2px 6px 1px 6px;
background-color: white;
border: 1px solid #5f99cf;
border-bottom: 1px solid white;
@@ -458,7 +465,7 @@ before enabling */
.midcol {
float: left;
margin-right: 2px;
margin-right: 4px;
margin-left: 7px;
background: transparent;
overflow: hidden;
@@ -609,6 +616,33 @@ before enabling */
.organic-help-button { padding: 0 .5ex; }
.menuarea {
border-bottom: 1px dotted gray;
padding: 5px 10px;
margin: 5px 310px 5px 5px;
font-size: larger;
}
.menuarea .spacer {display: inline; margin-right: 15px}
.commentarea h1 {
margin: 10px 310px 0px 10px;
padding-bottom: 3px;
border-bottom: 1px dotted gray;
}
.commentarea .menuarea {
border: none;
margin: 0 310px 10px 10px;
padding: 0;
color: gray;
}
.commentarea > .usertext {
margin: 0 0 10px 10px;
overflow: auto;
}
.infobar {
background-color: #f6e69f;
padding: 5px 10px;
@@ -642,16 +676,6 @@ before enabling */
margin-right: 15px;
}
.menuarea {
border-bottom: 1px dotted gray;
padding: 5px 10px;
margin: 5px 310px 5px 5px;
font-size: larger;
}
.menuarea .spacer {display: inline; margin-right: 15px}
/*top link*/
a.star { text-decoration: none; color: #ff8b60 }
@@ -662,7 +686,8 @@ a.star { text-decoration: none; color: #ff8b60 }
.entry .buttons li {
display: inline;
padding: 0 4px;
border: none;}
border: none;
}
.entry .buttons li.first {padding-left: 0px;}
@@ -753,6 +778,7 @@ a.star { text-decoration: none; color: #ff8b60 }
.linkcompressed .midcol { width: 15px; }
.linkcompressed .entry .buttons li a:hover { text-decoration: underline}
.linkcompressed .expando-button {display: none}
.warm-entry .rank { color: #EDA179; }
@@ -822,57 +848,8 @@ a.star { text-decoration: none; color: #ff8b60 }
margin-left: 15px;
}
.commentreply {
margin: 10px;
margin-left: 15px;
width: 40em;
}
textarea.gray { color: gray; }
.commentreply textarea {
border: 1px solid #369;
width: 100%;
margin: 0px;
margin-bottom: -1px;
}
.commentreply .buttons {
margin: 0px;
float: left;
}
.commentreply .buttons button {
margin: 5px;
}
.commentreply table.help {
margin: 0px;
margin-top: 5px;
font-size: larger;
width: 100%;
}
.commentreply .help,
.commentreply .help td,
.commentreply .help tr {
border: 1px solid #C0C0C0;
padding: 4px;
margin: 0px;
}
.commentreply .help-toggle {
float:right;
margin-top: 7px;
}
.permamessage {
font-size: larger;
border: 1px dotted black;
padding: 5px 5px 5px 18px;
white-space: nowrap;
background-image: url(/static/permalink-arrow.png);
background-repeat: no-repeat;
background-position: 5px center;
}
.deepthread {
padding-right: 30px;
background-image: url(/static/continue-thread.png);
@@ -916,8 +893,6 @@ textarea.gray { color: gray; }
margin-right: 2px;
}
.commentbody { }
.commentbody.border { background-color: #ffc; padding-left: 5px}
.commentbody.grayed {
color: gray;
@@ -1108,7 +1083,7 @@ textarea.gray { color: gray; }
}
.status { color: red; }
.status { margin-left: 5px; color: red; font-size: small;}
.error { color: red; font-size: small; margin: 5px; }
.line-through { text-decoration: line-through }
@@ -1220,6 +1195,9 @@ textarea.gray { color: gray; }
width: 250px;
}
#passform h1 {margin-bottom: 0px}
#passform p {margin-bottom: 5px; font-size: small}
/* cover */
.cover {
position: fixed;
@@ -1632,6 +1610,16 @@ textarea.gray { color: gray; }
}
/* default form styles */
form .spacer + .spacer {
margin: 15px 0;
}
form input[type=checkbox],
form input[type=radio] {margin: 2px .5em 0 0; }
.pretty-form {
font-size: larger;
vertical-align: top;
@@ -1639,7 +1627,7 @@ textarea.gray { color: gray; }
.pretty-form p {margin: 3px ;}
.pretty-form input[type=checkbox],
.pretty-form input[type=radio] {margin: 2px .5em 0px .5em; }
.pretty-form input[type=radio] {margin: 2px .5em 0 0; }
.pretty-form img { margin: 3px .5em}
.pretty-form input[type=text],
.pretty-form textarea,
@@ -1660,29 +1648,24 @@ textarea.gray { color: gray; }
.pretty-form select,
.pretty-form b,
.pretty-form textarea,
.pretty-form button { margin: 3px .5em; }
.pretty-form button { margin: 3px 5px; }
.pretty-form th { text-align: right }
/* delete page */
.delete-field {
background-color: white;
padding: 10px;
}
/*pref page boxes*/
.pretty-form.short-text input[type=text],
.pretty-form.short-text textarea,
.pretty-form.short-text input[type=password] {width: 2em }
/*update password*/
.pretty-form.medium-text input[type=text],
.pretty-form.medium-text textarea,
.pretty-form.medium-text input[type=password] {width: 15em }
/*submit*/
.pretty-form.long-text input[type=text],
.pretty-form.long-text textarea,
.pretty-form.long-text input[type=password] {padding: 2px; width: 40em }
/*forgot password*/
#passform h1 { margin: 0px; }
#passform p { font-size: smaller; color: orangered; margin-bottom: 7px}
#passform.pretty-form button { padding: 0px 1px; }
#url-field button {margin: 10px 5px 0 0;}
#url-field .title-status { color: red; font-size: small}
/*opt-out/in form*/
.opt-form { font-size: larger }
@@ -1815,8 +1798,8 @@ ul#image-preview-list .description pre {
#preview-table > table > tbody > tr { padding-bottom: 10px; }
#preview-table > table > tbody > tr > td { padding: 5px; padding-right: 15px;}
#preview-table > table > tbody > tr > th { padding: 5px; padding-right: 15px;}
#preview-table > table > tbody > tr > th {
padding: 5px; padding-right: 15px;
font-weight: bold;
vertical-align: top;
font-size: larger;
@@ -1984,6 +1967,293 @@ ul#image-preview-list .description pre {
.toggle.deltranslator-button { display: inline; }
/****************/
#sr { margin-left: 0px }
#sr-list-wrapper {
width: 454px;
height: 200px;
border: 1px solid gray;
border-top: none;
margin: 0 5px;
font-size: smaller;
position: relative;
}
#sr-list-cover {
position: absolute;
background: gray url(/static/throbber.gif) no-repeat scroll center center;
height: 100%;
width: 100%;
opacity: .7;
filter:alpha(opacity=70); /* IE patch */
z-index: 1000;
display: none;
}
#sr-list {
overflow: auto;
position: absolute;
height: 100%;
width: 100%;
}
#sr-searchfield { margin: 0 5px; }
.sr-name {
font-size: small;
vertical-align: top;
padding: 3px 3px 3px 0;
}
.sr-description {
padding: 3px
}
.sr-row {
cursor: default;
}
.sr-row.sr-selected {
background: #EFF7FF url(/static/rightarrow.png) no-repeat scroll 0px 5px;
}
.sr-arrow {
width: 10px;
height: 12px;
}
#sr-autocomplete-area {
position: relative;
z-index: 100;
}
#sr-drop-down {
position: absolute;
width: 498px;
border: 1px solid gray;
background: white;
display: none;
}
#sr-drop-down table {
width: 100%;
}
.sr-name-row {
cursor: default;
}
.sr-name-row.sr-selected {
background-color: #369;
color: white;
}
.submit-header {
font-size: larger;
font-weight: bold;
}
#suggested-reddits {
margin-top: 5px;
font-size: small;
}
#suggested-reddits ul {
}
#suggested-reddits li {
display: inline;
padding-right: 5px;
}
/*** new menu shit ***/
.formtabs-content {
width: 520px;
border-top: 4px solid #5f99cf;
padding-top: 10px;
}
.formtabs-content .infobar {
margin: 0;
padding: 5px;
}
ul.tabmenu.formtab {
display: block;
padding-left: 10px;
font-size: larger;
}
.tabmenu.formtab li {
margin: 0;
}
.tabmenu.formtab a {
font-weight: normal;
outline: none;
padding: 0px 12px;
vertical-align: bottom;
border: 1px solid #c1c1c1;
border-bottom: none;
}
.tabmenu.formtab .selected a {
color:white;
font-size: 130%;
background-color: #5f99cf;
border: none;
}
/******* embed stuff ******/
.expando {
clear: left;
margin: 5px 0 5px 0;
}
.expando-content {
display: none;
}
.expando-button {
float: left;
height: 23px;
width: 23px;
margin: 2px 5px 2px 0;
background: white none no-repeat scroll center center;
}
.expando-button.selftext.collapsed {background-image: url(/static/blog-collapsed.png);}
.expando-button.selftext.collapsed:hover, .eb-sch {background-image: url(/static/blog-collapsed-hover.png);}
.expando-button.selftext.expanded, .eb-se {
margin-bottom: 5px;
background-image: url(/static/blog-expanded.png);
}
.expando-button.selftext.expanded:hover, .eb-seh {background-image: url(/static/blog-expanded-hover.png);}
.expando-button.video.collapsed {background-image: url(/static/vid-collapsed.png);}
.expando-button.video.collapsed:hover, .eb-vch {background-image: url(/static/vid-collapsed-hover.png);}
.expando-button.video.expanded, .eb-ve {background-image: url(/static/vid-expanded.png);}
.expando-button.video.expanded:hover, .eb-veh {background-image: url(/static/vid-expanded-hover.png);}
/******** self text stuff ****/
.link .usertext .md,
.linkcompressed .usertext .md {
padding: 0 5px;
background-color: #fafafa;
border: 1px solid #369;
-moz-border-radius: 7px;
-webkit-border-radius: 7px;
}
.usertext {
font-size: small;
position: relative;
}
.usertext-edit {
margin-top: 5px;
padding: 0 1px; /* so the border of help/textbox don't get chopped off */
width: 500px;
}
.usertext-edit textarea {
width: 500px;
height: 100px;
}
/*permalinks*/
.usertext.border .usertext-body {
background-color: #ffc; padding-left: 5px;
}
/*admin see deleted comment*/
.usertext.grayed .usertext-body {
color: gray;
background-color: #e0e0e0;
padding-left: 5px;
}
.usertext button {
margin: 5px 5px 10px 0;
}
.usertext .help-toggle {
font-size: smaller;
float:right;
margin-top: 5px;
}
.usertext .bottom-area {
/* this restricts children floats to the container */
overflow: hidden;
width: 100%;
}
.usertext table.markhelp {
background-color: white;
margin: 5px 0px;
width: 100%;
}
.usertext .markhelp,
.usertext .markhelp td,
.usertext .markhelp tr {
border: 1px solid #C0C0C0;
padding: 4px;
margin: 0px;
}
.usertext .markhelp .spaces {background-color: #c0c0c0}
/*** roundfield stuff *******/
.roundfield {
width: 500px;
background-color: #cee3f8;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
padding: 5px 10px 10px 10px;
font-size: large;
}
.roundfield .title {
}
.roundfield .roundfield-content {
margin-top: 5px;
border: none;
vertical-align: top;
}
.roundfield .usertext-edit {
width: 500px;
}
.roundfield textarea,
.roundfield input[type=text],
.roundfield input[type=password] {
font-size: 100%;
width: 492px;
padding: 3px;
margin: 0;
border: 1px solid gray;
}
.roundfield.captcha .capimage {
margin-bottom: 10px;
}
/***traffic stuff***/
.traffic-table {margin: 10px 20px; }
.traffic-table a:hover { text-decoration: underline; }
.traffic-table th { font-weight: bold; text-align: center;}
@@ -2005,3 +2275,6 @@ ul#image-preview-list .description pre {
border: 1px solid #B0B0B0;
margin-left: 10px;
margin-bottom: 10px; }

View File

@@ -125,8 +125,9 @@ function handleResponse(action) {
objs[new_i] = objs[old_i][args];
if(objs[new_i])
objs[new_i]._obj = objs[old_i];
else
else {
$.debug("unrecognized");
}
}
else {
$.debug("unrecognized");
@@ -137,7 +138,7 @@ function handleResponse(action) {
};
var api_loc = '/api/';
$.request = function(op, parameters, worker_in, block, get_only) {
$.request = function(op, parameters, worker_in, block, type, get_only) {
/*
Uniquitous reddit AJAX poster. Automatically addes
handleResponse(action) worker to deal with the API result. The
@@ -153,6 +154,7 @@ $.request = function(op, parameters, worker_in, block, get_only) {
parameters = $.with_default(parameters, {});
worker_in = $.with_default(worker_in, handleResponse(action));
type = $.with_default(type, "json");
if (typeof(worker_in) != 'function')
worker_in = handleResponse(action);
var worker = function(r) {
@@ -178,10 +180,11 @@ $.request = function(op, parameters, worker_in, block, get_only) {
op = api_loc + op;
/*if( document.location.host == reddit.ajax_domain )
/* normal AJAX post */
if(get_only) {
$.get(op, parameters, worker, "json");
$.get(op, parameters, worker, type);
} else {
$.post(op, parameters, worker, "json");
$.post(op, parameters, worker, type);
}
/*else { /* cross domain it is... * /
op = "http://" + reddit.ajax_domain + op + "?callback=?";
@@ -359,7 +362,7 @@ $.fn.randomize_ids = function() {
return $(this);
}
$.replace_things = function(things, keep_children, reveal, stubs) {
$.fn.replace_things = function(things, keep_children, reveal, stubs) {
/* Given the api-html structured things, insert them into the DOM
* in such a way as to remove any elements with the same thing_id.
* "keep_children" is a boolean to determine whether or not any
@@ -369,9 +372,10 @@ $.replace_things = function(things, keep_children, reveal, stubs) {
* animate the transition from old to new. */
var midcol = $(".midcol:visible:first").css("width");
var numcol = $(".rank:visible:first").css("width");
var self = this;
return $.map(things, function(thing) {
var data = thing.data;
var existing = $.things(data.id);
var existing = $(self).things(data.id);
if(stubs)
existing = existing.filter(".stub");
existing.after($.unsafe(data.content));

View File

@@ -56,7 +56,7 @@ function post_form(form, where, statusfunc, nametransformfunc, block) {
function get_form_fields(form, fields) {
fields = fields || {};
/* consolidate the form's inputs for submission */
$(form).find("select, input, textarea").not(".gray").each(function() {
$(form).find("select, input, textarea").not(".gray, :disabled").each(function() {
if (($(this).attr("type") != "radio" &&
$(this).attr("type") != "checkbox") ||
$(this).attr("checked"))
@@ -158,7 +158,15 @@ function toggle_label (elem, callback, cancelback) {
function toggle(elem, callback, cancelback) {
var self = $(elem).parent().andSelf().filter(".option");
var sibling = self.removeClass("active")
.siblings().addClass("active").get(0);
.siblings().addClass("active").get(0);
/*
var self = $(elem).siblings().andSelf();
var sibling = self.filter(":hidden").debug();
self = self.filter(":visible").removeClass("active");
sibling = sibling.addClass("active").get(0);
*/
if(cancelback && !sibling.onclick) {
sibling.onclick = function() {
return toggle(sibling, cancelback, callback);
@@ -238,10 +246,7 @@ function get_organic(elem, next) {
/* links */
function linkstatus(form) {
var title = $(form).find("#title").attr("value");
if(title)
return reddit.status_msg.submitting;
return reddit.status_msg.fetching;
return reddit.status_msg.submitting;
};
@@ -271,22 +276,8 @@ function unfriend(user_name, container_name, type) {
}
};
function show_media(obj) {
obj = $.unsafe(obj);
return function(elem) {
var where = $(elem).thing().find(".embededmedia");
if (where.length)
where.show().html(obj);
else
$(elem).new_thing_child('<div class="embededmedia">' + obj + '</div>');
}
};
function cancelMedia(elem) {
return cancelToggleForm(elem, ".embededmedia", ".media-button");
};
function share(elem) {
$.request("new_captcha");
$(elem).new_thing_child($(".sharelink:first").clone(true)
.attr("id", "sharelink_" + $(elem).thing_id()),
false);
@@ -299,56 +290,12 @@ function cancelShare(elem) {
/* Comment generation */
function helpon(elem) {
$(elem).parents("form:first").children(".markhelp:first").show();
$(elem).parents(".usertext-edit:first").children(".markhelp:first").show();
};
function helpoff(elem) {
$(elem).parents("form:first").children(".markhelp:first").hide();
$(elem).parents(".usertext-edit:first").children(".markhelp:first").hide();
};
function chkcomment(form) {
var entry = $(form).find("textarea");
if( entry.hasClass("gray") || !entry.attr("value") ) {
return false;
} else if(form.replace.value)
return post_form(form, 'editcomment', null, null, true);
else
return post_form(form, 'comment', null, null, true);
};
function comment_edit(elem) {
return $(".commentreply:first").clone(true)
.find("button[name=cancel]").show().end()
.attr("id", "commentreply_" + $(elem).thing_id());
};
function reply(elem) {
$(elem).new_thing_child(comment_edit(elem))
.find('textarea:first').focus();
};
function editcomment(elem) {
var comment = $(elem).thing();
var thing_name = comment.thing_id();
var edit = comment_edit(elem);
var content = comment.find(".edit-body:first").html();
content = decodeURIComponent(content.replace(/\+/g, " "));
edit.prependTo(comment)
.hide()
.find("button[name=comment]").hide().end()
.find("button[name=edit]").show().end()
.find("textarea")
.attr("value", content)
.removeClass("gray")
edit.attr("parent").value = thing_name;
edit.attr("replace").value = 1;
comment.children(".midcol, .entry").hide();
edit.find("textarea:first").focus();
edit.show();
};
function hidecomment(elem) {
$(elem).thing().hide()
.find(".noncollapsed:first, .midcol:first, .child:first").hide().end()
@@ -364,15 +311,6 @@ function showcomment(elem) {
return false;
};
function cancelReply(elem) {
var on_hide = function(form) {
$.things($(form).attr("parent").value)
.children(".midcol, .entry").show();
};
return cancelToggleForm(elem, ".commentreply", ".reply-button", on_hide);
};
function morechildren(form, link_id, children, depth) {
$(form).html(reddit.status_msg.loading)
.css("color", "red");
@@ -650,6 +588,304 @@ function register(elem) {
return post_user(this, "register");
};
/***submit stuff***/
function fetch_title() {
var url_field = $("#url-field");
var error = url_field.find(".NO_URL");
var status = url_field.find(".title-status");
var url = $("#url").val();
if (url) {
status.show().text("loading...");
error.hide();
$.request("fetch_title", {url: url});
}
else {
status.hide();
error.show().text("a url is required");
}
}
/**** sr completing ****/
function sr_cache() {
if (!$.defined(reddit.sr_cache)) {
reddit.sr_cache = new Array();
}
return reddit.sr_cache;
}
function highlight_reddit(item) {
$("#sr-drop-down").children('.sr-selected').removeClass('sr-selected');
if (item) {
$(item).addClass('sr-selected');
}
}
function update_dropdown(sr_names) {
var drop_down = $("#sr-drop-down");
if (!sr_names.length) {
drop_down.hide();
return;
}
var first_row = drop_down.children(":first");
first_row.removeClass('sr-selected');
drop_down.children().remove();
$.each(sr_names, function(i) {
if (i > 10) return;
var name = sr_names[i];
var new_row = first_row.clone();
new_row.text(name);
drop_down.append(new_row);
});
drop_down.show();
}
function sr_search(query) {
query = query.toLowerCase();
var cache = sr_cache();
if (!cache[query]) {
$.request('search_reddit_names', {query: query},
function (r) {
cache[query] = r['names'];
update_dropdown(r['names']);
});
}
else {
update_dropdown(cache[query]);
}
}
function sr_name_up(e) {
var new_sr_name = $("#sr-autocomplete").val();
var old_sr_name = window.old_sr_name || '';
window.old_sr_name = new_sr_name;
if (new_sr_name == '') {
hide_sr_name_list();
}
else if (e.keyCode == 38 || e.keyCode == 40 || e.keyCode == 9) {
}
else if (e.keyCode == 27 && reddit.orig_sr) {
$("#sr-autocomplete").val(reddit.orig_sr);
hide_sr_name_list();
}
else if (new_sr_name != old_sr_name) {
reddit.orig_sr = new_sr_name;
sr_search($("#sr-autocomplete").val());
}
}
function sr_name_down(e) {
var input = $("#sr-autocomplete");
if (e.keyCode == 38 || e.keyCode == 40) {
var dir = e.keyCode == 38 && 'up' || 'down';
var cur_row = $("#sr-drop-down .sr-selected:first");
var first_row = $("#sr-drop-down .sr-name-row:first");
var last_row = $("#sr-drop-down .sr-name-row:last");
var new_row = null;
if (dir == 'down') {
if (!cur_row.length) new_row = first_row;
else if (cur_row.get(0) == last_row.get(0)) new_row = null;
else new_row = cur_row.next(':first');
}
else {
if (!cur_row.length) new_row = last_row;
else if (cur_row.get(0) == first_row.get(0)) new_row = null;
else new_row = cur_row.prev(':first');
}
highlight_reddit(new_row);
if (new_row) {
input.val($.trim(new_row.text()));
}
else {
input.val(reddit.orig_sr);
}
return false;
}
else if (e.keyCode == 13) {
hide_sr_name_list();
input.parents("form").submit();
return false;
}
}
function hide_sr_name_list(e) {
$("#sr-drop-down").hide();
}
function sr_dropdown_mdown(row) {
reddit.sr_mouse_row = row; //global
return false;
}
function sr_dropdown_mup(row) {
if (reddit.sr_mouse_row == row) {
var name = $(row).text();
$("#sr-autocomplete").val(name);
$("#sr-drop-down").hide();
}
}
function set_sr_name(link) {
var name = $(link).text();
$("#sr-autocomplete").trigger('focus').val(name);
}
/*** tabbed pane stuff ***/
function select_form_tab(elem, to_show, to_hide) {
//change the menu
var link_parent = $(elem).parent();
link_parent
.addClass('selected')
.siblings().removeClass('selected');
//swap content and enable/disable form elements
var content = link_parent.parent('ul').next('.formtabs-content');
content.find(to_show)
.show()
.find(":input").removeAttr("disabled").end();
content.find(to_hide)
.hide()
.find(":input").attr("disabled", true);
}
/**** expando stuff ********/
function expando_cache() {
if (!$.defined(reddit.thing_child_cache)) {
reddit.thing_child_cache = new Array();
}
return reddit.thing_child_cache;
}
function expando_child(elem) {
var child_cache = expando_cache();
var thing = $(elem).thing();
//swap button
var button = thing.find(".expando-button");
button
.addClass("expanded")
.removeClass("collapsed")
.get(0).onclick = function() {unexpando_child(elem)};
//load content
var expando = thing.find(".expando");
var key = thing.thing_id() + "_cache";
if (!child_cache[key]) {
$.request("expando",
{"link_id":thing.thing_id()},
function(r) {
child_cache[key] = r;
expando.html($.unsafe(r));
},
false, "html");
}
else {
expando.html($.unsafe(child_cache[key]));
}
expando.show();
}
function unexpando_child(elem) {
var thing = $(elem).thing();
var button = thing.find(".expando-button");
button
.addClass("collapsed")
.removeClass("expanded")
.get(0).onclick = function() {expando_child(elem)};
thing.find(".expando").hide().empty();
}
/******* editting comments *********/
function show_edit_usertext(form) {
var edit = form.find(".usertext-edit");
var body = form.find(".usertext-body");
var textarea = edit.find('div > textarea');
//max of the height of the content or the min values from the css.
var body_width = Math.max(body.children(".md").width(), 500);
var body_height = Math.max(body.children(".md").height(), 100);
//we need to show the textbox first so it has dimensions
body.hide();
edit.show();
//restore original (?) css width/height. I can't explain why, but
//this is important.
textarea.css('width', '');
textarea.css('height', '');
//if there would be scroll bars, expand the textarea to the size
//of the rendered body text
if (textarea.get(0).scrollHeight > textarea.height()) {
var new_width = Math.max(body_width - 5, textarea.width());
textarea.width(new_width);
edit.width(new_width);
var new_height = Math.max(body_height, textarea.height());
textarea.height(new_height);
}
form
.find(".cancel, .save").show().end()
.find(".help-toggle").show().end();
textarea.focus();
}
function hide_edit_usertext(form) {
form
.find(".usertext-edit").hide().end()
.find(".usertext-body").show().end()
.find(".cancel, .save").hide().end()
.find(".help-toggle").hide().end()
.find(".markhelp").hide().end()
}
function comment_reply_for_elem(elem) {
elem = $(elem);
var thing = elem.thing();
var thing_id = elem.thing_id();
//try to find a previous form
var form = thing.find(".child .usertext:first");
if (!form.length || form.parent().thing_id() != thing.thing_id()) {
form = $(".usertext.cloneable:first").clone(true);
elem.new_thing_child(form);
form.attr("thing_id").value = thing_id;
form.attr("id", "commentreply_" + thing_id);
form.find(".error").hide();
}
return form;
}
function edit_usertext(elem) {
show_edit_usertext($(elem).thing().find(".usertext:first"));
}
function cancel_usertext(elem) {
hide_edit_usertext($(elem).thing().find(".usertext:first"));
}
function save_usertext(elem) {
}
function reply(elem) {
var form = comment_reply_for_elem(elem);
//show the right buttons
show_edit_usertext(form);
//re-show the whole form if required
form.show();
//update the cancel button to call the toggle button's click
form.find(".cancel").get(0).onclick = function() {form.hide()};
}
function populate_click_gadget() {
/* if we can find the click-gadget, populate it */
if($('.click-gadget').length) {
@@ -659,7 +895,8 @@ function populate_click_gadget() {
clicked = $.uniq(clicked, 5);
clicked.sort();
$.request('gadget/click/' + clicked.join(','), undefined, undefined, undefined, true);
$.request('gadget/click/' + clicked.join(','), undefined, undefined,
undefined, "json", true);
}
}
}
@@ -809,4 +1046,3 @@ $(function() {
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

View File

@@ -23,7 +23,35 @@
<%! from r2.lib.template_helpers import static %>
<%namespace file="utils.html" import="error_field"/>
${captchagen(thing.iden, thing.error)}
<%namespace name="utils" file="utils.html"/>
##${captchagen(thing.iden, thing.error)}
${rounded_captcha()}
<%def name="captcha_basics(iden='')">
<%
iden = getattr(thing, "iden", iden)
%>
<input name="iden" value="${iden}" type="hidden"/>
<img class="capimage"
alt="i wonder if these things even work"
%if hasattr(thing, "iden"):
src="/captcha/${thing.iden}.png"
%else:
src="${static('kill.png')}"
%endif
/>
</%def>
<%def name="rounded_captcha()">
<%utils:round_field title="${_('are you human?')}" description="${_('(sorry)')}" css_class="captcha">
${captcha_basics()}
<input name="captcha" class="captcha" type="text" />
${error_field("BAD_CAPTCHA", "captcha")}
</%utils:round_field>
</%def>
<%def name="captchagen(iden, error='', tabulate=False, tabular = True, size=60, label=True, show_error = True)">
%if tabulate:
@@ -34,14 +62,7 @@ ${captchagen(thing.iden, thing.error)}
<td></td>
<td>
%endif
<img class="capimage"
alt="i wonder if these things even work"
%if iden:
src="/captcha/${iden}.png"
%else:
src="${static('kill.png')}"
%endif
/>
${captcha_basics(iden)}
%if tabular:
</td>
</tr>
@@ -57,8 +78,7 @@ ${captchagen(thing.iden, thing.error)}
</td>
<td>
%endif
<input name="iden" type="hidden" value="${iden}"/>
<input class="captcha cap-text" id="captcha_"
<input class="captcha cap-text" id="captcha_"
name="captcha" type="text" size="${size}" />
<script type="text/javascript">
emptyInput($('input.captcha'), "${_('type the letters from the image above')}");
@@ -70,7 +90,7 @@ ${captchagen(thing.iden, thing.error)}
</span>
%endif
%if show_error:
${error_field("BAD_CAPTCHA", "span")}
${error_field("BAD_CAPTCHA", "captcha")}
%endif
%if tabular:
</td>
@@ -80,3 +100,4 @@ ${captchagen(thing.iden, thing.error)}
</table>
%endif
</%def>

View File

@@ -102,20 +102,7 @@ ${parent.Child(not thing.collapsed)}
</%def>
<%def name="commentBody()">
%if c.user_is_admin or not thing.deleted:
${unsafe(safemarkdown(thing.body, nofollow=thing.nofollow,
target=thing.target))}
%else:
<div class="gray md">
${_("[deleted]")}
</div>
%endif
</%def>
<%def name="commentText()">
%if c.user == thing.author:
${edit_comment_filter(thing.body)}
%endif
${parent.commentBody()}
</%def>
<%def name="arrows()">
@@ -141,9 +128,9 @@ ${parent.midcol()}
${parent.bylink_button(_("parent"), thing.parent_permalink)}
</li>
%endif
%if c.user_is_loggedin and thing.author.name == c.user.name:
%if thing.usertext.editable:
<li>
${parent.simple_button(_("edit"), "editcomment")}
${parent.simple_button(_("edit"), "edit_usertext")}
</li>
%endif
%endif
@@ -151,9 +138,7 @@ ${parent.midcol()}
${parent.buttons()}
%if not c.profilepage and thing.can_reply:
<li>
${parent.toggle_button("reply-button", _("reply {verb}"),
_("cancel"), "reply", "cancelReply",
login_required = True)}
${parent.simple_button(_("reply"), "reply")}
</li>
%endif
%endif

View File

@@ -37,12 +37,10 @@ ${self.tagline(True)}
</%def>
<%def name="commentBody()">
${thing.usertext.render(style="html")}
</%def>
<%def name="commentText()">
</%def>
<%def name="entry()">
<% collapse = thing.collapsed %>
<div class="collapsed" ${(not collapse and "style='display:none'" or "")}>
@@ -52,18 +50,7 @@ ${self.tagline(True)}
<p class="tagline">
${self.tagline()}
</p>
<%
if c.user_is_admin and thing.deleted:
colorclass = 'grayed'
elif c.focal_comment and c.focal_comment == thing._id36:
colorclass = 'border'
else:
colorclass = ''
%>
<div class="commentbody ${colorclass}">
${self.commentBody()}
</div>
<div class="edit-body">${self.commentText()}</div>
${self.commentBody()}
<ul class="flat-list buttons">
${self.buttons()}
</ul>

View File

@@ -1,90 +0,0 @@
## 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-2009
## CondeNet, Inc. All Rights Reserved.
################################################################################
<%namespace file="utils.html" import="error_field"/>
<form id="commentreply_${thing.link_name}"
${'' if thing.link_name else "style='display:none'"}
onsubmit="return chkcomment(this);" action="#"
method="get" class="commentreply">
%if thing.link_name:
<input type="hidden" name="isroot" value="1" />
%endif
<input type="hidden" name="replace" value="" />
<input type="hidden" name="parent" value="${thing.link_name}" />
${error_field("BAD_COMMENT")}
${error_field("DELETED_COMMENT")}
${error_field("COMMENT_TOO_LONG")}
${error_field("RATELIMIT")}
## I know we don't need this div. You know we don't need this div. IE
## begs to differ.
<div>
<textarea name="comment" rows="7">
</textarea>
</div>
<div class="buttons">
<button type="submit" class="btn"
name="comment">${_("comment {verb}")}
</button>
<button style="display:none;" name="edit"
type="submit" class="btn">${_( "edit")}</button>
<button class="btn" name="cancel" style="display:none;"
onclick="cancelReply(this);return false">
${_("cancel")}</button>
<span class="status error"></span>
</div>
<div class="help-toggle toggle">
<a href="#" class="option active"
onclick='return toggle(this, helpon, helpoff)'>
${_("help")}
</a>
<a href="#" class="option">${_("hide help")}</a>
</div>
<div class="clearleft"></div>
<script type="text/javascript">
var form = $("#commentreply_${thing.link_name}").find("textarea");
emptyInput(form, "${_('enter a comment here')}");
</script>
<div class="markhelp" style="display: none">
<table class="help">
<tr style="background-color: #ffff99; text-align: center">
<td><em>${_( "you type:")}</em></td>
<td><em>${_( "you see:")}</em></td></tr>
<tr><td>*${_( "italics")}*</td>
<td><em>${_( "italics")}</em></td></tr>
<tr><td>**${_( "bold")}**</td>
<td><b>${_( "bold")}</b></td></tr>
<tr><td>[reddit!](http://reddit.com)</td>
<td><a href="http://reddit.com">reddit!</a></td></tr>
<tr><td>* ${_( "item")} 1<br/>
* ${_( "item")} 2<br/>
* ${_( "item")} 3</td>
<td><ul><li>${_( "item")} 1</li>
<li>${_( "item")} 2</li>
<li>${_( "item")} 3</li></ul></td></tr>
<tr><td>&gt; ${_( "quoted text")}</td>
<td><blockquote>${_( "quoted text" )}</blockquote></td></tr>
</table>
</div>
</form>

View File

@@ -1,22 +0,0 @@
## 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-2009
## CondeNet, Inc. All Rights Reserved.
################################################################################

View File

@@ -1,22 +0,0 @@
## 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-2009
## CondeNet, Inc. All Rights Reserved.
################################################################################

View File

@@ -85,8 +85,8 @@ function update_title(elem) {
</td>
<td class="note" id="note_name">
%if not thing.site:
${error_field("SUBREDDIT_EXISTS", "span")}
${error_field("BAD_SR_NAME", "span")}
${error_field("SUBREDDIT_EXISTS", "name")}
${error_field("BAD_SR_NAME", "name")}
<span class="gray" id="example_name">
${_("no spaces, e.g. slashdot")}
</span>
@@ -106,8 +106,8 @@ function update_title(elem) {
%endif
</td>
<td class="note" id="note_title">
${error_field("NO_TITLE", "span")}
${error_field("TITLE_TOO_LONG", "span")}
${error_field("NO_TEXT", "title")}
${error_field("TOO_LONG", "title")}
%if not thing.site:
<span class="gray" id="example_title">
${_("e.g. slashdot: news for nerds, stuff that matters")}
@@ -129,7 +129,7 @@ function update_title(elem) {
</textarea>
</td>
<td class="note" id="note_description">
${error_field("DESC_TOO_LONG", "span")}
${error_field("TOO_LONG", "description")}
</td>
</tr>
<tr>
@@ -199,8 +199,8 @@ function update_title(elem) {
<td class="note" id="note_domain">
<a id="example_domain" href="#"
onclick="$('#domainhelp').slideDown(); $('#example_domain').fadeOut(); return false;">what's this?</a>
${error_field("BAD_CNAME", "span")}
${error_field("USED_CNAME", "span")}
${error_field("BAD_CNAME", "domain")}
${error_field("USED_CNAME", "domain")}
</td>
</tr>
%if thing.site and thing.site.can_change_stylesheet(c.user) and not g.css_killswitch:
@@ -265,5 +265,5 @@ function update_title(elem) {
>${text}</button>
&#32;
<span class="status error"></span>
${error_field("RATELIMIT", "span")}
${error_field("RATELIMIT", "ratelimit")}
</div>

View File

@@ -20,93 +20,57 @@
## CondeNet, Inc. All Rights Reserved.
################################################################################
<%namespace file="utils.html" import="error_field"/>
<%namespace file="utils.html" import="error_field"/
<%namespace name="utils" file="utils.html"/>
<%!
from r2.lib.pages import UserText
%>
%if thing.title:
<h1>${thing.title}</h1>
%endif
%if not thing.success:
<form onsubmit="return post_form(this, 'feedback');"
class="pretty-form"
%if action:
action="/${thing.action}" \
%else:
action=''
%endif
action="/${thing.action}"
method="post" class="content" id="feedback">
<p class="success" class="error">${thing.success or ''}
</p>
%if c.user_is_loggedin:
<input type="hidden" name="whodunnit" value="${c.user.name}" />
<input type="hidden" name="whodunnit" value="${thing.name}" />
%endif
<input type="hidden" name="reason" value="${thing.action}" />
<div>
<table>
<tr>
<th><label for="name">${_("your name")}</label></th>
<td>
<input id="name" name="name" type="text"
value="${thing.name or (c.user_is_loggedin and c.user.name or '')}"
size="30"/>
${error_field("NO_NAME", "span")}
</td>
</tr>
<tr>
<th><label for="email">${_("email")}</label></th>
<td>
%if c.user_is_loggedin and hasattr(c.user, "email"):
<input id="email" name="email" value="${c.user.email}"
type="text" disabled=disabled size="30"/>
</td>
</tr>
<tr>
<th>
<label for="replyto">${_("reply to")}</label>
</th>
<td>
%if thing.replyto:
<input id="replyto" name="replyto" type="text"
value="${thing.email}"
size="30"/>
%else:
<input id="replyto" name="replyto" class="gray"
value="${_('optional')}" type="text"
size="30"/>
%endif
${error_field("BAD_EMAILS", "span")}
%else:
%if thing.email:
<input id="email" name="email" type="text"
value="${thing.email}" size="30"/>
%else:
<input id="email" type="text" name="email" size="30"
value="${ _('put your email here')}" class="gray"/>
%endif
%endif
<span class="little gray">${_("(only used to reply)")}</span>
</td><td>
${error_field("NO_EMAIL", "span")}
</td>
</tr><tr>
<th valign="top">
<label for="message">${_("message")}</label>
</th>
<td>
<textarea id="personal" name="message" rows="5" cols="60">${thing.message or ''}</textarea>
${error_field("NO_MESSAGE", "span")}
</td>
</tr>
%if thing.captcha:
${thing.captcha.render()}
%endif
</table>
<div style="margin: 10px 0 20px 0">
<button class="btn" type="submit">${_("send")}</button>
<span class="status"></span>
</div>
</div>
</form>
%endif
<input type="hidden" name="reason" value="${thing.action}" />
<div class="spacer">
<%utils:round_field title="${_('your name')}">
<input type="text" name="name" value="${thing.name}"/>
${error_field("NO_NAME", "name", "span")}
</%utils:round_field>
</div>
<div class="spacer">
<%utils:round_field title="${_('email')}" description="${_('(only used to reply)')}">
<input type="text" name="email" value="${thing.email}"/>
${error_field("BAD_EMAILS", "email", "span")}
${error_field("NO_EMAIL", "email", "span")}
</%utils:round_field>
</div>
<div class="spacer">
<%utils:round_field title="${_('message')}">
${UserText(None, have_form = False, creating = True).render()}
</%utils:round_field>
</div>
<div class="spacer">
%if thing.captcha:
${thing.captcha.render()}
%endif
</div>
<button class="btn" type="submit">${_("send")}</button>
&nbsp;
<span class="status"></span>
</form>

View File

@@ -61,6 +61,13 @@
</a>
</%def>
<%def name="bottom_buttons()">
<ul class="flat-list buttons">
${self.buttons()}
${self.admintagline()}
</ul>
</%def>
<%def name="entry()">
<p class="title">
<%call expr="make_link('title', 'title')">
@@ -69,13 +76,50 @@
&#32;
${self.domain()}
</p>
##the expando button
%if thing.link_child and not thing.link_child.expand:
<div class="expando-button collapsed
${thing.link_child.css_style}"
onclick="expando_child(this)"></div>
%endif
<p class="tagline">
${self.tagline()}
</p>
<ul class="flat-list buttons">
${self.buttons()}
${self.admintagline()}
</ul>
<%
child_content = ""
if thing.link_child and thing.link_child.load:
child_content = unsafe(thing.link_child.content())
expand = thing.link_child and thing.link_child.expand
%>
##if we're not on a permalink page we'll render the buttons on top
%if not expand:
${bottom_buttons()}
%endif
<div class="expando" ${"style='display: none'" if not expand else ""}>
%if expand:
${child_content}
%else:
<span class="error">loading...</span>
%endif
</div>
##if we are on a permalink page, we'll render the buttons below
%if expand:
${bottom_buttons()}
%endif
##populate the expando cache if we have something
%if not expand and child_content:
<script type="text/javascript">
var cache = expando_cache();
cache["${thing._fullname}_cache"] = "${websafe(child_content)}";
</script>
%endif
</%def>
<%def name="subreddit()" buffered="True">
@@ -145,6 +189,11 @@
newwindow = c.user.pref_newwindow)}
</li>
%endif
%if thing.editable:
<li>
${parent.simple_button(_("edit"), "edit_usertext")}
</li>
%endif
<li class="share">
${parent.toggle_button("share-button", _("share"), _("cancel"),
"share", "cancelShare", login_required = True)}
@@ -171,18 +220,6 @@
${parent.delete_or_report_buttons(delete=delete,report=report)}
${parent.buttons(ban=ban)}
${additional}
${self.media_embed()}
</%def>
<%def name="media_embed()">
%if thing.media_object:
<li>
${parent.toggle_button("media-button", _("watch"), _("cancel"),
'show_media("%s")' % websafe(thing.media_object),
"cancelMedia")}
</li>
%endif
</%def>
<%def name="thumbnail()">

View File

@@ -21,7 +21,7 @@
################################################################################
<%namespace file="printable.html" import="state_button, thing_css_class" />
<div class="raisedbox linkinfo ${thing_css_class(thing.a)}">
<div class="raisedbox linkinfo">
<table class="details">
%if not thing.a.is_self:
<tr><th>${_("toolbar link")}</th>

View File

@@ -67,8 +67,8 @@
<input value="${user}" name="user" id="user_${op}"
type="text" maxlength="20"/>
%if register:
${error_field("BAD_USERNAME", kind="span")}
${error_field("USERNAME_TAKEN", kind="span")}
${error_field("BAD_USERNAME", "user", kind="span")}
${error_field("USERNAME_TAKEN", "user", kind="span")}
%endif
</li>
%if register:
@@ -79,7 +79,7 @@
<input value="" name="email" id="email_${op}"
type="text" maxlength="50"/>
%if register:
${error_field("BAD_EMAILS", kind="span")}
${error_field("BAD_EMAILS", "email", kind="span")}
%endif
</li>
%endif
@@ -88,9 +88,9 @@
<input id="passwd_${op}" name="passwd" type="password"
maxlength="20"/>
%if register:
${error_field("BAD_PASSWORD", kind="span")}
${error_field("BAD_PASSWORD", "passwd", kind="span")}
%else:
${error_field("WRONG_PASSWORD", kind="span")}
${error_field("WRONG_PASSWORD", "passwd", kind="span")}
%endif
</li>
%if register:
@@ -98,12 +98,12 @@
<label for="passwd2_${op}">${_('verify password')}:</label>
<input name="passwd2" id="passwd2_${op}"
type="password" maxlength="20" />
${error_field("BAD_PASSWORD_MATCH", kind="span")}
${error_field("BAD_PASSWORD_MATCH", "passwd2", kind="span")}
</li>
<li>
${captchagen('', tabulate=True, label=False, size=30)}
${error_field("DRACONIAN", kind="span")}
${error_field("RATELIMIT", kind="span")}
<% iden = hasattr(thing, "captcha") and thing.captcha.iden or '' %>
${captchagen(iden, tabulate=True, label=False, size=30)}
${error_field("RATELIMIT", "ratelimit")}
</li>
%endif
<li>

View File

@@ -44,7 +44,7 @@
<input name="user" type="text" maxlength="20" tabindex="1"/>
<input name="passwd" type="password" maxlength="20" tabindex="2"/>
${error_field("WRONG_PASSWORD", "div")}
${error_field("WRONG_PASSWORD", "passwd", "div")}
<div class="status error"></div>
<div id="remember-me">

View File

@@ -66,10 +66,6 @@
${unsafe(safemarkdown(thing.body))}
</%def>
<%def name="commentText()">
${edit_comment_filter(thing.body)}
</%def>
<%def name="buttons()">
%if hasattr(thing, "was_comment"):
<li>
@@ -81,7 +77,7 @@ ${unsafe(safemarkdown(thing.body))}
${parent.buttons()}
%if c.user_is_loggedin:
<li>
${parent.simple_button(_("reply {verb}"), "reply")}
${parent.simple_button(_("reply"), "reply")}
</li>
%endif
%endif
@@ -94,11 +90,7 @@ ${unsafe(safemarkdown(thing.body))}
</p>
<div class="body">${self.commentBody()}
</div>
%if thing.author == c.user:
<div style="display: none;">${self.commentText()}\
</div>
%endif
<ul class="flat-list buttons" ${(collapse and "style='display:none'" or '')}>
<ul class="flat-list buttons" ${(collapse and "style='display:none'" or '')}>
${self.buttons()}
</ul>
</div>

View File

@@ -20,57 +20,48 @@
## CondeNet, Inc. All Rights Reserved.
################################################################################
<%namespace file="utils.html" import="error_field, submit_form, success_field"/>
<%namespace file="utils.html" import="error_field, submit_form"/>
<%namespace name="utils" file="utils.html"/>
<%!
from r2.lib.pages import UserText
%>
<h1>${_("send a message")}</h1>
<%call expr="submit_form(
onsubmit='return post_form(this, \'compose\', null, null, true)',
method='post', _id = 'compose-message',
action='/message/compose', _class='iform')">
${success_field(_("your message has been delivered"), successful=thing.success)}
<table>
<tr>
<th>
<label for="to">${_("to (username)")}</label></th>
<td><input type="text" name="to" value="${thing.to or ''}"
id="to" size="40"/></td>
<td>${error_field("NO_USER", "span")}
${error_field("USER_DOESNT_EXIST", "span")}</td>
</tr>
<tr>
<th>
<label for="subject">${_("subject")}</label>
</th>
<td>
<input type="text" name="subject" size="40" id="subject"\
value="${thing.subject or ''}"/>
</td>
<td>${error_field("NO_SUBJECT", "span")}</td>
</tr>
<tr>
<th>
<label for="message">${_("message")}</label>
</th>
<td>
<textarea id="message" name="message" rows="10" cols="40">
${thing.message or ''}
</textarea></td>
<td>${error_field("NO_MSG_BODY", "span")}
${error_field("COMMENT_TOO_LONG", "span")}</td>
</tr>
%if thing.captcha:
${thing.captcha.render()}
%endif
<tr>
<th>
<label for="send"></label>
</th>
<td>
<button id="send" name="send" type="submit">${_("send")}</button>
<span id='status' class='error'></span>
</td>
<td id="note_send"></td></tr>
</table>
</%call>
<%utils:submit_form onsubmit="return post_form(this, 'compose', null, null, true)",
method="post", _id = "compose-message",
action="/message/compose">
<div class="spacer">
<%utils:round_field title="${_('to')}", description="${_('(username)')}">
<input type="text" name="to" value="${thing.to or ''}"/>
${error_field("NO_USER", "to")}
${error_field("USER_DOESNT_EXIST", "to")}
</%utils:round_field>
</div>
<div class="spacer">
<%utils:round_field title="${_('subject')}">
<input type="text" name="subject" value="${thing.subject or ''}"/>
${error_field("NO_SUBJECT", "subject", "span")}
</%utils:round_field>
</div>
<div class="spacer">
<%utils:round_field title="${_('message')}">
${UserText(None, have_form = False, creating = True).render()}
</%utils:round_field>
</div>
<div class="spacer">
%if thing.captcha:
${thing.captcha.render()}
%endif
</div>
<button id="send" name="send" type="submit">${_("send")}</button>
<span class="status"></span>
</%utils:submit_form>

View File

@@ -38,9 +38,6 @@ load more comments <span class="gray">&nbsp;(${thing.count} ${ungettext("reply",
</span>
</%def>
<%def name="commentText()">
</%def>
<%def name="arrows()">
</%def>

View File

@@ -29,9 +29,6 @@
<span class="deepthread"><a href="${thing.parent_permalink}">${_("continue this thread")}</a></span>
</%def>
<%def name="commentText()">
</%def>
<%def name="arrows()">
</%def>

View File

@@ -31,7 +31,7 @@
<%def name="js(selected = False)">
${plain_link(thing.selected_title() if selected else thing.title,
'/', _sr_path = False, nocname = thing.nocname,
thing.path, _sr_path = False, nocname = True,
_class = thing.css_class, _id = thing._id, onclick = thing.onclick)}
</%def>

View File

@@ -111,7 +111,8 @@
<%def name="tabmenu()">
%if thing:
<ul class="tabmenu"
<% css_class = str(thing.css_class) if thing.css_class else "" %>
<ul class="tabmenu ${css_class}"
${"id='%s'" % thing._id if thing._id else ""}>
%for i, option in enumerate(thing):
<%

View File

@@ -22,90 +22,109 @@
<%!
from r2.lib.strings import strings
from r2.lib.pages import UserText
from r2.lib.template_helpers import add_sr
%>
<%namespace file="utils.html" import="error_field, submit_form, plain_link, text_with_links"/>
<%namespace name="utils" file="utils.html"/>
%if thing.subreddits:
<h1>${_("submit")}</h1>
%else:
<h1>${_("submit to %(site)s") % dict(site=c.site.name)}</h1>
<h1>${_("submit to reddit")}</h1>
<%utils:submit_form onsubmit="return post_form(this, 'submit', linkstatus, null, true)"
action=${add_sr("/submit")},
_class="submit content",
_id="newlink">
${thing.formtabs_menu.render()}
<div class="formtabs-content">
<div class="spacer">
<div id="link-desc" class="infobar">${strings.submit_link}</div>
<div id="text-desc" class="infobar">${strings.submit_text}</div>
</div>
<div class="spacer">
<%utils:round_field title="${_('title')}" id="title-field">
<textarea name="title" rows="2" cols="1" wrap="hard">${thing.title}</textarea>
${error_field("NO_TEXT", "title", "span")}
${error_field("TOO_LONG", "title", "span")}
</%utils:round_field>
</div>
<div class="spacer">
<%utils:round_field title="${_('url')}" id="url-field">
<input name="kind" value="link" type="hidden"/>
<input id="url" name="url" type="text" value="${thing.url}"/>
${error_field("NO_URL", "url", "span")}
${error_field("BAD_URL", "url", "span")}
${error_field("ALREADY_SUB", "url", "span")}
<div>
<button type="button" onclick="fetch_title()">${_("suggest title")}</button>
<span class="title-status"></span>
</div>
</%utils:round_field>
</div>
<div class="spacer">
<%utils:round_field title="${_('text')}", description="${_('(optional)')}" id="text-field">
<input name="kind" value="self" type="hidden"/>
${UserText(None, have_form = False, creating = True).render()}
</%utils:round_field>
</div>
<div class="spacer">
<%utils:round_field title="${_('reddit')}" id="reddit-field">
<div id="sr-autocomplete-area">
<input id="sr-autocomplete" name="sr" type="text"
autocomplete="off"
onkeyup="sr_name_up(event)"
onkeydown="return sr_name_down(event)"
onblur="hide_sr_name_list()"
value = "${thing.default_sr}" />
<ul id="sr-drop-down">
<li class="sr-name-row"
onmouseover="highlight_reddit(this)"
onmousedown="return sr_dropdown_mdown(this)"
onmouseup="sr_dropdown_mup(this)">nothin</li>
</ul>
</div>
<script type="text/javascript">
reddit.sr_cache = ${unsafe(thing.sr_searches)};
</script>
${error_field("SUBREDDIT_NOEXIST", "sr", "span")}
${error_field("SUBREDDIT_REQUIRED", "sr", "span")}
<div id="suggested-reddits">
<span class="">${_("popular choices")}&#32;</span>
<ul>
%for name in thing.subreddits:
<li>
<a href="#" onclick="set_sr_name(this); return false">${name}</a>&#32;
</li>
%endfor
</ul>
</div>
</%utils:round_field>
</div>
%if thing.captcha:
${thing.captcha.render()}
%endif
</div>
<%call expr="submit_form(onsubmit='return post_form(this, \'submit\', linkstatus, null, true)',
action=add_sr('/submit'), _class='long-text content', _id='newlink')">
%if not thing.subreddits:
<input type="hidden" name="sr" value="${c.site.name}" />
%endif
<input type="hidden" name="then" value="${thing.then}" />
<table>
<tr>
<th>
<label for="url">${_("url")}</label>
</th>
<td>
<input id="url" name="url" type="text" value="${thing.url}"/>
${error_field("NO_URL", "span")}
${error_field("BAD_URL", "span")}
${error_field("ALREADY_SUB", "span")}
</td>
</tr>
<tr>
<th>
<label for="title">${_("title")}</label>
</th>
<td><input id="title" name="title" value="${thing.title}" type="text" />
${error_field("NO_TITLE", "span")}
${error_field("TITLE_TOO_LONG", "span")}
</td>
</tr>
%if thing.subreddits:
<tr>
<th>
<label for="sr">${_("subreddit")}</label>
</th>
<td>
<select id="sr" name="sr">
%for sr in thing.subreddits:
<option value="${sr}">${sr}</option>
%endfor
</select>
</td>
</tr>
%endif
<tr>
<td></td>
<td>
<input id="save" name="save" type="checkbox" />
<label for="save">${_("add to my saved sites")}</label>
</td>
</tr>
%if thing.captcha:
${thing.captcha.render()}
%endif
<tr>
<td></td>
<td>
<button class="btn" type="submit">${_("submit")}</button>
<span class="status"></span>
${error_field("RATELIMIT", "span")}
</td>
</tr>
</table>
<script type="text/javascript">
var form = $("#newlink");
if(form.length) {
emptyInput(form.find("#url"),
'${unsafe(_('type "self" here to make this post refer to itself.'))}');
emptyInput(form.find("#title"),
"${_('enter a title, or click submit to find one automatically.')}")
}
</script>
<p>
${text_with_links(_("use reddit from your toolbar with the %(bookmarklets)s"),
bookmarklets = (_('reddit bookmarklets'), '/bookmarklets'))}
</p>
</%call>
<div class="spacer">
<button class="btn" name="submit" value="form" type="submit" tabindex=1>${_("submit")}</button>
<span class="status"></span>
${error_field("RATELIMIT", "ratelimit")}
</div>
</%utils:submit_form>
<script type="text/javascript">
var form = $("#newlink");
if(form.length) {
var default_menu = form.find(".${thing.default_tab}:first");
select_form_tab(default_menu, "${thing.default_show}", "${thing.default_hide}");
}
</script>

View File

@@ -26,6 +26,10 @@
${thing.css_class and ("class='%s'" % str(thing.css_class)) or ""}>
%endif
%if thing.title:
<h1>${thing.title}</h1>
%endif
${t.render() if t else ''}
%if thing.div:

View File

@@ -21,33 +21,24 @@
################################################################################
<%namespace file="utils.html" import="error_field, success_field"/>
${success_field(_('you should receive an email shortly'),
successful=thing.success, hide='passform')}
<%namespace name="utils" file="utils.html"/>
<form id="passform" action="/api/password" method="post"
class="content pretty-form medium-text"
class="content"
onsubmit="return post_form(this, 'password');">
<h1>${_("what's my password?")}</h1>
<p> ${_("enter your user name below to receive your login information")}</p>
<table>
<tr>
<td>
<label for="name">${_("username")}</label>
</td>
<td>
<input type="text" id="name" name="name" />
</td>
<td>
<button type="submit" class="btn">${_("email me")}</button>
</td>
</tr>
<tr>
<td colspan="2">
<span class="status"></span>
${error_field("USER_DOESNT_EXIST", "span")}
${error_field("NO_EMAIL_FOR_USER", "span")}
</td>
</tr>
</table>
<p>${_("enter your user name below to receive your login information")}</p>
<div class="spacer">
<%utils:round_field title="${_('username')}">
<input type="text" name="name" />
${error_field("USER_DOESNT_EXIST", "name")}
${error_field("NO_EMAIL_FOR_USER", "name")}
</%utils:round_field>
</div>
<button type="submit" class="btn">${_("email me")}</button>
<span class="status"></span>
</form>

View File

@@ -20,11 +20,10 @@
## CondeNet, Inc. All Rights Reserved.
################################################################################
<div class="autosize">
<div class="permamessage">
<p style="margin:0">
${_("you are viewing a single comment's thread")}
</p>
<p style="margin: 4px 0 0 0"><a href="${thing.comments_url}">${_("see the above comments")}</a></p>
</div>
<div class="infobar">
${_("you are viewing a single comment's thread.")}
<p>
<a href="${thing.comments_url}">${_("view the rest of the comments")}</a>
&nbsp;&#8594;
</p>
</div>

View File

@@ -21,28 +21,36 @@
################################################################################
<%namespace file="utils.html" import="error_field"/>
<%namespace name="utils" file="utils.html"/>
<% import random %>
<%def name="areyousure(name)">
<tr><td>${_("are you sure?")}</td>
<% yes, no = random.choice(((_("yes"), _("no")), (_("no"), _("yes")))) %>
<td><input name="${name}" id="${name}yes" type="radio" value="${yes}"/>
<% yes, no = random.choice(((_("yes"), _("no")), (_("no"), _("yes")))) %>
<div class="spacer">
<%utils:round_field title="${_('are you sure?')}">
<div class="rounded delete-field">
<input name="${name}" id="${name}yes" type="radio" value="${yes}"/>
<label for="${name}yes">${yes}</label>
</td>
<td><input name="${name}" id="${name}no" type="radio" value="${no}"/>
<br/>
<input name="${name}" id="${name}no" type="radio" value="${no}"/>
<label for="${name}no">${no}</label>
</td>
</tr>
</div>
</%utils:round_field>
</div>
</%def>
<h1>${_("delete your reddit account? hope you have a good reason.")}</h1>
<form action="/post/delete" method="post" class="pretty-form"
<form action="/post/delete" method="post"
onsubmit="${"return post_form(this, 'delete_user', function(x) {return '%s'})" % _("deleting...")}" id="pref-delete">
<table>
${areyousure("areyousure1")}
${areyousure("areyousure2")}
${areyousure("areyousure3")}
</table>
<input type="submit" class="btn" value="${_('delete')}"/>
<span class="status"></span>
</form>

View File

@@ -21,34 +21,42 @@
################################################################################
<%namespace file="utils.html" import="error_field"/>
<%namespace name="utils" file="utils.html"/>
<form action="/post/update" method="post" class="pretty-form medium-text"
<h1>${_("update your email or password")}</h1>
<form action="/post/update" method="post"
onsubmit="return post_form(this, 'update')" id="pref-update">
<table>
<tr>
<td align="right">${_("current password (required)")}:</td>
<td><input name="curpass" type="password" /></td>
<td>${error_field("WRONG_PASSWORD")}</td>
</tr>
<tr>
<td align="right">${_("email")}:</td>
<td><input name="email" type="text" value="${hasattr(c.user,'email') and c.user.email or ''}"/></td>
<td>
${error_field("BAD_EMAILS")}
</td>
</tr>
<tr>
<td align="right">${_("new password")}:</td>
<td><input name="newpass" type="password" maxlength="20"/></td>
<td>${error_field("BAD_PASSWORD")}</td>
</tr>
<tr>
<td align="right">${_("verify password")}:</td>
<td><input name="verpass" type="password" maxlength="20"/></td>
<td> ${error_field("BAD_PASSWORD_MATCH")}
</td>
</tr>
</table>
<div class="spacer">
<%utils:round_field title="${_('current password')}" description="${_('(required)')}">
<input type="password" name="curpass" />
${error_field("WRONG_PASSWORD", "curpass")}
</%utils:round_field>
</div>
<div class="spacer">
<%utils:round_field title="${_('email')}">
<input type="text" name="email" value="${getattr(c.user, 'email', '')}"/>
${error_field("BAD_EMAILS", "email")}
</%utils:round_field>
</div>
<div class="spacer">
<%utils:round_field title="${_('new password')}">
<input type="password" name="newpass"/>
${error_field("BAD_PASSWORD", "newpass")}
</%utils:round_field>
</div>
<div class="spacer">
<%utils:round_field title="${_('verify password')}">
<input type="password" name="verpass"/>
${error_field("BAD_PASSWORD_MATCH", "verpass")}
</%utils:round_field>
</div>
<button type="submit" class="btn">${_('save')}</button>
<span class="status error"></span>
</form>

View File

@@ -305,14 +305,15 @@ thing id-${what._fullname}
callback, cancelback,
css_class = '', alt_css_class = '',
reverse = False,
login_required = False)">
login_required = False,
style = '',)">
<%
if reverse:
callback, cancelback = cancelback, callback
title, alt_title = alt_title, title
css_class, alt_css_class = alt_css_class, css_class
%>
<span class="${class_name} toggle">
<span class="${class_name} toggle" style="${style}">
<a class="option active ${css_class}" href="#"
%if login_required and not c.user_is_loggedin:
onclick="return showcover('', '${callback}_' + $(this).thing_id());"

View File

@@ -46,7 +46,8 @@
<input name="title" type="text" value="${thing.link.title if thing.link else ""}" />
</td>
<td>
${error_field("NO_TITLE", "span")}
${error_field("NO_TEXT", "title")}
${error_field("TOO_LONG", "title")}
</td>
</tr>
<tr>
@@ -56,9 +57,9 @@
value="${('self' if thing.link.is_self else thing.link.url) if thing.link else ""}"/>
</td>
<td>
${error_field("NO_URL", "span")}
${error_field("BAD_URL", "span")}
${error_field("ALREADY_SUB", "span")}
${error_field("NO_URL", "url")}
${error_field("BAD_URL", "url")}
${error_field("ALREADY_SUB", "url")}
</td>
</tr>
<tr>
@@ -78,7 +79,7 @@
%endif
</td>
<td>
${error_field("SUBREDDIT_NOEXIST")}
${error_field("SUBREDDIT_NOEXIST", "sr")}
</td>
</tr>
<tr>
@@ -121,7 +122,7 @@
</select>
%endif
</td>
<td>${error_field("BAD_NUMBER", "span")}</td>
<td>${error_field("BAD_NUMBER", "timelimitlength")}</td>
</tr>
</table>
</form>
@@ -148,5 +149,5 @@
>${text}</button>
&#32;
<span class="status error"></span>
${error_field("RATELIMIT", "span")}
${error_field("RATELIMIT", "ratelimit")}
</div>

View File

@@ -97,4 +97,10 @@
<div class="arrow upmod"></div>
<div class="arrow down"></div>
<div class="arrow downmod"></div>
<div class="eb-sch"></div>
<div class="eb-se"></div>
<div class="eb-seh"></div>
<div class="eb-vch"></div>
<div class="eb-ve"></div>
<div class="eb-veh"></div>
</div>

View File

@@ -21,6 +21,7 @@
################################################################################
<%namespace file="utils.html" import="error_field, success_field"/>
<%namespace name="utils" file="utils.html"/>
%if thing.done:
<p class="error">your password has been reset and you've been logged in. Go use the site!</p>
@@ -28,52 +29,27 @@
${error_field("EXPIRED", 'p')}
<form id="chpass" method="post" action="/api/resetpassword"
onsubmit="return post_form(this,'resetpassword')"
class="pretty-form">
<h1>reset your password</h1>
onsubmit="return post_form(this,'resetpassword')">
<h1>choose a new password</h1>
<input type="hidden" name="key" value="${thing.key}"/>
<table>
<tr>
<td>
${_("username")}
</td>
<td>
<input class="logtxt" name="name"
id="name" type="text"/>
</td>
</tr>
<tr>
<td>
${_("new password")}
</td>
<td>
<input class="logtxt" name="passwd"
id="passwd" type="password"/>
</td>
<td>
${error_field("BAD_PASSWORD")}
</td>
</tr>
<tr>
<td>
${_("verify password")}
</td>
<td>
<input class="logtxt" name="passwd2"
id="passwd2" type="password"/>
</td>
<td>
${error_field("BAD_PASSWORD_MATCH")}
</td>
</tr>
<tr>
<td>
</td>
<td>
<button class="btn" type="submit">submit</button>
<span class="status"></span>
</td><td></td>
</tr>
</table>
<div class="spacer">
<%utils:round_field title="${_('new password')}">
<input type="password" name="passwd" />
${error_field("BAD_PASSWORD", "passwd")}
</%utils:round_field>
</div>
<div class="spacer">
<%utils:round_field title="${_('verify password')}">
<input type="password" name="passwd2" />
${error_field("BAD_PASSWORD_MATCH", "passwd2")}
</%utils:round_field>
</div>
<button class="btn" type="submit">submit</button>
<span class="status"></span>
</form>
%endif

View File

@@ -16,10 +16,17 @@
## 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-2009
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved.
################################################################################
<%!
from r2.lib.filters import unsafe, safemarkdown, keep_space
%>
<%namespace file="utils.html" import="error_field"/>
<div class="selftextcontainer">
<div class="selftext rounded">
${unsafe(safemarkdown(thing.link.selftext))}
</div>
</div>

View File

@@ -6,7 +6,7 @@
class="pretty-form sharelink" action="/post/share"
${"" if thing.link_name else "style='display:none'"}>
<div class="clearleft"><!--IEsux--></div>
${error_field("RATELIMIT")}
${error_field("RATELIMIT", "ratelimit")}
<input type="hidden" name="parent" value="${thing.link_name}"/>
<table class="sharetable preftable">
<tr class="">
@@ -22,9 +22,9 @@
</textarea>
</td>
<td class="share-to-errors">
${error_field("BAD_EMAILS")}
${error_field("TOO_MANY_EMAILS")}
${error_field("NO_EMAILS")}
${error_field("BAD_EMAILS", "share_to")}
${error_field("TOO_MANY_EMAILS", "share_to")}
${error_field("NO_EMAILS", "share_to")}
</td>
</tr>
<tr>
@@ -42,7 +42,7 @@
name="share_from" />
</td>
<td class="share-from-errors">
${error_field("COMMENT_TOO_LONG" )}
${error_field("COMMENT_TOO_LONG", "share_from" )}
</td>
</tr>
<tr>
@@ -60,8 +60,8 @@
value="${c.user.email if hasattr(c.user, 'email') else ''}"/>
</td>
<td class="reply-to-errors">
${error_field("BAD_EMAILS")}
${error_field("TOO_MANY_EMAILS")}
${error_field("BAD_EMAILS", "replyto")}
${error_field("TOO_MANY_EMAILS", "replyto")}
</td>
</tr>
<tr>
@@ -80,18 +80,16 @@
</textarea>
</td>
<td class="message-errors">
${error_field("COMMENT_TOO_LONG")}
${error_field("TOO_LONG", "message")}
</td>
</tr>
%if thing.captcha:
${captchagen(thing.captcha.iden, thing.captcha.error,
tabulate = False, size = 30)}
${captchagen('', tabulate = False, size = 30)}
%endif
<tr>
<td>
</td>
<td>
<span class="status error"></span>
<button type="submit" class="btn">
${_("share")}
</button>
@@ -99,6 +97,7 @@
onclick="return cancelShare(this);">
${_("cancel")}
</button>
<span class="status error"></span>
</td>
<td>
</td>

View File

@@ -108,7 +108,7 @@
</th>
<td class="prefright">
<input id="img-name" name="name" value="" type="text"/>
${error_field("BAD_CSS_NAME", "span")}
${error_field("BAD_CSS_NAME", "name")}
<br/>
<span class="little gray">
${_("(image names should consist of alphanumeric characters and '-' only)")}

View File

@@ -0,0 +1,14 @@
${thing.tabmenu.render()}
<div class="tabpane-content">
%for i, (name, title, pane) in enumerate(thing.tabs):
<div id="tabbedpane-${name}" class="tabbedpane"
${"style='display:none'" if i > 0 else ""}>
${pane.render()}
</div>
%endfor
##select the front tab and init the other tabs
<script type="text/javascript">
select_tab("${thing.tabs[0][0]}", true)
</script>
</div>

View File

@@ -34,7 +34,7 @@
<input type="text" name="name" id="name"/>
<button class="btn" type="submit">${_("add")}</button>
<span class="status"></span>
${error_field("USER_DOESNT_EXIST", "span")}
${error_field("USER_DOESNT_EXIST", "name")}
</form>
%endif

View File

@@ -0,0 +1,145 @@
## 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-2009
## CondeNet, Inc. All Rights Reserved.
################################################################################
<%!
from r2.lib.filters import unsafe, safemarkdown, keep_space
from r2.lib.utils import randstr
%>
<%namespace file="printable.html" import="toggle_button" />
<%namespace file="utils.html" import="error_field"/>
<%def name="action_button(name, btn_type, onclick, display)">
<button type="${btn_type}" onclick="${onclick}" class="${name}"
${"style='display:none'" if not display else ""}>
${name}
</button>
</%def>
%if thing.have_form:
<form action="#" class="${thing.css_class}"
onsubmit="return post_form(this, '${thing.post_form}')"
${"style='display:none'" if not thing.display else ""}
id="form-${(thing.item._fullname if thing.item else "") + randstr(3)}">
%else:
<div class="${thing.css_class}">
%endif
##this is set for both editting selftext and creating new comments
<input type="hidden" name="thing_id"
value="${thing.item and thing.item._fullname or ''}"/>
%if not thing.creating:
<div class="usertext-body">
${unsafe(safemarkdown(thing.text, nofollow = thing.nofollow))}
</div>
%endif
%if thing.editable or thing.creating:
##keep this on one line so we don't add extra spaces
<div class="usertext-edit"
style="${"" if thing.creating else 'display: none'}">
##this div prevents IE7 from adding extra margin to the textarea.
<div>
<textarea rows="1" cols="1"
name="text"
>${keep_space(thing.text)}</textarea>
</div>
<div class="bottom-area">
${toggle_button("help-toggle", _("formatting help"), _("hide help"),
"helpon", "helpoff",
style = "" if thing.creating else "display: none")}
${error_field("TOO_LONG", "text", "span")}
${error_field("NO_TEXT", "text", "span")}
<div class="usertext-buttons">
${action_button("save", "submit", "",
thing.creating and thing.have_form)}
${action_button("cancel", "button", "cancel_usertext(this)", False)}
%if thing.have_form:
<span class="status"></span>
%endif
</div>
</div>
<table class="markhelp md" style="display: none">
<tr style="background-color: #ffff99; text-align: center">
<td><em>${_( "you type:")}</em></td>
<td><em>${_( "you see:")}</em></td>
</tr>
<tr>
<td>*${_( "italics")}*</td>
<td><em>${_( "italics")}</em></td>
</tr>
<tr>
<td>**${_( "bold")}**</td>
<td><b>${_( "bold")}</b></td>
</tr>
<tr>
<td>[reddit!](http://reddit.com)</td>
<td><a href="http://reddit.com">reddit!</a></td>
</tr>
<tr>
<td>
* ${_( "item")} 1<br/>
* ${_( "item")} 2<br/>
* ${_( "item")} 3
</td>
<td>
<ul>
<li>${_( "item")} 1</li>
<li>${_( "item")} 2</li>
<li>${_( "item")} 3</li>
</ul>
</td>
</tr>
<tr>
<td>&gt; ${_( "quoted text")}</td>
<td><blockquote>${_( "quoted text" )}</blockquote></td>
</tr>
<tr>
<td>
Here is some code:<br/>
<span class="spaces">
&nbsp;&nbsp;&nbsp;&nbsp;
</span>
def foo():<br/>
<span class="spaces">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
</span>
print "hello, world!"<br/>
</td>
<td>Here is come code:<br/>
<pre>def foo():<br/>&nbsp;&nbsp;&nbsp;&nbsp;print "hello,
world!"</pre>
</td>
</tr>
</table>
</div>
%endif
%if thing.have_form:
</form>
%else:
</div>
%endif

View File

@@ -51,7 +51,7 @@ id="${arg}_${thing and thing._fullname or ''}"
</%def>
<%def name="submit_form(onsubmit='', action='', _class='', method='post', _id='', **params)">
<form class="pretty-form ${_class or ''}" onsubmit="${onsubmit or ''}"
<form class="${_class or ''}" onsubmit="${onsubmit or ''}"
action="${action or ''}" ${_id and "id='" + _id + "'" or ""} method="${method}"
%if c.cname:
target="_top"
@@ -73,11 +73,11 @@ ${first_defined(kw[1:])}
%endif
</%def>
<%def name="error_field(name, kind='p')">
<${kind} class="error ${name}"
style="${'' if name in c.errors else 'display:none'}">
%if name in c.errors:
${c.errors[name].message}
<%def name="error_field(error_name, field_name, kind='span')">
<${kind} class="error ${error_name} field-${field_name}"
style="${'' if error_name in c.errors else 'display:none'}">
%if error_name in c.errors:
${c.errors[error_name].message}
%endif
</${kind}>
</%def>
@@ -393,3 +393,20 @@ ${unsafe(txt)}
</a>
</form>
</%def>
<%def name="round_field(title, description = '', css_class= '', **kw)">
<div class="roundfield ${css_class}"
%for k, v in kw.iteritems():
${k}="${v}"
%endfor
>
<span class="title">${title}</span>
&#32;
%if description:
<span class="gray">${description}</span>
%endif
<div class="roundfield-content">
${caller.body()}
</div>
</div>
</%def>