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 href="%s"' % url - if c.cname: + + if target: + link += ' target="%s"' % target + elif c.cname: link += ' target="_top"' + if nofollow: link += ' rel="nofollow"' return link diff --git a/r2/r2/lib/menus.py b/r2/r2/lib/menus.py index 569b5a179..27b244da0 100644 --- a/r2/r2/lib/menus.py +++ b/r2/r2/lib/menus.py @@ -99,6 +99,7 @@ menu = MenuHandler(hot = _('hot'), store = _("store"), ad_inq = _("advertise on reddit"), toplinks = _("top links"), + random = _('random'), iphone = _("iPhone app"), #preferences diff --git a/r2/r2/lib/pages/pages.py b/r2/r2/lib/pages/pages.py index 495b666d1..9fcf17fdf 100644 --- a/r2/r2/lib/pages/pages.py +++ b/r2/r2/lib/pages/pages.py @@ -24,7 +24,7 @@ from r2.models import IDBuilder, LinkListing, Account, Default, FakeSubreddit, S from r2.config import cache from r2.lib.jsonresponse import json_respond from r2.lib.jsontemplates import is_api -from pylons.i18n import _ +from pylons.i18n import _, ungettext from pylons import c, request, g from pylons.controllers.util import abort @@ -33,11 +33,13 @@ 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.strings import plurals, rand_strings, strings -from r2.lib.utils import title_to_url, query_string, UrlParser, to_js +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 -import sys, random, datetime, locale, calendar + +import sys, random, datetime, locale, calendar, re import graph +from urllib import quote datefmt = _force_utf8(_('%d %b %Y')) @@ -71,20 +73,23 @@ class Reddit(Wrapped): create_reddit_box = True submit_box = True + footer = True searchbox = True extension_handling = True enable_login_cover = True site_tracking = True + show_firsttext = True def __init__(self, space_compress = True, nav_menus = None, loginbox = True, infotext = '', content = None, title = '', robots = None, - show_sidebar = True, **context): + show_sidebar = True, footer = True, **context): Wrapped.__init__(self, **context) self.title = title self.robots = robots self.infotext = infotext self.loginbox = True self.show_sidebar = show_sidebar + self.footer = footer self.space_compress = space_compress #put the sort menus at the top @@ -92,7 +97,7 @@ class Reddit(Wrapped): #add the infobar self.infobar = None - if c.firsttime and c.site.firsttext and not infotext: + if self.show_firsttext and c.firsttime and c.site.firsttext and not infotext: infotext = c.site.firsttext if infotext: self.infobar = InfoBar(message = infotext) @@ -190,6 +195,7 @@ class Reddit(Wrapped): OffsiteButton("rss", dest = '/.rss'), NamedButton("store", False, nocname=True), NamedButton("stats", False, nocname=True), + NamedButton('random', False, nocname=False), NamedButton("feedback", False),], title = 'site links', type = 'flat_vert', separator = ''), @@ -297,6 +303,13 @@ class Reddit(Wrapped): """returns a Wrapped (or renderable) item for the main content div.""" return self.content_stack(self.infobar, self.nav_menu, self._content) +class RedditMin(Reddit): + """a version of Reddit that has no sidebar, toolbar, footer, + etc""" + footer = False + show_sidebar = False + show_firsttext = False + class LoginFormWide(Wrapped): """generates a login form suitable for the 300px rightbox.""" pass @@ -455,6 +468,17 @@ class SearchPage(BoringPage): 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 + comments of a link""" + + def __init__(self, link = None, listing = None, expanded = False, *a, **kw): + self.link = link + self.listing = listing + self.expanded = expanded + + Wrapped.__init__(self, *a, **kw) + class LinkInfoPage(Reddit): """Renders the varied /info pages for a link. The Link object is passed via the link argument and the content passed to this class @@ -531,12 +555,8 @@ class LinkInfoPage(Reddit): class LinkInfoBar(Wrapped): """Right box for providing info about a link.""" def __init__(self, a = None): - if a: - a = Wrapped(a) - Wrapped.__init__(self, a = a, datefmt = datefmt) - class EditReddit(Reddit): """Container for the about page for a reddit""" extension_handling= False @@ -880,27 +900,73 @@ class SearchBar(Wrapped): class Frame(Wrapped): - """Frameset for the FrameToolbar used when a user hits /goto and - has pref_toolbar set. The top 30px of the page are dedicated to - the toolbar, while the rest of the page will show the results of - following the link.""" - def __init__(self, url='', title='', fullname=''): + """Frameset for the FrameToolbar used when a user hits /tb/. The + top 30px of the page are dedicated to the toolbar, while the rest + of the page will show the results of following the link.""" + def __init__(self, url='', title='', fullname=None): + if title: + title = (_('%(site_title)s via %(domain)s') + % dict(site_title = _force_unicode(title), + domain = g.domain)) + else: + title = g.domain + Wrapped.__init__(self, url = url, title = title, fullname = fullname) +dorks_re = re.compile(r"https?://?([-\w.]*\.)?digg\.com/\w+$") class FrameToolbar(Wrapped): """The reddit voting toolbar used together with Frame.""" extension_handling = False - def __init__(self, link = None, **kw): - self.title = link.title - Wrapped.__init__(self, link = link, *kw) - + def __init__(self, link = None, title = None, url = None, expanded = False, **kw): + self.title = title + self.url = url + self.expanded = expanded + self.link = link + + self.dorks = dorks_re.match(link.url if link else url) + + if link: + self.tblink = add_sr("/tb/"+link._id36) + + likes = link.likes + self.upstyle = "mod" if likes else "" + self.downstyle = "mod" if likes is False else "" + if c.user_is_loggedin: + self.vh = vote_hash(c.user, link, 'valid') + score = link.score + + if not link.num_comments: + # generates "comment" the imperative verb + self.com_label = _("comment {verb}") + else: + # generates "XX comments" as a noun + com_label = ungettext("comment", "comments", link.num_comments) + self.com_label = strings.number_label % (link.num_comments, com_label) + + # generates "XX points" as a noun + self.score_label = Score.safepoints(score) + + else: + self.tblink = add_sr("/s/"+quote(url)) + submit_url_options = dict(url = _force_unicode(url), + then = 'tb') + if title: + submit_url_options['title'] = _force_unicode(title) + self.submit_url = add_sr('/submit' + query_string(submit_url_options)) + + if not c.user_is_loggedin: + self.loginurl = add_sr("/login?dest="+quote(self.tblink)) + + Wrapped.__init__(self, **kw) class NewLink(Wrapped): """Render the link submission form""" - def __init__(self, captcha = None, url = '', title= '', subreddits = ()): + def __init__(self, captcha = None, url = '', title= '', subreddits = (), + then = 'comments'): Wrapped.__init__(self, captcha = captcha, url = url, - title = title, subreddits = subreddits) + title = title, subreddits = subreddits, + then = then) class ShareLink(Wrapped): def __init__(self, link_name = "", emails = None): @@ -994,7 +1060,13 @@ class Socialite(Wrapped): class Bookmarklets(Wrapped): """The bookmarklets page.""" - def __init__(self, buttons=["reddit", "serendipity!"]): + def __init__(self, buttons=None): + if buttons is None: + buttons = ["submit", "serendipity!"] + # only include the toolbar link if we're not on an + # unathorised cname. See toolbar.py:GET_s for discussion + if not (c.cname and c.site.domain not in g.authorized_cnames): + buttons.insert(0, "reddit toolbar") Wrapped.__init__(self, buttons = buttons) @@ -1377,4 +1449,6 @@ class RedditTraffic(Traffic): "%5.2f%%" % f)) return res - +class InnerToolbarFrame(Wrapped): + def __init__(self, link, expanded = False): + Wrapped.__init__(self, link = link, expanded = expanded) diff --git a/r2/r2/lib/template_helpers.py b/r2/r2/lib/template_helpers.py index 70919a7ca..29bea083b 100644 --- a/r2/r2/lib/template_helpers.py +++ b/r2/r2/lib/template_helpers.py @@ -196,9 +196,15 @@ def dockletStr(context, type, browser): if type == "serendipity!": return "http://"+site_domain+"/random" - elif type == "reddit": - return "javascript:location.href='http://"+site_domain+"/submit?url='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)" + elif type == "submit": + return ("javascript:location.href='http://"+site_domain+ + "/submit?url='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)") + elif type == "reddit toolbar": + return ("javascript:%20var%20h%20=%20window.location.href;%20h%20=%20'http://" + + site_domain + "/s/'%20+%20escape(h);%20window.location%20=%20h;") else: + # these are the linked/disliked buttons, which we have removed + # from the UI return (("javascript:function b(){var u=encodeURIComponent(location.href);" "var i=document.getElementById('redstat')||document.createElement('a');" "var s=i.style;s.position='%(position)s';s.top='0';s.left='0';" @@ -283,3 +289,7 @@ def choose_width(link, width): return 100 + (10 * (len(str(link._ups - link._downs)))) else: return 110 + +def panel_size(state): + "the frame.cols of the reddit-toolbar's inner frame" + return '100%, 400px' if state =='expanded' else '100%x, 0px' diff --git a/r2/r2/lib/utils/utils.py b/r2/r2/lib/utils/utils.py index a8a9e09e1..7df807c63 100644 --- a/r2/r2/lib/utils/utils.py +++ b/r2/r2/lib/utils/utils.py @@ -26,6 +26,7 @@ import Queue from copy import deepcopy import cPickle as pickle import re, datetime, math, random, string, sha, os +from operator import attrgetter from datetime import datetime, timedelta from pylons.i18n import ungettext, _ @@ -1037,3 +1038,47 @@ def interleave_lists(*args): for a in args: if i < len(a): yield a[i] + +def link_from_url(path, filter_spam = False, multiple = True): + from pylons import c + from r2.models import IDBuilder, Link, Subreddit, NotFound + + if not path: + return + + try: + links = Link._by_url(path, c.site) + except NotFound: + return [] if multiple else None + + links = tup(links) + + # run the list through a builder to remove any that the user + # isn't allowed to see + links = IDBuilder([link._fullname for link in links], + skip = False).get_items()[0] + if not links: + return + + if filter_spam: + # first, try to remove any spam + links_nonspam = [ link for link in links + if not link._spam ] + if links_nonspam: + links = links_nonspam + + # if it occurs in one or more of their subscriptions, show them + # that one first + subs = set(Subreddit.user_subreddits(c.user, limit = None)) + def cmp_links(a, b): + if a.sr_id in subs and b.sr_id not in subs: + return -1 + elif a.sr_id not in subs and b.sr_id in subs: + return 1 + else: + return cmp(a._hot, b._hot) + links = sorted(links, cmp = cmp_links) + + # among those, show them the hottest one + return links if multiple else links[0] + diff --git a/r2/r2/models/account.py b/r2/r2/models/account.py index ad33efb11..d31aac0bf 100644 --- a/r2/r2/models/account.py +++ b/r2/r2/models/account.py @@ -39,6 +39,7 @@ class Account(Thing): _int_prop_suffix = '_karma' _defaults = dict(pref_numsites = 25, pref_frame = False, + pref_frame_commentspanel = True, pref_newwindow = False, pref_public_votes = False, pref_hide_ups = False, diff --git a/r2/r2/models/builder.py b/r2/r2/models/builder.py index 7d3a5d73a..9bf518a09 100644 --- a/r2/r2/models/builder.py +++ b/r2/r2/models/builder.py @@ -400,11 +400,16 @@ class SearchBuilder(QueryBuilder): return done, new_items class CommentBuilder(Builder): - def __init__(self, link, sort, comment = None, context = None): - Builder.__init__(self) + def __init__(self, link, sort, comment = None, context = None, + load_more=True, continue_this_thread=True, + max_depth = MAX_RECURSION, **kw): + Builder.__init__(self, **kw) self.link = link self.comment = comment self.context = context + self.load_more = load_more + self.max_depth = max_depth + self.continue_this_thread = continue_this_thread if sort.col == '_date': self.sort_key = lambda x: x._date @@ -419,7 +424,7 @@ class CommentBuilder(Builder): for j in self.item_iter(i.child.things): yield j - def get_items(self, num, nested = True, starting_depth = 0): + def get_items(self, num, starting_depth = 0): r = link_comments(self.link._id) cids, comment_tree, depth, num_children = r if cids: @@ -496,14 +501,14 @@ class CommentBuilder(Builder): comments.remove(to_add) if to_add._deleted and not comment_tree.has_key(to_add._id): pass - elif depth[to_add._id] < MAX_RECURSION: + elif depth[to_add._id] < self.max_depth: #add children if comment_tree.has_key(to_add._id): candidates.extend(comment_tree[to_add._id]) sort_candidates() items.append(to_add) num_have += 1 - else: + elif self.continue_this_thread: #add the recursion limit p_id = to_add.parent_id w = Wrapped(MoreRecursion(self.link, 0, @@ -541,6 +546,9 @@ class CommentBuilder(Builder): parent.child = empty_listing(morelink) parent.child.parent_name = parent._fullname + if not self.load_more: + return final + #put the remaining comments into the tree (the show more comments link) more_comments = {} while candidates: @@ -587,3 +595,16 @@ class CommentBuilder(Builder): mc2.count += 1 return final + +class TopCommentBuilder(CommentBuilder): + """A comment builder to fetch only the top-level, non-spam, + non-deleted comments""" + def __init__(self, link, sort, wrap = Wrapped): + CommentBuilder.__init__(self, link, sort, + load_more = False, + continue_this_thread = False, + max_depth = 1, wrap = wrap) + + def get_items(self, num = 10): + final = CommentBuilder.get_items(self, num = num, starting_depth = 0) + return [ cm for cm in final if not cm.deleted ] diff --git a/r2/r2/models/link.py b/r2/r2/models/link.py index 5238aca85..fe662237a 100644 --- a/r2/r2/models/link.py +++ b/r2/r2/models/link.py @@ -328,7 +328,17 @@ class Link(Thing, Printable): item.domain_path = "/domain/%s" % item.domain if item.is_self: item.domain_path = item.subreddit_path - + + item.tblink = "http://%s/tb/%s" % ( + get_domain(cname = c.cname, subreddit=False), + item._id36) + + item.click_url = item.url + if item.is_self: + item.click_url = item.permalink + elif c.user.pref_frame: + item.click_url = item.tblink + if c.user_is_loggedin: incr_counts(wrapped) @@ -461,13 +471,16 @@ class Comment(Thing, Printable): wrapped.likes, wrapped.friend, wrapped.collapsed, - wrapped.moderator_banned, + wrapped.nofollow, wrapped.show_spam, wrapped.show_reports, + wrapped.target, wrapped.can_ban, wrapped.moderator_banned, wrapped.can_reply, - wrapped.deleted)) + wrapped.deleted, + wrapped.render_class, + )) s = ''.join(s) return s @@ -481,8 +494,8 @@ class Comment(Thing, Printable): @classmethod def add_props(cls, user, wrapped): #fetch parent links - links = Link._byID(set(l.link_id for l in wrapped), True) - + links = Link._byID(set(l.link_id for l in wrapped), data = True, + return_dict = True) #get srs for comments that don't have them (old comments) for cm in wrapped: @@ -499,8 +512,11 @@ class Comment(Thing, Printable): for item in wrapped: item.link = links.get(item.link_id) + if not hasattr(item, 'subreddit'): item.subreddit = item.subreddit_slow + if not hasattr(item, 'target'): + item.target = None if hasattr(item, 'parent_id'): if cids.has_key(item.parent_id): item.parent_permalink = '#' + utils.to36(item.parent_id) @@ -542,6 +558,11 @@ class Comment(Thing, Printable): item.score_fmt = Score.points item.permalink = item.make_permalink(item.link, item.subreddit) +class StarkComment(Comment): + """Render class for the comments in the top-comments display in + the reddit toolbar""" + _nodb = True + class MoreComments(object): show_spam = False show_reports = False diff --git a/r2/r2/models/listing.py b/r2/r2/models/listing.py index 6e1fec961..abe1dc509 100644 --- a/r2/r2/models/listing.py +++ b/r2/r2/models/listing.py @@ -114,7 +114,6 @@ class NestedListing(Listing): def __init__(self, *a, **kw): Listing.__init__(self, *a, **kw) - self.nested = kw.get('nested', True) self.num = kw.get('num', g.num_comments) self.parent_name = kw.get('parent_name') @@ -122,7 +121,7 @@ class NestedListing(Listing): ##TODO use the local builder with the render cache. this may ##require separating the builder's get_items and tree-building ##functionality - wrapped_items = self.get_items(num = self.num, nested = True) + wrapped_items = self.get_items(num = self.num) self.things = wrapped_items diff --git a/r2/r2/models/populatedb.py b/r2/r2/models/populatedb.py index c1c77982b..0a671e0af 100644 --- a/r2/r2/models/populatedb.py +++ b/r2/r2/models/populatedb.py @@ -21,6 +21,7 @@ ################################################################################ from r2.models import * from r2.lib import promote +from r2.lib.utils import fetch_things2 import string import random @@ -64,3 +65,12 @@ def create_links(num): if random.choice(([False] * 50) + [True]): promote.promote(l) + +def by_url_cache(): + q = Link._query(Link.c._spam == (True,False), + data = True, + sort = desc('_date')) + for i, link in enumerate(fetch_things2(q)): + if i % 100 == 0: + print "%s..." % i + link.set_url_cache() diff --git a/r2/r2/public/static/aminidowngray.gif b/r2/r2/public/static/aminidowngray.gif new file mode 100755 index 000000000..4d7ba63f8 Binary files /dev/null and b/r2/r2/public/static/aminidowngray.gif differ diff --git a/r2/r2/public/static/aminidownmod.gif b/r2/r2/public/static/aminidownmod.gif new file mode 100755 index 000000000..7656347d9 Binary files /dev/null and b/r2/r2/public/static/aminidownmod.gif differ diff --git a/r2/r2/public/static/aminiupgray.gif b/r2/r2/public/static/aminiupgray.gif new file mode 100755 index 000000000..fef891ddd Binary files /dev/null and b/r2/r2/public/static/aminiupgray.gif differ diff --git a/r2/r2/public/static/aminiupmod.gif b/r2/r2/public/static/aminiupmod.gif new file mode 100755 index 000000000..2d675cdff Binary files /dev/null and b/r2/r2/public/static/aminiupmod.gif differ diff --git a/r2/r2/public/static/button-normal.png b/r2/r2/public/static/button-normal.png new file mode 100644 index 000000000..ccff15fd9 Binary files /dev/null and b/r2/r2/public/static/button-normal.png differ diff --git a/r2/r2/public/static/button-pressed.png b/r2/r2/public/static/button-pressed.png new file mode 100644 index 000000000..915c59f0f Binary files /dev/null and b/r2/r2/public/static/button-pressed.png differ diff --git a/r2/r2/public/static/css/reddit-ie7-hax.css b/r2/r2/public/static/css/reddit-ie7-hax.css new file mode 100644 index 000000000..7a5cb7cc9 --- /dev/null +++ b/r2/r2/public/static/css/reddit-ie7-hax.css @@ -0,0 +1,4 @@ +.toolbar .middle-side input[type=text] { + border: none; + height: 17px; +} diff --git a/r2/r2/public/static/css/reddit.css b/r2/r2/public/static/css/reddit.css index 36d55469a..535976118 100644 --- a/r2/r2/public/static/css/reddit.css +++ b/r2/r2/public/static/css/reddit.css @@ -1,3 +1,7 @@ +html { + height: 100%; /* Needed for toolbar's comments panel's pinstripe */ +} + body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,p,blockquote,th,td,iframe { margin:0; padding:0; @@ -19,8 +23,6 @@ body { z-index: 1; } -/*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. */ @@ -725,6 +727,10 @@ a.star { text-decoration: none; color: #ff8b60 } font-size: medium; text-align: right; } +.linkcompressed .arrow.down, .linkcompressed .arrow.downmod { + margin-top: 2px; +} + .linkcompressed .tagline { display: inline; margin-top: 0px; margin-bottom: 1px; } .linkcompressed .flat-list { display: inline } @@ -733,7 +739,7 @@ a.star { text-decoration: none; color: #ff8b60 } .linkcompressed .entry .buttons li.first {padding-left: .5em;} .linkcompressed .entry .buttons li a { padding: 0 2px; - background-color: #f6f6f6; + background-color: #f7f7f7; font-weight: bold } @@ -882,6 +888,20 @@ textarea.gray { color: gray; } padding-left: 5px; } +.starkcomment + .clearleft + .starkcomment { + margin-top: 10px +} +.starkcomment .commentbox { + color: black; + background-color: #f0f0f0; + padding: 5px; + margin-left: 10px; + margin-right: 5px; +} +.starkcomment .tagline { + text-align: right; +} + .fixedwidth { float: left; width: 100px; height: 0px; } .clearleft { clear: left; height: 0px; } .clear { clear: both; } @@ -1246,7 +1266,9 @@ textarea.gray { color: gray; } padding-right: 5px; font-weight: bold; } - +.details td { + vertical-align: top; +} .bottommenu { color: gray; font-size: smaller; clear: both} .bottommenu a { color: gray; text-decoration: underline; } @@ -1407,18 +1429,154 @@ textarea.gray { color: gray; } font-size: larger; } -/* the toolbar */ -.toolbar { height: 30px; border-bottom: 1px solid black;} -.toolbar td { vertical-align: center; } -.toolbar td#frame-left > 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 @@ <div class="instructions"> <script type="text/javascript"> - function dragme() {window.alert("drag this link to your toolbar")};function favme() {window.alert('right-click this link and choose "Add to Favorites"')} + function dragme() {window.alert("drag this link to your bookmark bar")};function favme() {window.alert('right-click this link and choose "Add to Favorites"')} </script> <h1> ${_("install the %(site)s bookmarklet set") % dict(site=c.site.name)} </h1> - <p>${_("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.")}</p> + <p>${_("once added to your bookmark bar, you can click these buttons from any site to get quick access to reddit functionality.")}</p> <h2>${_("in Firefox")}</h2> <p> - Try out our new firefox add-on, <a href="/socialite">Socialite</a>. - </p> - <p> - or ${_("click and drag")}  + ${_("click and drag")}  %for type in thing.buttons: <a style="padding: 0 3px 0 3px" href="${dockletStr(type, 'firefox')}" onclick="dragme(); return false"> - <img alt="${type}" src="/static/${type}_firefox.png" /> + <span class="bookmarklet">${type}</span>  </a> %endfor - ${_("to your toolbar.")} + ${_("to your bookmark bar.")} </p> <p> <img border="0" src="/static/firefox.png" alt="firefox screenshot" /> </p> + <p> + See also our Firefox add-on, <a href="/socialite">Socialite</a>. + </p> <h2>${_("in Internet Explorer")}</h2> <p> ${_("right-click on")}  %for type in thing.buttons: <a style="padding: 0 3px 0 3px" href="${dockletStr(type, 'ie')|}" onclick="favme(); return false"> - <img alt="${type}" src="/static/${type}_ie.png" /> + <span class="bookmarklet">${type}</span>  </a> %endfor - ${_('choose "Add to Favorites" and add to the "Links" folder.')} + ${_('then choose "Add to Favorites" and add to the "Links" folder.')} </p> <p> <img border="0" src="/static/ie.png" alt="ie screenshot" /> @@ -64,10 +64,10 @@ ${_("click and drag")}  %for type in thing.buttons: <a style="padding: 0 3px 0 3px" href="${dockletStr(type, 'safari')}" onclick="dragme(); return false"> - <img alt="${type}" src="/static/${type}_safari.png" /> + <span class="bookmarklet">${type}</span>  </a> %endfor - ${_("to your toolbar.")} + ${_("to your bookmark bar.")} </p> <p> <img border="0" src="/static/safari.png" alt="safari screenshot"/> 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:  <br /> @@ -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:  <br /> @@ -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:  <br /> 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: <em>*</em>  %endif %endif - <a href="#" class="expand" - %if collapse: - onclick="return showcomment(this)"> - %else: - onclick="return hidecomment(this)"> - %endif - [${_("+") if collapse else _("-")}] - %if collapse: - (${thing.num_children} - ${ungettext("child", "children", thing.num_children)}) + %if showexpandcollapse: + <a href="#" class="expand" + %if collapse: + onclick="return showcomment(this)"> + %else: + onclick="return hidecomment(this)"> + %endif + [${_("+") if collapse else _("-")}] + %if collapse: + (${thing.num_children} + ${ungettext("child", "children", thing.num_children)}) + %endif + </a> %endif -</a> - </%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: <div class="gray md"> ${_("[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"/> + +<script type="text/javascript"> + toolbar = new toolbar_p(); +</script> + +<h1> + ${_('top comments')} +</h1> +<h2> + <a target="_top" href="${thing.link.permalink}"> + ${_("full discussion")} + </a> +</h2> + +${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 %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/html4/frameset.dtd"> @@ -28,8 +30,36 @@ <head> <title>${thing.title} - - - + + %if thing.fullname: + + + %else: + + + %endif + + diff --git a/r2/r2/templates/frametoolbar.html b/r2/r2/templates/frametoolbar.html index 8cd7f7b88..61fc3fa8c 100644 --- a/r2/r2/templates/frametoolbar.html +++ b/r2/r2/templates/frametoolbar.html @@ -20,118 +20,164 @@ ## CondeNet, Inc. All Rights Reserved. ################################################################################ <%! - from r2.lib.template_helpers import static, get_domain + from r2.lib.template_helpers import static, get_domain, add_sr, panel_size + from r2.lib.strings import Score %> <%inherit file="reddit.html"/> <%namespace file="utils.html" import="plain_link, logout"/> -<%namespace file="printable.html" import="state_button, comment_button, thing_css_class" /> +<%namespace file="printable.html" import="state_button, comment_button, thing_css_class, score" /> + +<%def name="javascript_run()"> + ${parent.javascript_run()} + toolbar = new toolbar_p("${panel_size('expanded')}", "${panel_size('collapsed')}"); + <%def name="bodyHTML()"> - - <% fullname = thing.link._fullname %> - <% upstyle = "mod" if thing.link.likes else "" %> - <% downstyle = "mod" if thing.link.likes is False else "" %> - - - - - - - - - - -
- - reddit.com - - - ${_("like")} - - - ${_("dislike")} - - %if c.user_is_loggedin: - %if thing.link.saved: - ${state_button("unsave", _("unsave"), - "return change_state(this, 'unsave');", _("unsaved"))} - %else: - ${state_button("save", _("save"), - "return change_state(this, 'save');", _("saved"))} - %endif - %endif - <% - if not thing.link.num_comments: - # generates "comment" the imperative verb - com_label = _("comment {verb}") - else: - # generates "XX comments" as a noun - com_label = ungettext("comment", "comments", thing.link.num_comments) - %> - - ${comment_button("comment", com_label, - thing.link.num_comments, - thing.link.permalink)} - -
+ + + %if thing.link: + ${withlink()} + %endif + + +
+ + ${_('show url')} + + + + %if c.default_sr: + ${_("serendipity")} + %else: + ${"%(subreddit)s serendipity" % dict (subreddit=c.site.name)} + %endif + + + %if c.user_is_loggedin: + ${c.user.name} + ${logout(top=True,dest=thing.tblink,a_class="clickable")} + %elif thing.link: + + ${_("login / register")} + + %endif + + + ${_('help')} + + + + ${_('turn off the toolbar')} + +
+ +
+ %if thing.link: + + ${thing.link.title} + + %else: + ${plain_link(_('click to submit this link to reddit'), thing.submit_url, + target="_top", _class="submit")} + %endif + +
+ + %else: - ${self.score(thing, thing.likes, inline=False)} + ${self.score(thing, thing.likes, tag='div')} %endif ${self.arrow(thing, 0, thing.likes == False)} diff --git a/r2/r2/templates/linkinfobar.html b/r2/r2/templates/linkinfobar.html index f763e2bdd..f8ca8184e 100644 --- a/r2/r2/templates/linkinfobar.html +++ b/r2/r2/templates/linkinfobar.html @@ -23,6 +23,8 @@
+ + diff --git a/r2/r2/templates/newlink.html b/r2/r2/templates/newlink.html index 963324f6d..937436636 100644 --- a/r2/r2/templates/newlink.html +++ b/r2/r2/templates/newlink.html @@ -22,6 +22,7 @@ <%! from r2.lib.strings import strings + from r2.lib.template_helpers import add_sr %> <%namespace file="utils.html" import="error_field, submit_form, plain_link, text_with_links"/> @@ -31,11 +32,12 @@

