diff --git a/r2/Makefile b/r2/Makefile
index 025500d44..8ad089956 100644
--- a/r2/Makefile
+++ b/r2/Makefile
@@ -23,7 +23,7 @@
# Jacascript files to be compressified
js_targets = jquery.js jquery.json.js jquery.reddit.js reddit.js
# CSS targets
-css_targets = reddit.css reddit-ie6-hax.css
+css_targets = reddit.css reddit-ie6-hax.css reddit-ie7-hax.css
SED=sed
diff --git a/r2/r2/config/routing.py b/r2/r2/config/routing.py
index 18b5012b0..04cd87b5e 100644
--- a/r2/r2/config/routing.py
+++ b/r2/r2/config/routing.py
@@ -134,8 +134,16 @@ def make_map(global_conf={}, app_conf={}):
requirements=dict(action="help|blog"))
mc('/help/:anything', controller='embed', action='help')
- mc('/:action', controller='toolbar',
- requirements=dict(action="goto|toolbar"))
+ mc('/goto', controller='toolbar', action='goto')
+ mc('/tb/:id', controller='toolbar', action='tb')
+ mc('/toolbar/:action', controller='toolbar',
+ requirements=dict(action="toolbar|inner|login"))
+ mc('/toolbar/comments/:id', controller='toolbar', action='comments')
+
+ mc('/s/*rest', controller='toolbar', action='s')
+ # additional toolbar-related rules just above the catchall
+
+ mc('/d/:what', controller='api', action='bookmarklet')
mc('/resetpassword/:key', controller='front',
action='resetpassword')
@@ -150,8 +158,6 @@ def make_map(global_conf={}, app_conf={}):
mc('/api/:action/:url_user', controller='api',
requirements=dict(action="login|register"))
mc('/api/:action', controller='api')
-
- mc('/d/:what', controller='api', action='bookmarklet')
mc('/captcha/:iden', controller='captcha', action='captchaimg')
@@ -175,6 +181,15 @@ def make_map(global_conf={}, app_conf={}):
# displayed properly.
mc('/error/document/:id', controller='error', action="document")
+ # these should be near the buttom, because they should only kick
+ # in if everything else fails. It's the attempted catch-all
+ # reddit.com/http://... and reddit.com/34fr, but these redirect to
+ # the less-guessy versions at /s/ and /tb/
+ mc('/:linkoid', controller='toolbar', action='linkoid',
+ requirements=dict(linkoid='[0-9a-z]{1,6}'))
+ mc('/:urloid', controller='toolbar', action='urloid',
+ requirements=dict(urloid=r'(\w+\.\w{2,}|https?).*'))
+
mc("/*url", controller='front', action='catchall')
return map
diff --git a/r2/r2/controllers/api.py b/r2/r2/controllers/api.py
index 9034dd374..5cc1c7284 100644
--- a/r2/r2/controllers/api.py
+++ b/r2/r2/controllers/api.py
@@ -33,7 +33,7 @@ import r2.models.thing_changes as tc
from r2.controllers import ListingController
from r2.lib.utils import get_title, sanitize_url, timeuntil, set_last_modified
-from r2.lib.utils import query_string, to36, timefromnow
+from r2.lib.utils import query_string, to36, timefromnow, link_from_url
from r2.lib.wrapped import Wrapped
from r2.lib.pages import FriendList, ContributorList, ModList, \
BannedList, BoringPage, FormPage, NewLink, CssError, UploadedImage
@@ -55,25 +55,6 @@ from md5 import md5
from r2.lib.promote import promote, unpromote, get_promoted
-def link_listing_by_url(url, count = None):
- """
- Generates a listing of links which share a common url
- """
- links = ()
- try:
- if url:
- links = list(tup(Link._by_url(url, sr = c.site)))
- links.sort(key = lambda x: -x._score)
- if count is not None:
- links = links[:count]
- except NotFound:
- pass
- names = [l._fullname for l in links]
- builder = IDBuilder(names, num = 25)
- return LinkListing(builder).listing()
-
-
-
class ApiController(RedditController):
"""
Controller which deals with almost all AJAX site interaction.
@@ -95,10 +76,14 @@ class ApiController(RedditController):
count = VLimit('limit'))
def GET_info(self, link, count):
"""
- Get's a listing of links which have the provided url.
+ Gets a listing of links which have the provided url.
"""
- listing = link_listing_by_url(request.params.get('url'),
- count = count)
+ if not link or 'url' not in request.params:
+ return abort(404, 'not found')
+
+ links = link_from_url(request.params.get('url'), filter_spam = False)
+ builder = IDBuilder([link._fullname for link in links], num = count)
+ listing = LinkListing(builder, nextprev = False).listing()
return BoringPage(_("API"), content = listing).render()
@validatedForm(VCaptcha(),
@@ -169,8 +154,9 @@ 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):
+ def POST_submit(self, form, jquery, url, title, save, sr, ip, then):
if isinstance(url, (unicode, str)):
form.set_inputs(url = url)
@@ -245,13 +231,12 @@ class ApiController(RedditController):
# flag search indexer that something has changed
tc.changed(l)
- # make_permalink is designed for links that can be set to _top
- # here, we need to generate an ajax redirect as if we were not on a
- # cname.
- cname = c.cname
- c.cname = False
- path = l.make_permalink_slow()
- c.cname = cname
+ if then == 'comments':
+ path = add_sr(l.make_permalink_slow())
+ elif then == 'tb':
+ form.attr('target', '_top')
+ path = add_sr('/tb/%s' % l._id36)
+
form.redirect(path)
def _login(self, form, user, dest='', rem = None):
@@ -706,7 +691,9 @@ class ApiController(RedditController):
errors.BANNED_IP in c.errors or
errors.CHEATER in c.errors)
# TODO: temporary hack until we migrate the rest of the vote data
- if thing and thing._date > datetime(2009, 4, 17, 0, 0, 0, 0, g.tz):
+ if thing and thing._date < datetime(2009, 4, 17, 0, 0, 0, 0, g.tz):
+ g.log.debug("POST_vote: ignoring old vote on %s" % thing._fullname)
+ elif thing:
dir = (True if dir > 0
else False if dir < 0
else None)
@@ -1372,6 +1359,18 @@ class ApiController(RedditController):
return UploadedImage(_('saved'), thumbnail_url(link), "",
errors = errors).render()
+ @noresponse()
+ def POST_tb_commentspanel_show(self):
+ # this preference is allowed for non-logged-in users
+ c.user.pref_frame_commentspanel = True
+ c.user._commit()
+
+ @noresponse()
+ def POST_tb_commentspanel_hide(self):
+ # this preference is allowed for non-logged-in users
+ c.user.pref_frame_commentspanel = False
+ c.user._commit()
+
@validatedForm(promoted = VByName('ids', thing_cls = Link, multiple = True))
def POST_onload(self, form, jquery, promoted, *a, **kw):
# make sure that they are really promoted
diff --git a/r2/r2/controllers/error.py b/r2/r2/controllers/error.py
index af5fbc2a5..5d591981b 100644
--- a/r2/r2/controllers/error.py
+++ b/r2/r2/controllers/error.py
@@ -113,6 +113,8 @@ class ErrorController(RedditController):
def send404(self):
c.response.status_code = 404
+ if 'usable_error_content' in request.environ:
+ return request.environ['usable_error_content']
if c.site._spam and not c.user_is_admin:
message = (strings.banned_subreddit % dict(link = '/feedback'))
diff --git a/r2/r2/controllers/front.py b/r2/r2/controllers/front.py
index 4afe9f0fb..5cde45a3a 100644
--- a/r2/r2/controllers/front.py
+++ b/r2/r2/controllers/front.py
@@ -22,12 +22,12 @@
from validator import *
from pylons.i18n import _, ungettext
from reddit_base import RedditController, base_listing
-from api import link_listing_by_url
from r2 import config
from r2.models import *
from r2.lib.pages import *
from r2.lib.menus import *
-from r2.lib.utils import to36, sanitize_url, check_cheating, title_to_url, query_string, UrlParser
+from r2.lib.utils import to36, sanitize_url, check_cheating, title_to_url
+from r2.lib.utils import query_string, UrlParser, link_from_url
from r2.lib.template_helpers import get_domain
from r2.lib.emailer import has_opted_out, Email
from r2.lib.db.operators import desc
@@ -76,20 +76,20 @@ class FrontController(RedditController):
def GET_random(self):
"""The Serendipity button"""
- n = rand.randint(0, 9)
- sort = 'new' if n > 5 else 'hot'
+ sort = 'new' if rand.choice((True,False)) else 'hot'
links = c.site.get_links(sort, 'all')
if isinstance(links, thing.Query):
- links._limit = 25
- links = [x._fullname for x in links]
+ links._limit = 25
+ links = [x._fullname for x in links]
else:
- links = links[:25]
+ links = list(links)[:25]
+
if links:
- name = links[rand.randint(0, min(24, len(links)-1))]
+ name = rand.choice(links)
link = Link._by_fullname(name, data = True)
- return self.redirect(link.url)
+ return self.redirect(add_sr("/tb/" + link._id36))
else:
- return self.redirect('/')
+ return self.redirect(add_sr('/'))
def GET_password(self):
"""The 'what is my password' page"""
@@ -138,8 +138,8 @@ class FrontController(RedditController):
#check for 304
self.check_modified(article, 'comments')
- # if there is a focal comment, communicate down to comment_skeleton.html who
- # that will be
+ # if there is a focal comment, communicate down to
+ # comment_skeleton.html who that will be
if comment:
c.focal_comment = comment._id36
@@ -150,7 +150,9 @@ class FrontController(RedditController):
check_cheating('comments')
- # figure out number to show based on the menu
+ # figure out number to show based on the menu (when num_comments
+ # is 'true', the user wants to temporarily override their
+ # comments limit pref
user_num = c.user.pref_num_comments or g.num_comments
num = g.max_comments if num_comments == 'true' else user_num
@@ -411,8 +413,6 @@ class FrontController(RedditController):
return builder.total_num, timing, res
-
-
def GET_login(self):
"""The /login form. No link to this page exists any more on
the site (all actions invoking it now go through the login
@@ -432,10 +432,10 @@ class FrontController(RedditController):
@validate(VUser(),
VModhash())
- def POST_logout(self):
+ def POST_logout(self, dest = None):
"""wipe login cookie and redirect to referer."""
self.logout()
- dest = request.referer or '/'
+ dest = request.post.get('dest','') or request.referer or '/'
return self.redirect(dest)
@@ -474,38 +474,25 @@ class FrontController(RedditController):
@validate(VUser(),
VSRSubmitPage(),
url = VRequired('url', None),
- title = VRequired('title', None))
- def GET_submit(self, url, title):
+ title = VRequired('title', None),
+ then = VOneOf('then', ('tb','comments'), default = 'comments'))
+ def GET_submit(self, url, title, then):
"""Submit form."""
if url and not request.get.get('resubmit'):
# check to see if the url has already been submitted
- listing = link_listing_by_url(url)
- redirect_link = None
- if listing.things:
- # if there is only one submission, the operation is clear
- if len(listing.things) == 1:
- redirect_link = listing.things[0]
- # if there is more than one, check the users' subscriptions
- else:
- subscribed = [l for l in listing.things
- if c.user_is_loggedin
- and l.subreddit.is_subscriber_defaults(c.user)]
+ links = link_from_url(url)
+ if links and len(links) == 1:
+ return self.redirect(links[0].already_submitted_link)
+ elif links:
+ builder = IDBuilder([link._fullname for link in links])
+ listing = LinkListing(builder, nextprev=False).listing()
+ infotext = (strings.multiple_submitted
+ % links[0].resubmit_link())
+ res = BoringPage(_("seen it"),
+ content = listing,
+ infotext = infotext).render()
+ return res
- #if there is only 1 link to be displayed, just go there
- if len(subscribed) == 1:
- redirect_link = subscribed[0]
- else:
- infotext = strings.multiple_submitted % \
- listing.things[0].resubmit_link()
- res = BoringPage(_("seen it"),
- content = listing,
- infotext = infotext).render()
- return res
-
- # we've found a link already. Redirect to its permalink page
- if redirect_link:
- return self.redirect(redirect_link.already_submitted_link)
-
captcha = Captcha() if c.user.needs_captcha() else None
sr_names = Subreddit.submit_sr_names(c.user) if c.default_sr else ()
@@ -513,7 +500,8 @@ class FrontController(RedditController):
content=NewLink(url=url or '',
title=title or '',
subreddits = sr_names,
- captcha=captcha)).render()
+ captcha=captcha,
+ then = then)).render()
def _render_opt_in_out(self, msg_hash, leave):
"""Generates the form for an optin/optout page"""
diff --git a/r2/r2/controllers/reddit_base.py b/r2/r2/controllers/reddit_base.py
index bc82fded6..f87df1720 100644
--- a/r2/r2/controllers/reddit_base.py
+++ b/r2/r2/controllers/reddit_base.py
@@ -47,7 +47,7 @@ from r2.lib.tracking import encrypt, decrypt
NEVER = 'Thu, 31 Dec 2037 23:59:59 GMT'
-cache_affecting_cookies = ('reddit_first','over18')
+cache_affecting_cookies = ('reddit_first','over18','_options')
r_subnet = re.compile("^(\d+\.\d+)\.\d+\.\d+$")
@@ -73,7 +73,7 @@ class Cookie(object):
class UnloggedUser(FakeAccount):
_cookie = 'options'
- allowed_prefs = ('pref_content_langs', 'pref_lang')
+ allowed_prefs = ('pref_content_langs', 'pref_lang', 'pref_frame_commentspanel')
def __init__(self, browser_langs, *a, **kw):
FakeAccount.__init__(self, *a, **kw)
@@ -90,6 +90,7 @@ class UnloggedUser(FakeAccount):
self._defaults = self._defaults.copy()
self._defaults['pref_lang'] = lang
self._defaults['pref_content_langs'] = content_langs
+ self._defaults['pref_frame_commentspanel'] = False
self._load()
@property
diff --git a/r2/r2/controllers/toolbar.py b/r2/r2/controllers/toolbar.py
index cf81a53e8..b452a9d2a 100644
--- a/r2/r2/controllers/toolbar.py
+++ b/r2/r2/controllers/toolbar.py
@@ -21,31 +21,228 @@
################################################################################
from reddit_base import RedditController
from r2.lib.pages import *
+from r2.models import *
+from r2.lib.menus import CommentSortMenu
from r2.lib.filters import spaceCompress
+from r2.lib.memoize import memoize
+from r2.lib.template_helpers import add_sr
+from r2.lib import utils
from validator import *
-from pylons import c
+from pylons import c, Response
+
+import string
+
+# strips /r/foo/, /s/, or both
+strip_sr = re.compile('^/r/[a-zA-Z0-9_-]+')
+strip_s_path = re.compile('^/s/')
+leading_slash = re.compile('^/+')
+has_protocol = re.compile('^https?:')
+need_insert_slash = re.compile('^https?:/[^/]')
+def demangle_url(path):
+ # there's often some URL mangling done by the stack above us, so
+ # let's clean up the URL before looking it up
+ path = strip_sr.sub('', path)
+ path = strip_s_path.sub('', path)
+ path = leading_slash.sub("", path)
+
+ if not has_protocol.match(path):
+ path = 'http://%s' % path
+
+ if need_insert_slash.match(path):
+ path = string.replace(path, '/', '//', 1)
+
+ path = utils.sanitize_url(path)
+
+ return path
+
+def force_html():
+ """Because we can take URIs like /s/http://.../foo.png, and we can
+ guarantee that the toolbar will never be used with a non-HTML
+ render style, we don't want to interpret the extension from the
+ target URL. So here we rewrite Middleware's interpretation of
+ the extension to force it to be HTML
+ """
+
+ c.render_style = 'html'
+ c.extension = None
+ c.content_type = 'text/html; charset=UTF-8'
+
+def auto_expand_panel(link):
+ if not link.num_comments:
+ return False
+ else:
+ return c.user.pref_frame_commentspanel
+
+@memoize('toolbar.get_title', time = 500)
+def get_title(url):
+ """Find the
in the page contained at 'url'. Copied here
+ from utils so that we can memoize it"""
+ return utils.get_title(url)
class ToolbarController(RedditController):
-
- @validate(link = VByName('id'))
- def GET_toolbar(self, link):
- if not link: return self.abort404()
- link_builder = IDBuilder((link._fullname,))
- link_listing = LinkListing(link_builder, nextprev=False).listing()
- res = FrameToolbar(link = link_listing.things[0]).render()
- return spaceCompress(res)
-
- @validate(link = VByName('id'),
+ @validate(link1 = VByName('id'),
link2 = VLink('id', redirect = False))
- def GET_goto(self, link, link2):
- link = link2 if link2 else link
+ def GET_goto(self, link1, link2):
+ """Support old /goto?id= urls. deprecated"""
+ link = link2 if link2 else link1
if link:
- link._load()
- if c.user and c.user.pref_frame:
- return Frame(title = link.title,
- url = link.url,
- fullname = link._fullname).render()
- else:
- return self.redirect(link.url)
+ return self.redirect(add_sr("/tb/" + link._id36))
return self.abort404()
+ @validate(link = VLink('id'))
+ def GET_tb(self, link):
+ "/tb/$id36, show a given link with the toolbar"
+ if not link:
+ return self.abort404()
+
+ res = Frame(title = link.title,
+ url = link.url,
+ fullname = link._fullname)
+ return spaceCompress(res.render())
+
+ def GET_s(self, rest):
+ """/s/http://..., show a given URL with the toolbar. if it's
+ submitted, redirect to /tb/$id36"""
+ force_html()
+ path = demangle_url(request.fullpath)
+
+ if not path:
+ # it was malformed
+ self.abort404()
+
+ link = utils.link_from_url(path, multiple = False)
+
+ if c.cname and not c.authorized_cname:
+ # In this case, we make some bad guesses caused by the
+ # cname frame on unauthorised cnames.
+ # 1. User types http://foo.com/http://myurl?cheese=brie
+ # (where foo.com is an unauthorised cname)
+ # 2. We generate a frame that points to
+ # http://www.reddit.com/r/foo/http://myurl?cnameframe=0.12345&cheese=brie
+ # 3. Because we accept everything after the /r/foo/, and
+ # we've now parsed, modified, and reconstituted that
+ # URL to add cnameframe, we really can't make any good
+ # assumptions about what we've done to a potentially
+ # already broken URL, and we can't assume that we've
+ # rebuilt it in the way that it was originally
+ # submitted (if it was)
+ # We could try to work around this with more guesses (by
+ # having demangle_url try to remove that param, hoping
+ # that it's not already a malformed URL, and that we
+ # haven't re-ordered the GET params, removed
+ # double-slashes, etc), but for now, we'll just refuse to
+ # do this operation
+ return self.abort404()
+
+ if link:
+ # we were able to find it, let's send them to the
+ # link-id-based URL so that their URL is reusable
+ return self.redirect(add_sr("/tb/" + link._id36))
+
+ title = get_title(path)
+ if not title:
+ title = utils.domain(path)
+ res = Frame(title = title, url = path)
+
+ # we don't want clients to think that this URL is actually a
+ # valid URL for search-indexing or the like
+ c.response = Response()
+ c.response.status_code = 404
+ request.environ['usable_error_content'] = spaceCompress(res.render())
+
+ return c.response
+
+ @validate(link = VLink('id'))
+ def GET_comments(self, link):
+ if not link:
+ self.abort404()
+ if not link.subreddit_slow.can_view(c.user):
+ abort(403, 'forbidden')
+
+ def builder_wrapper(cm):
+ w = Wrapped(cm)
+ w.render_class = StarkComment
+ w.target = "_top"
+ return w
+
+ link_builder = IDBuilder((link._fullname,))
+ link_listing = LinkListing(link_builder, nextprev=False).listing()
+ links = link_listing.things[0],
+ if not links:
+ # they aren't allowed to see this link
+ return self.abort(403, 'forbidden')
+ link = links[0]
+
+ res = FrameToolbar(link = link,
+ title = link.title,
+ url = link.url)
+
+
+ b = TopCommentBuilder(link, CommentSortMenu.operator('top'),
+ wrap = builder_wrapper)
+
+ listing = NestedListing(b, num = 10, # TODO: add config var
+ parent_name = link._fullname)
+
+ res = RedditMin(content=CommentsPanel(link=link, listing=listing.listing(),
+ expanded = auto_expand_panel(link)))
+
+ return res.render()
+
+ @validate(link = VByName('id'),
+ url = nop('url'))
+ def GET_toolbar(self, link, url):
+ """The visible toolbar, with voting buttons and all"""
+ if not link:
+ link = utils.link_from_url(url, multiple = False)
+
+ if link:
+ link_builder = IDBuilder((link._fullname,))
+ res = FrameToolbar(link = link_builder.get_items()[0][0],
+ title = link.title,
+ url = link.url,
+ expanded = auto_expand_panel(link))
+ else:
+ res = FrameToolbar(link = None,
+ title = get_title(url),
+ url = url,
+ expanded = False)
+
+ return spaceCompress(res.render())
+
+ @validate(link = VByName('id'))
+ def GET_inner(self, link):
+ """The intermediate frame that displays the comments side-bar
+ on one side and the link on the other"""
+ if not link:
+ return self.abort404()
+
+ res = InnerToolbarFrame(link = link, expanded = auto_expand_panel(link))
+ return spaceCompress(res.render())
+
+ @validate(link = VLink('linkoid'))
+ def GET_linkoid(self, link):
+ if not link:
+ return self.abort404()
+ return self.redirect(add_sr("/tb/" + link._id36))
+
+ slash_fixer = re.compile('(/s/https?:)/+')
+ @validate(urloid = nop('urloid'))
+ def GET_urloid(self, urloid):
+ # they got here from "/http://..."
+ path = demangle_url(request.fullpath)
+
+ if not path:
+ # malformed URL
+ self.abort404()
+
+ redir_path = add_sr("/s/" + path)
+ force_html()
+
+ # Redirect to http://reddit.com/s/http://google.com
+ # rather than http://reddit.com/s/http:/google.com
+ redir_path = self.slash_fixer.sub(r'\1///', redir_path, 1)
+ # ^^^
+ # 3=2 when it comes to self.redirect()
+ return self.redirect(redir_path)
+
diff --git a/r2/r2/lib/filters.py b/r2/r2/lib/filters.py
index c29a17a1f..7f3a4378e 100644
--- a/r2/r2/lib/filters.py
+++ b/r2/r2/lib/filters.py
@@ -115,7 +115,7 @@ fix_url = re.compile('<(http://[^\s\'\"\]\)]+)>')
#TODO markdown should be looked up in batch?
#@memoize('markdown')
-def safemarkdown(text, nofollow = False):
+def safemarkdown(text, nofollow=False, target=None):
from contrib.markdown import markdown
if text:
# increase escaping of &, < and > once
@@ -133,8 +133,12 @@ def safemarkdown(text, nofollow = False):
def href_handler(m):
url = m.group(1).replace('&', '&')
link = ' a,
-.toolbar td#frame-left > form { margin-right: 20px; }
-.toolbar td#frame-right { text-align: right; }
-.toolbar td#frame-right > a,
-.toolbar td#frame-right > form { margin: 0 5px; }
-.toolbar .arrow {
- background-position: center left;
- padding-left: 18px;
- display: inline;
+.bookmarklet {
+ border: solid #888888 1px;
+ padding: 0px 2px;
+}
+
+.toolbar {
+ font-size: small;
+ border-bottom: 1px solid #336699;
+ background-color: #CEE3F8;
+}
+
+.toolbar .left-side {
+ height: 19px;
+ float: left;
+ border-right: solid #336699 1px;
+ background-image: url(/static/button-normal.png);
+}
+
+.toolbar .middle-side {
+ text-align: center;
+ background-image: url(/static/button-normal.png);
+}
+
+.toolbar .middle-side a, .toolbar .middle-side b {
+ display: block;
+ border-left: none;
+}
+
+.toolbar .middle-side input[type=text] {
+ font-size: 14px;
+ vertical-align: baseline;
+ width: 100%;
+ height: 18px;
+ border: none;
+ border-top: solid transparent 1px;
+}
+.toolbar .middle-side .url {
+ overflow: hidden;
+}
+.toolbar .right-side {
+ float: right;
+ background-image: url(/static/button-normal.png);
+}
+
+.toolbar a, .toolbar b {
+ font-weight: normal;
+ display: inline-block;
+ height: 18px;
+ border-left: solid #336699 1px;
+ white-space: nowrap;
+ padding: 1px 4px 0px;
+ overflow: hidden;
+ outline: none;
+ -moz-outline: none;
+}
+
+.toolbar a, .toolbar .clickable {
+ cursor: pointer;
+ color: #336699;
+ text-decoration: none !important;
+}
+
+.toolbar .clickable:active, .pushed-button {
+ background-image: url(/static/button-pressed.png) !important;
+ color: orangered !important;
+}
+
+.toolbar a img, toolbar b img {
+ vertical-align: middle;
+ padding-top: 3px;
+}
+
+.toolbar .content {
+ float: left;
+ vertical-align: middle;
+}
+.toolbar .logo {
+ margin: 0px;
+ padding: 0 2px;
+ border-left: none;
+ vertical-align: top;
+}
+.toolbar .title {
+ padding-left: 20px;
+ color: black;
+ font-style: italic;
+}
+.toolbar .controls {
+ float: right;
+}
+.toolbar .arrow {
+ display: inline-block;
+ width: auto;
+ margin: 0px;
+ background-position: left center;
+ padding-left: 18px;
+}
+
+.toolbar .arrow.upmod { background-image: url(/static/aminiupmod.gif); }
+.toolbar .arrow.downmod { background-image: url(/static/aminidownmod.gif); }
+.toolbar .arrow.up { background-image: url(/static/aminiupgray.gif); }
+.toolbar .arrow.down { background-image: url(/static/aminidowngray.gif); }
+
+.toolbar-status-bar {
+ border-top: solid #336699 1px;
+ border-bottom: solid #336699 1px;
+ background-color: #F6E69F;
+ padding: 0px 2px;
+ overflow: auto;
+}
+.toolbar-status-bar .login-arrow-left {
+ overflow: auto;
+ background-image: url(/static/tb-loginarrow-left.png);
+ background-position: top right;
+}
+.toolbar-status-bar .login-arrow-right {
+ float: right;
+ margin-right: 75px;
+}
+.toolbar-status-bar .login-message {
+ float: left;
+ background-color: #F6E69F;
+ padding-right: 3px;
+}
+.tb-comments-panel-toggle {
+ display: block;
+ font-size: larger;
+ margin: 15px 13px;
+}
+.min-body {
+ height: 100%;
+}
+
+.min-body .content {
+ margin-top: 0px;
+ border-left: solid #369 1px;
+ min-height: 100%;
+ max-width: 60em;
+ overflow: auto;
+}
+
+.min-body .content h1, .min-body .content h2 {
+ padding-left: 13px;
+ display: inline-block;
+ margin-bottom: 15px;
+}
+.min-body .content #noresults {
+ margin: 0 0 0 13px;
}
/* default form styles */
diff --git a/r2/r2/public/static/dorks-toolbar.png b/r2/r2/public/static/dorks-toolbar.png
new file mode 100644
index 000000000..64077d3a7
Binary files /dev/null and b/r2/r2/public/static/dorks-toolbar.png differ
diff --git a/r2/r2/public/static/firefox.png b/r2/r2/public/static/firefox.png
index ab206758a..dc8b00b4e 100644
Binary files a/r2/r2/public/static/firefox.png and b/r2/r2/public/static/firefox.png differ
diff --git a/r2/r2/public/static/help/tb-ss-close.png b/r2/r2/public/static/help/tb-ss-close.png
new file mode 100644
index 000000000..be50c0a7d
Binary files /dev/null and b/r2/r2/public/static/help/tb-ss-close.png differ
diff --git a/r2/r2/public/static/help/tb-ss-comment.png b/r2/r2/public/static/help/tb-ss-comment.png
new file mode 100644
index 000000000..e3c8e9f6b
Binary files /dev/null and b/r2/r2/public/static/help/tb-ss-comment.png differ
diff --git a/r2/r2/public/static/help/tb-ss-help.png b/r2/r2/public/static/help/tb-ss-help.png
new file mode 100644
index 000000000..feca0d991
Binary files /dev/null and b/r2/r2/public/static/help/tb-ss-help.png differ
diff --git a/r2/r2/public/static/help/tb-ss-left.png b/r2/r2/public/static/help/tb-ss-left.png
new file mode 100644
index 000000000..a29954641
Binary files /dev/null and b/r2/r2/public/static/help/tb-ss-left.png differ
diff --git a/r2/r2/public/static/help/tb-ss-link.png b/r2/r2/public/static/help/tb-ss-link.png
new file mode 100644
index 000000000..7ae37966f
Binary files /dev/null and b/r2/r2/public/static/help/tb-ss-link.png differ
diff --git a/r2/r2/public/static/help/tb-ss-logo.png b/r2/r2/public/static/help/tb-ss-logo.png
new file mode 100644
index 000000000..3256cb4c4
Binary files /dev/null and b/r2/r2/public/static/help/tb-ss-logo.png differ
diff --git a/r2/r2/public/static/help/tb-ss-logout.png b/r2/r2/public/static/help/tb-ss-logout.png
new file mode 100644
index 000000000..228ee5628
Binary files /dev/null and b/r2/r2/public/static/help/tb-ss-logout.png differ
diff --git a/r2/r2/public/static/help/tb-ss-middle.png b/r2/r2/public/static/help/tb-ss-middle.png
new file mode 100644
index 000000000..9a4b82913
Binary files /dev/null and b/r2/r2/public/static/help/tb-ss-middle.png differ
diff --git a/r2/r2/public/static/help/tb-ss-right.png b/r2/r2/public/static/help/tb-ss-right.png
new file mode 100644
index 000000000..47bbd2202
Binary files /dev/null and b/r2/r2/public/static/help/tb-ss-right.png differ
diff --git a/r2/r2/public/static/help/tb-ss-scoreetc.png b/r2/r2/public/static/help/tb-ss-scoreetc.png
new file mode 100644
index 000000000..998dd7330
Binary files /dev/null and b/r2/r2/public/static/help/tb-ss-scoreetc.png differ
diff --git a/r2/r2/public/static/help/tb-ss-serendipity.png b/r2/r2/public/static/help/tb-ss-serendipity.png
new file mode 100644
index 000000000..38079757a
Binary files /dev/null and b/r2/r2/public/static/help/tb-ss-serendipity.png differ
diff --git a/r2/r2/public/static/help/tb-ss-username.png b/r2/r2/public/static/help/tb-ss-username.png
new file mode 100644
index 000000000..67ee1a26d
Binary files /dev/null and b/r2/r2/public/static/help/tb-ss-username.png differ
diff --git a/r2/r2/public/static/help/tb-ss-whole.png b/r2/r2/public/static/help/tb-ss-whole.png
new file mode 100644
index 000000000..41f8bd259
Binary files /dev/null and b/r2/r2/public/static/help/tb-ss-whole.png differ
diff --git a/r2/r2/public/static/ie.png b/r2/r2/public/static/ie.png
index 4d5d37a25..41d643c45 100644
Binary files a/r2/r2/public/static/ie.png and b/r2/r2/public/static/ie.png differ
diff --git a/r2/r2/public/static/js/jquery.reddit.js b/r2/r2/public/static/js/jquery.reddit.js
index 7f0506a4f..76c02f976 100644
--- a/r2/r2/public/static/js/jquery.reddit.js
+++ b/r2/r2/public/static/js/jquery.reddit.js
@@ -33,7 +33,16 @@ $.redirect = function(dest) {
$.fn.redirect = function(dest) {
/* for forms which are "posting" by ajax leading to a redirect */
$(this).filter("form").find(".status").show().html("redirecting...");
- $.redirect(dest);
+ var target = $(this).attr('target');
+ if(target == "_top") {
+ var w = window;
+ while(w != w.parent) {
+ w = w.parent;
+ }
+ w.location = dest;
+ } else {
+ $.redirect(dest);
+ }
/* this should never happen, but for the sake of internal consistency */
return $(this)
}
diff --git a/r2/r2/public/static/js/reddit.js b/r2/r2/public/static/js/reddit.js
index 0e84a5aaf..c40d1d8ea 100644
--- a/r2/r2/public/static/js/reddit.js
+++ b/r2/r2/public/static/js/reddit.js
@@ -621,6 +621,118 @@ function register(elem) {
return post_user(this, "register");
};
+var toolbar_p = function(expanded_size, collapsed_size) {
+ /* namespace for functions related to the reddit toolbar frame */
+
+ this.toggle_linktitle = function(s) {
+ $('.title, .submit, .url').toggle();
+ if($(s).is('.pushed-button')) {
+ $(s).parents('.middle-side').removeClass('clickable');
+ } else {
+ $(s).parents('.middle-side').addClass('clickable');
+ }
+ return this.toggle_pushed(s);
+ };
+
+ this.toggle_pushed = function(s) {
+ s = $(s);
+ if(s.is('.pushed-button')) {
+ s.removeClass('pushed-button').addClass('popped-button');
+ } else {
+ s.removeClass('popped-button').addClass('pushed-button');
+ }
+ return false;
+ };
+
+ this.push_button = function(s) {
+ $(s).removeClass("popped-button").addClass("pushed-button");
+ };
+
+ this.pop_button = function(s) {
+ $(s).removeClass("pushed-button").addClass("popped-button");
+ };
+
+ this.serendipity = function() {
+ this.push_button('.serendipity');
+ return true;
+ };
+
+ this.show_panel = function() {
+ parent.inner_toolbar.document.body.cols = expanded_size;
+ };
+
+ this.hide_panel = function() {
+ parent.inner_toolbar.document.body.cols = collapsed_size;
+ };
+
+ this.resize_toolbar = function() {
+ var height = $("body").height();
+ parent.document.body.rows = height + "px, 100%";
+ };
+
+ this.login_msg = function() {
+ $(".toolbar-status-bar").show();
+ $(".login-arrow").show();
+ this.resize_toolbar();
+ return false;
+ };
+
+ this.top_window = function() {
+ var w = window;
+ while(w != w.parent) {
+ w = w.parent;
+ }
+ return w.parent;
+ };
+
+ var pop_obj = null;
+ this.panel_loadurl = function(url) {
+ try {
+ var cur = window.parent.inner_toolbar.reddit_panel.location;
+ if (cur == url) {
+ return false;
+ } else {
+ if (pop_obj != null) {
+ this.pop_button(pop_obj);
+ pop_obj = null;
+ }
+ return true;
+ }
+ } catch (e) {
+ return true;
+ }
+ };
+
+ var comments_on = 0;
+ this.comments_pushed = function(ctl) {
+ comments_on = ! comments_on;
+
+ if (comments_on) {
+ this.push_button(ctl);
+ this.show_panel();
+ } else {
+ this.pop_button(ctl);
+ this.hide_panel();
+ }
+ };
+
+ this.gourl = function(form, base_url) {
+ var url = $(form).find('input[type=text]').attr('value');
+ var newurl = base_url + escape(url);
+
+ this.top_window().location.href = newurl;
+
+ return false;
+ };
+
+ this.pref_commentspanel_hide = function() {
+ $.request('tb_commentspanel_hide');
+ };
+ this.pref_commentspanel_show = function() {
+ $.request('tb_commentspanel_show');
+ };
+};
+
/* The ready method */
$(function() {
/* set function to be called on thing creation/replacement,
diff --git a/r2/r2/public/static/link.png b/r2/r2/public/static/link.png
new file mode 100644
index 000000000..80757543a
Binary files /dev/null and b/r2/r2/public/static/link.png differ
diff --git a/r2/r2/public/static/logo-toolbar.png b/r2/r2/public/static/logo-toolbar.png
new file mode 100644
index 000000000..b3b4dd62f
Binary files /dev/null and b/r2/r2/public/static/logo-toolbar.png differ
diff --git a/r2/r2/public/static/safari.png b/r2/r2/public/static/safari.png
index 5c3b3c37c..8635632e0 100644
Binary files a/r2/r2/public/static/safari.png and b/r2/r2/public/static/safari.png differ
diff --git a/r2/r2/public/static/tb-loginarrow-left.png b/r2/r2/public/static/tb-loginarrow-left.png
new file mode 100644
index 000000000..4fe4fd063
Binary files /dev/null and b/r2/r2/public/static/tb-loginarrow-left.png differ
diff --git a/r2/r2/public/static/tb-loginarrow-right.png b/r2/r2/public/static/tb-loginarrow-right.png
new file mode 100644
index 000000000..13c2883d7
Binary files /dev/null and b/r2/r2/public/static/tb-loginarrow-right.png differ
diff --git a/r2/r2/templates/bookmarklets.html b/r2/r2/templates/bookmarklets.html
index 2c508e74b..06d127ef3 100644
--- a/r2/r2/templates/bookmarklets.html
+++ b/r2/r2/templates/bookmarklets.html
@@ -24,37 +24,37 @@
${_("install the %(site)s bookmarklet set") % dict(site=c.site.name)}
-
${_("once added to your toolbar, these buttons will let you \"reddit this\" (submit the link, or if it's already been submitted, share it and/or comment on it), like, dislike, and save links while you surf.")}
+
${_("once added to your bookmark bar, you can click these buttons from any site to get quick access to reddit functionality.")}
${_("in Firefox")}
- Try out our new firefox add-on, Socialite.
-
-
- or ${_("click and drag")}
+ ${_("click and drag")}
%for type in thing.buttons:
-
+ ${type}
%endfor
- ${_("to your toolbar.")}
+ ${_("to your bookmark bar.")}
+
+ See also our Firefox add-on, Socialite.
+
${_("in Internet Explorer")}
${_("right-click on")}
%for type in thing.buttons:
-
+ ${type}
%endfor
- ${_('choose "Add to Favorites" and add to the "Links" folder.')}
+ ${_('then choose "Add to Favorites" and add to the "Links" folder.')}
@@ -64,10 +64,10 @@
${_("click and drag")}
%for type in thing.buttons:
-
+ ${type}
%endfor
- ${_("to your toolbar.")}
+ ${_("to your bookmark bar.")}
diff --git a/r2/r2/templates/buttontypes.html b/r2/r2/templates/buttontypes.html
index d5d9e3ba8..294d4b1ae 100644
--- a/r2/r2/templates/buttontypes.html
+++ b/r2/r2/templates/buttontypes.html
@@ -52,7 +52,7 @@ ${class_def(1, width=choose_width(thing.link, thing.width))}
%if thing.vote:
${arrow(thing.link, 1, thing.likes)}
${arrow(thing.link, 0, thing.likes == False)}
- ${score(thing.link, thing.likes, inline=False)}
+ ${score(thing.link, thing.likes, tag='div')}
%else:
${thing.link.score}
%endif
@@ -68,7 +68,7 @@ ${class_def(1, width=choose_width(thing.link, thing.width))}
%if thing.link:
%if thing.vote:
${arrow(thing.link, 1, thing.likes)}
- ${score(thing.link, thing.likes, inline=False)}
+ ${score(thing.link, thing.likes, tag='div')}
${arrow(thing.link, 0, thing.likes == False)}
%else:
@@ -96,7 +96,7 @@ ${class_def(3)}
%if thing.link:
%if thing.vote:
${arrow(thing.link, 1, thing.likes)}
- ${score(thing.link, thing.likes, inline=False)}
+ ${score(thing.link, thing.likes, tag='div')}
${arrow(thing.link, 0, thing.likes == False)}
%else:
@@ -121,7 +121,7 @@ ${class_def(3)}
%if thing.link:
%if thing.vote:
${arrow(thing.link, 1, thing.likes)}
- ${score(thing.link, thing.likes, inline=False)}
+ ${score(thing.link, thing.likes, tag='div')}
${arrow(thing.link, 0, thing.likes == False)}
%else:
diff --git a/r2/r2/templates/comment.html b/r2/r2/templates/comment.html
index fcd37e4c2..85ffd650e 100644
--- a/r2/r2/templates/comment.html
+++ b/r2/r2/templates/comment.html
@@ -54,7 +54,7 @@ ${parent.midcol(not thing.collapsed)}
${parent.collapsed()}
%def>
-<%def name="tagline(collapse=False)">
+<%def name="tagline(collapse=False,showexpandcollapse=True)">
<%
if c.user_is_admin:
show = True
@@ -76,24 +76,25 @@ ${parent.collapsed()}
%if show:
${unsafe(self.score(thing, likes = thing.likes))}
%endif
- ${thing.timesince} ${_("ago")}
+ ${_("%(timeago)s ago") % dict(timeago=thing.timesince)}
%if thing.editted:
*
%endif
%endif
-
- %else:
- onclick="return hidecomment(this)">
- %endif
- [${_("+") if collapse else _("-")}]
- %if collapse:
- (${thing.num_children}
- ${ungettext("child", "children", thing.num_children)})
+ %if showexpandcollapse:
+
+ %else:
+ onclick="return hidecomment(this)">
+ %endif
+ [${_("+") if collapse else _("-")}]
+ %if collapse:
+ (${thing.num_children}
+ ${ungettext("child", "children", thing.num_children)})
+ %endif
+
%endif
-
-
%def>
<%def name="Child()">
@@ -102,7 +103,8 @@ ${parent.Child(not thing.collapsed)}
<%def name="commentBody()">
%if c.user_is_admin or not thing.deleted:
-${unsafe(safemarkdown(thing.body, thing.nofollow))}
+${unsafe(safemarkdown(thing.body, nofollow=thing.nofollow,
+ target=thing.target))}
%else:
${_("[deleted]")}
diff --git a/r2/r2/templates/commentspanel.html b/r2/r2/templates/commentspanel.html
new file mode 100644
index 000000000..15b2151f6
--- /dev/null
+++ b/r2/r2/templates/commentspanel.html
@@ -0,0 +1,46 @@
+## 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="printable.html" import="toggle_button"/>
+
+
+
+
+ ${_('top comments')}
+
+
+
+${thing.listing.render()}
+
+${toggle_button("tb-comments-panel-toggle",
+ _("hide this panel by default"),
+ _("show this panel " +
+ "when there are comments"),
+ "toolbar.pref_commentspanel_hide",
+ "toolbar.pref_commentspanel_show",
+ reverse = not c.user.pref_frame_commentspanel)}
diff --git a/r2/r2/templates/frame.html b/r2/r2/templates/frame.html
index 0fc537fbf..04f914d8d 100644
--- a/r2/r2/templates/frame.html
+++ b/r2/r2/templates/frame.html
@@ -21,6 +21,8 @@
################################################################################
<%
from r2.lib.template_helpers import add_sr
+ from r2.lib.utils import query_string
+ from r2.lib.filters import _force_unicode
%>
@@ -28,8 +30,36 @@
${thing.title}
-