1. Allow a reddit to have a cname, like www.proggit.com, that renders

the listing for that reddit

2. Allow a reddit to have a custom CSS stylesheet that appears to
   visitors

3. Allow a reddit to upload a custom reddit alien logo
This commit is contained in:
ketralnis
2008-08-26 07:15:02 -07:00
parent 1a107601fa
commit 6bcef0037b
67 changed files with 1624 additions and 267 deletions

1
.gitignore vendored
View File

@@ -14,6 +14,7 @@
.hgignore
lighttpd.**
development.ini
development_*.ini
production.ini
r2/r2/public/static/frame.js
r2/r2/public/static/reddit.js

View File

@@ -64,6 +64,8 @@ write_query_queue = False
stylesheet = reddit.css
stylesheet_rtl = reddit_rtl.css
allowed_css_linked_domains = my.domain.com, my.otherdomain.com
login_cookie = reddit_session
domain = localhost
default_sr = localhost

View File

@@ -173,11 +173,24 @@ class DomainMiddleware(object):
def __call__(self, environ, start_response):
# get base domain as defined in INI file
base_domain = config['global_conf']['domain']
sub_domains = environ['HTTP_HOST']
if not sub_domains.endswith(base_domain):
#if the domain doesn't end with base_domain, don't do anything
return self.app(environ, start_response)
try:
sub_domains, request_port = environ['HTTP_HOST'].split(':')
environ['request_port'] = int(request_port)
except ValueError:
sub_domains = environ['HTTP_HOST'].split(':')[0]
#If the domain doesn't end with base_domain, assume
#this is a cname, and redirect to the frame controller.
#Ignore localhost so paster shell still works.
#If this is an error, don't redirect
if (not sub_domains.endswith(base_domain)
and (not sub_domains == 'localhost')):
environ['sub_domain'] = sub_domains
if (not environ.get('extension')
and (not environ['PATH_INFO'].startswith('/error'))):
environ['original_path'] = environ['PATH_INFO']
environ['PATH_INFO'] = '/frame'
return self.app(environ, start_response)
sub_domains = sub_domains[:-len(base_domain)].strip('.')
sub_domains = sub_domains.split('.')
@@ -294,6 +307,22 @@ class RequestLogMiddleware(object):
pass
return r
class LimitUploadSize(object):
def __init__(self, app, max_size=1024*500):
self.app = app
self.max_size = max_size
def __call__(self, environ, start_response):
cl_key = 'CONTENT_LENGTH'
if environ['REQUEST_METHOD'] == 'POST':
if ((cl_key not in environ)
or int(environ[cl_key]) > self.max_size):
r = Response()
r.status_code = 500
r.content = 'request too big'
return r(environ, start_response)
return self.app(environ, start_response)
#god this shit is disorganized and confusing
class RedditApp(PylonsBaseWSGIApp):
@@ -335,13 +364,15 @@ def make_app(global_conf, full_stack=True, **app_conf):
# CUSTOM MIDDLEWARE HERE (filtered by the error handling middlewares)
app = LimitUploadSize(app)
app = ProfilingMiddleware(app)
app = SourceViewMiddleware(app)
app = DomainMiddleware(app)
app = SubredditMiddleware(app)
app = DomainMiddleware(app)
app = ExtensionMiddleware(app)
log_path = global_conf.get('log_path')
if log_path:
process_iden = global_conf.get('scgi_port', 'default')

View File

@@ -94,6 +94,8 @@ def make_map(global_conf={}, app_conf={}):
mc('/mail/optout', controller='front', action = 'optout')
mc('/mail/optin', controller='front', action = 'optin')
mc('/stylesheet', controller = 'front', action = 'stylesheet')
mc('/frame', controller='front', action = 'frame')
mc('/', controller='hot', action='listing')

View File