${_("submit to %(site)s") % dict(site=c.site.name)}

%endif -<%call expr="submit_form(onsubmit='return post_form(this, \'submit\', linkstatus, null, true)', - action='/submit', _class='long-text content', _id='newlink')"> +<%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: %endif +
${_("toolbar link")}${thing.a.tblink}
${_("submitted on")} ${thing.a._date.strftime(thing.datefmt)}
${ungettext('point', 'points', 5)}
diff --git a/r2/r2/templates/printable.html b/r2/r2/templates/printable.html index 711eeb1e7..8e369256e 100644 --- a/r2/r2/templates/printable.html +++ b/r2/r2/templates/printable.html @@ -54,11 +54,12 @@ thing id-${what._fullname} <%def name="RenderPrintable()"> <% cls = thing.lookups[0].__class__.__name__.lower() %> <% - if hasattr(thing, 'render_class'): - cls = thing.render_class + if hasattr(thing, 'render_css_class'): + cls = thing.render_css_class + elif hasattr(thing, 'render_class'): + cls = thing.render_class.__name__.lower() else: - cls = thing.lookups[0].__class__ - cls = cls.__name__.lower() + cls = thing.lookups[0].__class__.__name__.lower() if thing.show_spam: rowclass = thing.rowstyle + " spam" @@ -145,6 +146,11 @@ thing id-${what._fullname} %if thing.deleted and not c.user_is_admin: [deleted] %else: + <% + target = None + if hasattr(thing, "target"): + target = thing.target + %> %if thing.author._deleted: [deleted] %else: @@ -155,11 +161,12 @@ thing id-${what._fullname} author_cls += " friend" elif gray: author_cls += " gray" - name = websafe(author.name) - href = unsafe('href="%s"' % add_sr("/user/%s/" % name, sr_path = False)) - if c.user_is_admin: name += " (%d)" % (author.link_karma) + disp_name = websafe(author.name) + if c.user_is_admin: + disp_name += " (%d)" % (author.link_karma) %> - ${name} + ${plain_link(disp_name, '/user/%s' % websafe(author.name), + _class = author_cls, _sr_path = False, target=target)} %endif %endif %if c.user_is_admin and hasattr(thing, 'ip') and thing.ip: @@ -184,25 +191,26 @@ thing id-${what._fullname} -<%def name="score(this, likes=None, inline=True)"> +<%def name="score(this, likes=None, tag='span', score_fmt = None)"> <% - tag = "span" if inline else "div" _class = "" if likes is None else "likes" if likes else "dislikes" # figure out alterna-points score = this.score base_score = score - 1 if likes else score if likes is None else score + 1 base_score = [base_score + x for x in range(-1, 2)]; - + + if score_fmt is None: + score_fmt = thing.score_fmt %> <${tag} class="score ${_class}"> - ${thing.score_fmt(this.score)} + ${score_fmt(this.score)} diff --git a/r2/r2/templates/reddit.html b/r2/r2/templates/reddit.html index 018d6b836..2d24fd6b4 100644 --- a/r2/r2/templates/reddit.html +++ b/r2/r2/templates/reddit.html @@ -73,6 +73,16 @@ %endif + + + + <%def name="javascript()"> @@ -81,10 +91,6 @@ - <%def name="javascript_run()"> @@ -110,7 +116,10 @@ %endif - <%include file="redditfooter.html"/> + %if thing.footer: + <%include file="redditfooter.html"/> + %endif + ${framebuster()} diff --git a/r2/r2/templates/redditheader.html b/r2/r2/templates/redditheader.html index d21eab017..f18f5f5d1 100644 --- a/r2/r2/templates/redditheader.html +++ b/r2/r2/templates/redditheader.html @@ -72,12 +72,14 @@ mail_img ="mail" if c.have_messages: mail_img += ".png" + mail_img_class = 'havemail' else: mail_img += "gray.png" + mail_img_class = 'nohavemail' mail_img = static(mail_img) %> ${img_link(_("messages"), mail_img, path="/message/inbox/", - _id = "mail" )} + _id = "mail", _class=mail_img_class)} ${separator("|")} %endif diff --git a/r2/r2/templates/redditmin.html b/r2/r2/templates/redditmin.html new file mode 100644 index 000000000..286f32b3f --- /dev/null +++ b/r2/r2/templates/redditmin.html @@ -0,0 +1,32 @@ +## 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. +############ +<%inherit file="reddit.html"/> + +<%def name="bodyHTML()"> + + %if thing.content: +
+ ${thing.content().render()} +
+ %endif + + diff --git a/r2/r2/templates/starkcomment.html b/r2/r2/templates/starkcomment.html new file mode 100644 index 000000000..bece6abf3 --- /dev/null +++ b/r2/r2/templates/starkcomment.html @@ -0,0 +1,44 @@ +## 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. +################################################################################ +<%inherit file="comment.html"/> + +<%def name="entry()"> +
+ + ${self.commentBody()} + +

+ ${self.tagline()} + + + ${_("context")} + +

+
+ + +<%def name="tagline(**kw)"> + ${parent.tagline(collapse=False,showexpandcollapse=False,**kw)} + + +<%def name="arrow(*a, **kw)"> + diff --git a/r2/r2/templates/utils.html b/r2/r2/templates/utils.html index a6bf45917..253e67d54 100644 --- a/r2/r2/templates/utils.html +++ b/r2/r2/templates/utils.html @@ -52,7 +52,11 @@ id="${arg}_${thing and thing._fullname or ''}" <%def name="submit_form(onsubmit='', action='', _class='', method='post', _id='', **params)">
+ action="${action or ''}" ${_id and "id='" + _id + "'" or ""} method="${method}" + %if c.cname: + target="_top" + %endif + > %if c.user_is_loggedin: %endif @@ -367,10 +371,23 @@ ${unsafe(txt)} }; -<%def name="logout()"> - +<%def name="logout(top=False,dest=None,a_class='')"> + - + + %if dest: + + %endif + + ${_("logout")}