Implemented private pastes and improved some code. Added TODO file.
This commit is contained in:
parent
dffba5bfbf
commit
790402b5df
4
TODO
Normal file
4
TODO
Normal file
@ -0,0 +1,4 @@
|
||||
* Put all cookie stuff into *one* securecookie
|
||||
* Make it possible to tag and find pastes
|
||||
* Add a button to find all the personal (and private) pastes
|
||||
(cookie bound)
|
@ -17,7 +17,7 @@ from werkzeug import SharedDataMiddleware, ClosingIterator
|
||||
from werkzeug.exceptions import HTTPException, NotFound
|
||||
|
||||
from lodgeit.utils import _local_manager, ctx, jinja_environment, \
|
||||
Request, generate_user_hash
|
||||
Request
|
||||
from lodgeit.database import metadata, session, Paste
|
||||
from lodgeit.urls import urlmap
|
||||
from lodgeit.controllers import get_controller
|
||||
|
@ -27,18 +27,18 @@ class PasteController(BaseController):
|
||||
"""The 'create a new paste' view."""
|
||||
code = error = ''
|
||||
language = 'text'
|
||||
pastes = session.query(Paste)
|
||||
show_captcha = False
|
||||
show_captcha = private = False
|
||||
parent = None
|
||||
getform = ctx.request.form.get
|
||||
|
||||
if ctx.request.method == 'POST':
|
||||
code = getform('code')
|
||||
language = getform('language')
|
||||
try:
|
||||
parent = pastes.filter(Paste.paste_id ==
|
||||
int(getform('parent'))).first()
|
||||
except (ValueError, TypeError):
|
||||
parent = None
|
||||
|
||||
parent_id = getform('parent')
|
||||
if parent_id is not None:
|
||||
parent = Paste.get(parent_id)
|
||||
|
||||
spam = ctx.request.form.get('webpage') or antispam.is_spam(code)
|
||||
if spam:
|
||||
error = 'your paste contains spam'
|
||||
@ -51,16 +51,19 @@ class PasteController(BaseController):
|
||||
show_captcha = True
|
||||
if code and language and not error:
|
||||
paste = Paste(code, language, parent, ctx.request.user_hash)
|
||||
session.save(paste)
|
||||
if 'private' in ctx.request.form:
|
||||
paste.private = True
|
||||
session.flush()
|
||||
return redirect(paste.url)
|
||||
|
||||
else:
|
||||
parent = ctx.request.args.get('reply_to')
|
||||
if parent is not None and parent.isdigit():
|
||||
parent = pastes.filter(Paste.paste_id == parent).first()
|
||||
code = parent.code
|
||||
language = parent.language
|
||||
parent_id = ctx.request.args.get('reply_to')
|
||||
if parent_id is not None:
|
||||
parent = Paste.get(parent_id)
|
||||
if parent is not None:
|
||||
code = parent.code
|
||||
language = parent.language
|
||||
private = parent.private
|
||||
|
||||
return render_template('new_paste.html',
|
||||
languages=LANGUAGES,
|
||||
@ -68,14 +71,14 @@ class PasteController(BaseController):
|
||||
code=code,
|
||||
language=language,
|
||||
error=error,
|
||||
show_captcha=show_captcha
|
||||
show_captcha=show_captcha,
|
||||
private=private
|
||||
)
|
||||
|
||||
def show_paste(self, paste_id, raw=False):
|
||||
def show_paste(self, identifier, raw=False):
|
||||
"""Show an existing paste."""
|
||||
linenos = ctx.request.args.get('linenos') != 'no'
|
||||
pastes = session.query(Paste)
|
||||
paste = pastes.filter(Paste.c.paste_id == paste_id).first()
|
||||
paste = Paste.get(identifier)
|
||||
if paste is None:
|
||||
raise NotFound()
|
||||
if raw:
|
||||
@ -90,18 +93,18 @@ class PasteController(BaseController):
|
||||
linenos=linenos,
|
||||
)
|
||||
|
||||
def raw_paste(self, paste_id):
|
||||
def raw_paste(self, identifier):
|
||||
"""Show an existing paste in raw mode."""
|
||||
return self.show_paste(paste_id, raw=True)
|
||||
return self.show_paste(identifier, raw=True)
|
||||
|
||||
def show_tree(self, paste_id):
|
||||
def show_tree(self, identifier):
|
||||
"""Display the tree of some related pastes."""
|
||||
paste = Paste.resolve_root(paste_id)
|
||||
paste = Paste.resolve_root(identifier)
|
||||
if paste is None:
|
||||
raise NotFound()
|
||||
return render_template('paste_tree.html',
|
||||
paste=paste,
|
||||
current=paste_id
|
||||
current=identifier
|
||||
)
|
||||
|
||||
def show_all(self, page=1):
|
||||
@ -112,31 +115,30 @@ class PasteController(BaseController):
|
||||
return '/all/'
|
||||
return '/all/%d' % page
|
||||
|
||||
pastes = session.query(Paste).order_by(
|
||||
Paste.c.pub_date.desc()
|
||||
).limit(10).offset(10*(page-1)).all()
|
||||
all = Paste.find_all()
|
||||
pastes = all.limit(10).offset(10 * (page -1)).all()
|
||||
if not pastes and page != 1:
|
||||
raise NotFound()
|
||||
|
||||
return render_template('show_all.html',
|
||||
pastes=pastes,
|
||||
pagination=generate_pagination(page, 10,
|
||||
Paste.count(), link),
|
||||
pagination=generate_pagination(page, 10, all.count(), link),
|
||||
css=get_style(ctx.request)[1]
|
||||
)
|
||||
|
||||
def compare_paste(self, new_id=None, old_id=None):
|
||||
"""Render a diff view for two pastes."""
|
||||
# redirect for the compare form box
|
||||
if old_id is new_id is None:
|
||||
if old_id is None:
|
||||
old_id = ctx.request.form.get('old', '-1').lstrip('#')
|
||||
new_id = ctx.request.form.get('new', '-1').lstrip('#')
|
||||
return redirect('/compare/%s/%s' % (old_id, new_id))
|
||||
pastes = session.query(Paste)
|
||||
old = pastes.filter(Paste.c.paste_id == old_id).first()
|
||||
new = pastes.filter(Paste.c.paste_id == new_id).first()
|
||||
|
||||
old = Paste.get(old_id)
|
||||
new = Paste.get(new_id)
|
||||
if old is None or new is None:
|
||||
raise NotFound()
|
||||
|
||||
return render_template('compare_paste.html',
|
||||
old=old,
|
||||
new=new,
|
||||
@ -145,11 +147,12 @@ class PasteController(BaseController):
|
||||
|
||||
def unidiff_paste(self, new_id=None, old_id=None):
|
||||
"""Render an udiff for the two pastes."""
|
||||
pastes = session.query(Paste)
|
||||
old = pastes.filter(Paste.c.paste_id == old_id).first()
|
||||
new = pastes.filter(Paste.c.paste_id == new_id).first()
|
||||
old = Paste.get(old_id)
|
||||
new = Paste.get(new_id)
|
||||
|
||||
if old is None or new is None:
|
||||
raise NotFound()
|
||||
|
||||
return Response(old.compare_to(new), mimetype='text/plain')
|
||||
|
||||
def set_colorscheme(self):
|
||||
|
@ -34,10 +34,15 @@ def pastes_new_paste(language, code, parent_id=None,
|
||||
"""
|
||||
if not language:
|
||||
language = get_language_for(filename or '', mimetype or '')
|
||||
paste = Paste(code, language, parent_id)
|
||||
session.save(paste)
|
||||
parent = None
|
||||
if parent_id:
|
||||
parent = Paste.get(parent_id)
|
||||
if parent is None:
|
||||
raise ValueError('parent paste not found')
|
||||
|
||||
paste = Paste(code, language, parent)
|
||||
session.flush()
|
||||
return paste.paste_id
|
||||
return paste.identifier
|
||||
|
||||
|
||||
@exported('pastes.getPaste')
|
||||
@ -48,8 +53,7 @@ def pastes_get_paste(paste_id):
|
||||
`paste_id`, `code`, `parsed_code`, `pub_date`, `language`,
|
||||
`parent_id`, `url`.
|
||||
"""
|
||||
paste = session.query(Paste).filter(Paste.c.paste_id ==
|
||||
paste_id).first()
|
||||
paste = Paste.get(paste_id)
|
||||
if paste is None:
|
||||
return False
|
||||
return paste.to_xmlrpc_dict()
|
||||
@ -58,11 +62,10 @@ def pastes_get_paste(paste_id):
|
||||
@exported('pastes.getDiff')
|
||||
def pastes_get_diff(old_id, new_id):
|
||||
"""Compare the two pastes and return an unified diff."""
|
||||
pastes = session.query(Paste)
|
||||
old = pastes.filter(Paste.c.paste_id == old_id).first()
|
||||
new = pastes.filter(Paste.c.paste_id == new_id).first()
|
||||
old = Paste.get(old_id)
|
||||
new = Paste.get(new_id)
|
||||
if old is None or new is None:
|
||||
return False
|
||||
raise ValueError('argument error, paste not found')
|
||||
return old.compare_to(new)
|
||||
|
||||
|
||||
@ -72,10 +75,7 @@ def pastes_get_recent(amount=5):
|
||||
`amount` pastes.
|
||||
"""
|
||||
amount = min(amount, 20)
|
||||
return [x.to_xmlrpc_dict() for x in
|
||||
session.query(Paste).order_by(
|
||||
Paste.pub_date.desc()
|
||||
).limit(amount)]
|
||||
return [x.to_xmlrpc_dict() for x in Paste.find_all().limit(amount)]
|
||||
|
||||
|
||||
@exported('pastes.getLast')
|
||||
|
@ -19,7 +19,7 @@ from sqlalchemy.orm import create_session, mapper, backref, relation
|
||||
from sqlalchemy.orm.scoping import ScopedSession
|
||||
from werkzeug import cached_property
|
||||
|
||||
from lodgeit.utils import _local_manager, ctx
|
||||
from lodgeit.utils import _local_manager, ctx, generate_paste_hash
|
||||
from lodgeit.lib.highlighting import highlight, LANGUAGES
|
||||
|
||||
|
||||
@ -29,9 +29,10 @@ metadata = MetaData()
|
||||
|
||||
pastes = Table('pastes', metadata,
|
||||
Column('paste_id', Integer, primary_key=True),
|
||||
Column('private_id', String(40), unique=True, nullable=True),
|
||||
Column('code', Text),
|
||||
Column('parent_id', Integer, ForeignKey('pastes.paste_id'),
|
||||
nullable=True),
|
||||
nullable=True),
|
||||
Column('pub_date', DateTime),
|
||||
Column('language', String(30)),
|
||||
Column('user_hash', String(40), nullable=True),
|
||||
@ -40,6 +41,7 @@ pastes = Table('pastes', metadata,
|
||||
|
||||
|
||||
class Paste(object):
|
||||
"""Represents a paste."""
|
||||
|
||||
def __init__(self, code, language, parent=None, user_hash=None):
|
||||
if language not in LANGUAGES:
|
||||
@ -54,16 +56,83 @@ class Paste(object):
|
||||
self.handled = False
|
||||
self.user_hash = user_hash
|
||||
|
||||
@staticmethod
|
||||
def get(identifier):
|
||||
"""Return the paste for an identifier. Private pastes must be loaded
|
||||
with their unique hash and public with the paste id.
|
||||
"""
|
||||
if isinstance(identifier, basestring) and not identifier.isdigit():
|
||||
return Paste.query.filter(Paste.private_id == identifier).first()
|
||||
return Paste.query.filter(
|
||||
(Paste.paste_id == int(identifier)) &
|
||||
(Paste.private_id == None)
|
||||
).first()
|
||||
|
||||
@staticmethod
|
||||
def find_all():
|
||||
"""Return a query for all public pastes ordered by the publication
|
||||
date in reverse order.
|
||||
"""
|
||||
return Paste.query.filter(Paste.private_id == None) \
|
||||
.order_by(Paste.pub_date.desc())
|
||||
|
||||
@staticmethod
|
||||
def count():
|
||||
"""COunt all pastes."""
|
||||
s = select([func.count(pastes.c.paste_id)])
|
||||
return session.execute(s).fetchone()[0]
|
||||
|
||||
@staticmethod
|
||||
def resolve_root(identifier):
|
||||
"""Find the root paste for a paste tree."""
|
||||
paste = Paste.get(identifier)
|
||||
if paste is None:
|
||||
return
|
||||
while paste.parent_id is not None:
|
||||
paste = paste.parent
|
||||
return paste
|
||||
|
||||
def _get_private(self):
|
||||
return self.private_id is not None
|
||||
|
||||
def _set_private(self, value):
|
||||
if not value:
|
||||
self.private_id = None
|
||||
return
|
||||
if self.private_id is None:
|
||||
while 1:
|
||||
self.private_id = generate_paste_hash()
|
||||
paste = Paste.query.filter(Paste.private_id ==
|
||||
self.private_id).first()
|
||||
if paste is None:
|
||||
break
|
||||
private = property(_get_private, _set_private, doc='''
|
||||
The private status of the paste. If the paste is private it gets
|
||||
a unique hash as identifier, otherwise an integer.
|
||||
''')
|
||||
del _get_private, _set_private
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
"""The paste identifier. This is a string, the same the `get`
|
||||
method accepts.
|
||||
"""
|
||||
if self.private:
|
||||
return self.private_id
|
||||
return str(self.paste_id)
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return '/show/%d/' % self.paste_id
|
||||
"""The URL to the paste."""
|
||||
return '/show/%s/' % self.identifier
|
||||
|
||||
def compare_to(self, other, context_lines=4, template=False):
|
||||
"""Compare the paste with another paste."""
|
||||
udiff = u'\n'.join(difflib.unified_diff(
|
||||
self.code.splitlines(),
|
||||
other.code.splitlines(),
|
||||
fromfile='Paste #%d' % self.paste_id,
|
||||
tofile='Paste #%d' % other.paste_id,
|
||||
fromfile='Paste #%s' % self.identifier,
|
||||
tofile='Paste #%s' % other.identifier,
|
||||
lineterm='',
|
||||
n=context_lines
|
||||
))
|
||||
@ -75,9 +144,11 @@ class Paste(object):
|
||||
|
||||
@cached_property
|
||||
def parsed_code(self):
|
||||
"""The paste as rendered code."""
|
||||
return highlight(self.code, self.language)
|
||||
|
||||
def to_xmlrpc_dict(self):
|
||||
"""Convert the paste into a dict for XMLRCP."""
|
||||
from lodgeit.lib.xmlrpc import strip_control_chars
|
||||
return {
|
||||
'paste_id': self.paste_id,
|
||||
@ -90,6 +161,7 @@ class Paste(object):
|
||||
}
|
||||
|
||||
def render_preview(self):
|
||||
"""Render a preview for this paste."""
|
||||
try:
|
||||
start = self.parsed_code.index('</pre>')
|
||||
code = self.parsed_code[
|
||||
@ -110,7 +182,7 @@ class Paste(object):
|
||||
Paste.user_hash == ctx.request.user_hash
|
||||
)
|
||||
|
||||
paste_list = session.query(Paste).filter(and_(
|
||||
paste_list = Paste.query.filter(and_(
|
||||
Paste.parent_id.in_(s),
|
||||
Paste.handled == False,
|
||||
Paste.user_hash != ctx.request.user_hash,
|
||||
@ -121,24 +193,8 @@ class Paste(object):
|
||||
values={'handled': True}))
|
||||
return paste_list
|
||||
|
||||
@staticmethod
|
||||
def count():
|
||||
s = select([func.count(pastes.c.paste_id)])
|
||||
return session.execute(s).fetchone()[0]
|
||||
|
||||
@staticmethod
|
||||
def resolve_root(paste_id):
|
||||
pastes = session.query(Paste)
|
||||
while True:
|
||||
paste = pastes.filter(Paste.c.paste_id == paste_id).first()
|
||||
if paste is None:
|
||||
return
|
||||
if paste.parent_id is None:
|
||||
return paste
|
||||
paste_id = paste.parent_id
|
||||
|
||||
|
||||
mapper(Paste, pastes, properties={
|
||||
session.mapper(Paste, pastes, properties={
|
||||
'children': relation(Paste,
|
||||
primaryjoin=pastes.c.parent_id==pastes.c.paste_id,
|
||||
cascade='all',
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
The URL mapping.
|
||||
|
||||
:copyright: 2007 by Armin Ronacher.
|
||||
:copyright: 2007-2008 by Armin Ronacher.
|
||||
:license: BSD
|
||||
"""
|
||||
from werkzeug.routing import Map, Rule
|
||||
@ -13,12 +13,12 @@ from werkzeug.routing import Map, Rule
|
||||
urlmap = Map([
|
||||
# paste interface
|
||||
Rule('/', endpoint='pastes/new_paste'),
|
||||
Rule('/show/<int:paste_id>/', endpoint='pastes/show_paste'),
|
||||
Rule('/raw/<int:paste_id>/', endpoint='pastes/raw_paste'),
|
||||
Rule('/show/<identifier>/', endpoint='pastes/show_paste'),
|
||||
Rule('/raw/<identifier>/', endpoint='pastes/raw_paste'),
|
||||
Rule('/compare/', endpoint='pastes/compare_paste'),
|
||||
Rule('/compare/<int:new_id>/<int:old_id>/', endpoint='pastes/compare_paste'),
|
||||
Rule('/unidiff/<int:new_id>/<int:old_id>/', endpoint='pastes/unidiff_paste'),
|
||||
Rule('/tree/<int:paste_id>/', endpoint='pastes/show_tree'),
|
||||
Rule('/compare/<new_id>/<old_id>/', endpoint='pastes/compare_paste'),
|
||||
Rule('/unidiff/<new_id>/<old_id>/', endpoint='pastes/unidiff_paste'),
|
||||
Rule('/tree/<identifier>/', endpoint='pastes/show_tree'),
|
||||
|
||||
# captcha for new paste
|
||||
Rule('/_captcha.png', endpoint='pastes/show_captcha'),
|
||||
|
@ -8,6 +8,7 @@
|
||||
:copyright: 2007 by Christopher Grebs.
|
||||
:license: BSD
|
||||
"""
|
||||
import re
|
||||
import time
|
||||
from os import path
|
||||
try:
|
||||
@ -15,6 +16,7 @@ try:
|
||||
except:
|
||||
from sha import new as sha1
|
||||
from random import random
|
||||
from functools import partial
|
||||
|
||||
from werkzeug import Local, LocalManager, LocalProxy, \
|
||||
Request as RequestBase, Response
|
||||
@ -30,9 +32,12 @@ jinja_environment = Environment(loader=FileSystemLoader(
|
||||
path.join(path.dirname(__file__), 'views')))
|
||||
|
||||
|
||||
_word_only = partial(re.compile(r'[^a-zA-Z0-9]').sub, '')
|
||||
|
||||
|
||||
def datetimeformat(obj):
|
||||
"""Helper filter for the template"""
|
||||
return obj.strftime('%Y-%m-%d %H:%M')
|
||||
return obj.strftime('%Y-%m-%d @ %H:%M')
|
||||
|
||||
jinja_environment.filters['datetimeformat'] = datetimeformat
|
||||
|
||||
@ -42,6 +47,16 @@ def generate_user_hash():
|
||||
return sha1('%s|%s' % (random(), time.time())).hexdigest()
|
||||
|
||||
|
||||
def generate_paste_hash():
|
||||
"""Generates a more or less unique-truncated SHA1 hash."""
|
||||
while 1:
|
||||
digest = sha1('%s|%s' % (random(), time.time())).digest()
|
||||
val = _word_only(digest.encode('base64').strip().splitlines()[0])[:20]
|
||||
# sanity check. number only not allowed (though unlikely)
|
||||
if not val.isdigit():
|
||||
return val
|
||||
|
||||
|
||||
class Request(RequestBase):
|
||||
"""Subclass of the `Request` object. automatically creates a new
|
||||
`user_hash` and sets `first_visit` to `True` if it's a new user.
|
||||
|
@ -4,8 +4,8 @@
|
||||
{% block body %}
|
||||
<p>
|
||||
Differences between the pastes
|
||||
<a href="{{ old.url|e }}">#{{ old.paste_id }}</a> ({{ old.pub_date|datetimeformat }})
|
||||
and <a href="{{ new.url|e }}">#{{ new.paste_id }}</a> ({{ new.pub_date|datetimeformat }}). Download as <a href="/unidiff/{{ old.paste_id }}/{{ new.paste_id }}/">unified diff</a>.
|
||||
<a href="{{ old.url|e }}">#{{ old.identifier }}</a> ({{ old.pub_date|datetimeformat }})
|
||||
and <a href="{{ new.url|e }}">#{{ new.identifier }}</a> ({{ new.pub_date|datetimeformat }}). Download as <a href="/unidiff/{{ old.identifier }}/{{ new.identifier }}/">unified diff</a>.
|
||||
</p>
|
||||
{% if diff.chunks %}
|
||||
<table class="diff">
|
||||
|
@ -10,7 +10,8 @@
|
||||
{%- if show_captcha %}
|
||||
<div class="captcha">
|
||||
<p>Please fill out the CAPTCHA to proceed:</p>
|
||||
<img src="/_captcha.png" alt="a captcha you can't see. Sorry :(">
|
||||
<img src="{{ url_for('pastes/captcha')
|
||||
}}" alt="a captcha you can't see. Sorry :(">
|
||||
<input type="text" name="captcha" size="20">
|
||||
</div>
|
||||
{%- else %}
|
||||
@ -19,7 +20,7 @@
|
||||
</div>
|
||||
{%- endif %}
|
||||
{%- if parent %}
|
||||
<input type="hidden" name="parent" value="{{ parent.paste_id }}">
|
||||
<input type="hidden" name="parent" value="{{ parent.identifier }}">
|
||||
{%- endif %}
|
||||
<textarea name="code" rows="10" cols="80">{{ code|e }}</textarea>
|
||||
<select name="language">
|
||||
@ -32,5 +33,7 @@
|
||||
<input type="submit" value="Paste!">
|
||||
<input type="button" value="▲" onclick="LodgeIt.resizeTextarea(-100)">
|
||||
<input type="button" value="▼" onclick="LodgeIt.resizeTextarea(100)">
|
||||
<input type="checkbox" name="private" id="private"{% if private
|
||||
%} checked{% endif %}><label for="private">make this paste private</label>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
@ -5,7 +5,7 @@
|
||||
<ul class="paste_list">
|
||||
{% for paste in pastes %}
|
||||
<li class="{{ loop.cycle('even', 'odd') }}"><p><a href="{{ paste.url|e
|
||||
}}">Paste #{{ paste.paste_id }}</a>,
|
||||
}}">Paste #{{ paste.identifier }}</a>,
|
||||
pasted on {{ paste.pub_date|datetimeformat }}</p>
|
||||
{{ paste.render_preview() }}</li>
|
||||
{%- endfor %}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% extends "layout.html" %}
|
||||
{% set page_title = 'Paste #%d'|format(paste.paste_id) %}
|
||||
{% set page_title = 'Paste #' ~ paste.identifier %}
|
||||
{% set active_page = 'all' %}
|
||||
{% block body %}
|
||||
<div class="related">
|
||||
@ -7,26 +7,26 @@
|
||||
<div class="content">
|
||||
<p>posted on {{ paste.pub_date|datetimeformat }}</p>
|
||||
<ul>
|
||||
<li><a class="autoclose" href="/?reply_to={{ paste.paste_id }}">reply to this paste</a></li>
|
||||
<li><a class="autoclose" href="/?reply_to={{ paste.identifier }}">reply to this paste</a></li>
|
||||
{% if paste.parent %}
|
||||
<li><a class="autoclose" href="/compare/{{ paste.paste_id }}/{{
|
||||
paste.parent.paste_id }}/">compare it with the parent paste</a></li>
|
||||
<li><a class="autoclose" href="/compare/{{ paste.identifier }}/{{
|
||||
paste.parent.identifier }}/">compare it with the parent paste</a></li>
|
||||
<li><a class="autoclose" href="{{ paste.parent.url|e }}">look at the parent paste</a></li>
|
||||
{% endif %}
|
||||
{% if paste.children %}
|
||||
<li>the following pastes replied to this paste:
|
||||
{% for child in paste.children %}
|
||||
<a class="autoclose" href="{{ child.url|e }}">#{{ child.paste_id }}</a>
|
||||
<a class="autoclose" href="{{ child.url|e }}">#{{ child.identifier }}</a>
|
||||
{%- if not loop.last %},{% endif -%}
|
||||
{% endfor %}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if paste.parent or paste.children %}
|
||||
<li><a href="/tree/{{ paste.paste_id }}/">show paste tree</a></li>
|
||||
<li><a href="/tree/{{ paste.identifier }}/">show paste tree</a></li>
|
||||
{% endif %}
|
||||
<li><a href="/raw/{{ paste.paste_id }}/">download paste</a></li>
|
||||
<li><a href="/raw/{{ paste.identifier }}/">download paste</a></li>
|
||||
<li>compare with paste <form action="/compare/" method="post">
|
||||
<input type="hidden" name="old" value="{{ paste.paste_id }}">
|
||||
<input type="hidden" name="old" value="{{ paste.identifier }}">
|
||||
<input type="text" name="new" value="#">
|
||||
<input type="submit" value="compare">
|
||||
</form></li>
|
||||
|
Loading…
x
Reference in New Issue
Block a user