@@ -27,12 +27,13 @@ from pylons import c, request
from validator import *
from r2.models import *
from r2.models.subreddit import Default as DefaultSR
import r2.models.thing_changes as tc
from r2.lib.utils import get_title, sanitize_url, timeuntil, set_last_modified
from r2.lib.wrapped import Wrapped
from r2.lib.pages import FriendList, ContributorList, ModList, \
BannedList, BoringPage, FormPage, NewLink
BannedList, BoringPage, FormPage, NewLink, CssError, UploadedImage
from r2.lib.menus import CommentSortMenu
from r2.lib.translation import Translator
@@ -40,15 +41,18 @@ from r2.lib.normalized_hot import expire_hot
from r2.lib.captcha import get_iden
from r2.lib import emailer
from r2.lib.strings import strings
from r2.lib.memoize import clear_memo
from r2.lib.filters import _force_unicode
from r2.lib.db import queries
from r2.config import cache
from r2.lib.jsonresponse import JsonResponse, Json
from r2.lib.jsontemplates import api_type
from r2.lib import cssfilter
from simplejson import dumps
from r2.lib.jsonresponse import JsonResponse, Json
from r2.lib.jsontemplates import api_type
from datetime import datetime, timedelta
from md5 import md5
from r2.lib.organic import update_pos
def link_listing_by_url(url, count = None):
@@ -64,7 +68,7 @@ def link_listing_by_url(url, count = None):
builder = IDBuilder(names, num = 25)
listing = LinkListing(builder).listing()
return listing
class ApiController(RedditController):
def response_func(self, **kw):
@@ -74,9 +78,9 @@ class ApiController(RedditController):
try:
l = Link._by_url(url, sr)
if message:
return l.permalink + '?already_submitted=true'
return l.already_submitted_link()
else:
return l.permalink
return l.make_permalink_slow()
except NotFound:
pass
@@ -272,7 +276,7 @@ class ApiController(RedditController):
# well, nothing left to do but submit it
l = Link._submit(request.post.title, url, c.user, sr, ip, spam)
if url.lower() == 'self':
l.url = l.permalink
l.url = l.make_permalink_slow()
l.is_self = True
l._commit()
Vote.vote(c.user, l, True, ip, spam)
@@ -292,8 +296,16 @@ 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
res._redirect(l.permalink)
res._redirect(path)
def _login(self, res, user, dest='', rem = None):
@@ -719,6 +731,109 @@ class ApiController(RedditController):
# flag search indexer that something has changed
tc.changed(thing)
@Json
@validate(VUser(),
VModhash(),
stylesheet_contents = nop('stylesheet_contents'),
op = VOneOf('op',['save','preview']))
def POST_subreddit_stylesheet(self, res, stylesheet_contents = '', op='save'):
if not c.site.can_change_stylesheet(c.user):
return self.abort(403,'forbidden')
if g.css_killswitch:
return self.abort(403,'forbidden')
parsed, report = cssfilter.validate_css(stylesheet_contents)
if report.errors:
error_items = [ CssError(x).render(style='html')
for x in sorted(report.errors) ]
res._update('status', innerHTML = _('validation errors'))
res._update('validation-errors', innerHTML = ''.join(error_items))
res._show('error-header')
else:
res._hide('error-header')
res._update('status', innerHTML = '')
res._update('validation-errors', innerHTML = '')
if not report.errors and op == 'save':
stylesheet_contents_user = stylesheet_contents
stylesheet_contents_parsed = parsed.cssText if parsed else ''
c.site.stylesheet_contents = stylesheet_contents_parsed
c.site.stylesheet_contents_user = stylesheet_contents_user
c.site.stylesheet_hash = md5(stylesheet_contents_parsed).hexdigest()
set_last_modified(c.site,'stylesheet_contents')
tc.changed(c.site)
c.site._commit()
res._update('status', innerHTML = 'saved')
res._call('applyStylesheetFromTextbox("stylesheet_contents");')
res._update('validation-errors', innerHTML = '')
elif op == 'preview':
# try to find a link to use, otherwise give up and
# return
links = cssfilter.find_preview_links(c.site)
if not links:
# we're probably not going to be able to find any
# comments, either; screw it
return
res._show('preview-table')
# do a regular link
cssfilter.rendered_link('preview_link_normal',
res, links,
media = 'off', compress=False)
# now do one with media
cssfilter.rendered_link('preview_link_media',
res, links,
media = 'on', compress=False)
# do a compressed link
cssfilter.rendered_link('preview_link_compressed',
res, links,
media = 'off', compress=True)
# and do a comment
comments = cssfilter.find_preview_comments(c.site)
if not comments:
return
cssfilter.rendered_comment('preview_comment',res,comments)
@validate(VUser(),
VModhash(),
VRatelimit(rate_user = True,
rate_ip = True,
prefix = 'upload_reddit_img_'),
file = VLength('file',length=1024*500),
op = VOneOf('op',['upload','delete']))
def POST_upload_header_img(self, file, op):
if not c.site.can_change_stylesheet(c.user):
return self.abort403()
if g.css_killswitch:
return self.abort(403,'forbidden')
if op == 'upload':
try:
cleaned = cssfilter.clean_image(file,'PNG')
new_url = cssfilter.save_header_image(c.site, cleaned)
except cssfilter.BadImage:
return UploadedImage(_('bad image'),c.site.header).render()
c.site.header = new_url
c.site._commit()
return UploadedImage(_('saved'),new_url,'upload').render()
elif op == 'delete':
c.site.header = None
c.site._commit()
return UploadedImage(_('deleted'),DefaultSR.header,'delete').render()
@Json
@validate(VUser(),
VModhash(),
@@ -727,6 +842,7 @@ class ApiController(RedditController):
prefix = 'create_reddit_'),
name = VSubredditName("name"),
title = VSubredditTitle("title"),
domain = VCnameDomain("domain"),
description = VSubredditDesc("description"),
firsttext = nop("firsttext"),
header = nop("headerfile"),
@@ -740,11 +856,10 @@ class ApiController(RedditController):
type = VOneOf('type', ('public', 'private', 'restricted'))
)
def POST_site_admin(self, res, name ='', sr = None, **kw):
res._update('status', innerHTML = '')
redir = False
kw = dict((k, v) for k, v in kw.iteritems()
if v is not None
and k in ('name', 'title', 'description', 'firsttext',
and k in ('name', 'title', 'domain', 'description', 'firsttext',
'static_path', 'ad_file', 'over_18', 'show_media',
'type', 'header', 'lang', 'stylesheet'))
@@ -753,6 +868,12 @@ class ApiController(RedditController):
time = timeuntil(datetime.now(g.tz) + timedelta(seconds=600))
c.errors.add(errors.RATELIMIT, {'time': time})
domain = kw['domain']
cname_sr = domain and Subreddit._by_domain(domain)
if cname_sr and (not sr or sr != cname_sr):
kw['domain'] = None
c.errors.add(errors.USED_CNAME)
if not sr and res._chk_error(errors.RATELIMIT):
pass
elif not sr and res._chk_errors((errors.SUBREDDIT_EXISTS,
@@ -764,6 +885,9 @@ class ApiController(RedditController):
res._focus('title')
elif res._chk_error(errors.INVALID_SUBREDDIT_TYPE):
pass
elif res._chk_errors((errors.BAD_CNAME, errors.USED_CNAME)):
res._hide('example_domain')
res._focus('domain')
elif res._chk_error(errors.DESC_TOO_LONG):
res._focus('description')
@@ -784,6 +908,7 @@ class ApiController(RedditController):
if not res.error:
#assume sr existed, or was just built
clear_memo('subreddit._by_domain', Subreddit, _force_unicode(sr.domain))
for k, v in kw.iteritems():
setattr(sr, k, v)
sr._commit()
@@ -791,6 +916,9 @@ class ApiController(RedditController):
# flag search indexer that something has changed
tc.changed(sr)
res._update('status', innerHTML = _('saved'))
if redir:
res._redirect(redir)

View File

@@ -98,8 +98,13 @@ class ErrorController(RedditController):
try:
return RedditController.__call__(self, environ, start_response)
except:
c.response.content = "something really awful just happened"
return c.response
if g.debug:
# if we're in debug mode, let this hit Pylons so we
# get a stack trace
raise
else:
c.response.content = "something really awful just happened"
return c.response
def send403(self):

View File

@@ -57,6 +57,8 @@ error_list = dict((
('DRACONIAN', _('you must accept the terms first')),
('BANNED_IP', "IP banned"),
('BANNED_DOMAIN', "Domain banned"),
('BAD_CNAME', "that domain isn't going to work"),
('USED_CNAME', "that cname is already in use"),
('INVALID_SUBREDDIT_TYPE', _('that option is not valid')),
('DESC_TOO_LONG', _('description is too long')),
('CHEATER', 'what do you think you\'re doing there?'),

View File

@@ -154,7 +154,7 @@ class FrontController(RedditController):
# if permalink page, add that message first to the content
if comment:
displayPane.append(PermalinkMessage(article.permalink))
displayPane.append(PermalinkMessage(article.make_permalink_slow()))
# insert reply box only for logged in user
if c.user_is_loggedin and article.subreddit_slow.can_comment(c.user):
@@ -328,10 +328,21 @@ class FrontController(RedditController):
).render()
return res
def GET_stylesheet(self):
if hasattr(c.site,'stylesheet_contents') and not g.css_killswitch:
self.check_modified(c.site,'stylesheet_contents')
c.response.content = c.site.stylesheet_contents
c.response_content_type = 'text/css'
return c.response
else:
return self.abort404()
@base_listing
@validate(location = nop('location'))
def GET_editreddit(self, location, num, after, reverse, count):
"""Edit reddit form. """
"""Edit reddit form."""
if isinstance(c.site, FakeSubreddit):
return self.abort404()
@@ -346,6 +357,17 @@ class FrontController(RedditController):
pane = BannedList(editable = is_moderator)
elif location == 'contributors' and c.site.type != 'public':
pane = ContributorList(editable = is_moderator)
elif (location == 'stylesheet'
and c.site.can_change_stylesheet(c.user)
and not g.css_killswitch):
if hasattr(c.site,'stylesheet_contents_user') and c.site.stylesheet_contents_user:
stylesheet_contents = c.site.stylesheet_contents_user
elif hasattr(c.site,'stylesheet_contents') and c.site.stylesheet_contents:
stylesheet_contents = c.site.stylesheet_contents
else:
stylesheet_contents = ''
pane = SubredditStylesheet(site = c.site,
stylesheet_contents = stylesheet_contents)
elif is_moderator and location == 'spam':
links = Link._query(Link.c._spam == True)
comments = Comment._query(Comment.c._spam == True)
@@ -486,6 +508,10 @@ class FrontController(RedditController):
"""wipe login cookie and redirect to referer."""
self.logout()
dest = request.referer or '/'
if c.cname:
dest = '/?cnameframe=1'
if not dest.startswith("http://"):
return self.redirect(c.site.path + dest)
return self.redirect(dest)
@@ -591,3 +617,14 @@ class FrontController(RedditController):
ApiController.POST_optin."""
return self._render_opt_in_out(msg_hash, False)
def GET_frame(self):
"""used for cname support. makes a frame and
puts the proper url as the frame source"""
sub_domain = request.environ.get('sub_domain')
original_path = request.environ.get('original_path')
sr = Subreddit._by_domain(sub_domain)
if sub_domain and sr and original_path:
return Cnameframe(original_path, sr.name, sr.title, sub_domain).render()
else:
return self.abort404()

View File

@@ -350,7 +350,7 @@ class MessageController(ListingController):
@staticmethod
def builder_wrapper(thing):
if isinstance(thing, Comment):
p = thing.permalink
p = thing.make_permalink_slow()
f = thing._fullname
thing.__class__ = Message
w = Wrapped(thing)

View File

@@ -101,10 +101,14 @@ class PostController(ApiController):
pref_min_comment_score = VInt('min_comment_score', -100, 100),
pref_num_comments = VInt('num_comments', 1, g.max_comments,
default = g.num_comments),
pref_show_stylesheets = VBoolean('show_stylesheets'),
all_langs = nop('all-langs', default = 'all'))
def POST_options(self, all_langs, pref_lang, **kw):
self.set_options(all_langs, pref_lang, **kw)
return self.redirect("/prefs?done=true")
q_string = {'done': 'true'}
if c.cname:
q_string['cnameframe'] = '1'
return self.redirect((request.referer or "/prefs") + query_string(q_string))
def GET_over18(self):
return BoringPage(_("over 18?"),

View File

@@ -34,7 +34,7 @@ import r2.config as config
from r2.models import *
from errors import ErrorSet
from validator import *
from r2.lib.template_helpers import reddit_link
from r2.lib.template_helpers import add_sr
from r2.lib.jsontemplates import api_type
from copy import copy
@@ -105,7 +105,7 @@ def set_user_cookie(name, val):
uname = c.user.name if c.user_is_loggedin else ""
c.response.set_cookie(uname + '_' + name,
value = val,
domain = c.domain)
domain = g.domain)
def read_click_cookie():
if c.user_is_loggedin:
@@ -129,7 +129,7 @@ def firsttime():
if not request.cookies.get("reddit_first"):
c.response.set_cookie("reddit_first", "first",
expires = NEVER,
domain = c.domain)
domain = g.domain)
return True
return False
@@ -146,7 +146,9 @@ def set_subreddit():
sr_name=request.environ.get("subreddit", request.params.get('r'))
if not sr_name or sr_name == Default.name:
c.site = Default
sub_domain = request.environ.get('sub_domain')
sr = Subreddit._by_domain(sub_domain) if sub_domain else None
c.site = sr or Default
elif sr_name == 'r':
c.site = Sub
else:
@@ -191,6 +193,12 @@ def set_content_type():
c.response_wrappers.append(utils.to_js)
elif extension == 'mobile':
c.render_style = 'mobile'
#Insert new extentions above this line
elif extension not in ('', 'html'):
dest = "http://%s%s" % (request.host, request.path)
if request.get:
dest += utils.query_string(request.get)
redirect_to(dest)
def get_browser_langs():
browser_langs = []
@@ -252,6 +260,11 @@ def set_content_lang():
else:
c.content_langs = c.user.pref_content_langs
def set_cnameframe():
if (bool(request.params.get('cnameframe'))
or request.host.split(":")[0] != g.domain):
c.cname = True
def ratelimit_agents():
user_agent = request.user_agent
for s in g.agents:
@@ -308,6 +321,7 @@ class RedditController(BaseController):
key = ''.join((str(c.lang),
str(c.content_langs),
request.host,
c.cname,
request.fullpath,
str(c.firsttime),
str(c.over18)))
@@ -367,14 +381,15 @@ class RedditController(BaseController):
set_content_type()
set_iface_lang()
set_content_lang()
set_cnameframe()
# check if the user has access to this subreddit
if not c.site.can_view(c.user):
abort(403, "forbidden")
#check over 18
if c.site.over_18 and not c.over18:
d = dict(dest=reddit_link(request.path, url = True) + utils.query_string(request.GET))
if c.site.over_18 and not c.over18 and not request.path == "/frame":
d = dict(dest=add_sr(request.path) + utils.query_string(request.GET))
return redirect_to("/over18" + utils.query_string(d))
#check content cache
@@ -425,7 +440,7 @@ class RedditController(BaseController):
def check_modified(self, thing, action):
if c.user_is_loggedin:
return
date = utils.is_modified_since(thing, action, request.if_modified_since)
if date is True:
abort(304, 'not modified')

View File

@@ -21,6 +21,7 @@
################################################################################
from pylons.controllers.util import redirect_to
from r2.lib.base import BaseController
from pylons import c
class RedirectController(BaseController):
def GET_redirect(self, dest):

View File

@@ -26,7 +26,7 @@ from r2.lib import utils, captcha
from r2.lib.filters import unkeep_space, websafe
from r2.lib.db.operators import asc, desc
from r2.config import cache
from r2.lib.template_helpers import reddit_link
from r2.lib.template_helpers import add_sr
from r2.lib.jsonresponse import json_respond
from r2.models import *
@@ -77,8 +77,10 @@ def validate(*simple_vals, **param_vals):
return fn(self, *a, **kw)
except UserRequiredException:
d = dict(dest=reddit_link(request.path, url = True)
+ utils.query_string(request.GET))
d = dict(dest=add_sr(request.path) +
utils.query_string(request.GET))
if c.cname:
d['cnameframe'] = 1
path = "/login"
if request.environ.get('extension'):
path += ".%s" % request.environ['extension']
@@ -633,19 +635,22 @@ class VReason(Validator):
if reason.startswith('redirect_'):
dest = reason[9:]
if (not dest.startswith(c.site.path) and
not dest.startswith("http:")):
dest = (c.site.path + dest).replace('//', '/')
return ('redirect', dest)
if reason.startswith('vote_'):
fullname = reason[5:]
t = Thing._by_fullname(fullname, data=True)
return ('redirect', t.permalink)
return ('redirect', t.make_permalink_slow())
elif reason.startswith('share_'):
fullname = reason[6:]
t = Thing._by_fullname(fullname, data=True)
return ('redirect', t.permalink)
return ('redirect', t.make_permalink_slow())
elif reason.startswith('reply_'):
fullname = reason[6:]
t = Thing._by_fullname(fullname, data=True)
return ('redirect', t.permalink)
return ('redirect', t.make_permalink_slow())
elif reason.startswith('sr_change_'):
sr_list = reason[10:].split(',')
fullnames = dict(i.split(':') for i in sr_list)
@@ -695,6 +700,17 @@ class ValidEmails(Validator):
# return single email if one is expected, list otherwise
return list(emails)[0] if self.num == 1 else emails
class VCnameDomain(Validator):
domain_re = re.compile(r'.+\..+')
def run(self, domain):
if (domain
and (not self.domain_re.match(domain)
or domain.endswith('.reddit.com'))):
c.errors.add(errors.BAD_CNAME)
return domain or ''
# NOTE: make sure *never* to have res check these are present
# otherwise, the response could contain reference to these errors...!
class ValidIP(Validator):

View File

@@ -49,7 +49,8 @@ class Globals(object):
'uncompressedJS',
'enable_doquery',
'use_query_cache',
'write_query_queue']
'write_query_queue',
'css_killswitch']
tuple_props = ['memcaches',
'rec_cache',
@@ -57,6 +58,7 @@ class Globals(object):
'monitored_servers',
'default_srs',
'agents',
'allowed_css_linked_domains',
'query_caches']
def __init__(self, global_conf, app_conf, paths, **extra):
@@ -157,7 +159,15 @@ class Globals(object):
self.log.addHandler(logging.StreamHandler())
if self.debug:
self.log.setLevel(logging.DEBUG)
#read in our CSS so that it can become a default for subreddit
#stylesheets
stylesheet_path = os.path.join(paths.get('static_files'),
self.static_path.lstrip('/'),
self.stylesheet)
with open(stylesheet_path) as s:
self.default_stylesheet = s.read()
def __del__(self):
"""
Put any cleanup code to be run when the application finally exits

View File

@@ -94,8 +94,12 @@ class BaseController(WSGIController):
@staticmethod
def redirect(dest, code = 302):
c.response.headers['Location'] = _force_unicode(dest).encode('utf8')
dest = _force_unicode(dest).encode('utf8')
if c.cname and "?cnameframe=1" not in red:
dest += "?cnameframe=1"
c.response.headers['Location'] = dest
c.response.status_code = code
return c.response
def sendjs(self,js, callback="document.write", escape=True):

359
r2/r2/lib/cssfilter.py Normal file
View File

@@ -0,0 +1,359 @@
# 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-2008
# CondeNet, Inc. All Rights Reserved.
################################################################################
from __future__ import with_statement
from r2.models import *
from r2.lib.utils import sanitize_url, domain
from r2.lib.strings import string_dict
from pylons import g
from pylons.i18n import _
import re
import cssutils
from cssutils import CSSParser
from cssutils.css import CSSStyleRule
from cssutils.css import CSSValue, CSSValueList
from cssutils.css import CSSPrimitiveValue
from cssutils.css import cssproperties
from xml.dom import DOMException
msgs = string_dict['css_validator_messages']
custom_macros = {
'num': r'[-]?\d+|[-]?\d*\.\d+',
'percentage': r'{num}%',
'length': r'0|{num}(em|ex|px|in|cm|mm|pt|pc)',
'color': r'orangered|dimgray|lightgray|whitesmoke|pink',
}
custom_values = {
'_height': r'{length}|{percentage}|auto|inherit',
'_width': r'{length}|{percentage}|auto|inherit',
'_overflow': r'visible|hidden|scroll|auto|inherit',
'color': r'{color}',
'background-color': r'{color}',
'border-color': r'{color}',
'background-position': r'(({percentage}|{length}){0,3})?\s*(top|center|left)?\s*(left|center|right)?',
'opacity': r'{num}',
'filter': r'alpha\(opacity={num}\)',
}
def _expand_macros(tokdict,macrodict):
""" Expand macros in token dictionary """
def macro_value(m):
return '(?:%s)' % macrodict[m.groupdict()['macro']]
for key, value in tokdict.items():
while re.search(r'{[a-z][a-z0-9-]*}', value):
value = re.sub(r'{(?P<macro>[a-z][a-z0-9-]*)}',
macro_value, value)
tokdict[key] = value
return tokdict
def _compile_regexes(tokdict):
""" Compile all regular expressions into callable objects """
for key, value in tokdict.items():
tokdict[key] = re.compile('^(?:%s)$' % value, re.I).match
return tokdict
_compile_regexes(_expand_macros(custom_values,custom_macros))
class ValidationReport(object):
def __init__(self, original_text=''):
self.errors = []
self.original_text = original_text.split('\n') if original_text else ''
def __str__(self):
"only for debugging"
return "Report:\n" + '\n'.join(['\t' + str(x) for x in self.errors])
def append(self,error):
if hasattr(error,'line'):
error.offending_line = self.original_text[error.line-1]
self.errors.append(error)
class ValidationError(Exception):
def __init__(self, message, obj = None, line = None):
self.message = message
if obj is not None:
self.obj = obj
# self.offending_line is the text of the actual line that
# caused the problem; it's set by the ValidationReport that
# owns this ValidationError
if obj is not None and line is None and hasattr(self.obj,'_linetoken'):
(_type1,_type2,self.line,_char) = obj._linetoken
elif line is not None:
self.line = line
def __cmp__(self, other):
if hasattr(self,'line') and not hasattr(other,'line'):
return -1
elif hasattr(other,'line') and not hasattr(self,'line'):
return 1
else:
return cmp(self.line,other.line)
def __str__(self):
"only for debugging"
line = (("(%d)" % self.line)
if hasattr(self,'line') else '')
obj = str(self.obj) if hasattr(self,'obj') else ''
return "ValidationError%s: %s (%s)" % (line, self.message, obj)
local_urls = re.compile(r'^/static/[a-z./-]+$')
def valid_url(prop,value,report):
url = value.getStringValue()
if local_urls.match(url):
pass
elif domain(url) in g.allowed_css_linked_domains:
pass
else:
report.append(ValidationError(msgs['broken_url']
% dict(brokenurl = value.cssText),
value))
#elif sanitize_url(url) != url:
# report.append(ValidationError(msgs['broken_url']
# % dict(brokenurl = value.cssText),
# value))
def valid_value(prop,value,report):
if not (value.valid and value.wellformed):
if (value.wellformed
and prop.name in cssproperties.cssvalues
and cssproperties.cssvalues[prop.name](prop.value)):
# it's actually valid. cssutils bug.
pass
elif (not value.valid
and value.wellformed
and prop.name in custom_values
and custom_values[prop.name](prop.value)):
# we're allowing it via our own custom validator
value.valid = True
# see if this suddenly validates the entire property
prop.valid = True
prop.cssValue.valid = True
if prop.cssValue.cssValueType == CSSValue.CSS_VALUE_LIST:
for i in range(prop.cssValue.length):
if not prop.cssValue.item(i).valid:
prop.cssValue.valid = False
prop.valid = False
break
elif not (prop.name in cssproperties.cssvalues or prop.name in custom_values):
error = (msgs['invalid_property']
% dict(cssprop = prop.name))
report.append(ValidationError(error,value))
else:
error = (msgs['invalid_val_for_prop']
% dict(cssvalue = value.cssText,
cssprop = prop.name))
report.append(ValidationError(error,value))
if value.primitiveType == CSSPrimitiveValue.CSS_URI:
valid_url(prop,value,report)
error_message_extract_re = re.compile('.*\\[([0-9]+):[0-9]*:.*\\]$')
only_whitespace = re.compile('^\s*$')
def validate_css(string):
p = CSSParser(raiseExceptions = True)
if not string or only_whitespace.match(string):
return ('',ValidationReport())
report = ValidationReport(string)
# avoid a very expensive parse
max_size_kb = 100;
if len(string) > max_size_kb * 1024:
report.append(ValidationError((msgs['too_big']
% dict (max_size = max_size_kb))))
return (string, report)
try:
parsed = p.parseString(string)
except DOMException,e:
# yuck; xml.dom.DOMException can't give us line-information
# directly, so we have to parse its error message string to
# get it
line = None
line_match = error_message_extract_re.match(e.message)
if line_match:
line = line_match.group(1)
if line:
line = int(line)
error_message= (msgs['syntax_error']
% dict(syntaxerror = e.message))
report.append(ValidationError(error_message,e,line))
return (None,report)
for rule in parsed.cssRules:
if rule.type == CSSStyleRule.IMPORT_RULE:
report.append(ValidationError(msgs['no_imports'],rule))
elif rule.type == CSSStyleRule.COMMENT:
pass
elif rule.type == CSSStyleRule.STYLE_RULE:
style = rule.style
for prop in style.getProperties():
if prop.cssValue.cssValueType == CSSValue.CSS_VALUE_LIST:
for i in range(prop.cssValue.length):
valid_value(prop,prop.cssValue.item(i),report)
if not (prop.cssValue.valid and prop.cssValue.wellformed):
report.append(ValidationError(msgs['invalid_property_list']
% dict(proplist = prop.cssText),
prop.cssValue))
elif prop.cssValue.cssValueType == CSSValue.CSS_PRIMITIVE_VALUE:
valid_value(prop,prop.cssValue,report)
# cssutils bug: because valid values might be marked
# as invalid, we can't trust cssutils to properly
# label valid properties, so we're going to rely on
# the value validation (which will fail if the
# property is invalid anyway). If this bug is fixed,
# we should uncomment this 'if'
# a property is not valid if any of its values are
# invalid, or if it is itself invalid. To get the
# best-quality error messages, we only report on
# whether the property is valid after we've checked
# the values
#if not (prop.valid and prop.wellformed):
# report.append(ValidationError(_('invalid property'),prop))
else:
report.append(ValidationError(msgs['unknown_rule_type']
% dict(ruletype = rule.cssText),
rule))
return parsed,report
def builder_wrapper(thing):
if c.user.pref_compress and isinstance(thing, Link):
thing.__class__ = LinkCompressed
thing.score_fmt = Score.points
return Wrapped(thing)
def find_preview_comments(sr):
comments = Comment._query(Comment.c.sr_id == c.site._id,
limit=25, data=True)
comments = list(comments)
if not comments:
comments = Comment._query(limit=25, data=True)
comments = list(comments)
return comments
def find_preview_links(sr):
from r2.lib.normalized_hot import get_hot
# try to find a link to use, otherwise give up and return
links = get_hot(c.site)
if not links:
sr = Subreddit._by_name(g.default_sr)
if sr:
links = get_hot(sr)
return links
def rendered_link(id, res, links, media, compress):
from pylons.controllers.util import abort
try:
render_style = c.render_style
c.render_style = 'html'
with c.user.safe_set_attr:
c.user.pref_compress = compress
c.user.pref_media = media
b = IDBuilder([l._fullname for l in links],
num = 1, wrap = builder_wrapper)
l = LinkListing(b, nextprev=False,
show_nums=True).listing().render(style='html')
res._update(id, innerHTML=l)
finally:
c.render_style = render_style
def rendered_comment(id, res, comments):
try:
render_style = c.render_style
c.render_style = 'html'
b = IDBuilder([x._fullname for x in comments],
num = 1)
l = LinkListing(b, nextprev=False,
show_nums=False).listing().render(style='html')
res._update('preview_comment', innerHTML=l)
finally:
c.render_style = render_style
class BadImage(Exception): pass
def clean_image(data,format):
import Image
from StringIO import StringIO
try:
in_file = StringIO(data)
out_file = StringIO()
im = Image.open(in_file)
im = im.resize(im.size)
im.save(out_file,format)
ret = out_file.getvalue()
except IOError,e:
raise BadImage(e)
finally:
out_file.close()
in_file.close()
return ret
def save_header_image(sr, data):
import tempfile
from r2.lib import s3cp
from md5 import md5
hash = md5(data).hexdigest()
try:
f = tempfile.NamedTemporaryFile(suffix = '.png')
f.write(data)
f.flush()
resource = g.s3_thumb_bucket + sr._fullname + '.png'
s3cp.send_file(f.name, resource, 'image/png', 'public-read', None, False)
finally:
f.close()
return 'http:/%s%s.png?v=%s' % (g.s3_thumb_bucket, sr._fullname, hash)

View File

@@ -35,7 +35,7 @@ import logging
log_format = logging.Formatter('sql: %(message)s')
settings = storage()
settings.DEBUG = g.debug
settings.DEBUG = False
settings.DB_CREATE_TABLES = True
settings.DB_APP_NAME = 'reddit'

View File

@@ -114,7 +114,11 @@ def safemarkdown(text):
#wipe malicious javascript
text = jscript_url.sub('', text)
def href_handler(m):
return '<a href="%s"' % m.group(1).replace('&amp;', '&')
x = m.group(1).replace('&amp;', '&')
if c.cname:
return '<a target="_top" href="%s"' % x
else:
return '<a href="%s"' % x
def code_handler(m):
l = m.group(1)
return '<code>%s</code>' % l.replace('&amp;','&')
@@ -125,7 +129,10 @@ def safemarkdown(text):
def keep_space(text):
return unsafe(websafe(text).replace(' ', '&#32;').replace('\n', '&#10;').replace('\t', '&#09;'))
text = websafe(text)
for i in " \n\r\t":
text=text.replace(i,'&#%02d;' % ord(i))
return unsafe(text)
def unkeep_space(text):

View File

@@ -46,7 +46,7 @@ class JsonListingStub(object):
class JsonResponse():
# handled entried in the response object
__slots__ = ['update', 'blur', 'focus', 'object', 'hide', 'show',
'captcha', 'success']
'captcha', 'success', 'call']
def __init__(self):
self.update = []
@@ -59,6 +59,10 @@ class JsonResponse():
self.error = None
self.success = None
self.redirect = None
self.call = []
def _call(self, fn):
self.call.append(fn)
def _success(self):
self.success = 1
@@ -77,7 +81,11 @@ class JsonResponse():
self.blur = f
def _redirect(self, red):
self.redirect = red
from pylons import c
if c.cname and "?cnameframe=1" not in red:
self.redirect = red + "?cnameframe=1"
else:
self.redirect = red
def _update(self, name, **kw):

View File

@@ -238,15 +238,17 @@ class NavButton(Styled):
must also have its build() method called with the current path to
set self.path. This step is done automatically if the button is
passed to a NavMenu instance upon its construction."""
def __init__(self, title, dest, sr_path = True, opt = '', aliases = [],
style = "plain", **kw):
def __init__(self, title, dest, sr_path = True,
nocname=False, opt = '', aliases = [],
target = "", style = "plain", **kw):
# keep original dest to check against c.location when rendering
self.aliases = set(a.rstrip('/') for a in aliases)
self.aliases.add(dest.rstrip('/'))
self.dest = dest
Styled.__init__(self, style = style, sr_path = sr_path,
Styled.__init__(self, style = style, sr_path = sr_path,
nocname = nocname, target = target,
title = title, opt = opt, **kw)
def build(self, base_path = ''):
@@ -295,10 +297,10 @@ class NamedButton(NavButton):
'dest' defaults to the 'name' as well (unless specified
separately)."""
def __init__(self, name, sr_path = True, dest = None, **kw):
def __init__(self, name, sr_path = True, nocname=False, dest = None, **kw):
self.name = name.strip('/')
NavButton.__init__(self, menu[self.name], name if dest is None else dest,
sr_path = sr_path, **kw)
sr_path = sr_path, nocname=nocname, **kw)
def selected_title(self):
"""Overrides selected_title to use menu_selected dictionary"""

View File

@@ -32,7 +32,8 @@ from r2.lib.captcha import get_iden
from r2.lib.filters import spaceCompress, _force_unicode
from r2.lib.menus import NavButton, NamedButton, NavMenu, PageNameNav, JsButton, menu
from r2.lib.strings import plurals, rand_strings, strings
from r2.lib.utils import title_to_url
from r2.lib.utils import title_to_url, query_string
from r2.lib.template_helpers import add_sr
import sys
def get_captcha():
@@ -119,15 +120,15 @@ class Reddit(Wrapped):
if self.submit_box:
ps.append(SideBox(_('Submit a link'),
c.site.path + 'submit', 'submit',
'/submit', 'submit',
subtitles = [_('to anything interesting: news article, blog entry, video, picture...')],
show_cover = True))
if self.create_reddit_box:
ps.append(SideBox(_('Create your own reddit'),
ps.append(SideBox(_('Create your own reddit'),
'/reddits/create', 'create',
subtitles = rand_strings.get("create_reddit", 2),
show_cover = True))
show_cover = True, nocname=True))
return ps
def render(self, *a, **kw):
@@ -160,9 +161,11 @@ class Reddit(Wrapped):
if c.user_is_loggedin:
if c.user.name in g.admins:
if c.user_is_admin:
buttons += [NamedButton("adminoff", False)]
buttons += [NamedButton("adminoff", False, nocname=True,
target = "_self")]
else:
buttons += [NamedButton("adminon", False)]
buttons += [NamedButton("adminon", False, nocname=True,
target = "_self")]
buttons += [NamedButton("prefs", False,
css_class = "pref-lang")]
else:
@@ -170,12 +173,13 @@ class Reddit(Wrapped):
buttons += [JsButton(g.lang_name.get(lang, lang),
onclick = "return showlang();",
css_class = "pref-lang")]
buttons += [NamedButton("stats", False)]
buttons += [NamedButton("help", False),
NamedButton("blog", False)]
buttons += [NamedButton("stats", False, nocname=True)]
buttons += [NamedButton("help", False, nocname=True),
NamedButton("blog", False, nocname=True)]
if c.user_is_loggedin:
buttons += [NamedButton("logout", False)]
buttons += [NamedButton("logout", False, nocname=True,
target = "_self")]
return NavMenu(buttons, base_path = "/", type = "flatlist")
@@ -185,10 +189,10 @@ class Reddit(Wrapped):
NamedButton("bookmarklets", False),
NamedButton("buttons", False),
NamedButton("widget", False),
NamedButton("code", False),
NamedButton("mobile", False),
NamedButton("store", False),
NamedButton("ad_inq", False),
NamedButton("code", False, nocname=True),
NamedButton("mobile", False, nocname=True),
NamedButton("store", False, nocname=True),
NamedButton("ad_inq", False, nocname=True),
]
return NavMenu(buttons, base_path = "/", type = "flatlist")
@@ -217,7 +221,7 @@ class Reddit(Wrapped):
toolbar = [NavMenu(main_buttons, type='tabmenu')]
if more_buttons:
toolbar.append(NavMenu(more_buttons, title=menu.more, type='tabdrop'))
if c.site != Default:
if c.site != Default and not c.cname:
toolbar.insert(0, PageNameNav('subreddit'))
return toolbar
@@ -259,10 +263,10 @@ class SubredditInfoBar(Wrapped):
class SideBox(Wrapped):
"""Generic sidebox used to generate the 'submit' and 'create a reddit' boxes."""
def __init__(self, title, link, css_class='', subtitles = [],
show_cover = False):
Wrapped.__init__(self, link = link,
show_cover = False, nocname=False):
Wrapped.__init__(self, link = link, target = '_top',
title = title, css_class = css_class,
subtitles = subtitles, show_cover = show_cover)
subtitles = subtitles, show_cover = show_cover, nocname=nocname)
class PrefsPage(Reddit):
@@ -429,7 +433,7 @@ class LinkInfoPage(Reddit):
toolbar = [NavMenu(buttons, base_path = "", type="tabmenu")]
if c.site != Default:
if c.site != Default and not c.cname:
toolbar.insert(0, PageNameNav('subreddit'))
return toolbar
@@ -463,7 +467,10 @@ class EditReddit(Reddit):
Reddit.__init__(self, title = title, *a, **kw)
def build_toolbars(self):
return [PageNameNav('subreddit')]
if not c.cname:
return [PageNameNav('subreddit')]
else:
return []
@@ -659,6 +666,24 @@ class CreateSubreddit(Wrapped):
def __init__(self, site = None, name = ''):
Wrapped.__init__(self, site = site, name = name)
class SubredditStylesheet(Wrapped):
"""form for editing or creating subreddit stylesheets"""
def __init__(self, site = None,
stylesheet_contents = ''):
Wrapped.__init__(self, site = site,
stylesheet_contents = stylesheet_contents)
class CssError(Wrapped):
"""Rendered error returned to the stylesheet editing page via ajax"""
def __init__(self, error):
# error is an instance of cssutils.py:ValidationError
Wrapped.__init__(self, error = error)
class UploadedImage(Wrapped):
"The page rendered in the iframe during an upload of a header image"
def __init__(self,status,img_src,op):
Wrapped.__init__(self,
status=status, img_src=img_src, op=op)
class Password(Wrapped):
"""Form encountered when 'recover password' is clicked in the LoginFormWide."""
@@ -1042,3 +1067,15 @@ class DetailsPage(LinkInfoPage):
from admin_pages import Details
return self.content_stack(self.link_listing, Details(link = self.link))
class Cnameframe(Wrapped):
"""The frame page."""
def __init__(self, original_path, sr_name, sr_title, sub_domain):
Wrapped.__init__(self, original_path=original_path)
self.title = "%s - %s" % (sr_title, sub_domain)
port = request.environ.get('request_port')
request.get['cnameframe'] = 1
path = original_path + query_string(request.get)
if port > 0:
self.frame_target = "http://%s:%d/r/%s%s" % (c.domain, port, sr_name, path)
else:
self.frame_target = "http://%s/r/%s%s" % (c.domain, sr_name, path)

View File

@@ -93,7 +93,19 @@ string_dict = dict(
searching_a_reddit = _('you\'re searching within the [%(reddit_name)s](%(reddit_link)s) reddit. '+
'you can search within [your subscribed reddits](%(my_reddits_link)s) ' +
'or [all reddits](%(all_reddits_link)s)')
'or [all reddits](%(all_reddits_link)s)'),
css_validator_messages = dict(
broken_url = _('"%(brokenurl)s" is not a valid URL'),
invalid_property = _('"%(cssprop)s" is not a valid CSS property'),
invalid_val_for_prop = _('"%(cssvalue)s" is not a valid value for CSS property "%(cssprop)s"'),
too_big = _('too big. keep it under %(max_size)dkb'),
syntax_error = _('syntax error: "%(syntaxerror)s"'),
no_imports = _('@imports are not allowed'),
invalid_property_list = _('invalid CSS property list "%(proplist)s"'),
unknown_rule_type = _('unknown CSS rule type "%(ruletype)s"')
)
)
class StringHandler(object):

View File

@@ -53,7 +53,6 @@ def static(file):
return file + v
return os.path.join(c.site.static_path, file) + v
def generateurl(context, path, **kw):
if kw:
return path + '?' + '&'.join(["%s=%s"%(k, url_escape(v)) \
@@ -145,45 +144,56 @@ def replace_render(listing, item, style = None, display = True):
rendered_item = replace_fn(u"$ListClass", listing._js_cls)
#$votehash is only present when voting arrows are present
if u'$votehash' in rendered_item:
if c.user_is_loggedin and u'$votehash' in rendered_item:
hash = vote_hash(c.user, item, listing.vote_hash_type)
rendered_item = replace_fn(u'$votehash', hash)
rendered_item = replace_fn(u"$display", "" if display else "style='display:none'")
return rendered_item
from pylons import c as cur
def dockletStr(context, type, browser):
domain = c.domain
if c.cname:
domain = c.site.domain
if type == "serendipity!":
return "http://"+cur.domain+"/random"
return "http://"+domain+"/random"
elif type == "reddit":
return "javascript:location.href='http://"+cur.domain+"/submit?url='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)"
return "javascript:location.href='http://"+domain+"/submit?url='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)"
else:
f = "fixed"
if browser == "ie": f = "absolute"
return "javascript:function b(){var u=encodeURIComponent(location.href);var i=document.getElementById('redstat')||document.createElement('a');var s=i.style;s.position='%s';s.top='0';s.left='0';s.zIndex='10002';i.id='redstat';i.href='http://%s/submit?url='+u+'&title='+encodeURIComponent(document.title);var q=i.firstChild||document.createElement('img');q.src='http://%s/d/%s'+Math.random()+'?uh=%s&u='+u;i.appendChild(q);document.body.appendChild(i)};b()" % \
(f, cur.domain, cur.domain, type,
c.modhash if cur.user else '')
(f, domain, domain, type,
c.modhash if c.user else '')
def reddit_link(path, url = False, get = False):
if url or get:
(scheme, netloc, path, params, query, fragment) = urlparse(path)
if url:
#noslash fixes /reddits/
noslash = c.site.path.rstrip('/')
#if it's a relative path, don't include the sitename
if path.startswith('/') and not path.startswith(noslash):
def add_sr(path, sr_path = True, nocname=False):
"""Given a link, returns that link with the subreddit added.
Also adds the domain for cname requests."""
(scheme, netloc, path, params, query, fragment) = urlparse(path)
if sr_path:
#noslash fixes /reddits/
noslash = c.site.path.rstrip('/')
#if it's a relative path, don't include the sitename
if (path.startswith('/') and not path.startswith(noslash)
and not path.startswith('/r/')):
if not c.cname:
path = c.site.path + path[1:]
else:
newparam = "r=" + url_escape(c.site.name)
if query:
query += "&" + newparam
else:
query = newparam
return urlunparse((scheme, netloc, path, params, query, fragment))
return path
if not netloc and c.cname and not nocname:
netloc = getattr(c.site, 'domain', None)
if netloc:
port = request.environ.get('request_port')
if port > 0:
netloc = "%s:%d" % (netloc, port)
if c.render_style == 'mobile' and not path.endswith('.mobile'):
path += '.mobile'
return urlunparse((scheme, netloc, path, params, query, fragment))
def join_urls(*urls):
"""joins a series of urls together without doubles slashes"""

View File

@@ -11,7 +11,7 @@ def is_modified_since(thing, action, date):
which means a 304 should be returned. Otherwise returns the date
that should be sent as the last-modified header."""
from pylons import g
prop = 'last_' + action
if not hasattr(thing, prop):
last_modified = make_last_modified()

View File

@@ -51,6 +51,7 @@ class Account(Thing):
pref_over_18 = False,
pref_compress = False,
pref_organic = True,
pref_show_stylesheets = True,
reported = 0,
report_made = 0,
report_correct = 0,

View File

@@ -79,7 +79,7 @@ class Link(Thing, Printable):
@property
def already_submitted_link(self):
return self.permalink + '?already_submitted=true'
return self.make_permalink_slow() + '?already_submitted=true'
def resubmit_link(self, sr_url = False):
submit_url = self.subreddit_slow.path if sr_url else '/'
@@ -193,7 +193,9 @@ class Link(Thing, Printable):
c.user.pref_newwindow,
c.user.pref_frame,
c.user.pref_compress,
c.user.pref_media,
request.host,
c.cname,
wrapped.author == c.user,
wrapped.likes,
wrapped.saved,
@@ -208,10 +210,19 @@ class Link(Thing, Printable):
s = ''.join(s)
return s
@property
def permalink(self):
return "/comments/%s/%s/" % (self._id36, title_to_url(self.title))
def make_permalink(self, sr):
p = "comments/%s/%s/" % (self._id36, title_to_url(self.title))
if not c.cname:
res = "/r/%s/%s" % (sr.name, p)
elif sr != c.site:
res = "http://%s/r/%s/%s" % (g.domain, sr.name, p)
else:
res = "/%s" % p
return res
def make_permalink_slow(self):
return self.make_permalink(self.subreddit_slow)
@classmethod
def add_props(cls, user, wrapped):
from r2.lib.count import incr_counts
@@ -247,6 +258,7 @@ class Link(Thing, Printable):
item.clicked = bool(clicked.get((user, item, 'click')))
item.num = None
item.score_fmt = Score.number_only
item.permalink = item.make_permalink(item.subreddit)
if c.user_is_loggedin:
incr_counts(wrapped)
@@ -333,6 +345,7 @@ class Comment(Thing, Printable):
bool(c.user_is_loggedin),
c.focal_comment == self._id36,
request.host,
c.cname,
wrapped.author == c.user,
wrapped.likes,
wrapped.friend,
@@ -347,22 +360,18 @@ class Comment(Thing, Printable):
s = ''.join(s)
return s
@property
def permalink(self):
if not self._loaded:
self._load()
try:
l = Link._byID(self.link_id, True)
return l.permalink + self._id36
except NotFound:
return ""
def make_permalink(self, link, sr=None):
return link.make_permalink(sr) + self._id36
def make_permalink_slow(self):
l = Link._byID(self.link_id, data=True)
return self.make_permalink(l, l.subreddit_slow)
@classmethod
def add_props(cls, user, wrapped):
#fetch parent links
links = Link._byID(set(l.link_id for l in wrapped), True)
#get srs for comments that don't have them (old comments)
for cm in wrapped:
@@ -378,19 +387,20 @@ class Comment(Thing, Printable):
cids = dict((w._id, w) for w in wrapped)
for item in wrapped:
item.link = links.get(item.link_id)
if not hasattr(item, 'subreddit'):
item.subreddit = item.subreddit_slow
if hasattr(item, 'parent_id'):
if cids.has_key(item.parent_id):
item.parent_permalink = '#' + utils.to36(item.parent_id)
else:
parent = Comment._byID(item.parent_id)
item.parent_permalink = parent.permalink
item.parent_permalink = parent.make_permalink(item.link, item.subreddit)
else:
item.parent_permalink = None
item.can_reply = (item.sr_id in can_reply_srs)
if not hasattr(item, 'subreddit'):
item.subreddit = item.subreddit_slow
# not deleted on profile pages,
# deleted if spam and not author or admin
@@ -406,12 +416,12 @@ class Comment(Thing, Printable):
item.deleted or
c.user_is_admin))
item.link = links.get(item.link_id)
if not hasattr(item,'editted'):
item.editted = False
#will get updated in builder
item.num_children = 0
item.score_fmt = Score.points
item.permalink = item.make_permalink(item.link, item.subreddit)
class MoreComments(object):
show_spam = False
@@ -432,7 +442,7 @@ class MoreComments(object):
if parent:
self.parent_id = parent._id
self.parent_name = parent._fullname
self.parent_permalink = parent.permalink
self.parent_permalink = parent.make_permalink(link)
self.link_name = link._fullname
self.link_id = link._id
self.depth = depth

View File

@@ -21,10 +21,10 @@
################################################################################
from r2.models import *
def populate():
sr = Subreddit._new(name= 'reddit.com', title = "reddit.com: what's new online")
def populate(sr_name = 'reddit.com', start_account = 1, sr_title = "reddit.com: what's new online"):
sr = Subreddit._new(name= sr_name, title = sr_title)
sr._commit()
for i in range(1,5):
for i in range(start_account,(start_account + 4)):
name = 'test' + str(i)
password = name
user = register(name, password)

View File

@@ -30,6 +30,7 @@ from r2.lib.db.operators import lower, or_, and_, desc
from r2.lib.memoize import memoize, clear_memo
from r2.lib.utils import tup
from r2.lib.strings import strings, Score
from r2.lib.filters import _force_unicode
import os.path
@@ -37,6 +38,8 @@ class Subreddit(Thing, Printable):
_defaults = dict(static_path = g.static_path,
stylesheet = None,
stylesheet_rtl = None,
stylesheet_contents = '',
stylesheet_hash = '0',
description = None,
firsttext = strings.firsttext,
header = os.path.join(g.static_path,
@@ -45,6 +48,7 @@ class Subreddit(Thing, Printable):
reported = 0,
valid_votes = 0,
show_media = False,
domain = None,
)
@classmethod
@@ -91,6 +95,24 @@ class Subreddit(Thing, Printable):
else:
raise NotFound, 'Subreddit %s' % name
@classmethod
@memoize('subreddit._by_domain')
def _by_domain_cache(cls, name):
q = cls._query(cls.c.domain == name,
cls.c.over_18 == (True, False),
limit = 1)
l = list(q)
if l:
return l[0]._id
@classmethod
def _by_domain(cls, domain):
sr_id = cls._by_domain_cache(_force_unicode(domain))
if sr_id:
return cls._byID(sr_id, True)
else:
return None
@property
def moderators(self):
return self.moderator_ids()
@@ -138,6 +160,12 @@ class Subreddit(Thing, Printable):
and (c.user_is_admin
or self.is_moderator(user)))
def can_change_stylesheet(self, user):
if c.user_is_loggedin:
return c.user_is_admin or self.is_moderator(user)
else:
return False
def is_special(self, user):
return (user
and (c.user_is_admin
@@ -341,6 +369,9 @@ class FakeSubreddit(Subreddit):
def can_submit(self, user):
return False
def can_change_stylesheet(self, user):
return False
def is_banned(self, user):
return False

View File

@@ -39,7 +39,7 @@ document.write('<script language="JavaScript" src="http://ad.doubleclick.net/adj
</div>
<div id="adlink">
<a target="_parent" href="#" onclick="rwt(this)">reddit this ad</a>
<a target="_top" href="#" onclick="rwt(this)">reddit this ad</a>
</div>
</body>

View File

@@ -1,11 +1,17 @@
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;}
table{ border-collapse:collapse;}
fieldset,img{ border:0;}
address,caption,cite,code,dfn,em,strong,th,var{ font-style:normal;font-weight:normal;}
ol,ul { list-style:none;}
caption,th { text-align:left;}
h1,h2,h3,h4,h5,h6{ font-size:100%;}
q:before,q:after{ content:'';}
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;
}
table { border-collapse:collapse; }
fieldset,img { border:0; }
address,caption,cite,code,dfn,em,strong,th,var { font-style:normal; font-weight:normal; }
ol,ul { list-style:none; }
caption,th { text-align:left; }
h1,h2,h3,h4,h5,h6 { font-size:100%; }
q:before,q:after { content:''; }
body {
font: normal x-small verdana, arial, helvetica, sans-serif;
@@ -13,8 +19,12 @@ body {
z-index: 1;
}
html,body { height: 100% }
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. */
input[type=hidden] { position: absolute; }
/* html element defaults */
@@ -85,10 +95,7 @@ input.txt {
background-color: #cee3f8;
}
#header-img {
height: 40px;
width: 120px;
}
#header-img {}
#header-top {
position: absolute;
@@ -460,25 +467,14 @@ before enabling */
height: 14px;
display: block;
cursor: pointer;
background-position: center 0px;
background-position: center center;
background-repeat: no-repeat;
}
.arrow.upmod {
background: url(/static/aupmod.png);
background-repeat: no-repeat;
background-position: center 0px; }
.arrow.downmod {
background: url(/static/adownmod.png);
background-repeat: no-repeat;
background-position: center 0px; }
.arrow.up {
background: url(/static/aupgray.png);
background-repeat: no-repeat;
background-position: center 0px; }
.arrow.down {
background: url(/static/adowngray.png);
background-repeat: no-repeat;
background-position: center 0px;}
.arrow.upmod { background-image: url(/static/aupmod.png); }
.arrow.downmod { background-image: url(/static/adownmod.png); }
.arrow.up { background-image: url(/static/aupgray.png); }
.arrow.down { background-image: url(/static/adowngray.png); }
.midcol {
float: left;
@@ -1196,7 +1192,12 @@ a.star { text-decoration: none; color: #ff8b60 }
#preview { float: right; width: 20em; margin-right: 10px; }
#preview span { color: lightgray; }
#preview #previewbox { border: .2em dashed lightgray; padding: 1em; }
#preview #previewbox {
border-width: .2em;
border-style: dashed;
border-color: lightgray;
padding: 1em;
}
.pretty-form {
font-size: larger;
@@ -1204,6 +1205,7 @@ a.star { text-decoration: none; color: #ff8b60 }
}
.pretty-form input[type=checkbox] {margin: 3px .5em;}
.pretty-form p {margin: 3px .5em;}
.pretty-form input[type=radio] {margin: 3px .5em 0px .5em; vertical-align:top}
.pretty-form img { margin: 3px .5em}
.pretty-form input[type=text],
@@ -1280,3 +1282,63 @@ a.star { text-decoration: none; color: #ff8b60 }
overflow: hidden;
}
/* CSS customisation page */
.stylesheet-customize-container { }
.stylesheet-customize-container textarea { margin: 0; padding: 0px; }
.sheets { margin-right: 315px; }
.sheets .col { float: left; }
.sheets .col > div { margin: 0 5px; }
.sheets .col textarea { width: 100% }
.sheets .buttons { margin-left: 5px }
.sheets .btn { margin-left: 0px; margin-right: 5px; }
.sheets .btn.right { float: right; margin-right: 3px;}
.stylesheet-customize-container h2 { margin-top: 15px; margin-bottom: 10px; }
#validation-errors {
margin-left: 40px;
margin-top: 10px;
list-style-type: disc;
}
#validation-errors a,
#validation-errors li,
.errors h2 { color: red }
#validation-errors a:hover { text-decoration: underline; }
#validation-errors pre { padding: 10px; color: black; }
#preview-table {
padding-right: 15px;
}
#preview-table > table {
border-width: .2em;
border-style: dashed;
border-color: lightgray;
padding: 5px;
margin: 5px;
width: 100%;
}
#preview-table > table > tbody > tr { padding-bottom: 10px; }
#preview-table > table > tbody > tr > td { padding: 5px; padding-right: 15px;}
#preview-table > table > tbody > tr > th { padding: 5px; padding-right: 15px;}
#preview-table > table > tbody > tr > th {
font-weight: bold;
vertical-align: top;
font-size: larger;
text-align: right;
}
#header-img-preview-container {
border-width: .2em;
border-style: dashed;
border-color: lightgray;
padding: 5px;
margin: 5px;
float: left;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -112,6 +112,9 @@ function redditRequest(op, parameters, worker_in, block) {
if (post_site) {
parameters.r = post_site;
}
if (cnameframe) {
parameters.cnameframe = 1;
}
op = api_loc + op;
if(!worker) {
worker = handleResponse(action);
@@ -161,6 +164,183 @@ function tup(x) {
return x;
}
function stylesheetSave(form, formID) {
form.op.value = "save";
return post_form(form, formID);
}
function stylesheetPreview(formID, textboxID) {
var form = document.getElementById(formID);
form.op.value = "preview";
post_form(form, formID);
applyStylesheetFromTextbox(textboxID);
}
function applyStylesheetFromTextbox(textboxID) {
var textbox = document.getElementById(textboxID);
var cssText = textbox.value;
return applyStylesheet(cssText);
}
function applyStylesheet(cssText) {
/* also referred to in the reddit.html template, for the name of the
stylesheet set for this reddit. These must be in sync, because
I'm over-writing it here */
var sheet_title = 'applied_subreddit_stylesheet';
if(document.styleSheets[0].cssText) {
/* of course IE has to do this differently from everyone else. */
for(var x=0; x < document.styleSheets.length; x++) {
if(document.styleSheets[x].title == sheet_title) {
document.styleSheets[x].cssText = cssText;
break;
}
}
} else {
/* for everyone else, we walk <head> for the <link> or <style>
that has the old stylesheet, and delete it. Then we add a
<style> with the new one */
var headNode = document.getElementsByTagName("head")[0];
var headNodes = headNode.childNodes;
for(var x=0; x < headNodes.length; x++) {
var node = headNodes[x];
if(node.title == sheet_title) {
headNode.removeChild(node);
break;
}
}
var appliedCSSNode = document.createElement('style');
appliedCSSNode.type = 'text/css';
appliedCSSNode.rel = 'stylesheet';
appliedCSSNode.media = 'screen';
appliedCSSNode.title = sheet_title;
appliedCSSNode.textContent = cssText;
headNode.appendChild(appliedCSSNode);
}
}
function showDefaultStylesheet() {
return toggleDefaultStylesheet(true);
}
function hideDefaultStylesheet() {
return toggleDefaultStylesheet(false);
}
function toggleDefaultStylesheet(p_show) {
var stylesheet_contents = $('stylesheet_contents').parentNode.parentNode;
var default_stylesheet = $('default_stylesheet').parentNode.parentNode;
var show_button = $('show_default_stylesheet');
var hide_button = $('hide_default_stylesheet');
if(p_show) {
default_stylesheet.style.width = "50%";
stylesheet_contents.style.width = "50%";
show(default_stylesheet, hide_button);
hide(show_button);
} else {
stylesheet_contents.style.width = "100%";
default_stylesheet.style.width = "";
show(show_button);
hide(default_stylesheet, hide_button);
}
return false; // don't post the form
}
function gotoTextboxLine(textboxID, lineNo) {
var textbox = $(textboxID);
var text = textbox.value;
var newline = '\n';
var newline_length = 1;
var caret_pos = 0;
if ( text.indexOf('\r') > 0) {
/* IE hack */
newline = '\r';
newline_length = 0;
caret_pos = 1;
}
var lines = textbox.value.split(newline);
for(var x=0; x<lineNo-1; x++) {
caret_pos += lines[x].length + newline_length;
}
var end_pos = caret_pos;
if (lineNo < lines.length) {
end_pos += lines[lineNo-1].length + newline_length;
}
textbox.focus();
if(textbox.createTextRange) { /* IE */
var start = textbox.createTextRange();
start.move('character', caret_pos);
var end = textbox.createTextRange();
end.move('character', end_pos);
start.setEndPoint("StartToEnd", end);
start.select();
} else if (textbox.selectionStart) {
textbox.setSelectionRange(caret_pos, end_pos);
}
if(textbox.scrollHeight) {
var avgLineHight = textbox.scrollHeight / lines.length;
textbox.scrollTop = (lineNo-2) * avgLineHight;
}
}
function uploadHeaderImage(status) {
var form = $('upload-header-image');
var iframe = $('upload-header-iframe');
form.op.value = 'upload';
$('img-status').innerHTML = status;
show('img-status');
form.submit();
return false;
}
function deleteHeaderImage(status) {
var form = $('upload-header-image');
var iframe = $('upload-header-iframe');
form.reset();
form.op.value = 'delete';
$('img-status').innerHTML = status;
show('img-status');
form.submit();
return false;
}
function completedUploadHeaderImage(status,img_src,op) {
show('img-status');
$('img-status').innerHTML = status;
$('upload-header-image').reset();
$('header-img').src = img_src;
if(op == 'delete') {
hide('delete-header-img');
hide('header-img-preview-container');
} else {
$('header-img-preview').src = img_src;
show('delete-header-img');
show('header-img-preview-container');
}
}
function handleResponse(action) {
var my_iter = function(x, func) {
if(x) {
@@ -209,6 +389,13 @@ function handleResponse(action) {
}
});
}
// handle applied CSS
if(r.call) {
var calls = r.call;
for(var i=0; i<calls.length; i++) {
eval(calls[i]);
}
}
// handle shifts of focus
if (r.focus) {
var f = $(r.focus);
@@ -387,4 +574,4 @@ function view_embeded_media(id, media_link) {
closespan.style.display = "none";
}
}
}

View File

@@ -46,7 +46,10 @@ ${self.robots()}
var logged = ${c.user_is_loggedin and ("'%s'" % c.user.name) or "false"};
var post_site = "${c.site.name}";
var cnameframe = ${'true' if c.cname else 'false'};
var modhash = ${"'%s'" % c.modhash or "false"};
</script>
${self.javascript()}

View File

@@ -1,4 +1,4 @@
## "The contents of this file are subject to the Common Public Attribution
## 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

View File

@@ -0,0 +1,26 @@
<%!
from r2.lib.template_helpers import join_urls
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="${c.lang}"
xml:lang="${c.lang}" ${c.lang_rtl and unsafe('dir="rtl"') or ''}>
<head>
<title>${thing.title}</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="alternate" type="application/rss+xml" title="RSS"
href="${join_urls(thing.original_path,'.rss')}" />
</head>
<frameset>
<frame src="${thing.frame_target}">
</frameset>
<body>
<p class="error">Your browser does not support frames. Go <a href="${thing.frame_target}">here</a>.
</body>
</html>

View File

@@ -1,4 +1,4 @@
## "The contents of this file are subject to the Common Public Attribution
## 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
@@ -17,13 +17,13 @@
## the Original Code is CondeNet, Inc.
##
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved."
## CondeNet, Inc. All Rights Reserved.
################################################################################
<%!
from r2.lib.filters import keep_space
%>
<%namespace file="utils.html" import="error_field, language_tool"/>
<%namespace file="utils.html" import="error_field, language_tool, plain_link"/>
<script type="text/javascript">
function update_title() {
@@ -113,6 +113,7 @@ function update_title() {
%endif
</td>
</tr>
<tr>
<th>
<label for="description">${_("description")}</label>
@@ -129,6 +130,7 @@ function update_title() {
${error_field("DESC_TOO_LONG", "span")}
</td>
</tr>
%if c.user_is_admin:
<tr>
<th>
@@ -248,15 +250,36 @@ function update_title() {
</label>
</td>
</tr>
<tr>
<th>
<label for="domain">${_("domain")}</label>
</th>
<td>
%if thing.site:
<input id="domain" type="text" name="domain" class="text"
value="${getattr(thing.site, 'domain', None) or ""}"/>
%else:
<input id="domain" type="text" name="domain" class="text" />
%endif
</td>
<td id="note_domain">
${error_field("BAD_CNAME", "span")}
${error_field("USED_CNAME", "span")}
%if not thing.site:
<span class="gray" id="example_domain">
${_("e.g. reddit.slashdot.org (optional)")}
</span>
%endif
</td>
</tr>
<tr>
<th>
</th>
<td colspan="2">
%if thing.site:
<button name="edit" type="submit">${_("update")}</button>
<button name="edit" class="btn" type="submit">${_("update")}</button>
%else:
<button name="create" type="submit">${_("create")}</button>
<button name="create" class="btn" type="submit">${_("create")}</button>
%endif
&#32;
<span id="status" class="error"></span>
@@ -266,3 +289,64 @@ function update_title() {
</tr>
</table>
</form>
%if thing.site and thing.site.can_change_stylesheet(c.user) and not g.css_killswitch:
<h1>${_('change the look')}</h1>
<table class="content preftable pretty-form sr-form">
<tr>
<th>
<label for="stylesheet_contents">${_("edit stylesheet")}</label>
</th>
<td class="nowrap nopadding">
<p>${plain_link(_('edit the stylesheet'),
'/about/stylesheet',
sr_path = True)}</p>
</td>
<td id="note_stylesheet_contents"></td>
</tr>
<tr>
<th><label for="file">${_('upload header image')}</label></th>
<td>
<form
onsubmit="return uploadHeaderImage('${_('submitting')}');"
name="upload-header-image" id="upload-header-image"
enctype="multipart/form-data"
class="pretty-form sr-form"
target="upload-header-iframe"
action="/api/upload_header_img" method="post">
<input type="file" name="file"
onchange="return uploadHeaderImage('${_('submitting')}')" />
<button id="delete-header-img"
class="btn"
onclick="return deleteHeaderImage('${_('submitting')}')"
%if not thing.site.header:
style="display: none;"
%endif
>
${_('restore default')}
</button>
<input type="hidden" name="uh" value="${c.modhash}" />
<input type="hidden" name="r" value="${c.site.name}" />
<input type="hidden" name="op" value="upload" />
<span style="display: none;" class="error" id="img-status"></span>
<iframe src="about:blank"
width="600" height="200" style="display: none;"
name="upload-header-iframe" id="upload-header-iframe"></iframe>
</form>
<div id="header-img-preview-container" style="display: none;">
<img id="header-img-preview"
%if thing.site.header:
src="${thing.site.header}"
%else:
src="/static/kill.png"
%endif
/ ><br />
</div>
</td>
</tr>
</table>
%endif

View File

@@ -0,0 +1,43 @@
## 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-2008
## CondeNet, Inc. All Rights Reserved.
################################################################################
<% error = thing.error %>
<li>
%if hasattr(error,'line'):
<a href="javascript:gotoTextboxLine('stylesheet_contents',${error.line})">
[line ${error.line}]
%endif
${error.message}
%if hasattr(error,'offending_line'):
<div>
<tt>
<pre>${error.offending_line}</pre>
</tt>
</div>
%endif
%if hasattr(error,'line'):
</a>
%endif
</li>

View File

@@ -56,6 +56,9 @@
%if c.user.pref_newwindow:
target="_blank"
%endif
%if c.cname:
target="_top"
%endif
>
${caller.body()}
</a>
@@ -142,7 +145,7 @@
com_label = _("comment {verb}")
else:
# generates "XX comments" as a noun
com_label = ungettext("comment", "comments", thing.num_comments)
com_label = ungettext("comment", "comments", thing.num_comments)
%>
<li class="first">
${parent.comment_button("comment", fullname, com_label,

View File

@@ -25,6 +25,7 @@
from pylons.i18n import _, ungettext
%>
<%namespace file="utils.html" import="plain_link" />
<%inherit file="printable.mobile" />
<%def name="entry()">
@@ -39,7 +40,8 @@
com_label = _("comment")
%>
<p class="link"><a href="${thing.url}" class="title">${thing.title}</a></p>
<p class="byline">${thing.score} ${ungettext("point", "points", thing.score)}&nbsp;|&nbsp;<span class="buttons"><a href="${thing.permalink}">${com_label}</a></span>&nbsp;|&nbsp;${tagline()}</p>
<p class="byline">${thing.score} ${ungettext("point", "points", thing.score)}&nbsp;|&nbsp;<span class="buttons">
${plain_link(com_label, thing.permalink)}</span>&nbsp;|&nbsp;${tagline()}</p>
</%def>
<%def name="domain()" buffered="True">

View File

@@ -22,7 +22,7 @@
<%!
from pylons.i18n import _, ungettext
from r2.lib.template_helpers import reddit_link
from r2.lib.template_helpers import add_sr
%>
<item>
@@ -32,7 +32,7 @@
<dc:date>${thing._date.isoformat()}</dc:date>
<description>
&lt;a href="${thing.url}"&gt;[link]&lt;/a&gt;
&lt;a href="http://${request.host}${reddit_link(thing.permalink, url=True)}"&gt;[comments]&lt;/a&gt;
&lt;a href="http://${request.host}${add_sr(thing.permalink, nocname=True)}"&gt;[comments]&lt;/a&gt;
</description>
</item>

View File

@@ -17,10 +17,11 @@
## the Original Code is CondeNet, Inc.
##
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved.
## CondeNet, Inc. All Rights Reserved."
################################################################################
<% from r2.lib.template_helpers import replace_render, reddit_link %>
<% from r2.lib.template_helpers import replace_render, add_sr %>
<%namespace file="utils.html" import="plain_link" />
<%
_id = ("_%s" % thing.parent_name) if hasattr(thing, 'parent_name') else ''
@@ -37,13 +38,13 @@
%if thing.nextprev and (thing.prev or thing.next):
<p class="nextprev"> ${_("view more:")}&#32;
%if thing.prev:
<a rel="nofollow,prev" href="${reddit_link(thing.prev, url = True)}">${_("prev")}</a>
${plain_link(_("prev"), thing.prev, rel="nofollow,prev")}
%endif
%if thing.prev and thing.next:
&#32;|&#32;
%endif
%if thing.next:
<a rel="nofollow,next" href="${reddit_link(thing.next, url = True)}">${_("next")}</a>
${plain_link(_("next"), thing.next, rel="nofollow,prev")}
%endif
</p>
%endif

View File

@@ -17,10 +17,11 @@
## the Original Code is CondeNet, Inc.
##
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved.
## CondeNet, Inc. All Rights Reserved."
################################################################################
<% from r2.lib.template_helpers import replace_render, reddit_link %>
<% from r2.lib.template_helpers import replace_render, add_sr %>
<%namespace file="utils.html" import="plain_link" />
<ul>
%for a in thing.things:
@@ -31,13 +32,13 @@
%if thing.nextprev and (thing.prev or thing.next):
<p class="nextprev"> ${_("view more:")}&#32;
%if thing.prev:
<a href="${reddit_link(thing.prev, url = True)}">${_("prev")}</a>
${plain_link(_("prev"), thing.prev)}
%endif
%if thing.prev and thing.next:
&#32;|&#32;
%endif
%if thing.next:
<a rel="nofollow" href="${reddit_link(thing.next, url = True)}">${_("next")}</a>
${plain_link(_("next"), thing.next)}
%endif
</p>
%endif

View File

@@ -17,20 +17,21 @@
## the Original Code is CondeNet, Inc.
##
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved.
## CondeNet, Inc. All Rights Reserved."
################################################################################
<%namespace file="utils.html" import="plain_link" />
<%def name="plain(selected = False)">
${plain_link(thing.selected_title() if selected else thing.title,
thing.path, _sr_path = thing.sr_path,
thing.path, _sr_path = thing.sr_path, nocname = thing.nocname,
target = thing.target,
_class = thing.css_class, _id = thing._id)}
</%def>
<%def name="js(selected = False)">
${plain_link(thing.selected_title() if selected else thing.title,
'/', _sr_path = False,
'/', _sr_path = False, nocname = thing.nocname,
_class = thing.css_class, _id = thing._id, onclick = thing.onclick)}
</%def>

View File

@@ -17,7 +17,7 @@
## the Original Code is CondeNet, Inc.
##
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved.
## CondeNet, Inc. All Rights Reserved."
################################################################################
<%namespace file="utils.html" import="plain_link, text_with_links, img_link, separator"/>

View File

@@ -17,18 +17,20 @@
## the Original Code is CondeNet, Inc.
##
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved.
## CondeNet, Inc. All Rights Reserved."
################################################################################
<%namespace file="utils.html" import="plain_link"/>
<%def name="subreddit()">
<span class="hover pagename redditname">
<a href="${c.site.path}">${c.site.name}</a>
${plain_link(c.site.name, c.site.path, _sr_path=False)}
</span>
</%def>
<%def name="reddits()">
<span class="hover pagename redditname">
<a href="/reddits/">${_("reddits")}</a>
${plain_link(_("reddits"), "/reddits/", _sr_path=False)}
</span>
</%def>
@@ -37,5 +39,5 @@
</%def>
<%def name="newpagelink()">
<span class="newpagelink">reddit all?&#32;<a href="/new/">click here to find new links.</a></span>
<span class="newpagelink">reddit all?&#32;${plain_link("click here to find new links.", "/new/", _sr_path=False)}</span>
</%def>

View File

@@ -19,8 +19,7 @@
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved."
################################################################################
<%namespace file="utils.html" import="language_tool, language_checkboxes"/>
<%namespace file="utils.html" import="language_tool, language_checkboxes, plain_link"/>
<%def name="checkbox(text, name)">
<input name="${name}" id="${name}" type="checkbox"\
@@ -60,10 +59,15 @@
%endif
<%
if c.user_is_loggedin:
action = "/post/options"
if c.cname:
param = "?cnameframe=1"
else:
action = "/post/unlogged_options"
param = ""
if c.user_is_loggedin:
action = "/post/options" + param
else:
action = "/post/unlogged_options" + param
%>
<form action="${action}" method="post" class="pretty-form short-text">
<input type="hidden" name="uh" value="${c.modhash}" />
@@ -75,7 +79,7 @@
${language_tool(allow_blank = False, show_regions = True,
default_lang = c.user.pref_lang)}
&#32;<span class="little gray hover">(*) ${_("incomplete")}
&#32;<a href="/feedback">${_("volunteer to translate")}</a></span>
&#32;${plain_link(_("volunteer to translate"), "/feedback")}</span>
</td>
</tr>
<tr>
@@ -156,7 +160,12 @@
&#32;<span class="little gray">(1 - ${g.max_comments})</span>
</td>
</tr>
<tr>
<th>${_("display options")}</th>
<td class="prefright">
${checkbox(_("allow reddits to show me custom styles"), "show_stylesheets")}
</td>
</tr>
<tr>
<th>${_("privacy options")}</th>
<td class="prefright">

View File

@@ -21,10 +21,12 @@
################################################################################
<%!
from r2.lib.template_helpers import reddit_link
from r2.lib.template_helpers import add_sr
from r2.lib.strings import strings
%>
<%namespace file="utils.html" import="plain_link" />
${self.RenderPrintable()}
<%def name="admintagline()">
@@ -148,7 +150,7 @@ ${self.RenderPrintable()}
elif gray:
author_cls += " gray"
name = websafe(author.name)
href = unsafe('href="/user/%s/"' % name)
href = unsafe('href="%s"' % add_sr("/user/%s/" % name, sr_path = False))
if c.user_is_admin: name += " (%d)" % (author.link_karma)
%>
<a id="author_${thing._fullname}" class="${author_cls}" ${href}>${name}</a>
@@ -270,16 +272,15 @@ ${k.strip('_')}="${v}" \
### originally in commentbutton
<%def name="comment_button(name, fullname, link_text, num, link,\
a_class='', title='', newwindow = False)">
<% cls = "" if num == 0 else "comments" %>
<a href="${reddit_link(link, url = True)}"
id="${name}_${fullname}" class="${a_class} ${cls}"
title="${title}"
target="${'_blank' if newwindow else '_parent'}" >
%if num > 0:
${strings.number_label % (num,link_text)}\
%else:
${link_text}\
%endif
</a>
<%
cls = "" if num == 0 else "comments"
if num > 0:
link_text = strings.number_label % (num, link_text)
target = '_blank' if newwindow else '_parent'
%>
${plain_link(link_text, link, id="%s_%s" % (name, fullname), _class="%s %s" % (a_class, cls), title=title, target=target)}
</%def>

View File

@@ -1,4 +1,4 @@
## "The contents of this file are subject to the Common Public Attribution
## 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
@@ -17,14 +17,14 @@
## the Original Code is CondeNet, Inc.
##
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved.
## CondeNet, Inc. All Rights Reserved."
################################################################################
<%!
from r2.lib.filters import edit_comment_filter, unsafe, safemarkdown
from r2.lib.utils import timesince
%>
<%namespace file="utils.html" import="submit_form"/>
<%namespace file="utils.html" import="submit_form, plain_link"/>
<% user = thing.user %>
%if thing.user:
<div class="raisedbox">
@@ -65,9 +65,7 @@
%if c.user_is_loggedin and not thing.isMe:
<li>
<a href="/message/compose/?to=${thing.user.name}">
${_("send message")}
</a>
${plain_link(_("send message"), "/message/compose/?to=%s" % thing.user.name)}
</li>
<li>
<form action="/post/friend" method="post">

View File

@@ -21,7 +21,7 @@
################################################################################
<%!
from r2.lib.template_helpers import reddit_link, static, join_urls, class_dict, path_info
from r2.lib.template_helpers import add_sr, static, join_urls, class_dict, path_info
from r2.lib.pages import SearchForm
from pylons import request
%>
@@ -52,9 +52,16 @@
%else:
<link rel="stylesheet" href="${static(g.stylesheet)}"
type="text/css" />
%if c.site.stylesheet:
<link rel="stylesheet" href="${static(c.site.stylesheet)}"
type="text/css" />
%if (not g.css_killswitch) and c.user.pref_show_stylesheets:
%if c.site.stylesheet:
<link rel="stylesheet" href="${static(c.site.stylesheet)}"
type="text/css" />
%endif
%if c.site.stylesheet_contents:
<link rel="stylesheet" href="${c.site.path}stylesheet?v=${c.site.stylesheet_hash}"
title="applied_subreddit_stylesheet"
type="text/css" />
%endif
%endif
%endif
@@ -62,7 +69,7 @@
type="image/x-icon" />
%if thing.extension_handling:
<link rel="alternate" type="application/rss+xml" title="RSS"
href="${reddit_link(join_urls(request.path,'.rss'), url=True)}" />
href="${add_sr(join_urls(request.path,'.rss'))}" />
%endif
</%def>

View File

@@ -17,7 +17,7 @@
## the Original Code is CondeNet, Inc.
##
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved.
## CondeNet, Inc. All Rights Reserved."
################################################################################
<%!
@@ -32,13 +32,13 @@
${thing.footer_nav().render()}
<p class="wired">
<a rel="nofollow" href="http://wired.com">
<a rel="nofollow" target="_top" href="http://wired.com">
<img src="${static('wired_w.png')}" alt="wired" /></a>
<a rel="nofollow" href="http://wired.com">WIRED.com</a>-
<a rel="nofollow" href="http://howto.wired.com">WIRED How-To</a>
<a rel="nofollow" target="_top" href="http://wired.com">WIRED.com</a>-
<a rel="nofollow" target="_top" href="http://howto.wired.com">WIRED How-To</a>
</p>
<p class="bottommenu">
${text_with_links(_("Use of this site constitutes acceptance of our %(user_agreement)s and %(privacy_policy)s"), user_agreement= (_("User Agreement {Genitive}"), "http://reddit.com/help/useragreement"), privacy_policy = (_("Privacy Policy {Genitive}"), "http://reddit.com/help/privacypolicy"))}.
${text_with_links(_("Use of this site constitutes acceptance of our %(user_agreement)s and %(privacy_policy)s"), nocname=True, user_agreement= (_("User Agreement {Genitive}"), "http://reddit.com/help/useragreement"), privacy_policy = (_("Privacy Policy {Genitive}"), "http://reddit.com/help/privacypolicy"))}.
<% old = _("(c) 2008 CondeNet, Inc. All rights reserved.") %>
${_("(c) %(year)d CondeNet, Inc. All rights reserved.") % \
dict(year=2008)}

View File

@@ -1,4 +1,4 @@
## "The contents of this file are subject to the Common Public Attribution
## 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
@@ -23,6 +23,7 @@
<%!
from r2.lib.template_helpers import static
from r2.models import Sub, FakeSubreddit
from r2.models.subreddit import DefaultSR
from r2.lib.pages import SearchForm
%>
<%namespace file="utils.html" import="plain_link, text_with_js, img_link, separator"/>
@@ -60,9 +61,13 @@
</div>
<div id="header-bottom-${'right' if c.lang_rtl else 'left'}">
<a href="/">
<img id="header-img" src="${c.site.header}" alt="${c.site.name}" />
</a>
<%
if g.css_killswitch or not c.site.header:
header_img = DefaultSR.header
else:
header_img = c.site.header
%>
${img_link(c.site.name, header_img, '/', _id = "header-img-a", img_id = 'header-img')}
##keeps the height of the header from varying when there isnt any content
&nbsp;

View File

@@ -17,14 +17,14 @@
## the Original Code is CondeNet, Inc.
##
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved.
## CondeNet, Inc. All Rights Reserved."
################################################################################
<%!
from r2.lib.template_helpers import reddit_link, static
from r2.lib.template_helpers import add_sr, static
%>
<form action="${reddit_link('/search', url=True)}" id="search">
<form action="${add_sr('/search')}" id="search">
<input type="text"
%if thing.prev_search:
value="${thing.prev_search}" style="color:black"

View File

@@ -30,7 +30,7 @@ http://${g.domain}/goto?share=true&id=${thing.link._fullname}
<% from r2.lib.strings import strings, plurals %>${_("There are currently %(num_comments)s on this link. You can view them here:") % dict(num_comments = strings.number_label % (thing.link.num_comments, plurals.N_comments(thing.link.num_comments)))}
<% from r2.lib.template_helpers import reddit_link %>http://${g.domain}${reddit_link(thing.link.permalink, url=True)}
<% from r2.lib.template_helpers import add_sr %>http://${g.domain}${add_sr(thing.link.permalink, url=True)}
___
If you would not like to receive emails from reddit.com in the future, visit http://${g.domain}/mail/optout?x=${thing.msg_hash}

View File

@@ -17,7 +17,8 @@
</label>
</th>
<td>
<textarea id="share_to_${thing.link_name}" name="share_to" rows="4">
<textarea id="share_to_${thing.link_name}"
name="share_to" rows="4" cols="40">
${unsafe('&#x0D;&#x0A;'.join(websafe(e) for e in thing.emails))}
</textarea>
</td>
@@ -55,7 +56,7 @@
</span>
</th>
<td>
<input name="replyto" type="text" size="30"
<input name="replyto_${thing.link_name}" type="text" size="30"
value="${c.user.email if hasattr(c.user, 'email') else ''}"/>
</td>
<td>
@@ -74,7 +75,7 @@
</span>
</th>
<td>
<textarea id="message_${thing.link_name}" name="message" rows="4">
<textarea id="message_${thing.link_name}" name="message" rows="4" cols="40">
${c.user.name} from http://${g.domain}/ has shared a link with you.
</textarea>
</td>

View File

@@ -17,15 +17,21 @@
## the Original Code is CondeNet, Inc.
##
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved.
## CondeNet, Inc. All Rights Reserved."
################################################################################
<%namespace file="utils.html" import="plain_link"/>
<div class="sidebox ${thing.css_class}">
<a class="morelink"
%if not c.user_is_loggedin and thing.show_cover:
onclick="return(showcover(true, 'redirect_${thing.link}'));"
%endif
href="${thing.link}">${thing.title} &raquo;</a>
<%
if not c.user_is_loggedin and thing.show_cover:
onclick="return(showcover(true, 'redirect_%s'));" % thing.link
else:
onclick = None
nocname = thing.nocname
%>
${plain_link(thing.title,thing.link, _sr_path = False, _class="morelink", onclick=onclick, nocname=nocname)}
%if thing.subtitles:
<div class="spacer">
%for subtitle in thing.subtitles:

View File

@@ -17,7 +17,7 @@
## the Original Code is CondeNet, Inc.
##
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved.
## CondeNet, Inc. All Rights Reserved."
################################################################################
<%!
@@ -25,6 +25,8 @@
%>
<%inherit file="printable.html"/>
<%namespace file="utils.html" import="plain_link"/>
<%def name="numcol()">
</%def>
@@ -32,9 +34,7 @@
<%def name="entry()">
<% fullname = thing._fullname %>
<p class="titlerow">
<a id="title_${fullname}" class="title" href="${thing.path}">
${thing.title}
</a>
${plain_link(thing.title, thing.path, _class="title", _id="title_%s" % fullname)}
<span class="domain">(${thing.path})</span>
%if c.user_is_admin:
${self.admintagline()}

View File

@@ -25,6 +25,8 @@
%>
<%inherit file="printable.mobile"/>
<%namespace file="utils.html" import="plain_link"/>
<%def name="numcol()">
</%def>
@@ -32,9 +34,7 @@
<%def name="entry()">
<% fullname = thing._fullname %>
<p>
<a class="title" href="${thing.path}">
${thing.title}
</a>
${plain_link(thing.title, thing.path, _class="title")}
<span class="domain">(${thing.path})</span>
</p>
%if thing.description:

View File

@@ -17,8 +17,9 @@
## the Original Code is CondeNet, Inc.
##
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved.
## CondeNet, Inc. All Rights Reserved."
################################################################################
<%namespace file="utils.html" import="plain_link"/>
<div class="subredditbox">
<form method="post" action="/post/subscriptions" id="sr_subscriptions"
@@ -50,7 +51,13 @@
onchange="return change_sr('${sr._fullname}');"
id="sr_sel_chx_${sr._fullname}"/>
%endif
<a href="${sr.path or '/'}">${sr.name}</a>
<%
if sr.path:
path = sr.path
else:
path = '/'
%>
${plain_link(sr.name, path, _sr_path=False, nocname=True)}
</li>
%endfor
</ul>
@@ -58,7 +65,6 @@
%endfor
<div class="clear"></div>
</form>
<a class="morelink" href="/reddits/">
${_("more")} &raquo;
</a>
${plain_link(unsafe('%s &raquo;' % _("more")), "/reddits/", _class="morelink",
fmt='%s', _sr_path=False, nocname=True)}
</div>

View File

@@ -0,0 +1,119 @@
## 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-2008
## CondeNet, Inc. All Rights Reserved.
################################################################################
<%!
from r2.lib.filters import keep_space
from r2.lib.template_helpers import add_sr
%>
<%namespace file="utils.html" import="error_field, language_tool"/>
<div class="stylesheet-customize-container">
<form
onsubmit="return stylesheetSave(this, 'subreddit_stylesheet')"
name="subreddit_stylesheet" id="subreddit_stylesheet"
class="pretty-form sr-form"
action="/post/subreddit_stylesheet" method="post" >
<input type="hidden" name="r" value="${thing.site.name}" />
<input type="hidden" name="op" value="save" />
<h2>${_("stylesheet")}</h2>
<div class="sheets">
<div style="width: 100%" class="col">
<div>
<textarea
rows="20"
cols="20"
id="stylesheet_contents"
name="stylesheet_contents"
>
${keep_space(thing.stylesheet_contents) or ''}
</textarea>
</div>
</div>
<div style="display:none" class="col">
<div>
<textarea
rows="20"
cols="20"
id="default_stylesheet"
name="default_stylesheet"
readonly="readonly"
>
${keep_space(g.default_stylesheet)}
</textarea>
</div>
</div>
<div class="clearleft"></div>
<div class="buttons">
<button class="btn right " id="show_default_stylesheet"
value="some_button"
onclick="return showDefaultStylesheet();">
${_('show the default stylesheet')}
</button>
<button class="btn right" id="hide_default_stylesheet"
value="some_button"
style="display: none;"
onclick="return hideDefaultStylesheet();">
${_('hide the default stylesheet')}
</button>
<button class="btn" name="save" type="submit">${_("save")}</button>
<button class="btn" name="preview" type="button"
onclick="stylesheetPreview('subreddit_stylesheet','stylesheet_contents');">${_("preview")}</button>
<span id="status" class="error"></span>
</div>
</div>
<div class="errors">
<h2 id="error-header" style="display:none">${_("errors")}</h2>
<ul id="validation-errors"><li style="display:none"></li>
<!-- populated from AJAX requests to /api/subreddit_stylesheet -->
</ul>
</div>
<div class="clear"></div>
</form>
<div id="preview-table" style="display:none">
<h2><a name="preview">${_("preview")}</a></h2>
<table>
<tr>
<th>${_("normal link")}</th>
<td id="preview_link_normal"></td>
</tr>
<tr>
<th>${_("compressed link")}</th>
<td id="preview_link_compressed"></td>
</tr>
<tr>
<th>${_("link with thumbnail")}</th>
<td id="preview_link_media"></td>
</tr>
<tr>
<th>${_("comment")}</th>
<td id="preview_comment"></td>
</tr>
</table>
</div>
</div>

View File

@@ -17,7 +17,7 @@
## the Original Code is CondeNet, Inc.
##
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved.
## CondeNet, Inc. All Rights Reserved."
################################################################################
<%

View File

@@ -17,7 +17,7 @@
## the Original Code is CondeNet, Inc.
##
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved.
## CondeNet, Inc. All Rights Reserved."
################################################################################
<%

View File

@@ -0,0 +1,31 @@
## 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-2008
## CondeNet, Inc. All Rights Reserved.
################################################################################
<html>
<head>
<script type="text/javascript">
parent.completedUploadHeaderImage('${thing.status}','${thing.img_src}', '${thing.op}');
</script>
</head>
<body>
you shouldn't be here
</body>
</html>

View File

@@ -17,15 +17,17 @@
## the Original Code is CondeNet, Inc.
##
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved.
## CondeNet, Inc. All Rights Reserved."
################################################################################
<%namespace file="utils.html" import="plain_link"/>
##<h1>awards</h1>
##<table class='stats'>
##<tr><td></td><th>today</th></tr>
###for a in $awards
## #set u = $user_objs[a["uid"]]
## <tr><td class='ri' style="padding-right: 10px">$a['award']</td><td class='k'><a href="/user/$u.name/">$u.name</a> ($sanekarma($u.pop))</td></tr>
## <tr><td class='ri' style="padding-right: 10px">$a['award']</td><td class='k'>${plain_link(u.name, "/user/%s" % u.name)} ($sanekarma($u.pop))</td></tr>
###end for
##</table>
###end if
@@ -36,7 +38,7 @@
<tr><th colspan="2">${_("today")}</th></tr>
%for user, change in thing.top_day:
<tr>
<td class="k"><a href="/user/${user.name}/">${user.name}</a> (${user.link_karma})</td>
<td class="k">${plain_link(user.name, "/user/%s" % user.name)} (${user.link_karma})</td>
<td class="ri">+${change}</td>
</tr>
%endfor
@@ -46,7 +48,7 @@
<tr><th colspan="2">${_("this week")}</th></tr>
%for user, change in thing.top_week:
<tr>
<td class="k"><a href="/user/${user.name}/">${user.name}</a> (${user.link_karma})</td>
<td class="k">${plain_link(user.name, "/user/%s" % user.name)} (${user.link_karma})</td>
<td class="ri">+${change}</td>
</tr>
%endfor
@@ -56,7 +58,7 @@
<tr><th colspan="2">${_("all-time")}</th></tr>
%for user in thing.top_users:
<tr>
<td class="k"><a href="/user/${user.name}/">${user.name}</a> (${user.link_karma})</td>
<td class="k">${plain_link(user.name, "/user/%s" % user.name)} (${user.link_karma})</td>
</tr>
%endfor
</table>

View File

@@ -17,7 +17,7 @@
## the Original Code is CondeNet, Inc.
##
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved.
## CondeNet, Inc. All Rights Reserved."
################################################################################
<%namespace file="printable.html" import="yes_no_button" />
@@ -39,7 +39,7 @@
&nbsp;(<b>${thing.user.safe_karma}</b>)
</span>&nbsp;
%elif c.user_is_loggedin and name == "sendmessage" and c.user != thing.user:
<a href="/message/compose?to=${thing.user.name}">${_("send message")}</a>&nbsp;
${plain_link(_("send message"), "/message/compose?to=%s" % (thing.user.name))}&nbsp;
%elif name == "remove" and thing.editable:
${yes_no_button("remove", thing.name, _("remove"),
"return deletetoggle(this, UserTable.del('%s', '%s'));"

View File

@@ -1,4 +1,4 @@
## "The contents of this file are subject to the Common Public Attribution.
## 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
@@ -17,13 +17,13 @@
## the Original Code is CondeNet, Inc.
##
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
## CondeNet, Inc. All Rights Reserved."
## CondeNet, Inc. All Rights Reserved.
################################################################################
<%!
from r2.controllers.errors import errors
from r2.lib.filters import spaceCompress
from r2.lib.template_helpers import reddit_link
from r2.lib.filters import spaceCompress, unsafe
from r2.lib.template_helpers import add_sr
from r2.lib.utils import cols
%>
<%def name="tags(**kw)">
@@ -94,23 +94,42 @@ ${errors.get(name).message}
</script>
</%def>
<%def name="img_link(title, img, path, _id='', **kw)">
<%def name="img_link(link_text, img, path, _id='', target='', img_id = None, **kw)">
<%
if (not target or target == '_parent') and c.cname:
target = '_top'
if target:
kw['target'] = target
path = add_sr(path, sr_path = False)
kw['target'] = target
%>
<%call expr="_a(href=path, _id=_id, **kw)">
<img src="${img}" alt="${title}"/>
<img ${("id='%s'" % img_id) if img_id else ''} src="${img}" alt="${link_text}"/>
</%call>
</%def>
<%def name="plain_link(title, path, _sr_path = True, fmt='', **kw)">
<% link = _a_buffered(title, href=reddit_link(path, url=_sr_path), **kw) %>
<%def name="plain_link(link_text, path, _sr_path = True, nocname=False, fmt='', target='', **kw)">
<%
if (not target or target == '_parent') and c.cname:
target = '_top'
if c.cname and path.startswith('http://'):
target = '_top'
if target:
kw['target'] = target
link = _a_buffered(link_text, href=add_sr(path, sr_path=_sr_path, nocname=nocname), **kw)
%>
${unsafe((fmt % link) if fmt else link)}
</%def>
<%def name="text_with_links(txt, _sr_path = False, **kw)">
<%def name="text_with_links(txt, _sr_path = False, nocname=False, **kw)">
<%
from r2.lib.filters import _force_unicode
for key, (text, href) in kw.iteritems():
kw[key]=spaceCompress(capture(plain_link, text, href, _sr_path=_sr_path))
kw[key]=spaceCompress(capture(plain_link, text, href, _sr_path=_sr_path, nocname=nocname))
txt = _force_unicode(txt) % kw
txt = txt.replace(" <", "&#32;<").replace("> ", ">&#32;")

View File

@@ -1,4 +1,4 @@
## "The contents of this file are subject to the Common Public Attribution
## 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
@@ -21,7 +21,9 @@
################################################################################
<script type="text/javascript">
<%
domain = c.site.domain if c.cname else c.domain
%>
function escapeHTML(text) {
var div = document.createElement('div');
var text = document.createTextNode(text);
@@ -39,11 +41,11 @@ function update() {
f = document.forms.widget;
which = getrval(f.which);
if (which == "all") {
url = "http://${c.domain}/" + f.what.value + "/.embed?limit=" +
url = "http://${domain}/" + f.what.value + "/.embed?limit=" +
f.num.value + "&t=" + f.when.value;
} else if (which == "one") {
if (!f.who2.value) return;
url = "http://${c.domain}/user/"+f.who2.value+"/"+
url = "http://${domain}/user/"+f.who2.value+"/"+
f.where2.value+".embed?limit=" + f.num.value +
"&sort="+f.what2.value;
} else {
@@ -67,7 +69,7 @@ function update() {
<div id="preview">
<span>preview</span>
<div id="previewbox">
<script src="http://${c.domain}/.embed?limit=5" type="text/javascript"></script>
<script src="http://${domain}/.embed?limit=5" type="text/javascript"></script>
</div>
</div>
@@ -139,7 +141,7 @@ function update() {
<p>
<textarea rows="5" cols="50" id="codebox">
&lt;script src="http://${c.domain}/.embed?limit=5" type="text/javascript">&lt;/script>
&lt;script src="http://${domain}/.embed?limit=5" type="text/javascript">&lt;/script>
</textarea>
</p>
</div>

View File

@@ -83,6 +83,7 @@ setup(
"simplejson",
"SQLAlchemy==0.3.10",
"BeautifulSoup >= 3",
"cssutils",
"chardet",
"psycopg2",
"py_interface"],