simplewiki
@@ -92,13 +92,14 @@ INSTALLED_APPS = (
|
||||
'django.contrib.sites',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.admin',
|
||||
# 'django.contrib.admin',
|
||||
'courseware',
|
||||
'auth',
|
||||
'django.contrib.humanize',
|
||||
'static_template_view',
|
||||
'textbook',
|
||||
'staticbook',
|
||||
'simplewiki',
|
||||
# Uncomment the next line to enable the admin:
|
||||
# 'django.contrib.admin',
|
||||
# Uncomment the next line to enable admin documentation:
|
||||
|
||||
6
simplewiki/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import sys, os
|
||||
|
||||
# allow mdx_* parsers to be just dropped in the simplewiki folder
|
||||
module_path = os.path.abspath(os.path.dirname(__file__))
|
||||
if module_path not in sys.path:
|
||||
sys.path.append(module_path)
|
||||
59
simplewiki/admin.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from django.contrib import admin
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext as _
|
||||
from models import Article, Revision, Permission, ArticleAttachment
|
||||
|
||||
class RevisionInline(admin.TabularInline):
|
||||
model = Revision
|
||||
extra = 1
|
||||
|
||||
class RevisionAdmin(admin.ModelAdmin):
|
||||
list_display = ('article', '__unicode__', 'revision_date', 'revision_user', 'revision_text')
|
||||
search_fields = ('article', 'counter')
|
||||
|
||||
class AttachmentAdmin(admin.ModelAdmin):
|
||||
list_display = ('article', '__unicode__', 'uploaded_on', 'uploaded_by')
|
||||
|
||||
class ArticleAdminForm(forms.ModelForm):
|
||||
def clean(self):
|
||||
cleaned_data = self.cleaned_data
|
||||
if cleaned_data.get("slug").startswith('_'):
|
||||
raise forms.ValidationError(_('Slug cannot start with _ character.'
|
||||
'Reserved for internal use.'))
|
||||
if not self.instance.pk:
|
||||
parent = cleaned_data.get("parent")
|
||||
slug = cleaned_data.get("slug")
|
||||
if Article.objects.filter(slug__exact=slug, parent__exact=parent):
|
||||
raise forms.ValidationError(_('Article slug and parent must be '
|
||||
'unique together.'))
|
||||
return cleaned_data
|
||||
class Meta:
|
||||
model = Article
|
||||
|
||||
class ArticleAdmin(admin.ModelAdmin):
|
||||
list_display = ('created_by', 'slug', 'modified_on', 'parent')
|
||||
search_fields = ('slug',)
|
||||
prepopulated_fields = {'slug': ('title',) }
|
||||
inlines = [RevisionInline]
|
||||
form = ArticleAdminForm
|
||||
save_on_top = True
|
||||
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
||||
if db_field.name == 'current_revision':
|
||||
# Try to determine the id of the article being edited
|
||||
id = request.path.split('/')
|
||||
import re
|
||||
if len(id) > 0 and re.match(r"\d+", id[-2]):
|
||||
kwargs["queryset"] = Revision.objects.filter(article=id[-2])
|
||||
return db_field.formfield(**kwargs)
|
||||
else:
|
||||
db_field.editable = False
|
||||
return db_field.formfield(**kwargs)
|
||||
return super(ArticleAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
|
||||
|
||||
class PermissionAdmin(admin.ModelAdmin):
|
||||
search_fields = ('article', 'counter')
|
||||
|
||||
admin.site.register(Article, ArticleAdmin)
|
||||
admin.site.register(Revision, RevisionAdmin)
|
||||
admin.site.register(Permission, PermissionAdmin)
|
||||
admin.site.register(ArticleAttachment, AttachmentAdmin)
|
||||
136
simplewiki/mdx_camelcase.py
Normal file
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
WikiLink Extention for Python-Markdown
|
||||
======================================
|
||||
|
||||
Converts CamelCase words to relative links. Requires Python-Markdown 1.6+
|
||||
|
||||
Basic usage:
|
||||
|
||||
>>> import markdown
|
||||
>>> text = "Some text with a WikiLink."
|
||||
>>> md = markdown.markdown(text, ['wikilink'])
|
||||
>>> md
|
||||
'\\n<p>Some text with a <a href="/WikiLink/" class="wikilink">WikiLink</a>.\\n</p>\\n\\n\\n'
|
||||
|
||||
To define custom settings the simple way:
|
||||
|
||||
>>> md = markdown.markdown(text,
|
||||
... ['wikilink(base_url=/wiki/,end_url=.html,html_class=foo)']
|
||||
... )
|
||||
>>> md
|
||||
'\\n<p>Some text with a <a href="/wiki/WikiLink.html" class="foo">WikiLink</a>.\\n</p>\\n\\n\\n'
|
||||
|
||||
Custom settings the complex way:
|
||||
|
||||
>>> md = markdown.Markdown(text,
|
||||
... extensions = ['wikilink'],
|
||||
... extension_configs = {'wikilink': [
|
||||
... ('base_url', 'http://example.com/'),
|
||||
... ('end_url', '.html'),
|
||||
... ('html_class', '') ]},
|
||||
... encoding='utf8',
|
||||
... safe_mode = True)
|
||||
>>> str(md)
|
||||
'\\n<p>Some text with a <a href="http://example.com/WikiLink.html">WikiLink</a>.\\n</p>\\n\\n\\n'
|
||||
|
||||
Use MetaData with mdx_meta.py (Note the blank html_class in MetaData):
|
||||
|
||||
>>> text = """wiki_base_url: http://example.com/
|
||||
... wiki_end_url: .html
|
||||
... wiki_html_class:
|
||||
...
|
||||
... Some text with a WikiLink."""
|
||||
>>> md = markdown.Markdown(text, ['meta', 'wikilink'])
|
||||
>>> str(md)
|
||||
'\\n<p>Some text with a <a href="http://example.com/WikiLink.html">WikiLink</a>.\\n</p>\\n\\n\\n'
|
||||
|
||||
From the command line:
|
||||
|
||||
python markdown.py -x wikilink(base_url=http://example.com/,end_url=.html,html_class=foo) src.txt
|
||||
|
||||
By [Waylan Limberg](http://achinghead.com/).
|
||||
|
||||
Project website: http://achinghead.com/markdown-wikilinks/
|
||||
Contact: waylan [at] gmail [dot] com
|
||||
|
||||
License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
|
||||
|
||||
Version: 0.4 (Oct 14, 2006)
|
||||
|
||||
Dependencies:
|
||||
* [Python 2.3+](http://python.org)
|
||||
* [Markdown 1.6+](http://www.freewisdom.org/projects/python-markdown/)
|
||||
* For older dependencies use [WikiLink Version 0.3]
|
||||
(http://code.limberg.name/svn/projects/py-markdown-ext/wikilinks/tags/release-0.3/)
|
||||
'''
|
||||
|
||||
import markdown
|
||||
|
||||
class CamelCaseExtension(markdown.Extension):
|
||||
def __init__(self, configs):
|
||||
# set extension defaults
|
||||
self.config = {
|
||||
'base_url' : ['/', 'String to append to beginning or URL.'],
|
||||
'end_url' : ['/', 'String to append to end of URL.'],
|
||||
'html_class' : ['wikilink', 'CSS hook. Leave blank for none.']
|
||||
}
|
||||
|
||||
# Override defaults with user settings
|
||||
for key, value in configs :
|
||||
# self.config[key][0] = value
|
||||
self.setConfig(key, value)
|
||||
|
||||
def add_inline(self, md, name, klass, re):
|
||||
pattern = klass(re)
|
||||
pattern.md = md
|
||||
pattern.ext = self
|
||||
md.inlinePatterns.add(name, pattern, "<reference")
|
||||
|
||||
def extendMarkdown(self, md, md_globals):
|
||||
self.add_inline(md, 'camel', CamelCaseLinks,
|
||||
r'''(?P<escape>\\|\b)(?P<camelcase>([A-Z]+[a-z-_]+){2,})(?:"")?\b''')
|
||||
|
||||
class CamelCaseLinks(markdown.inlinepatterns.Pattern):
|
||||
def handleMatch(self, m) :
|
||||
if m.group('escape') == '\\':
|
||||
a = markdown.etree.Element('a')#doc.createTextNode(m.group('camelcase'))
|
||||
else :
|
||||
url = m.group('camelcase')
|
||||
#'%s%s%s'% (self.md.wiki_config['base_url'][0], \
|
||||
#m.group('camelcase'), \
|
||||
#self.md.wiki_config['end_url'][0])
|
||||
label = m.group('camelcase').replace('_', ' ')
|
||||
a = markdown.etree.Element('a')
|
||||
a.set('href', url)
|
||||
a.text = label
|
||||
a.set('class', 'wikilink')
|
||||
return a
|
||||
|
||||
class CamelCasePreprocessor(markdown.preprocessors.Preprocessor) :
|
||||
|
||||
def run(self, lines) :
|
||||
'''
|
||||
Updates WikiLink Extension configs with Meta Data.
|
||||
Passes "lines" through unchanged.
|
||||
|
||||
Run as a preprocessor because must run after the
|
||||
MetaPreprocessor runs and only needs to run once.
|
||||
'''
|
||||
if hasattr(self.md, 'Meta'):
|
||||
if self.md.Meta.has_key('wiki_base_url'):
|
||||
self.md.wiki_config['base_url'][0] = self.md.Meta['wiki_base_url'][0]
|
||||
if self.md.Meta.has_key('wiki_end_url'):
|
||||
self.md.wiki_config['end_url'][0] = self.md.Meta['wiki_end_url'][0]
|
||||
if self.md.Meta.has_key('wiki_html_class'):
|
||||
self.md.wiki_config['html_class'][0] = self.md.Meta['wiki_html_class'][0]
|
||||
|
||||
return lines
|
||||
|
||||
def makeExtension(configs=None) :
|
||||
return CamelCaseExtension(configs=configs)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
61
simplewiki/mdx_image.py
Normal file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python
|
||||
'''
|
||||
Image Embedding Extension for Python-Markdown
|
||||
======================================
|
||||
|
||||
Converts lone links to embedded images, provided the file extension is allowed.
|
||||
|
||||
Ex:
|
||||
http://www.ericfehse.net/media/img/ef/blog/django-pony.jpg
|
||||
becomes
|
||||
<img src="http://www.ericfehse.net/media/img/ef/blog/django-pony.jpg">
|
||||
|
||||
mypic.jpg becomes <img src="/MEDIA_PATH/mypic.jpg">
|
||||
|
||||
Requires Python-Markdown 1.6+
|
||||
'''
|
||||
|
||||
import simplewiki.settings as settings
|
||||
import markdown
|
||||
|
||||
class ImageExtension(markdown.Extension):
|
||||
def __init__(self, configs):
|
||||
for key, value in configs :
|
||||
self.setConfig(key, value)
|
||||
|
||||
def add_inline(self, md, name, klass, re):
|
||||
pattern = klass(re)
|
||||
pattern.md = md
|
||||
pattern.ext = self
|
||||
md.inlinePatterns.add(name, pattern, "<reference")
|
||||
|
||||
def extendMarkdown(self, md, md_globals):
|
||||
self.add_inline(md, 'image', ImageLink,
|
||||
r'^(?P<proto>([^:/?#])+://)?(?P<domain>([^/?#]*)/)?(?P<path>[^?#]*\.(?P<ext>[^?#]{3,4}))(?:\?([^#]*))?(?:#(.*))?$')
|
||||
|
||||
class ImageLink(markdown.inlinepatterns.Pattern):
|
||||
def handleMatch(self, m):
|
||||
img = markdown.etree.Element('img')
|
||||
proto = m.group('proto') or "http://"
|
||||
domain = m.group('domain')
|
||||
path = m.group('path')
|
||||
ext = m.group('ext')
|
||||
|
||||
# A fixer upper
|
||||
if ext.lower() in settings.WIKI_IMAGE_EXTENSIONS:
|
||||
if domain:
|
||||
src = proto+domain+path
|
||||
elif path:
|
||||
# We need a nice way to source local attachments...
|
||||
src = "/wiki/media/" + path + ".upload"
|
||||
else:
|
||||
src = ''
|
||||
img.set('src', src)
|
||||
return img
|
||||
|
||||
def makeExtension(configs=None) :
|
||||
return ImageExtension(configs=configs)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
21
simplewiki/mdx_mathjax.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# Source: https://github.com/mayoff/python-markdown-mathjax
|
||||
|
||||
import markdown
|
||||
|
||||
class MathJaxPattern(markdown.inlinepatterns.Pattern):
|
||||
|
||||
def __init__(self):
|
||||
markdown.inlinepatterns.Pattern.__init__(self, r'(?<!\\)(\$\$?)(.+?)\2')
|
||||
|
||||
def handleMatch(self, m):
|
||||
return markdown.AtomicString(m.group(2) + m.group(3) + m.group(2))
|
||||
|
||||
class MathJaxExtension(markdown.Extension):
|
||||
def extendMarkdown(self, md, md_globals):
|
||||
# Needs to come before escape matching because \ is pretty important in LaTeX
|
||||
md.inlinePatterns.add('mathjax', MathJaxPattern(), '<escape')
|
||||
|
||||
def makeExtension(configs=None):
|
||||
return MathJaxExtension(configs)
|
||||
|
||||
|
||||
271
simplewiki/mdx_video.py
Normal file
@@ -0,0 +1,271 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Embeds web videos using URLs. For instance, if a URL to an youtube video is
|
||||
found in the text submitted to markdown and it isn't enclosed in parenthesis
|
||||
like a normal link in markdown, then the URL will be swapped with a embedded
|
||||
youtube video.
|
||||
|
||||
All resulting HTML is XHTML Strict compatible.
|
||||
|
||||
>>> import markdown
|
||||
|
||||
Test Metacafe
|
||||
|
||||
>>> s = "http://www.metacafe.com/watch/yt-tZMsrrQCnx8/pycon_2008_django_sprint_room/"
|
||||
>>> markdown.markdown(s, ['video'])
|
||||
u'<p><object data="http://www.metacafe.com/fplayer/yt-tZMsrrQCnx8/pycon_2008_django_sprint_room.swf" height="423" type="application/x-shockwave-flash" width="498"><param name="movie" value="http://www.metacafe.com/fplayer/yt-tZMsrrQCnx8/pycon_2008_django_sprint_room.swf" /><param name="allowFullScreen" value="true" /></object></p>'
|
||||
|
||||
|
||||
Test Metacafe with arguments
|
||||
|
||||
>>> markdown.markdown(s, ['video(metacafe_width=500,metacafe_height=425)'])
|
||||
u'<p><object data="http://www.metacafe.com/fplayer/yt-tZMsrrQCnx8/pycon_2008_django_sprint_room.swf" height="425" type="application/x-shockwave-flash" width="500"><param name="movie" value="http://www.metacafe.com/fplayer/yt-tZMsrrQCnx8/pycon_2008_django_sprint_room.swf" /><param name="allowFullScreen" value="true" /></object></p>'
|
||||
|
||||
|
||||
Test Link To Metacafe
|
||||
|
||||
>>> s = "[Metacafe link](http://www.metacafe.com/watch/yt-tZMsrrQCnx8/pycon_2008_django_sprint_room/)"
|
||||
>>> markdown.markdown(s, ['video'])
|
||||
u'<p><a href="http://www.metacafe.com/watch/yt-tZMsrrQCnx8/pycon_2008_django_sprint_room/">Metacafe link</a></p>'
|
||||
|
||||
|
||||
Test Markdown Escaping
|
||||
|
||||
>>> s = "\\http://www.metacafe.com/watch/yt-tZMsrrQCnx8/pycon_2008_django_sprint_room/"
|
||||
>>> markdown.markdown(s, ['video'])
|
||||
u'<p>http://www.metacafe.com/watch/yt-tZMsrrQCnx8/pycon_2008_django_sprint_room/</p>'
|
||||
>>> s = "`http://www.metacafe.com/watch/yt-tZMsrrQCnx8/pycon_2008_django_sprint_room/`"
|
||||
>>> markdown.markdown(s, ['video'])
|
||||
u'<p><code>http://www.metacafe.com/watch/yt-tZMsrrQCnx8/pycon_2008_django_sprint_room/</code></p>'
|
||||
|
||||
|
||||
Test Youtube
|
||||
|
||||
>>> s = "http://www.youtube.com/watch?v=u1mA-0w8XPo&hd=1&fs=1&feature=PlayList&p=34C6046F7FEACFD3&playnext=1&playnext_from=PL&index=1"
|
||||
>>> markdown.markdown(s, ['video'])
|
||||
u'<p><object data="http://www.youtube.com/v/u1mA-0w8XPo&hd=1&fs=1&feature=PlayList&p=34C6046F7FEACFD3&playnext=1&playnext_from=PL&index=1" height="344" type="application/x-shockwave-flash" width="425"><param name="movie" value="http://www.youtube.com/v/u1mA-0w8XPo&hd=1&fs=1&feature=PlayList&p=34C6046F7FEACFD3&playnext=1&playnext_from=PL&index=1" /><param name="allowFullScreen" value="true" /></object></p>'
|
||||
|
||||
|
||||
Test Youtube with argument
|
||||
|
||||
>>> markdown.markdown(s, ['video(youtube_width=200,youtube_height=100)'])
|
||||
u'<p><object data="http://www.youtube.com/v/u1mA-0w8XPo&hd=1&fs=1&feature=PlayList&p=34C6046F7FEACFD3&playnext=1&playnext_from=PL&index=1" height="100" type="application/x-shockwave-flash" width="200"><param name="movie" value="http://www.youtube.com/v/u1mA-0w8XPo&hd=1&fs=1&feature=PlayList&p=34C6046F7FEACFD3&playnext=1&playnext_from=PL&index=1" /><param name="allowFullScreen" value="true" /></object></p>'
|
||||
|
||||
|
||||
Test Youtube Link
|
||||
|
||||
>>> s = "[Youtube link](http://www.youtube.com/watch?v=u1mA-0w8XPo&feature=PlayList&p=34C6046F7FEACFD3&playnext=1&playnext_from=PL&index=1)"
|
||||
>>> markdown.markdown(s, ['video'])
|
||||
u'<p><a href="http://www.youtube.com/watch?v=u1mA-0w8XPo&feature=PlayList&p=34C6046F7FEACFD3&playnext=1&playnext_from=PL&index=1">Youtube link</a></p>'
|
||||
|
||||
|
||||
Test Dailymotion
|
||||
|
||||
>>> s = "http://www.dailymotion.com/relevance/search/ut2004/video/x3kv65_ut2004-ownage_videogames"
|
||||
>>> markdown.markdown(s, ['video'])
|
||||
u'<p><object data="http://www.dailymotion.com/swf/x3kv65_ut2004-ownage_videogames" height="405" type="application/x-shockwave-flash" width="480"><param name="movie" value="http://www.dailymotion.com/swf/x3kv65_ut2004-ownage_videogames" /><param name="allowFullScreen" value="true" /></object></p>'
|
||||
|
||||
|
||||
Test Dailymotion again (Dailymotion and their crazy URLs)
|
||||
|
||||
>>> s = "http://www.dailymotion.com/us/video/x8qak3_iron-man-vs-bruce-lee_fun"
|
||||
>>> markdown.markdown(s, ['video'])
|
||||
u'<p><object data="http://www.dailymotion.com/swf/x8qak3_iron-man-vs-bruce-lee_fun" height="405" type="application/x-shockwave-flash" width="480"><param name="movie" value="http://www.dailymotion.com/swf/x8qak3_iron-man-vs-bruce-lee_fun" /><param name="allowFullScreen" value="true" /></object></p>'
|
||||
|
||||
|
||||
Test Yahoo! Video
|
||||
|
||||
>>> s = "http://video.yahoo.com/watch/1981791/4769603"
|
||||
>>> markdown.markdown(s, ['video'])
|
||||
u'<p><object data="http://d.yimg.com/static.video.yahoo.com/yep/YV_YEP.swf?ver=2.2.40" height="322" type="application/x-shockwave-flash" width="512"><param name="movie" value="http://d.yimg.com/static.video.yahoo.com/yep/YV_YEP.swf?ver=2.2.40" /><param name="allowFullScreen" value="true" /><param name="flashVars" value="id=4769603&vid=1981791" /></object></p>'
|
||||
|
||||
|
||||
Test Veoh Video
|
||||
|
||||
>>> s = "http://www.veoh.com/search/videos/q/mario#watch%3De129555XxCZanYD"
|
||||
>>> markdown.markdown(s, ['video'])
|
||||
u'<p><object data="http://www.veoh.com/videodetails2.swf?permalinkId=e129555XxCZanYD" height="341" type="application/x-shockwave-flash" width="410"><param name="movie" value="http://www.veoh.com/videodetails2.swf?permalinkId=e129555XxCZanYD" /><param name="allowFullScreen" value="true" /></object></p>'
|
||||
|
||||
|
||||
Test Veoh Video Again (More fun URLs)
|
||||
|
||||
>>> s = "http://www.veoh.com/group/BigCatRescuers#watch%3Dv16771056hFtSBYEr"
|
||||
>>> markdown.markdown(s, ['video'])
|
||||
u'<p><object data="http://www.veoh.com/videodetails2.swf?permalinkId=v16771056hFtSBYEr" height="341" type="application/x-shockwave-flash" width="410"><param name="movie" value="http://www.veoh.com/videodetails2.swf?permalinkId=v16771056hFtSBYEr" /><param name="allowFullScreen" value="true" /></object></p>'
|
||||
|
||||
|
||||
Test Veoh Video Yet Again (Even more fun URLs)
|
||||
|
||||
>>> s = "http://www.veoh.com/browse/videos/category/anime/watch/v181645607JyXPWcQ"
|
||||
>>> markdown.markdown(s, ['video'])
|
||||
u'<p><object data="http://www.veoh.com/videodetails2.swf?permalinkId=v181645607JyXPWcQ" height="341" type="application/x-shockwave-flash" width="410"><param name="movie" value="http://www.veoh.com/videodetails2.swf?permalinkId=v181645607JyXPWcQ" /><param name="allowFullScreen" value="true" /></object></p>'
|
||||
|
||||
|
||||
Test Vimeo Video
|
||||
|
||||
>>> s = "http://www.vimeo.com/1496152"
|
||||
>>> markdown.markdown(s, ['video'])
|
||||
u'<p><object data="http://vimeo.com/moogaloop.swf?clip_id=1496152&amp;server=vimeo.com" height="321" type="application/x-shockwave-flash" width="400"><param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=1496152&amp;server=vimeo.com" /><param name="allowFullScreen" value="true" /></object></p>'
|
||||
|
||||
Test Vimeo Video with some GET values
|
||||
|
||||
>>> s = "http://vimeo.com/1496152?test=test"
|
||||
>>> markdown.markdown(s, ['video'])
|
||||
u'<p><object data="http://vimeo.com/moogaloop.swf?clip_id=1496152&amp;server=vimeo.com" height="321" type="application/x-shockwave-flash" width="400"><param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=1496152&amp;server=vimeo.com" /><param name="allowFullScreen" value="true" /></object></p>'
|
||||
|
||||
Test Blip.tv
|
||||
|
||||
>>> s = "http://blip.tv/file/get/Pycon-PlenarySprintIntro563.flv"
|
||||
>>> markdown.markdown(s, ['video'])
|
||||
u'<p><object data="http://blip.tv/scripts/flash/showplayer.swf?file=http://blip.tv/file/get/Pycon-PlenarySprintIntro563.flv" height="300" type="application/x-shockwave-flash" width="480"><param name="movie" value="http://blip.tv/scripts/flash/showplayer.swf?file=http://blip.tv/file/get/Pycon-PlenarySprintIntro563.flv" /><param name="allowFullScreen" value="true" /></object></p>'
|
||||
|
||||
Test Gametrailers
|
||||
|
||||
>>> s = "http://www.gametrailers.com/video/console-comparison-borderlands/58079"
|
||||
>>> markdown.markdown(s, ['video'])
|
||||
u'<p><object data="http://www.gametrailers.com/remote_wrap.php?mid=58079" height="392" type="application/x-shockwave-flash" width="480"><param name="movie" value="http://www.gametrailers.com/remote_wrap.php?mid=58079" /><param name="allowFullScreen" value="true" /></object></p>'
|
||||
"""
|
||||
|
||||
import markdown
|
||||
|
||||
version = "0.1.6"
|
||||
|
||||
class VideoExtension(markdown.Extension):
|
||||
def __init__(self, configs):
|
||||
self.config = {
|
||||
'bliptv_width': ['480', 'Width for Blip.tv videos'],
|
||||
'bliptv_height': ['300', 'Height for Blip.tv videos'],
|
||||
'dailymotion_width': ['480', 'Width for Dailymotion videos'],
|
||||
'dailymotion_height': ['405', 'Height for Dailymotion videos'],
|
||||
'gametrailers_width': ['480', 'Width for Gametrailers videos'],
|
||||
'gametrailers_height': ['392', 'Height for Gametrailers videos'],
|
||||
'metacafe_width': ['498', 'Width for Metacafe videos'],
|
||||
'metacafe_height': ['423', 'Height for Metacafe videos'],
|
||||
'veoh_width': ['410', 'Width for Veoh videos'],
|
||||
'veoh_height': ['341', 'Height for Veoh videos'],
|
||||
'vimeo_width': ['400', 'Width for Vimeo videos'],
|
||||
'vimeo_height': ['321', 'Height for Vimeo videos'],
|
||||
'yahoo_width': ['512', 'Width for Yahoo! videos'],
|
||||
'yahoo_height': ['322', 'Height for Yahoo! videos'],
|
||||
'youtube_width': ['425', 'Width for Youtube videos'],
|
||||
'youtube_height': ['344', 'Height for Youtube videos'],
|
||||
}
|
||||
|
||||
# Override defaults with user settings
|
||||
for key, value in configs:
|
||||
self.setConfig(key, value)
|
||||
|
||||
def add_inline(self, md, name, klass, re):
|
||||
pattern = klass(re)
|
||||
pattern.md = md
|
||||
pattern.ext = self
|
||||
md.inlinePatterns.add(name, pattern, "<reference")
|
||||
|
||||
def extendMarkdown(self, md, md_globals):
|
||||
self.add_inline(md, 'bliptv', Bliptv,
|
||||
r'([^(]|^)http://(\w+\.|)blip.tv/file/get/(?P<bliptvfile>\S+.flv)')
|
||||
self.add_inline(md, 'dailymotion', Dailymotion,
|
||||
r'([^(]|^)http://www\.dailymotion\.com/(?P<dailymotionid>\S+)')
|
||||
self.add_inline(md, 'gametrailers', Gametrailers,
|
||||
r'([^(]|^)http://www.gametrailers.com/video/[a-z0-9-]+/(?P<gametrailersid>\d+)')
|
||||
self.add_inline(md, 'metacafe', Metacafe,
|
||||
r'([^(]|^)http://www\.metacafe\.com/watch/(?P<metacafeid>\S+)/')
|
||||
self.add_inline(md, 'veoh', Veoh,
|
||||
r'([^(]|^)http://www\.veoh\.com/\S*(#watch%3D|watch/)(?P<veohid>\w+)')
|
||||
self.add_inline(md, 'vimeo', Vimeo,
|
||||
r'([^(]|^)http://(www.|)vimeo\.com/(?P<vimeoid>\d+)\S*')
|
||||
self.add_inline(md, 'yahoo', Yahoo,
|
||||
r'([^(]|^)http://video\.yahoo\.com/watch/(?P<yahoovid>\d+)/(?P<yahooid>\d+)')
|
||||
self.add_inline(md, 'youtube', Youtube,
|
||||
r'([^(]|^)http://www\.youtube\.com/watch\?\S*v=(?P<youtubeargs>[A-Za-z0-9_&=-]+)\S*')
|
||||
|
||||
class Bliptv(markdown.inlinepatterns.Pattern):
|
||||
def handleMatch(self, m):
|
||||
url = 'http://blip.tv/scripts/flash/showplayer.swf?file=http://blip.tv/file/get/%s' % m.group('bliptvfile')
|
||||
width = self.ext.config['bliptv_width'][0]
|
||||
height = self.ext.config['bliptv_height'][0]
|
||||
return flash_object(url, width, height)
|
||||
|
||||
class Dailymotion(markdown.inlinepatterns.Pattern):
|
||||
def handleMatch(self, m):
|
||||
url = 'http://www.dailymotion.com/swf/%s' % m.group('dailymotionid').split('/')[-1]
|
||||
width = self.ext.config['dailymotion_width'][0]
|
||||
height = self.ext.config['dailymotion_height'][0]
|
||||
return flash_object(url, width, height)
|
||||
|
||||
class Gametrailers(markdown.inlinepatterns.Pattern):
|
||||
def handleMatch(self, m):
|
||||
url = 'http://www.gametrailers.com/remote_wrap.php?mid=%s' % \
|
||||
m.group('gametrailersid').split('/')[-1]
|
||||
width = self.ext.config['gametrailers_width'][0]
|
||||
height = self.ext.config['gametrailers_height'][0]
|
||||
return flash_object(url, width, height)
|
||||
|
||||
class Metacafe(markdown.inlinepatterns.Pattern):
|
||||
def handleMatch(self, m):
|
||||
url = 'http://www.metacafe.com/fplayer/%s.swf' % m.group('metacafeid')
|
||||
width = self.ext.config['metacafe_width'][0]
|
||||
height = self.ext.config['metacafe_height'][0]
|
||||
return flash_object(url, width, height)
|
||||
|
||||
class Veoh(markdown.inlinepatterns.Pattern):
|
||||
def handleMatch(self, m):
|
||||
url = 'http://www.veoh.com/videodetails2.swf?permalinkId=%s' % m.group('veohid')
|
||||
width = self.ext.config['veoh_width'][0]
|
||||
height = self.ext.config['veoh_height'][0]
|
||||
return flash_object(url, width, height)
|
||||
|
||||
class Vimeo(markdown.inlinepatterns.Pattern):
|
||||
def handleMatch(self, m):
|
||||
url = 'http://vimeo.com/moogaloop.swf?clip_id=%s&server=vimeo.com' % m.group('vimeoid')
|
||||
width = self.ext.config['vimeo_width'][0]
|
||||
height = self.ext.config['vimeo_height'][0]
|
||||
return flash_object(url, width, height)
|
||||
|
||||
class Yahoo(markdown.inlinepatterns.Pattern):
|
||||
def handleMatch(self, m):
|
||||
url = "http://d.yimg.com/static.video.yahoo.com/yep/YV_YEP.swf?ver=2.2.40"
|
||||
width = self.ext.config['yahoo_width'][0]
|
||||
height = self.ext.config['yahoo_height'][0]
|
||||
obj = flash_object(url, width, height)
|
||||
param = markdown.etree.Element('param')
|
||||
param.set('name', 'flashVars')
|
||||
param.set('value', "id=%s&vid=%s" % (m.group('yahooid'),
|
||||
m.group('yahoovid')))
|
||||
obj.append(param)
|
||||
return obj
|
||||
|
||||
class Youtube(markdown.inlinepatterns.Pattern):
|
||||
def handleMatch(self, m):
|
||||
url = 'http://www.youtube.com/v/%s' % m.group('youtubeargs')
|
||||
width = self.ext.config['youtube_width'][0]
|
||||
height = self.ext.config['youtube_height'][0]
|
||||
return flash_object(url, width, height)
|
||||
|
||||
def flash_object(url, width, height):
|
||||
obj = markdown.etree.Element('object')
|
||||
obj.set('type', 'application/x-shockwave-flash')
|
||||
obj.set('width', width)
|
||||
obj.set('height', height)
|
||||
obj.set('data', url)
|
||||
param = markdown.etree.Element('param')
|
||||
param.set('name', 'movie')
|
||||
param.set('value', url)
|
||||
obj.append(param)
|
||||
param = markdown.etree.Element('param')
|
||||
param.set('name', 'allowFullScreen')
|
||||
param.set('value', 'true')
|
||||
obj.append(param)
|
||||
#param = markdown.etree.Element('param')
|
||||
#param.set('name', 'allowScriptAccess')
|
||||
#param.set('value', 'sameDomain')
|
||||
#obj.append(param)
|
||||
return obj
|
||||
|
||||
def makeExtension(configs=None) :
|
||||
return VideoExtension(configs=configs)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
177
simplewiki/media/css/autosuggest_inquisitor.css
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
================================================
|
||||
autosuggest, inquisitor style
|
||||
================================================
|
||||
*/
|
||||
|
||||
body
|
||||
{
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
div.autosuggest
|
||||
{
|
||||
position: absolute;
|
||||
background-image: url(img_inquisitor/as_pointer.gif);
|
||||
background-position: top;
|
||||
background-repeat: no-repeat;
|
||||
padding: 10px 0 0 0;
|
||||
}
|
||||
|
||||
div.autosuggest div.as_header,
|
||||
div.autosuggest div.as_footer
|
||||
{
|
||||
position: relative;
|
||||
height: 6px;
|
||||
padding: 0 6px;
|
||||
background-image: url(img_inquisitor/ul_corner_tr.gif);
|
||||
background-position: top right;
|
||||
background-repeat: no-repeat;
|
||||
overflow: hidden;
|
||||
}
|
||||
div.autosuggest div.as_footer
|
||||
{
|
||||
background-image: url(img_inquisitor/ul_corner_br.gif);
|
||||
}
|
||||
|
||||
div.autosuggest div.as_header div.as_corner,
|
||||
div.autosuggest div.as_footer div.as_corner
|
||||
{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
background-image: url(img_inquisitor/ul_corner_tl.gif);
|
||||
background-position: top left;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
div.autosuggest div.as_footer div.as_corner
|
||||
{
|
||||
background-image: url(img_inquisitor/ul_corner_bl.gif);
|
||||
}
|
||||
div.autosuggest div.as_header div.as_bar,
|
||||
div.autosuggest div.as_footer div.as_bar
|
||||
{
|
||||
height: 6px;
|
||||
overflow: hidden;
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
|
||||
div.autosuggest ul
|
||||
{
|
||||
list-style: none;
|
||||
margin: 0 0 -4px 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
div.autosuggest ul li
|
||||
{
|
||||
color: #ccc;
|
||||
padding: 0;
|
||||
margin: 0 4px 4px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.autosuggest ul li a
|
||||
{
|
||||
color: #ccc;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
text-shadow: #000 0px 0px 5px;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
div.autosuggest ul li a:hover
|
||||
{
|
||||
background-color: #444;
|
||||
}
|
||||
div.autosuggest ul li.as_highlight a:hover
|
||||
{
|
||||
background-color: #1B5CCD;
|
||||
}
|
||||
|
||||
div.autosuggest ul li a span
|
||||
{
|
||||
display: block;
|
||||
padding: 3px 6px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.autosuggest ul li a span small
|
||||
{
|
||||
font-weight: normal;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
div.autosuggest ul li.as_highlight a span small
|
||||
{
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
div.autosuggest ul li.as_highlight a
|
||||
{
|
||||
color: #fff;
|
||||
background-color: #1B5CCD;
|
||||
background-image: url(img_inquisitor/hl_corner_br.gif);
|
||||
background-position: bottom right;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
div.autosuggest ul li.as_highlight a span
|
||||
{
|
||||
background-image: url(img_inquisitor/hl_corner_bl.gif);
|
||||
background-position: bottom left;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
div.autosuggest ul li a .tl,
|
||||
div.autosuggest ul li a .tr
|
||||
{
|
||||
background-image: transparent;
|
||||
background-repeat: no-repeat;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
div.autosuggest ul li a .tr
|
||||
{
|
||||
right: 0;
|
||||
}
|
||||
|
||||
div.autosuggest ul li.as_highlight a .tl
|
||||
{
|
||||
left: 0;
|
||||
background-image: url(img_inquisitor/hl_corner_tl.gif);
|
||||
background-position: bottom left;
|
||||
}
|
||||
|
||||
div.autosuggest ul li.as_highlight a .tr
|
||||
{
|
||||
right: 0;
|
||||
background-image: url(img_inquisitor/hl_corner_tr.gif);
|
||||
background-position: bottom right;
|
||||
}
|
||||
|
||||
|
||||
|
||||
div.autosuggest ul li.as_warning
|
||||
{
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.autosuggest ul em
|
||||
{
|
||||
font-style: normal;
|
||||
color: #6EADE7;
|
||||
}
|
||||
281
simplewiki/media/css/base.css
Normal file
@@ -0,0 +1,281 @@
|
||||
body
|
||||
{
|
||||
font-family: 'Lucida Sans', 'Sans';
|
||||
}
|
||||
|
||||
a img
|
||||
{
|
||||
border: 0;
|
||||
}
|
||||
|
||||
div#wiki_article a {
|
||||
color: #06d;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
div#wiki_article a:hover {
|
||||
color: #f82;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
hr
|
||||
{
|
||||
background-color: #def;
|
||||
height: 2px;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
div#wiki_article .toc a
|
||||
{
|
||||
color: #025
|
||||
}
|
||||
|
||||
div#wiki_article p
|
||||
{
|
||||
/* font-size: 90%; looks funny when combined with lists/tables */
|
||||
line-height: 140%;
|
||||
}
|
||||
div#wiki_article h1
|
||||
{
|
||||
font-size: 200%;
|
||||
font-weight: normal;
|
||||
color: #048;
|
||||
}
|
||||
|
||||
div#wiki_article h2
|
||||
{
|
||||
font-size: 150%;
|
||||
font-weight: normal;
|
||||
color: #025;
|
||||
}
|
||||
|
||||
div#wiki_article h3
|
||||
{
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
table
|
||||
{
|
||||
border: 1px solid black;
|
||||
border-collapse: collapse;
|
||||
margin: 12px;
|
||||
}
|
||||
|
||||
table tr.dark
|
||||
{
|
||||
background-color: #F3F3F3;
|
||||
}
|
||||
|
||||
table thead tr
|
||||
{
|
||||
background-color: #def;
|
||||
border-bottom: 2px solid black;
|
||||
}
|
||||
|
||||
table td, th
|
||||
{
|
||||
padding: 6px 10px 6px 10px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
table thead th
|
||||
{
|
||||
padding-bottom: 8px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
div#wiki_panel
|
||||
{
|
||||
float: right;
|
||||
}
|
||||
|
||||
div.wiki_box
|
||||
{
|
||||
width: 230px;
|
||||
padding: 10px;
|
||||
color: #fff;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
div.wiki_box div.wiki_box_contents
|
||||
{ background-color: #222;
|
||||
padding: 5px 10px;}
|
||||
|
||||
div.wiki_box div.wiki_box_header,
|
||||
div.wiki_box div.wiki_box_footer
|
||||
{
|
||||
position: relative;
|
||||
height: 6px;
|
||||
padding: 0 6px;
|
||||
background-image: url(../img/box_corner_tr.gif);
|
||||
background-position: top right;
|
||||
background-repeat: no-repeat;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
div.wiki_box div.wiki_box_footer
|
||||
{
|
||||
background-image: url(../img/box_corner_br.gif);
|
||||
}
|
||||
|
||||
div.wiki_box div.wiki_box_header div.wiki_box_corner,
|
||||
div.wiki_box div.wiki_box_footer div.wiki_box_corner
|
||||
{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
background-image: url(../img/box_corner_tl.gif);
|
||||
background-position: top left;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
div.wiki_box div.wiki_box_footer div.wiki_box_corner
|
||||
{
|
||||
background-image: url(../img/box_corner_bl.gif);
|
||||
}
|
||||
|
||||
div.wiki_box div.wiki_box_header div.wiki_box_bar,
|
||||
div.wiki_box div.wiki_box_footer div.wiki_box_bar
|
||||
{
|
||||
height: 6px;
|
||||
overflow: hidden;
|
||||
background-color: #222;
|
||||
}
|
||||
|
||||
|
||||
div.wiki_box a
|
||||
{
|
||||
color: #acf;
|
||||
}
|
||||
|
||||
div.wiki_box p
|
||||
{
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
div.wiki_box ul
|
||||
{
|
||||
padding-left: 20px;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
div.wiki_box div.wiki_box_title
|
||||
{
|
||||
margin-bottom: 5px;
|
||||
font-size: 140%;
|
||||
}
|
||||
|
||||
form#wiki_revision #id_contents
|
||||
{
|
||||
width:500px;
|
||||
height: 400px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
form#wiki_revision #id_title
|
||||
{
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
form#wiki_revision #id_revision_text
|
||||
{
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
table#wiki_revision_table
|
||||
{
|
||||
border: none;
|
||||
border-collapse: collapse;
|
||||
padding-right: 250px;
|
||||
}
|
||||
|
||||
table#wiki_revision_table th
|
||||
{
|
||||
border: none;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
table#wiki_revision_table td
|
||||
{
|
||||
border: none;
|
||||
}
|
||||
|
||||
table#wiki_history_table
|
||||
{
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
padding-right: 250px;
|
||||
}
|
||||
|
||||
table#wiki_history_table th#modified
|
||||
{
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
table#wiki_history_table td
|
||||
{
|
||||
border: none;
|
||||
}
|
||||
|
||||
table#wiki_history_table tbody tr
|
||||
{
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
|
||||
table#wiki_history_table tbody td
|
||||
{
|
||||
vertical-align: top;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
table#wiki_history_table tfoot td
|
||||
{
|
||||
border: none;
|
||||
}
|
||||
|
||||
table#wiki_history_table tbody td.diff
|
||||
{
|
||||
font-family: monospace;
|
||||
overflow: hidden;
|
||||
border-left: 1px dotted black;
|
||||
border-right: 1px dotted black;
|
||||
}
|
||||
|
||||
table#wiki_history_table th
|
||||
{
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div#wiki_attach_progress_container
|
||||
{
|
||||
background-color: #333;
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
div#wiki_attach_progress
|
||||
{
|
||||
width: 25%;
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
margin-left: 50px;
|
||||
padding-left: 15px;
|
||||
border-left: 3px solid #666;
|
||||
color: #999;
|
||||
max-width: 400px ;
|
||||
}
|
||||
|
||||
blockquote p {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
6
simplewiki/media/css/base_print.css
Normal file
@@ -0,0 +1,6 @@
|
||||
div#wiki_panel
|
||||
{
|
||||
display:none;
|
||||
}
|
||||
|
||||
|
||||
BIN
simplewiki/media/css/img_inquisitor/_source/as_pointer.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
simplewiki/media/css/img_inquisitor/_source/li_corner.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
simplewiki/media/css/img_inquisitor/_source/ul_corner.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
simplewiki/media/css/img_inquisitor/as_pointer.gif
Normal file
|
After Width: | Height: | Size: 66 B |
BIN
simplewiki/media/css/img_inquisitor/hl_corner_bl.gif
Normal file
|
After Width: | Height: | Size: 73 B |
BIN
simplewiki/media/css/img_inquisitor/hl_corner_br.gif
Normal file
|
After Width: | Height: | Size: 73 B |
BIN
simplewiki/media/css/img_inquisitor/hl_corner_tl.gif
Normal file
|
After Width: | Height: | Size: 73 B |
BIN
simplewiki/media/css/img_inquisitor/hl_corner_tr.gif
Normal file
|
After Width: | Height: | Size: 73 B |
BIN
simplewiki/media/css/img_inquisitor/ul_corner_bl.gif
Normal file
|
After Width: | Height: | Size: 49 B |
BIN
simplewiki/media/css/img_inquisitor/ul_corner_br.gif
Normal file
|
After Width: | Height: | Size: 49 B |
BIN
simplewiki/media/css/img_inquisitor/ul_corner_tl.gif
Normal file
|
After Width: | Height: | Size: 50 B |
BIN
simplewiki/media/css/img_inquisitor/ul_corner_tr.gif
Normal file
|
After Width: | Height: | Size: 50 B |
BIN
simplewiki/media/img/box_corner_bl.gif
Normal file
|
After Width: | Height: | Size: 49 B |
BIN
simplewiki/media/img/box_corner_br.gif
Normal file
|
After Width: | Height: | Size: 49 B |
BIN
simplewiki/media/img/box_corner_tl.gif
Normal file
|
After Width: | Height: | Size: 50 B |
BIN
simplewiki/media/img/box_corner_tr.gif
Normal file
|
After Width: | Height: | Size: 50 B |
BIN
simplewiki/media/img/delete.gif
Normal file
|
After Width: | Height: | Size: 130 B |
BIN
simplewiki/media/img/delete_grey.gif
Normal file
|
After Width: | Height: | Size: 130 B |
961
simplewiki/media/js/bsn.AutoSuggest_c_2.0.js
Normal file
@@ -0,0 +1,961 @@
|
||||
/**
|
||||
* author: Timothy Groves - http://www.brandspankingnew.net
|
||||
* version: 1.2 - 2006-11-17
|
||||
* 1.3 - 2006-12-04
|
||||
* 2.0 - 2007-02-07
|
||||
*
|
||||
*/
|
||||
|
||||
var useBSNns;
|
||||
|
||||
if (useBSNns)
|
||||
{
|
||||
if (typeof(bsn) == "undefined")
|
||||
bsn = {}
|
||||
_bsn = bsn;
|
||||
}
|
||||
else
|
||||
{
|
||||
_bsn = this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (typeof(_bsn.Autosuggest) == "undefined")
|
||||
_bsn.Autosuggest = {}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest = function (fldID, param)
|
||||
{
|
||||
// no DOM - give up!
|
||||
//
|
||||
if (!document.getElementById)
|
||||
return false;
|
||||
|
||||
|
||||
|
||||
|
||||
// get field via DOM
|
||||
//
|
||||
this.fld = _bsn.DOM.getElement(fldID);
|
||||
|
||||
if (!this.fld)
|
||||
return false;
|
||||
|
||||
|
||||
|
||||
|
||||
// init variables
|
||||
//
|
||||
this.sInput = "";
|
||||
this.nInputChars = 0;
|
||||
this.aSuggestions = [];
|
||||
this.iHighlighted = 0;
|
||||
|
||||
|
||||
|
||||
|
||||
// parameters object
|
||||
//
|
||||
this.oP = (param) ? param : {};
|
||||
|
||||
// defaults
|
||||
//
|
||||
if (!this.oP.minchars) this.oP.minchars = 1;
|
||||
if (!this.oP.method) this.oP.meth = "get";
|
||||
if (!this.oP.varname) this.oP.varname = "input";
|
||||
if (!this.oP.className) this.oP.className = "autosuggest";
|
||||
if (!this.oP.timeout) this.oP.timeout = 2500;
|
||||
if (!this.oP.delay) this.oP.delay = 500;
|
||||
if (!this.oP.offsety) this.oP.offsety = -5;
|
||||
if (!this.oP.shownoresults) this.oP.shownoresults = true;
|
||||
if (!this.oP.noresults) this.oP.noresults = "No results!";
|
||||
if (!this.oP.maxheight && this.oP.maxheight !== 0) this.oP.maxheight = 250;
|
||||
if (!this.oP.cache && this.oP.cache != false) this.oP.cache = true;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// set keyup handler for field
|
||||
// and prevent autocomplete from client
|
||||
//
|
||||
var pointer = this;
|
||||
|
||||
// NOTE: not using addEventListener because UpArrow fired twice in Safari
|
||||
//_bsn.DOM.addEvent( this.fld, 'keyup', function(ev){ return pointer.onKeyPress(ev); } );
|
||||
|
||||
this.fld.onkeypress = function(ev){ return pointer.onKeyPress(ev); }
|
||||
this.fld.onkeyup = function(ev){ return pointer.onKeyUp(ev); }
|
||||
|
||||
this.fld.setAttribute("autocomplete","off");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.onKeyPress = function(ev)
|
||||
{
|
||||
|
||||
var key = (window.event) ? window.event.keyCode : ev.keyCode;
|
||||
|
||||
|
||||
|
||||
// set responses to keydown events in the field
|
||||
// this allows the user to use the arrow keys to scroll through the results
|
||||
// ESCAPE clears the list
|
||||
// TAB sets the current highlighted value
|
||||
//
|
||||
var RETURN = 13;
|
||||
var TAB = 9;
|
||||
var ESC = 27;
|
||||
|
||||
var bubble = true;
|
||||
|
||||
switch(key)
|
||||
{
|
||||
|
||||
case RETURN:
|
||||
this.setHighlightedValue();
|
||||
bubble = false;
|
||||
break;
|
||||
|
||||
|
||||
case ESC:
|
||||
this.clearSuggestions();
|
||||
break;
|
||||
}
|
||||
|
||||
return bubble;
|
||||
}
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.onKeyUp = function(ev)
|
||||
{
|
||||
var key = (window.event) ? window.event.keyCode : ev.keyCode;
|
||||
|
||||
|
||||
|
||||
// set responses to keydown events in the field
|
||||
// this allows the user to use the arrow keys to scroll through the results
|
||||
// ESCAPE clears the list
|
||||
// TAB sets the current highlighted value
|
||||
//
|
||||
|
||||
var ARRUP = 38;
|
||||
var ARRDN = 40;
|
||||
|
||||
var bubble = true;
|
||||
|
||||
switch(key)
|
||||
{
|
||||
|
||||
|
||||
case ARRUP:
|
||||
this.changeHighlight(key);
|
||||
bubble = false;
|
||||
break;
|
||||
|
||||
|
||||
case ARRDN:
|
||||
this.changeHighlight(key);
|
||||
bubble = false;
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
this.getSuggestions(this.fld.value);
|
||||
}
|
||||
|
||||
return bubble;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.getSuggestions = function (val)
|
||||
{
|
||||
|
||||
// if input stays the same, do nothing
|
||||
//
|
||||
if (val == this.sInput)
|
||||
return false;
|
||||
|
||||
|
||||
// input length is less than the min required to trigger a request
|
||||
// reset input string
|
||||
// do nothing
|
||||
//
|
||||
if (val.length < this.oP.minchars)
|
||||
{
|
||||
this.sInput = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// if caching enabled, and user is typing (ie. length of input is increasing)
|
||||
// filter results out of aSuggestions from last request
|
||||
//
|
||||
if (val.length>this.nInputChars && this.aSuggestions.length && this.oP.cache)
|
||||
{
|
||||
var arr = [];
|
||||
for (var i=0;i<this.aSuggestions.length;i++)
|
||||
{
|
||||
if (this.aSuggestions[i].value.substr(0,val.length).toLowerCase() == val.toLowerCase())
|
||||
arr.push( this.aSuggestions[i] );
|
||||
}
|
||||
|
||||
this.sInput = val;
|
||||
this.nInputChars = val.length;
|
||||
this.aSuggestions = arr;
|
||||
|
||||
this.createList(this.aSuggestions);
|
||||
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
// do new request
|
||||
//
|
||||
{
|
||||
this.sInput = val;
|
||||
this.nInputChars = val.length;
|
||||
|
||||
|
||||
var pointer = this;
|
||||
clearTimeout(this.ajID);
|
||||
this.ajID = setTimeout( function() { pointer.doAjaxRequest() }, this.oP.delay );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.doAjaxRequest = function ()
|
||||
{
|
||||
|
||||
var pointer = this;
|
||||
|
||||
// create ajax request
|
||||
var url = this.oP.script+this.oP.varname+"="+escape(this.fld.value);
|
||||
var meth = this.oP.meth;
|
||||
|
||||
var onSuccessFunc = function (req) { pointer.setSuggestions(req) };
|
||||
var onErrorFunc = function (status) { alert("AJAX error: "+status); };
|
||||
|
||||
var myAjax = new _bsn.Ajax();
|
||||
myAjax.makeRequest( url, meth, onSuccessFunc, onErrorFunc );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.setSuggestions = function (req)
|
||||
{
|
||||
this.aSuggestions = [];
|
||||
|
||||
if (this.oP.json)
|
||||
{
|
||||
var jsondata = eval('(' + req.responseText + ')');
|
||||
|
||||
for (var i=0;i<jsondata.results.length;i++)
|
||||
{
|
||||
this.aSuggestions.push( { 'id':jsondata.results[i].id, 'value':jsondata.results[i].value, 'info':jsondata.results[i].info } );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
var xml = req.responseXML;
|
||||
|
||||
// traverse xml
|
||||
//
|
||||
var results = xml.getElementsByTagName('results')[0].childNodes;
|
||||
|
||||
for (var i=0;i<results.length;i++)
|
||||
{
|
||||
if (results[i].hasChildNodes())
|
||||
this.aSuggestions.push( { 'id':results[i].getAttribute('id'), 'value':results[i].childNodes[0].nodeValue, 'info':results[i].getAttribute('info') } );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.idAs = "as_"+this.fld.id;
|
||||
|
||||
|
||||
this.createList(this.aSuggestions);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.createList = function(arr)
|
||||
{
|
||||
var pointer = this;
|
||||
|
||||
|
||||
// get rid of old list
|
||||
// and clear the list removal timeout
|
||||
//
|
||||
_bsn.DOM.removeElement(this.idAs);
|
||||
this.killTimeout();
|
||||
|
||||
|
||||
// create holding div
|
||||
//
|
||||
var div = _bsn.DOM.createElement("div", {id:this.idAs, className:this.oP.className});
|
||||
|
||||
var hcorner = _bsn.DOM.createElement("div", {className:"as_corner"});
|
||||
var hbar = _bsn.DOM.createElement("div", {className:"as_bar"});
|
||||
var header = _bsn.DOM.createElement("div", {className:"as_header"});
|
||||
header.appendChild(hcorner);
|
||||
header.appendChild(hbar);
|
||||
div.appendChild(header);
|
||||
|
||||
|
||||
|
||||
|
||||
// create and populate ul
|
||||
//
|
||||
var ul = _bsn.DOM.createElement("ul", {id:"as_ul"});
|
||||
|
||||
|
||||
|
||||
|
||||
// loop throught arr of suggestions
|
||||
// creating an LI element for each suggestion
|
||||
//
|
||||
for (var i=0;i<arr.length;i++)
|
||||
{
|
||||
// format output with the input enclosed in a EM element
|
||||
// (as HTML, not DOM)
|
||||
//
|
||||
var val = arr[i].value;
|
||||
var st = val.toLowerCase().indexOf( this.sInput.toLowerCase() );
|
||||
var output = val.substring(0,st) + "<em>" + val.substring(st, st+this.sInput.length) + "</em>" + val.substring(st+this.sInput.length);
|
||||
|
||||
|
||||
var span = _bsn.DOM.createElement("span", {}, output, true);
|
||||
if (arr[i].info != "")
|
||||
{
|
||||
var br = _bsn.DOM.createElement("br", {});
|
||||
span.appendChild(br);
|
||||
var small = _bsn.DOM.createElement("small", {}, arr[i].info);
|
||||
span.appendChild(small);
|
||||
}
|
||||
|
||||
var a = _bsn.DOM.createElement("a", { href:"#" });
|
||||
|
||||
var tl = _bsn.DOM.createElement("span", {className:"tl"}, " ");
|
||||
var tr = _bsn.DOM.createElement("span", {className:"tr"}, " ");
|
||||
a.appendChild(tl);
|
||||
a.appendChild(tr);
|
||||
|
||||
a.appendChild(span);
|
||||
|
||||
a.name = i+1;
|
||||
a.onclick = function () { pointer.setHighlightedValue(); return false; }
|
||||
a.onmouseover = function () { pointer.setHighlight(this.name); }
|
||||
|
||||
var li = _bsn.DOM.createElement( "li", {}, a );
|
||||
|
||||
ul.appendChild( li );
|
||||
}
|
||||
|
||||
|
||||
// no results
|
||||
//
|
||||
if (arr.length == 0)
|
||||
{
|
||||
var li = _bsn.DOM.createElement( "li", {className:"as_warning"}, this.oP.noresults );
|
||||
|
||||
ul.appendChild( li );
|
||||
}
|
||||
|
||||
|
||||
div.appendChild( ul );
|
||||
|
||||
|
||||
var fcorner = _bsn.DOM.createElement("div", {className:"as_corner"});
|
||||
var fbar = _bsn.DOM.createElement("div", {className:"as_bar"});
|
||||
var footer = _bsn.DOM.createElement("div", {className:"as_footer"});
|
||||
footer.appendChild(fcorner);
|
||||
footer.appendChild(fbar);
|
||||
div.appendChild(footer);
|
||||
|
||||
|
||||
|
||||
// get position of target textfield
|
||||
// position holding div below it
|
||||
// set width of holding div to width of field
|
||||
//
|
||||
var pos = _bsn.DOM.getPos(this.fld);
|
||||
|
||||
div.style.left = pos.x + "px";
|
||||
div.style.top = ( pos.y + this.fld.offsetHeight + this.oP.offsety ) + "px";
|
||||
div.style.width = this.fld.offsetWidth + "px";
|
||||
|
||||
|
||||
|
||||
// set mouseover functions for div
|
||||
// when mouse pointer leaves div, set a timeout to remove the list after an interval
|
||||
// when mouse enters div, kill the timeout so the list won't be removed
|
||||
//
|
||||
div.onmouseover = function(){ pointer.killTimeout() }
|
||||
div.onmouseout = function(){ pointer.resetTimeout() }
|
||||
|
||||
|
||||
// add DIV to document
|
||||
//
|
||||
document.getElementsByTagName("body")[0].appendChild(div);
|
||||
|
||||
|
||||
|
||||
// currently no item is highlighted
|
||||
//
|
||||
this.iHighlighted = 0;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// remove list after an interval
|
||||
//
|
||||
var pointer = this;
|
||||
this.toID = setTimeout(function () { pointer.clearSuggestions() }, this.oP.timeout);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.changeHighlight = function(key)
|
||||
{
|
||||
var list = _bsn.DOM.getElement("as_ul");
|
||||
if (!list)
|
||||
return false;
|
||||
|
||||
var n;
|
||||
|
||||
if (key == 40)
|
||||
n = this.iHighlighted + 1;
|
||||
else if (key == 38)
|
||||
n = this.iHighlighted - 1;
|
||||
|
||||
|
||||
if (n > list.childNodes.length)
|
||||
n = list.childNodes.length;
|
||||
if (n < 1)
|
||||
n = 1;
|
||||
|
||||
|
||||
this.setHighlight(n);
|
||||
}
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.setHighlight = function(n)
|
||||
{
|
||||
var list = _bsn.DOM.getElement("as_ul");
|
||||
if (!list)
|
||||
return false;
|
||||
|
||||
if (this.iHighlighted > 0)
|
||||
this.clearHighlight();
|
||||
|
||||
this.iHighlighted = Number(n);
|
||||
|
||||
list.childNodes[this.iHighlighted-1].className = "as_highlight";
|
||||
|
||||
|
||||
this.killTimeout();
|
||||
}
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.clearHighlight = function()
|
||||
{
|
||||
var list = _bsn.DOM.getElement("as_ul");
|
||||
if (!list)
|
||||
return false;
|
||||
|
||||
if (this.iHighlighted > 0)
|
||||
{
|
||||
list.childNodes[this.iHighlighted-1].className = "";
|
||||
this.iHighlighted = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.setHighlightedValue = function ()
|
||||
{
|
||||
if (this.iHighlighted)
|
||||
{
|
||||
this.sInput = this.fld.value = this.aSuggestions[ this.iHighlighted-1 ].value;
|
||||
|
||||
// move cursor to end of input (safari)
|
||||
//
|
||||
this.fld.focus();
|
||||
if (this.fld.selectionStart)
|
||||
this.fld.setSelectionRange(this.sInput.length, this.sInput.length);
|
||||
|
||||
|
||||
this.clearSuggestions();
|
||||
|
||||
// pass selected object to callback function, if exists
|
||||
//
|
||||
if (typeof(this.oP.callback) == "function")
|
||||
this.oP.callback( this.aSuggestions[this.iHighlighted-1] );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.killTimeout = function()
|
||||
{
|
||||
clearTimeout(this.toID);
|
||||
}
|
||||
|
||||
_bsn.AutoSuggest.prototype.resetTimeout = function()
|
||||
{
|
||||
clearTimeout(this.toID);
|
||||
var pointer = this;
|
||||
this.toID = setTimeout(function () { pointer.clearSuggestions() }, 1000);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.AutoSuggest.prototype.clearSuggestions = function ()
|
||||
{
|
||||
|
||||
this.killTimeout();
|
||||
|
||||
var ele = _bsn.DOM.getElement(this.idAs);
|
||||
var pointer = this;
|
||||
if (ele)
|
||||
{
|
||||
var fade = new _bsn.Fader(ele,1,0,250,function () { _bsn.DOM.removeElement(pointer.idAs) });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// AJAX PROTOTYPE _____________________________________________
|
||||
|
||||
|
||||
if (typeof(_bsn.Ajax) == "undefined")
|
||||
_bsn.Ajax = {}
|
||||
|
||||
|
||||
|
||||
_bsn.Ajax = function ()
|
||||
{
|
||||
this.req = {};
|
||||
this.isIE = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
_bsn.Ajax.prototype.makeRequest = function (url, meth, onComp, onErr)
|
||||
{
|
||||
|
||||
if (meth != "POST")
|
||||
meth = "GET";
|
||||
|
||||
this.onComplete = onComp;
|
||||
this.onError = onErr;
|
||||
|
||||
var pointer = this;
|
||||
|
||||
// branch for native XMLHttpRequest object
|
||||
if (window.XMLHttpRequest)
|
||||
{
|
||||
this.req = new XMLHttpRequest();
|
||||
this.req.onreadystatechange = function () { pointer.processReqChange() };
|
||||
this.req.open("GET", url, true); //
|
||||
this.req.send(null);
|
||||
// branch for IE/Windows ActiveX version
|
||||
}
|
||||
else if (window.ActiveXObject)
|
||||
{
|
||||
this.req = new ActiveXObject("Microsoft.XMLHTTP");
|
||||
if (this.req)
|
||||
{
|
||||
this.req.onreadystatechange = function () { pointer.processReqChange() };
|
||||
this.req.open(meth, url, true);
|
||||
this.req.send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_bsn.Ajax.prototype.processReqChange = function()
|
||||
{
|
||||
|
||||
// only if req shows "loaded"
|
||||
if (this.req.readyState == 4) {
|
||||
// only if "OK"
|
||||
if (this.req.status == 200)
|
||||
{
|
||||
this.onComplete( this.req );
|
||||
} else {
|
||||
this.onError( this.req.status );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// DOM PROTOTYPE _____________________________________________
|
||||
|
||||
|
||||
if (typeof(_bsn.DOM) == "undefined")
|
||||
_bsn.DOM = {}
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.DOM.createElement = function ( type, attr, cont, html )
|
||||
{
|
||||
var ne = document.createElement( type );
|
||||
if (!ne)
|
||||
return false;
|
||||
|
||||
for (var a in attr)
|
||||
ne[a] = attr[a];
|
||||
|
||||
if (typeof(cont) == "string" && !html)
|
||||
ne.appendChild( document.createTextNode(cont) );
|
||||
else if (typeof(cont) == "string" && html)
|
||||
ne.innerHTML = cont;
|
||||
else if (typeof(cont) == "object")
|
||||
ne.appendChild( cont );
|
||||
|
||||
return ne;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.DOM.clearElement = function ( id )
|
||||
{
|
||||
var ele = this.getElement( id );
|
||||
|
||||
if (!ele)
|
||||
return false;
|
||||
|
||||
while (ele.childNodes.length)
|
||||
ele.removeChild( ele.childNodes[0] );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.DOM.removeElement = function ( ele )
|
||||
{
|
||||
var e = this.getElement(ele);
|
||||
|
||||
if (!e)
|
||||
return false;
|
||||
else if (e.parentNode.removeChild(e))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.DOM.replaceContent = function ( id, cont, html )
|
||||
{
|
||||
var ele = this.getElement( id );
|
||||
|
||||
if (!ele)
|
||||
return false;
|
||||
|
||||
this.clearElement( ele );
|
||||
|
||||
if (typeof(cont) == "string" && !html)
|
||||
ele.appendChild( document.createTextNode(cont) );
|
||||
else if (typeof(cont) == "string" && html)
|
||||
ele.innerHTML = cont;
|
||||
else if (typeof(cont) == "object")
|
||||
ele.appendChild( cont );
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.DOM.getElement = function ( ele )
|
||||
{
|
||||
if (typeof(ele) == "undefined")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (typeof(ele) == "string")
|
||||
{
|
||||
var re = document.getElementById( ele );
|
||||
if (!re)
|
||||
return false;
|
||||
else if (typeof(re.appendChild) != "undefined" ) {
|
||||
return re;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (typeof(ele.appendChild) != "undefined")
|
||||
return ele;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.DOM.appendChildren = function ( id, arr )
|
||||
{
|
||||
var ele = this.getElement( id );
|
||||
|
||||
if (!ele)
|
||||
return false;
|
||||
|
||||
|
||||
if (typeof(arr) != "object")
|
||||
return false;
|
||||
|
||||
for (var i=0;i<arr.length;i++)
|
||||
{
|
||||
var cont = arr[i];
|
||||
if (typeof(cont) == "string")
|
||||
ele.appendChild( document.createTextNode(cont) );
|
||||
else if (typeof(cont) == "object")
|
||||
ele.appendChild( cont );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.DOM.getPos = function ( ele )
|
||||
{
|
||||
var ele = this.getElement(ele);
|
||||
|
||||
var obj = ele;
|
||||
|
||||
var curleft = 0;
|
||||
if (obj.offsetParent)
|
||||
{
|
||||
while (obj.offsetParent)
|
||||
{
|
||||
curleft += obj.offsetLeft
|
||||
obj = obj.offsetParent;
|
||||
}
|
||||
}
|
||||
else if (obj.x)
|
||||
curleft += obj.x;
|
||||
|
||||
|
||||
var obj = ele;
|
||||
|
||||
var curtop = 0;
|
||||
if (obj.offsetParent)
|
||||
{
|
||||
while (obj.offsetParent)
|
||||
{
|
||||
curtop += obj.offsetTop
|
||||
obj = obj.offsetParent;
|
||||
}
|
||||
}
|
||||
else if (obj.y)
|
||||
curtop += obj.y;
|
||||
|
||||
return {x:curleft, y:curtop}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// FADER PROTOTYPE _____________________________________________
|
||||
|
||||
|
||||
|
||||
if (typeof(_bsn.Fader) == "undefined")
|
||||
_bsn.Fader = {}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.Fader = function (ele, from, to, fadetime, callback)
|
||||
{
|
||||
if (!ele)
|
||||
return false;
|
||||
|
||||
this.ele = ele;
|
||||
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
|
||||
this.callback = callback;
|
||||
|
||||
this.nDur = fadetime;
|
||||
|
||||
this.nInt = 50;
|
||||
this.nTime = 0;
|
||||
|
||||
var p = this;
|
||||
this.nID = setInterval(function() { p._fade() }, this.nInt);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
_bsn.Fader.prototype._fade = function()
|
||||
{
|
||||
this.nTime += this.nInt;
|
||||
|
||||
var ieop = Math.round( this._tween(this.nTime, this.from, this.to, this.nDur) * 100 );
|
||||
var op = ieop / 100;
|
||||
|
||||
if (this.ele.filters) // internet explorer
|
||||
{
|
||||
try
|
||||
{
|
||||
this.ele.filters.item("DXImageTransform.Microsoft.Alpha").opacity = ieop;
|
||||
} catch (e) {
|
||||
// If it is not set initially, the browser will throw an error. This will set it if it is not set yet.
|
||||
this.ele.style.filter = 'progid:DXImageTransform.Microsoft.Alpha(opacity='+ieop+')';
|
||||
}
|
||||
}
|
||||
else // other browsers
|
||||
{
|
||||
this.ele.style.opacity = op;
|
||||
}
|
||||
|
||||
|
||||
if (this.nTime == this.nDur)
|
||||
{
|
||||
clearInterval( this.nID );
|
||||
if (this.callback != undefined)
|
||||
this.callback();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
_bsn.Fader.prototype._tween = function(t,b,c,d)
|
||||
{
|
||||
return b + ( (c-b) * (t/d) );
|
||||
}
|
||||
335
simplewiki/models.py
Normal file
@@ -0,0 +1,335 @@
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.db import models
|
||||
from django.db.models import signals
|
||||
from django.contrib.auth.models import User
|
||||
from markdown import markdown
|
||||
from django import forms
|
||||
from django.core.urlresolvers import reverse
|
||||
import difflib
|
||||
import os
|
||||
from settings import *
|
||||
|
||||
class ShouldHaveExactlyOneRootSlug(Exception):
|
||||
pass
|
||||
|
||||
class Article(models.Model):
|
||||
"""Wiki article referring to Revision model for actual content.
|
||||
'slug' and 'parent' field should be maintained centrally, since users
|
||||
aren't allowed to change them, anyways.
|
||||
"""
|
||||
|
||||
title = models.CharField(max_length=512, verbose_name=_('Article title'),
|
||||
blank=False)
|
||||
slug = models.SlugField(max_length=100, verbose_name=_('slug'),
|
||||
help_text=_('Letters, numbers, underscore and hyphen.'
|
||||
' Do not use reserved words \'create\','
|
||||
' \'history\' and \'edit\'.'),
|
||||
blank=True)
|
||||
created_by = models.ForeignKey(User, verbose_name=_('Created by'), blank=True, null=True)
|
||||
created_on = models.DateTimeField(auto_now_add = 1)
|
||||
modified_on = models.DateTimeField(auto_now_add = 1)
|
||||
parent = models.ForeignKey('self', verbose_name=_('Parent article slug'),
|
||||
help_text=_('Affects URL structure and possibly inherits permissions'),
|
||||
null=True, blank=True)
|
||||
locked = models.BooleanField(default=False, verbose_name=_('Locked for editing'))
|
||||
permissions = models.ForeignKey('Permission', verbose_name=_('Permissions'),
|
||||
blank=True, null=True,
|
||||
help_text=_('Permission group'))
|
||||
current_revision = models.OneToOneField('Revision', related_name='current_rev',
|
||||
blank=True, null=True, editable=True)
|
||||
related = models.ManyToManyField('self', verbose_name=_('Related articles'), symmetrical=True,
|
||||
help_text=_('Sets a symmetrical relation other articles'),
|
||||
blank=True, null=True)
|
||||
|
||||
def attachments(self):
|
||||
return ArticleAttachment.objects.filter(article__exact = self)
|
||||
|
||||
@classmethod
|
||||
def get_root(cls):
|
||||
"""Return the root article, which should ALWAYS exist..
|
||||
except the very first time the wiki is loaded, in which
|
||||
case the user is prompted to create this article."""
|
||||
try:
|
||||
return Article.objects.filter(parent__exact = None)[0]
|
||||
except:
|
||||
raise ShouldHaveExactlyOneRootSlug()
|
||||
|
||||
def get_url(self):
|
||||
"""Return the Wiki URL for an article"""
|
||||
if self.parent:
|
||||
return self.parent.get_url() + '/' + self.slug
|
||||
else:
|
||||
return self.slug
|
||||
|
||||
def get_abs_url(self):
|
||||
"""Return the absolute path for an article. This is necessary in cases
|
||||
where the template system isn't used for generating URLs..."""
|
||||
# TODO: Remove and create a reverse() lookup.
|
||||
return WIKI_BASE + self.get_url()
|
||||
|
||||
@models.permalink
|
||||
def get_absolute_url(self):
|
||||
return ('wiki_view', [self.get_url()])
|
||||
|
||||
@classmethod
|
||||
def get_url_reverse(cls, path, article, return_list=[]):
|
||||
"""Lookup a URL and return the corresponding set of articles
|
||||
in the path."""
|
||||
if path == []:
|
||||
return return_list + [article]
|
||||
# Lookup next child in path
|
||||
try:
|
||||
a = Article.objects.get(parent__exact = article, slug__exact=str(path[0]))
|
||||
return cls.get_url_reverse(path[1:], a, return_list+[article])
|
||||
except Exception, e:
|
||||
return None
|
||||
|
||||
def can_read(self, user):
|
||||
""" Check read permissions and return True/False."""
|
||||
if self.permissions:
|
||||
perms = self.permissions.can_read.all()
|
||||
return perms.count() == 0 or (user in perms)
|
||||
else:
|
||||
return self.parent.can_read(user) if self.parent else True
|
||||
|
||||
def can_write(self, user):
|
||||
""" Check write permissions and return True/False."""
|
||||
if self.permissions:
|
||||
perms = self.permissions.can_write.all()
|
||||
return perms.count() == 0 or (user in perms)
|
||||
else:
|
||||
return self.parent.can_write(user) if self.parent else True
|
||||
|
||||
def can_write_l(self, user):
|
||||
"""Check write permissions and locked status"""
|
||||
return not self.locked and self.can_write(user)
|
||||
|
||||
def can_attach(self, user):
|
||||
return self.can_write_l(user) and (WIKI_ALLOW_ANON_ATTACHMENTS or not user.is_anonymous())
|
||||
|
||||
def __unicode__(self):
|
||||
if self.slug == '' and not self.parent:
|
||||
return unicode(_('Root article'))
|
||||
else:
|
||||
return self.get_url()
|
||||
|
||||
class Meta:
|
||||
unique_together = (('slug', 'parent'),)
|
||||
verbose_name = _('Article')
|
||||
verbose_name_plural = _('Articles')
|
||||
|
||||
def get_attachment_filepath(instance, filename):
|
||||
"""Store file, appending new extension for added security"""
|
||||
dir_ = WIKI_ATTACHMENTS + instance.article.get_url()
|
||||
dir_ = '/'.join(filter(lambda x: x!='', dir_.split('/')))
|
||||
if not os.path.exists(WIKI_ATTACHMENTS_ROOT + dir_):
|
||||
os.makedirs(WIKI_ATTACHMENTS_ROOT + dir_)
|
||||
return dir_ + '/' + filename + '.upload'
|
||||
|
||||
class ArticleAttachment(models.Model):
|
||||
article = models.ForeignKey(Article, verbose_name=_('Article'))
|
||||
file = models.FileField(max_length=255, upload_to=get_attachment_filepath, verbose_name=_('Attachment'))
|
||||
uploaded_by = models.ForeignKey(User, blank=True, verbose_name=_('Uploaded by'), null=True)
|
||||
uploaded_on = models.DateTimeField(auto_now_add = True, verbose_name=_('Upload date'))
|
||||
|
||||
def download_url(self):
|
||||
return reverse('wiki_view_attachment', args=(self.article.get_url(), self.filename()))
|
||||
|
||||
def filename(self):
|
||||
return '.'.join(self.file.name.split('/')[-1].split('.')[:-1])
|
||||
|
||||
def get_size(self):
|
||||
try:
|
||||
size = self.file.size
|
||||
except OSError:
|
||||
size = 0
|
||||
return size
|
||||
|
||||
def filename(self):
|
||||
return '.'.join(self.file.name.split('/')[-1].split('.')[:-1])
|
||||
|
||||
def is_image(self):
|
||||
fname = self.filename().split('.')
|
||||
if len(fname) > 1 and fname[-1].lower() in WIKI_IMAGE_EXTENSIONS:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_thumb(self):
|
||||
return self.get_thumb_impl(*WIKI_IMAGE_THUMB_SIZE)
|
||||
|
||||
def get_thumb_small(self):
|
||||
return self.get_thumb_impl(*WIKI_IMAGE_THUMB_SIZE_SMALL)
|
||||
|
||||
def mk_thumbs(self):
|
||||
self.mk_thumb(*WIKI_IMAGE_THUMB_SIZE, **{'force':True})
|
||||
self.mk_thumb(*WIKI_IMAGE_THUMB_SIZE_SMALL, **{'force':True})
|
||||
|
||||
def mk_thumb(self, width, height, force=False):
|
||||
"""Requires Python Imaging Library (PIL)"""
|
||||
if not self.get_size():
|
||||
return False
|
||||
|
||||
if not self.is_image():
|
||||
return False
|
||||
|
||||
base_path = os.path.dirname(self.file.path)
|
||||
orig_name = self.filename().split('.')
|
||||
thumb_filename = "%s__thumb__%d_%d.%s" % ('.'.join(orig_name[:-1]), width, height, orig_name[-1])
|
||||
thumb_filepath = "%s%s%s" % (base_path, os.sep, thumb_filename)
|
||||
|
||||
if force or not os.path.exists(thumb_filepath):
|
||||
try:
|
||||
import Image
|
||||
img = Image.open(self.file.path)
|
||||
img.thumbnail((width,height), Image.ANTIALIAS)
|
||||
img.save(thumb_filepath)
|
||||
except IOError:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def get_thumb_impl(self, width, height):
|
||||
"""Requires Python Imaging Library (PIL)"""
|
||||
|
||||
if not self.get_size():
|
||||
return False
|
||||
|
||||
if not self.is_image():
|
||||
return False
|
||||
|
||||
self.mk_thumb(width, height)
|
||||
|
||||
orig_name = self.filename().split('.')
|
||||
thumb_filename = "%s__thumb__%d_%d.%s" % ('.'.join(orig_name[:-1]), width, height, orig_name[-1])
|
||||
thumb_url = settings.MEDIA_URL + WIKI_ATTACHMENTS + self.article.get_url() +'/' + thumb_filename
|
||||
|
||||
return thumb_url
|
||||
|
||||
def __unicode__(self):
|
||||
return self.filename()
|
||||
|
||||
class Revision(models.Model):
|
||||
|
||||
article = models.ForeignKey(Article, verbose_name=_('Article'))
|
||||
revision_text = models.CharField(max_length=255, blank=True, null=True,
|
||||
verbose_name=_('Description of change'))
|
||||
revision_user = models.ForeignKey(User, verbose_name=_('Modified by'),
|
||||
blank=True, null=True, related_name='wiki_revision_user')
|
||||
revision_date = models.DateTimeField(auto_now_add = True, verbose_name=_('Revision date'))
|
||||
contents = models.TextField(verbose_name=_('Contents (Use MarkDown format)'))
|
||||
contents_parsed = models.TextField(editable=False, blank=True, null=True)
|
||||
counter = models.IntegerField(verbose_name=_('Revision#'), default=1, editable=False)
|
||||
previous_revision = models.ForeignKey('self', blank=True, null=True, editable=False)
|
||||
|
||||
def get_user(self):
|
||||
return self.revision_user if self.revision_user else _('Anonymous')
|
||||
|
||||
def save(self, **kwargs):
|
||||
# Check if contents have changed... if not, silently ignore save
|
||||
if self.article and self.article.current_revision:
|
||||
if self.article.current_revision.contents == self.contents:
|
||||
return
|
||||
else:
|
||||
import datetime
|
||||
self.article.modified_on = datetime.datetime.now()
|
||||
self.article.save()
|
||||
|
||||
# Increment counter according to previous revision
|
||||
previous_revision = Revision.objects.filter(article=self.article).order_by('-counter')
|
||||
if previous_revision.count() > 0:
|
||||
if previous_revision.count() > previous_revision[0].counter:
|
||||
self.counter = previous_revision.count() + 1
|
||||
else:
|
||||
self.counter = previous_revision[0].counter + 1
|
||||
else:
|
||||
self.counter = 1
|
||||
self.previous_revision = self.article.current_revision
|
||||
|
||||
# Create pre-parsed contents - no need to parse on-the-fly
|
||||
ext = WIKI_MARKDOWN_EXTENSIONS
|
||||
ext += ["wikilinks(base_url=%s/)" % reverse('wiki_view', args=('',))]
|
||||
self.contents_parsed = markdown(self.contents,
|
||||
extensions=ext,
|
||||
safe_mode='escape',)
|
||||
super(Revision, self).save(**kwargs)
|
||||
|
||||
def delete(self, **kwargs):
|
||||
"""If a current revision is deleted, then regress to the previous
|
||||
revision or insert a stub, if no other revisions are available"""
|
||||
article = self.article
|
||||
if article.current_revision == self:
|
||||
prev_revision = Revision.objects.filter(article__exact = article,
|
||||
pk__not = self.pk).order_by('-counter')
|
||||
if prev_revision:
|
||||
article.current_revision = prev_revision[0]
|
||||
article.save()
|
||||
else:
|
||||
r = Revision(article=article,
|
||||
revision_user = article.created_by)
|
||||
r.contents = unicode(_('Auto-generated stub'))
|
||||
r.revision_text= unicode(_('Auto-generated stub'))
|
||||
r.save()
|
||||
article.current_revision = r
|
||||
article.save()
|
||||
super(Revision, self).delete(**kwargs)
|
||||
|
||||
def get_diff(self):
|
||||
if self.previous_revision:
|
||||
previous = self.previous_revision.contents.splitlines(1)
|
||||
else:
|
||||
previous = []
|
||||
|
||||
# Todo: difflib.HtmlDiff would look pretty for our history pages!
|
||||
diff = difflib.unified_diff(previous, self.contents.splitlines(1))
|
||||
# let's skip the preamble
|
||||
diff.next(); diff.next(); diff.next()
|
||||
|
||||
for d in diff:
|
||||
yield d
|
||||
|
||||
def __unicode__(self):
|
||||
return "r%d" % self.counter
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('article revision')
|
||||
verbose_name_plural = _('article revisions')
|
||||
|
||||
class Permission(models.Model):
|
||||
permission_name = models.CharField(max_length = 255, verbose_name=_('Permission name'))
|
||||
can_write = models.ManyToManyField(User, blank=True, null=True, related_name='write',
|
||||
help_text=_('Select none to grant anonymous access.'))
|
||||
can_read = models.ManyToManyField(User, blank=True, null=True, related_name='read',
|
||||
help_text=_('Select none to grant anonymous access.'))
|
||||
def __unicode__(self):
|
||||
return self.permission_name
|
||||
class Meta:
|
||||
verbose_name = _('Article permission')
|
||||
verbose_name_plural = _('Article permissions')
|
||||
|
||||
class RevisionForm(forms.ModelForm):
|
||||
contents = forms.CharField(label=_('Contents'), widget=forms.Textarea(attrs={'rows':8, 'cols':50}))
|
||||
class Meta:
|
||||
model = Revision
|
||||
fields = ['contents', 'revision_text']
|
||||
class RevisionFormWithTitle(forms.ModelForm):
|
||||
title = forms.CharField(label=_('Title'))
|
||||
class Meta:
|
||||
model = Revision
|
||||
fields = ['title', 'contents', 'revision_text']
|
||||
class CreateArticleForm(RevisionForm):
|
||||
title = forms.CharField(label=_('Title'))
|
||||
class Meta:
|
||||
model = Revision
|
||||
fields = ['title', 'contents',]
|
||||
|
||||
def set_revision(sender, *args, **kwargs):
|
||||
"""Signal handler to ensure that a new revision is always chosen as the
|
||||
current revision - automatically. It simplifies stuff greatly. Also
|
||||
stores previous revision for diff-purposes"""
|
||||
instance = kwargs['instance']
|
||||
created = kwargs['created']
|
||||
if created and instance.article:
|
||||
instance.article.current_revision = instance
|
||||
instance.article.save()
|
||||
|
||||
signals.post_save.connect(set_revision, Revision)
|
||||
111
simplewiki/settings.py
Normal file
@@ -0,0 +1,111 @@
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
||||
# Default settings.. overwrite in your own settings.py
|
||||
|
||||
# Planned feature.
|
||||
WIKI_USE_MARKUP_WIDGET = True
|
||||
|
||||
####################
|
||||
# LOGIN PROTECTION #
|
||||
####################
|
||||
# Before setting the below parameters, please note that permissions can
|
||||
# be set in the django permission system on individual articles and their
|
||||
# child articles. In this way you can add a user group and give them
|
||||
# special permissions, be it on the root article or some other. Permissions
|
||||
# are inherited on lower levels.
|
||||
|
||||
# Adds standard django login protection for viewing
|
||||
WIKI_REQUIRE_LOGIN_VIEW = getattr(settings, 'SIMPLE_WIKI_REQUIRE_LOGIN_VIEW',
|
||||
False)
|
||||
|
||||
# Adds standard django login protection for editing
|
||||
WIKI_REQUIRE_LOGIN_EDIT = getattr(settings, 'SIMPLE_WIKI_REQUIRE_LOGIN_EDIT',
|
||||
True)
|
||||
|
||||
####################
|
||||
# ATTACHMENTS #
|
||||
####################
|
||||
|
||||
# This should be a directory that's writable for the web server.
|
||||
# It's relative to the MEDIA_ROOT.
|
||||
WIKI_ATTACHMENTS = getattr(settings, 'SIMPLE_WIKI_ATTACHMENTS',
|
||||
'simplewiki/attachments/')
|
||||
|
||||
# If false, attachments will completely disappear
|
||||
WIKI_ALLOW_ATTACHMENTS = getattr(settings, 'SIMPLE_WIKI_ALLOW_ATTACHMENTS',
|
||||
True)
|
||||
|
||||
# If WIKI_REQUIRE_LOGIN_EDIT is False, then attachments can still be disallowed
|
||||
WIKI_ALLOW_ANON_ATTACHMENTS = getattr(settings, 'SIMPLE_WIKI_ALLOW_ANON_ATTACHMENTS', False)
|
||||
|
||||
# Attachments are automatically stored with a dummy extension and delivered
|
||||
# back to the user with their original extension.
|
||||
# This setting does not add server security, but might add user security
|
||||
# if set -- or force users to use standard formats, which might also
|
||||
# be a good idea.
|
||||
# Example: ('pdf', 'doc', 'gif', 'jpeg', 'jpg', 'png')
|
||||
WIKI_ATTACHMENTS_ALLOWED_EXTENSIONS = getattr(settings, 'SIMPLE_WIKI_ATTACHMENTS_ALLOWED_EXTENSIONS',
|
||||
None)
|
||||
|
||||
# At the moment this variable should not be modified, because
|
||||
# it breaks compatibility with the normal Django FileField and uploading
|
||||
# from the admin interface.
|
||||
WIKI_ATTACHMENTS_ROOT = settings.MEDIA_ROOT
|
||||
|
||||
# Bytes! Default: 1 MB.
|
||||
WIKI_ATTACHMENTS_MAX = getattr(settings, 'SIMPLE_WIKI_ATTACHMENTS_MAX',
|
||||
1 * 1024 * 1024)
|
||||
|
||||
# Allow users to edit titles of pages
|
||||
# (warning! titles are not maintained in the revision system.)
|
||||
WIKI_ALLOW_TITLE_EDIT = getattr(settings, 'SIMPLE_WIKI_ALLOW_TITLE_EDIT', False)
|
||||
|
||||
# Global context processors
|
||||
# These are appended to TEMPLATE_CONTEXT_PROCESSORS in your Django settings
|
||||
# whenever the wiki is in use. It can be used as a simple, but effective
|
||||
# way of extending simplewiki without touching original code (and thus keeping
|
||||
# everything easily maintainable)
|
||||
WIKI_CONTEXT_PREPROCESSORS = getattr(settings, 'SIMPLE_WIKI_CONTEXT_PREPROCESSORS',
|
||||
())
|
||||
|
||||
####################
|
||||
# AESTHETICS #
|
||||
####################
|
||||
|
||||
# List of extensions to be used by Markdown. Custom extensions (i.e., with file
|
||||
# names of mdx_*.py) can be dropped into the simplewiki (or project) directory
|
||||
# and then added to this list to be utilized. Wikilinks is always enabled.
|
||||
#
|
||||
# For more information, see
|
||||
# http://www.freewisdom.org/projects/python-markdown/Available_Extensions
|
||||
WIKI_MARKDOWN_EXTENSIONS = getattr(settings, 'SIMPLE_WIKI_MARKDOWN_EXTENSIONS',
|
||||
['footnotes',
|
||||
'tables',
|
||||
'headerid',
|
||||
'fenced_code',
|
||||
'def_list',
|
||||
'codehilite',
|
||||
'abbr',
|
||||
'toc',
|
||||
'camelcase', # CamelCase-style wikilinks
|
||||
'video', # In-line embedding for YouTube, etc.
|
||||
#'image' # In-line embedding for images - too many bugs. It has a failed REG EXP.
|
||||
])
|
||||
|
||||
|
||||
WIKI_IMAGE_EXTENSIONS = getattr(settings,
|
||||
'SIMPLE_WIKI_IMAGE_EXTENSIONS',
|
||||
('jpg','jpeg','gif','png','tiff','bmp'))
|
||||
# Planned features
|
||||
WIKI_PAGE_WIDTH = getattr(settings,
|
||||
'SIMPLE_WIKI_PAGE_WIDTH', "100%")
|
||||
|
||||
WIKI_PAGE_ALIGN = getattr(settings,
|
||||
'SIMPLE_WIKI_PAGE_ALIGN', "center")
|
||||
|
||||
WIKI_IMAGE_THUMB_SIZE = getattr(settings,
|
||||
'SIMPLE_WIKI_IMAGE_THUMB_SIZE', (200,150))
|
||||
|
||||
WIKI_IMAGE_THUMB_SIZE_SMALL = getattr(settings,
|
||||
'SIMPLE_WIKI_IMAGE_THUMB_SIZE_SMALL', (100,100))
|
||||
197
simplewiki/templates/simplewiki_base.html
Normal file
@@ -0,0 +1,197 @@
|
||||
{% load i18n simplewiki_utils %}
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html lang="{{ LANGUAGE_CODE }}">
|
||||
<head>
|
||||
<title>{{ wiki_title }}</title>
|
||||
<link rel="stylesheet" media="screen,print" href="/static/simplewiki/css/base.css" />
|
||||
<link rel="stylesheet" media="print" href="/static/simplewiki/css/base_print.css" />
|
||||
<link rel="stylesheet" href="/static/simplewiki/css/autosuggest_inquisitor.css" />
|
||||
<script type="text/javascript" src="/static/simplewiki/js/bsn.AutoSuggest_c_2.0.js"></script>
|
||||
<script type="text/javascript">
|
||||
function set_related_article_id(s) {
|
||||
document.getElementById('wiki_related_input_id').value = s.id;
|
||||
document.getElementById('wiki_related_input_submit').disabled=false;
|
||||
}
|
||||
var x = window.onload;
|
||||
window.onload = function(){
|
||||
var options = {
|
||||
script: "{% url search_related wiki_article.get_url %}/?self={{wiki_article.pk }}&",
|
||||
json: true,
|
||||
varname: "query",
|
||||
maxresults: 35,
|
||||
callback: set_related_article_id,
|
||||
noresults: "{% trans "Nothing found!" %}"
|
||||
};
|
||||
var as = new AutoSuggest('wiki_related_input', options);
|
||||
if (typeof x == 'function')
|
||||
x();
|
||||
}
|
||||
</script>
|
||||
{% block wiki_head %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>{% block wiki_page_title %}{% endblock %}</h1>
|
||||
<hr />
|
||||
|
||||
{% block wiki_panel %}
|
||||
|
||||
<div id="wiki_panel">
|
||||
|
||||
<div class="wiki_box">
|
||||
<div class="wiki_box_header">
|
||||
<div class="wiki_box_corner"></div>
|
||||
<div class="wiki_box_bar"></div>
|
||||
</div>
|
||||
<div class="wiki_box_contents">
|
||||
<div class="wiki_box_title">{% trans "Search" %}</div>
|
||||
<form method="POST" action='{% url wiki_search_articles wiki_article.get_url %}'>{% csrf_token %}
|
||||
<input type="text" name="value" id="wiki_search_input" style="width: 72%" value="{{wiki_search_query|escape}}"/>
|
||||
<input type="submit" id="wiki_search_input_submit" value={% trans "Go!" %} style="width: 20%" />
|
||||
</form>
|
||||
</div>
|
||||
<div class="wiki_box_footer">
|
||||
<div class="wiki_box_corner"></div>
|
||||
<div class="wiki_box_bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wiki_box">
|
||||
<div class="wiki_box_header">
|
||||
<div class="wiki_box_corner"></div>
|
||||
<div class="wiki_box_bar"></div>
|
||||
</div>
|
||||
<div class="wiki_box_contents" style="position: relative;">
|
||||
|
||||
<div style="border: 2px outset #CCC; width: 250px; padding: 10px; background-color: #FFF; position: absolute; right: 100px; top: -80px; display: none; color: #000;" id="wiki_create_form">
|
||||
{% with "this.wiki_article_name.value.replace(/([^a-zA-Z0-9\-])/g, '')+'/_create/'" as theaction %}
|
||||
<form method="GET" onsubmit="this.action='{% url wiki_view "" %}{{ wiki_article.get_url }}/' + {{ theaction }};">
|
||||
{% endwith %}
|
||||
<h2>{% trans "Create article" %}</h2>
|
||||
<p>
|
||||
<label for="id_wiki_article_name">{% trans "Title of article" %}</label>
|
||||
<input type="text" name="wiki_article_name" id="id_wiki_article_name" /><br/>
|
||||
<label for="id_wiki_article_is_child">{% trans "Create as a child of current article"%}</label>
|
||||
<input type="checkbox" name="wiki_article_is_child" id="id_wiki_artcile_is_child" disabled="true" checked={%if wiki_article%}"yes"{%else%}"no"{%endif%}>
|
||||
</p>
|
||||
<p>
|
||||
<input type="button" class="button" value="{% trans "Cancel" %}" style="display: inline-block; margin-right: 2px;" onclick="document.getElementById('wiki_create_form').style.display='none';" />
|
||||
<input type="submit" class="button" value="{% trans "Next" %} >" style="display: inline-block; margin-right: 2px; font-weight: bold;" />
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
<p>
|
||||
|
||||
{% if wiki_article %}
|
||||
<input type="button" onclick="javascript:location.href='{% url wiki_view wiki_article.get_url %}'" value="View" style="width: 100%;" /><br />
|
||||
<input type="button" onclick="javascript:location.href='{% url wiki_edit wiki_article.get_url %}'" value="Edit" style="width: 100%;"{% if not wiki_write %} disabled="true"{%endif%} /><br />
|
||||
<input type="button" onclick="javascript:location.href='{% url wiki_history wiki_article.get_url 1 %}'" value="History" style="width: 100%;" />
|
||||
{% endif %}
|
||||
<input type="button" onclick="document.getElementById('wiki_create_form').style.display='block';" value="{% trans "Create article" %}" style="width: 100%; margin-bottom: 2px;" class="button" />
|
||||
<input type="button" onclick="javascript:location.href='{% url wiki_random wiki_article.get_url %}'" value="{% trans "Random article" %}" style="width: 100%; margin-bottom: 2px;" class="button" />
|
||||
</p>
|
||||
{% if wiki_article %}
|
||||
{% if wiki_article.locked %}
|
||||
<p><strong>{% trans "This article has been locked" %}</strong></p>
|
||||
{% endif %}
|
||||
<p>
|
||||
<i>{% trans "Last modified" %}: {{ wiki_article.modified_on|date }}, {{ wiki_article.modified_on|time }}</i>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="wiki_box_footer">
|
||||
<div class="wiki_box_corner"></div>
|
||||
<div class="wiki_box_bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% if wiki_article %}
|
||||
<div class="wiki_box">
|
||||
<div class="wiki_box_header">
|
||||
<div class="wiki_box_corner"></div>
|
||||
<div class="wiki_box_bar"></div>
|
||||
</div>
|
||||
<div class="wiki_box_contents">
|
||||
|
||||
<div class="wiki_box_title">{% trans "Related articles" %}</div>
|
||||
{% if wiki_article.related.all %}
|
||||
<p>
|
||||
{% for rel in wiki_article.related.all %}
|
||||
<span class="related">
|
||||
{% if wiki_write %}
|
||||
<a href="javascript:if(confirm('{% trans "Are you sure, you want to delete this relation?" %}')) { location.href='{% url wiki_remove_relation wiki_article.get_url rel.id%}'; }">
|
||||
<img src="{{ "simplewiki/img/delete.gif"|prepend_media_url }}" alt="{% trans "remove relation" %}" />
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url wiki_view rel.get_url %}">{{rel.title}}</a>
|
||||
</span>
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% else %}
|
||||
<p><i>({% trans "none" %})</i></p>
|
||||
{% endif %}
|
||||
{% if wiki_write %}
|
||||
<form method="POST" action="{% url add_related wiki_article.get_url %}">{% csrf_token %}
|
||||
<input type="text" name="value" id="wiki_related_input" style="width: 72%" />
|
||||
<input type="submit" id="wiki_related_input_submit" disabled="true" value="Add" style="width: 20%" />
|
||||
<input type="hidden" name="id" value="" id="wiki_related_input_id" />
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<div class="wiki_box_footer">
|
||||
<div class="wiki_box_corner"></div>
|
||||
<div class="wiki_box_bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wiki_box">
|
||||
<div class="wiki_box_header">
|
||||
<div class="wiki_box_corner"></div>
|
||||
<div class="wiki_box_bar"></div>
|
||||
</div>
|
||||
<div class="wiki_box_contents">
|
||||
|
||||
<div class="wiki_box_title">{% trans "Attachments" %}</div>
|
||||
{% if wiki_article.attachments %}
|
||||
<ul>
|
||||
{% for a in wiki_article.attachments %}
|
||||
<li><a href="{{a.download_url}}">{{a.filename|slice:":13"|slice:":-3" }}{% if a.filename|slice:"10:" %}...{{ a.filename|slice:"-3:" }}{% endif %}</a> ({{a.get_size|filesizeformat}})</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p><i>({% trans "none" %})</i></p>
|
||||
{% endif %}
|
||||
|
||||
{% if wiki_attachments_write %}
|
||||
<form method="POST" action="{% url add_attachment wiki_article.get_url %}" enctype="multipart/form-data" target="wiki_attach_frame">{% csrf_token %}
|
||||
<input type="file" class="fileinput" name="attachment" size="10" /><br />
|
||||
<input type="checkbox" name="overwrite" id="wiki_attach_overwrite" value="1" /> Overwrite same filename
|
||||
<p><input type="submit" value="Attach" /></p>
|
||||
<div id="wiki_attach_progress_container">
|
||||
<div id="wiki_attach_progress"><br /></div>
|
||||
</div>
|
||||
</form>
|
||||
<iframe name="wiki_attach_frame" style="display:none"></iframe>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<div class="wiki_box_footer">
|
||||
<div class="wiki_box_corner"></div>
|
||||
<div class="wiki_box_bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block wiki_body %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
16
simplewiki/templates/simplewiki_create.html
Normal file
@@ -0,0 +1,16 @@
|
||||
{% extends "simplewiki_base.html" %}
|
||||
{% load i18n simplewiki_utils %}
|
||||
{% block wiki_page_title %}
|
||||
Create article
|
||||
{% endblock %}
|
||||
{% block wiki_body %}
|
||||
<form method="POST" id="wiki_revision">{% csrf_token %}
|
||||
<table id="wiki_revision_table">
|
||||
{{ wiki_form }}
|
||||
<tr>
|
||||
<td colspan="2" align="right">
|
||||
<input type="submit" value="{% trans "Create article" %}" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
{% endblock %}
|
||||
17
simplewiki/templates/simplewiki_edit.html
Normal file
@@ -0,0 +1,17 @@
|
||||
{% extends "simplewiki_base.html" %}
|
||||
{% load i18n simplewiki_utils %}
|
||||
{% block wiki_page_title %}
|
||||
{{wiki_article.title}}
|
||||
{% endblock %}
|
||||
|
||||
{% block wiki_body %}
|
||||
<form method="POST" id="wiki_revision">{% csrf_token %}
|
||||
<table id="wiki_revision_table">
|
||||
{{ wiki_form }}
|
||||
<tr>
|
||||
<td colspan="2" align="right">
|
||||
<input type="submit" value="{% trans "Edit article" %}" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
{% endblock %}
|
||||
98
simplewiki/templates/simplewiki_error.html
Normal file
@@ -0,0 +1,98 @@
|
||||
{% extends "simplewiki_base.html" %}
|
||||
{% load i18n simplewiki_utils %}
|
||||
{% block wiki_page_title %}
|
||||
Oops...
|
||||
{% endblock %}
|
||||
|
||||
{% block wiki_body %}
|
||||
<div class="wiki_error">
|
||||
{{ wiki_error|safe }}
|
||||
|
||||
{% if wiki_err_notfound %}
|
||||
{% if wiki_url %}
|
||||
<p>
|
||||
The page you requested could not be found.
|
||||
Click <a href="{% url wiki_create wiki_url %}">here</a> to create it.
|
||||
</p>
|
||||
{% else %}
|
||||
<p>
|
||||
Or maybe rather: Congratulations! It seems that there's no root
|
||||
article, which is probably because you just installed simple-wiki
|
||||
and your installation is working. Now you can create the root article.
|
||||
Click <a href="{% url wiki_create "" %}">here</a> to create it.
|
||||
</p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
|
||||
{% if wiki_err_noparent %}
|
||||
<p>
|
||||
You cannot create this page, because its parent
|
||||
does not exist. Click <a href="{% url wiki_create wiki_url_parent %}">here</a>
|
||||
to create it.
|
||||
</p>
|
||||
{% else %}
|
||||
|
||||
{% if wiki_err_keyword %}
|
||||
<p>
|
||||
The page you're trying to create <b>{{wiki_url}}</b> starts with <b>_</b>, which is reserved for internal use.
|
||||
</p>
|
||||
{% else %}
|
||||
|
||||
{% if wiki_err_locked %}
|
||||
<p>
|
||||
The article you are trying to modify is locked.
|
||||
</p>
|
||||
{% else %}
|
||||
|
||||
{% if wiki_err_noread %}
|
||||
<p>
|
||||
You do not have access to read this article.
|
||||
</p>
|
||||
{% else %}
|
||||
|
||||
{% if wiki_err_nowrite %}
|
||||
<p>
|
||||
You do not have access to edit this article.
|
||||
</p>
|
||||
{% else %}
|
||||
|
||||
{% if wiki_err_noanon %}
|
||||
<p>
|
||||
Anonymous attachments are not allowed. Try logging in.
|
||||
</p>
|
||||
{% else %}
|
||||
|
||||
{% if wiki_err_create %}
|
||||
<p>
|
||||
You do not have access to create this article.
|
||||
</p>
|
||||
{% else %}
|
||||
|
||||
{% if wiki_err_encode %}
|
||||
<p>
|
||||
The url you requested could not be handled by the wiki.
|
||||
Probably you used a bad character in the URL.
|
||||
Only use digits, English letters, underscore and dash. For instance
|
||||
/wiki/An_Article-1
|
||||
</p>
|
||||
|
||||
|
||||
{% else %}
|
||||
<p>
|
||||
An error has occured.
|
||||
</p>
|
||||
|
||||
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
57
simplewiki/templates/simplewiki_history.html
Normal file
@@ -0,0 +1,57 @@
|
||||
{% extends "simplewiki_base.html" %}
|
||||
{% load i18n simplewiki_utils %}
|
||||
{% block wiki_page_title %}
|
||||
{{ wiki_article.title }}
|
||||
{% endblock %}
|
||||
{% block wiki_body %}
|
||||
<form method="POST">{% csrf_token %}
|
||||
<table id="wiki_history_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th id="revision">Revision</th>
|
||||
<th id="comment">Comment</th>
|
||||
<th id="diff">Diff</th>
|
||||
<th id="modified">Modified</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for revision in wiki_history %}
|
||||
<tr style="border-top: 1px" {%cycle '' 'class="dark"'%}>
|
||||
<td width="15px">
|
||||
<input type="radio" name="revision" id="{{ revision.id }}" value="{{ revision.id }}"{%ifequal wiki_article.current_revision.id revision.id%} checked{%endifequal%} />
|
||||
<label for="{{ revision.id }}">
|
||||
{{ revision }}
|
||||
{% if revision.previous_revision %}
|
||||
{% ifnotequal revision.counter revision.previous_revision.counter|add:1 %}
|
||||
<br/>(based on {{ revision.previous_revision }})
|
||||
{% endifnotequal %}
|
||||
{% endif %}
|
||||
</label>
|
||||
</td>
|
||||
<td>{% if revision.revision_text %}{{ revision.revision_text}}{% else %}<i>None</i>{% endif %}</td>
|
||||
<td class="diff">{% for x in revision.get_diff %}{{x|escape}}<br />{% endfor %}</td>
|
||||
<td>{{ revision.get_user}}
|
||||
<br/>
|
||||
{{ revision.revision_date|date}} {{ revision.revision_date|time}}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% if wiki_prev_page or wiki_next_page %}
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
{% if wiki_prev_page %}
|
||||
<a href="{% url wiki_history wiki_article.get_url wiki_prev_page %}">{% trans "Previous page" %}</a>
|
||||
{% endif %}
|
||||
{% if wiki_next_page %}
|
||||
<a href="{% url wiki_history wiki_article.get_url wiki_next_page %}">{% trans "Next page" %}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
{% endif %}
|
||||
</table>
|
||||
<input type="submit" value="Change revision"{% if not wiki_write %} disabled="true"{% endif %} />
|
||||
</form>
|
||||
{% endblock %}
|
||||
21
simplewiki/templates/simplewiki_searchresults.html
Normal file
@@ -0,0 +1,21 @@
|
||||
{% extends "simplewiki_base.html" %}
|
||||
{% load i18n simplewiki_utils %}
|
||||
{% block wiki_page_title %}
|
||||
{% if wiki_search_query %}
|
||||
{% trans "Search results for" %} '{{ wiki_search_query|escape }}'
|
||||
{% else %}
|
||||
{% trans "Displaying all articles" %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block wiki_body %}
|
||||
{% for article in wiki_search_results %}
|
||||
{% if article.get_url %}
|
||||
<a href="{% url wiki_view article.get_url %}">{{ article.get_url }}</a><br/>
|
||||
{% else %}
|
||||
<a href="{% url wiki_view '' %}">/</a><br/>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
{% trans "No articles were found!" %}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
32
simplewiki/templates/simplewiki_updateprogressbar.html
Normal file
@@ -0,0 +1,32 @@
|
||||
{% load i18n simplewiki_utils %}
|
||||
|
||||
{% if started %}
|
||||
<script type="text/javascript">
|
||||
parent.document.getElementById("wiki_attach_progress_container").style.display='block';
|
||||
</script>
|
||||
{% else %}
|
||||
{% if finished %}
|
||||
<script type="text/javascript">
|
||||
parent.document.getElementById("wiki_attach_progress_container").style.display='none';
|
||||
parent.location.reload();
|
||||
</script>
|
||||
{% else %}
|
||||
{% if overwrite_warning %}
|
||||
<script type="text/javascript">
|
||||
if (confirm('{% trans "Warning: The filename already exists? Really overwrite" %} {{ filename }}?'))
|
||||
parent.document.getElementById("wiki_attach_overwrite").checked=true;
|
||||
parent.document.getElementById("wiki_attach_overwrite").form.submit();
|
||||
</script>
|
||||
{% else %}
|
||||
{% if too_big %}
|
||||
<script type="text/javascript">
|
||||
alert('File is too big. Maximum: {{max_size|filesizeformat}}\nYour file was: {{file.size|filesizeformat}}');
|
||||
</script>
|
||||
{% else %}
|
||||
<script type="text/javascript">
|
||||
parent.document.getElementById("wiki_attach_progress").style.width='{{progress_width}}%';
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
10
simplewiki/templates/simplewiki_view.html
Normal file
@@ -0,0 +1,10 @@
|
||||
{% extends "simplewiki_base.html" %}
|
||||
{% load i18n simplewiki_utils %}
|
||||
{% block wiki_page_title %}
|
||||
{{ wiki_article.title }}
|
||||
{% endblock %}
|
||||
{% block wiki_body %}
|
||||
<div id="wiki_article">
|
||||
{{ wiki_article.current_revision.contents_parsed|safe }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
0
simplewiki/templatetags/__init__.py
Normal file
17
simplewiki/templatetags/simplewiki_utils.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from django import template
|
||||
from django.template.defaultfilters import stringfilter
|
||||
from simplewiki.settings import *
|
||||
from django.conf import settings
|
||||
from django.utils.http import urlquote as django_urlquote
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.filter()
|
||||
def prepend_media_url(value):
|
||||
"""Prepend user defined media root to url"""
|
||||
return settings.MEDIA_URL + value
|
||||
|
||||
@register.filter()
|
||||
def urlquote(value):
|
||||
"""Prepend user defined media root to url"""
|
||||
return django_urlquote(value)
|
||||
23
simplewiki/tests.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
This file demonstrates two different styles of tests (one doctest and one
|
||||
unittest). These will both pass when you run "manage.py test".
|
||||
|
||||
Replace these with more appropriate tests for your application.
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
class SimpleTest(TestCase):
|
||||
def test_basic_addition(self):
|
||||
"""
|
||||
Tests that 1 + 1 always equals 2.
|
||||
"""
|
||||
self.failUnlessEqual(1 + 1, 2)
|
||||
|
||||
__test__ = {"doctest": """
|
||||
Another way to test that 1 + 1 is equal to 2.
|
||||
|
||||
>>> 1 + 1 == 2
|
||||
True
|
||||
"""}
|
||||
|
||||
18
simplewiki/urls.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', 'simplewiki.views.root_redirect', name='wiki_root'),
|
||||
url(r'^/?([a-zA-Z\d/_-]*)/_edit/$', 'simplewiki.views.edit', name='wiki_edit'),
|
||||
url(r'^/?([a-zA-Z\d/_-]*)/_create/$', 'simplewiki.views.create', name='wiki_create'),
|
||||
url(r'^/?([a-zA-Z\d/_-]*)/_history/([0-9]*)/$', 'simplewiki.views.history', name='wiki_history'),
|
||||
url(r'^/?([a-zA-Z\d/_-]*)/_random/$', 'simplewiki.views.random_article', name='wiki_random'),
|
||||
url(r'^/?([a-zA-Z\d/_-]*)/_search/articles/$', 'simplewiki.views.search_articles', name='wiki_search_articles'),
|
||||
url(r'^/?([a-zA-Z\d/_-]*)/_search/related/$', 'simplewiki.views.search_add_related', name='search_related'),
|
||||
url(r'^/?([a-zA-Z\d/_-]*)/_related/add/$', 'simplewiki.views.add_related', name='add_related'),
|
||||
url(r'^/?([a-zA-Z\d/_-]*)/_related/remove/(\d+)$', 'simplewiki.views.remove_related', name='wiki_remove_relation'),
|
||||
url(r'^/?([a-zA-Z\d/_-]*)/_add_attachment/$', 'simplewiki.views_attachments.add_attachment', name='add_attachment'),
|
||||
url(r'^/?([a-zA-Z\d/_-]*)/_view_attachment/(.+)?$', 'simplewiki.views_attachments.view_attachment', name='wiki_view_attachment'),
|
||||
# url(r'^/?([a-zA-Z\d/_-]*)/_view_attachment/?$', 'simplewiki.views_attachments.list_attachments', name='wiki_list_attachments'),
|
||||
url(r'^/?([a-zA-Z\d/_-]*)$', 'simplewiki.views.view', name='wiki_view'),
|
||||
url(r'^(.*)$', 'simplewiki.views.encode_err', name='wiki_encode_err')
|
||||
)
|
||||
800
simplewiki/usage.txt
Normal file
@@ -0,0 +1,800 @@
|
||||
# Markdown: Syntax
|
||||
|
||||
[TOC]
|
||||
|
||||
## Overview
|
||||
|
||||
### Philosophy
|
||||
|
||||
Markdown is intended to be as easy-to-read and easy-to-write as is feasible.
|
||||
|
||||
Readability, however, is emphasized above all else. A Markdown-formatted
|
||||
document should be publishable as-is, as plain text, without looking
|
||||
like it's been marked up with tags or formatting instructions. While
|
||||
Markdown's syntax has been influenced by several existing text-to-HTML
|
||||
filters -- including [Setext] [1], [atx] [2], [Textile] [3], [reStructuredText] [4],
|
||||
[Grutatext] [5], and [EtText] [6] -- the single biggest source of
|
||||
inspiration for Markdown's syntax is the format of plain text email.
|
||||
|
||||
[1]: http://docutils.sourceforge.net/mirror/setext.html
|
||||
[2]: http://www.aaronsw.com/2002/atx/
|
||||
[3]: http://textism.com/tools/textile/
|
||||
[4]: http://docutils.sourceforge.net/rst.html
|
||||
[5]: http://www.triptico.com/software/grutatxt.html
|
||||
[6]: http://ettext.taint.org/doc/
|
||||
|
||||
To this end, Markdown's syntax is comprised entirely of punctuation
|
||||
characters, which punctuation characters have been carefully chosen so
|
||||
as to look like what they mean. E.g., asterisks around a word actually
|
||||
look like \*emphasis\*. Markdown lists look like, well, lists. Even
|
||||
blockquotes look like quoted passages of text, assuming you've ever
|
||||
used email.
|
||||
|
||||
### Automatic Escaping for Special Characters
|
||||
|
||||
In HTML, there are two characters that demand special treatment: `<`
|
||||
and `&`. Left angle brackets are used to start tags; ampersands are
|
||||
used to denote HTML entities. If you want to use them as literal
|
||||
characters, you must escape them as entities, e.g. `<`, and
|
||||
`&`.
|
||||
|
||||
Ampersands in particular are bedeviling for web writers. If you want to
|
||||
write about 'AT&T', you need to write '`AT&T`'. You even need to
|
||||
escape ampersands within URLs. Thus, if you want to link to:
|
||||
|
||||
http://images.google.com/images?num=30&q=larry+bird
|
||||
|
||||
you need to encode the URL as:
|
||||
|
||||
http://images.google.com/images?num=30&q=larry+bird
|
||||
|
||||
in your anchor tag `href` attribute. Needless to say, this is easy to
|
||||
forget, and is probably the single most common source of HTML validation
|
||||
errors in otherwise well-marked-up web sites.
|
||||
|
||||
Markdown allows you to use these characters naturally, taking care of
|
||||
all the necessary escaping for you. If you use an ampersand as part of
|
||||
an HTML entity, it remains unchanged; otherwise it will be translated
|
||||
into `&`.
|
||||
|
||||
So, if you want to include a copyright symbol in your article, you can write:
|
||||
|
||||
©
|
||||
|
||||
and Markdown will leave it alone. But if you write:
|
||||
|
||||
AT&T
|
||||
|
||||
Markdown will translate it to:
|
||||
|
||||
AT&T
|
||||
|
||||
Similarly, because Markdown supports [inline HTML](#html), if you use
|
||||
angle brackets as delimiters for HTML tags, Markdown will treat them as
|
||||
such. But if you write:
|
||||
|
||||
4 < 5
|
||||
|
||||
Markdown will translate it to:
|
||||
|
||||
4 < 5
|
||||
|
||||
However, inside Markdown code spans and blocks, angle brackets and
|
||||
ampersands are *always* encoded automatically. This makes it easy to use
|
||||
Markdown to write about HTML code. (As opposed to raw HTML, which is a
|
||||
terrible format for writing about HTML syntax, because every single `<`
|
||||
and `&` in your example code needs to be escaped.)
|
||||
|
||||
|
||||
* * *
|
||||
|
||||
|
||||
## Block Elements
|
||||
|
||||
### Paragraphs and Line Breaks
|
||||
|
||||
A paragraph is simply one or more consecutive lines of text, separated
|
||||
by one or more blank lines. (A blank line is any line that looks like a
|
||||
blank line -- a line containing nothing but spaces or tabs is considered
|
||||
blank.) Normal paragraphs should not be indented with spaces or tabs.
|
||||
|
||||
The implication of the "one or more consecutive lines of text" rule is
|
||||
that Markdown supports "hard-wrapped" text paragraphs. This differs
|
||||
significantly from most other text-to-HTML formatters (including Movable
|
||||
Type's "Convert Line Breaks" option) which translate every line break
|
||||
character in a paragraph into a `<br />` tag.
|
||||
|
||||
When you *do* want to insert a `<br />` break tag using Markdown, you
|
||||
end a line with two or more spaces, then type return.
|
||||
|
||||
Yes, this takes a tad more effort to create a `<br />`, but a simplistic
|
||||
"every line break is a `<br />`" rule wouldn't work for Markdown.
|
||||
Markdown's email-style [blockquoting][bq] and multi-paragraph [list items][l]
|
||||
work best -- and look better -- when you format them with hard breaks.
|
||||
|
||||
[bq]: #blockquote
|
||||
[l]: #list
|
||||
|
||||
### Headers
|
||||
|
||||
Markdown supports two styles of headers, [Setext] [1] and [atx] [2].
|
||||
|
||||
Setext-style headers are "underlined" using equal signs (for first-level
|
||||
headers) and dashes (for second-level headers). For example:
|
||||
|
||||
This is an H1
|
||||
=============
|
||||
|
||||
This is an H2
|
||||
-------------
|
||||
|
||||
This is an H3
|
||||
_____________
|
||||
|
||||
Any number of underlining `=`'s or `-`'s will work.
|
||||
|
||||
Atx-style headers use 1-6 hash characters at the start of the line,
|
||||
corresponding to header levels 1-6. For example:
|
||||
|
||||
# This is an H1
|
||||
|
||||
## This is an H2
|
||||
|
||||
###### This is an H6
|
||||
|
||||
Optionally, you may "close" atx-style headers. This is purely
|
||||
cosmetic -- you can use this if you think it looks better. The
|
||||
closing hashes don't even need to match the number of hashes
|
||||
used to open the header. (The number of opening hashes
|
||||
determines the header level.) :
|
||||
|
||||
# This is an H1 #
|
||||
|
||||
## This is an H2 ##
|
||||
|
||||
### This is an H3 ######
|
||||
|
||||
|
||||
### Blockquotes
|
||||
|
||||
Markdown uses email-style `>` characters for blockquoting. If you're
|
||||
familiar with quoting passages of text in an email message, then you
|
||||
know how to create a blockquote in Markdown. It looks best if you hard
|
||||
wrap the text and put a `>` before every line:
|
||||
|
||||
> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
|
||||
> consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
|
||||
> Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
|
||||
>
|
||||
> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
|
||||
> id sem consectetuer libero luctus adipiscing.
|
||||
|
||||
Markdown allows you to be lazy and only put the `>` before the first
|
||||
line of a hard-wrapped paragraph:
|
||||
|
||||
> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
|
||||
consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
|
||||
Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
|
||||
|
||||
> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
|
||||
id sem consectetuer libero luctus adipiscing.
|
||||
|
||||
Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by
|
||||
adding additional levels of `>`:
|
||||
|
||||
> This is the first level of quoting.
|
||||
>
|
||||
> > This is nested blockquote.
|
||||
>
|
||||
> Back to the first level.
|
||||
|
||||
Blockquotes can contain other Markdown elements, including headers, lists,
|
||||
and code blocks:
|
||||
|
||||
> ## This is a header.
|
||||
>
|
||||
> 1. This is the first list item.
|
||||
> 2. This is the second list item.
|
||||
>
|
||||
> Here's some example code:
|
||||
>
|
||||
> return shell_exec("echo $input | $markdown_script");
|
||||
|
||||
Any decent text editor should make email-style quoting easy. For
|
||||
example, with BBEdit, you can make a selection and choose Increase
|
||||
Quote Level from the Text menu.
|
||||
|
||||
|
||||
### Lists
|
||||
|
||||
Markdown supports ordered (numbered) and unordered (bulleted) lists.
|
||||
|
||||
Unordered lists use asterisks, pluses, and hyphens -- interchangably
|
||||
-- as list markers:
|
||||
|
||||
* Red
|
||||
* Green
|
||||
* Blue
|
||||
|
||||
is equivalent to:
|
||||
|
||||
+ Red
|
||||
+ Green
|
||||
+ Blue
|
||||
|
||||
and:
|
||||
|
||||
- Red
|
||||
- Green
|
||||
- Blue
|
||||
|
||||
Ordered lists use numbers followed by periods:
|
||||
|
||||
1. Bird
|
||||
2. McHale
|
||||
3. Parish
|
||||
|
||||
It's important to note that the actual numbers you use to mark the
|
||||
list have no effect on the HTML output Markdown produces. The HTML
|
||||
Markdown produces from the above list is:
|
||||
|
||||
<ol>
|
||||
<li>Bird</li>
|
||||
<li>McHale</li>
|
||||
<li>Parish</li>
|
||||
</ol>
|
||||
|
||||
If you instead wrote the list in Markdown like this:
|
||||
|
||||
1. Bird
|
||||
1. McHale
|
||||
1. Parish
|
||||
|
||||
or even:
|
||||
|
||||
3. Bird
|
||||
1. McHale
|
||||
8. Parish
|
||||
|
||||
you'd get the exact same HTML output. The point is, if you want to,
|
||||
you can use ordinal numbers in your ordered Markdown lists, so that
|
||||
the numbers in your source match the numbers in your published HTML.
|
||||
But if you want to be lazy, you don't have to.
|
||||
|
||||
If you do use lazy list numbering, however, you should still start the
|
||||
list with the number 1. At some point in the future, Markdown may support
|
||||
starting ordered lists at an arbitrary number.
|
||||
|
||||
List markers typically start at the left margin, but may be indented by
|
||||
up to three spaces. List markers must be followed by one or more spaces
|
||||
or a tab.
|
||||
|
||||
To make lists look nice, you can wrap items with hanging indents:
|
||||
|
||||
* Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
|
||||
Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
|
||||
viverra nec, fringilla in, laoreet vitae, risus.
|
||||
* Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
|
||||
Suspendisse id sem consectetuer libero luctus adipiscing.
|
||||
|
||||
But if you want to be lazy, you don't have to:
|
||||
|
||||
* Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
|
||||
Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
|
||||
viverra nec, fringilla in, laoreet vitae, risus.
|
||||
* Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
|
||||
Suspendisse id sem consectetuer libero luctus adipiscing.
|
||||
|
||||
If list items are separated by blank lines, Markdown will wrap the
|
||||
items in `<p>` tags in the HTML output. For example, this input:
|
||||
|
||||
* Bird
|
||||
* Magic
|
||||
|
||||
will turn into:
|
||||
|
||||
<ul>
|
||||
<li>Bird</li>
|
||||
<li>Magic</li>
|
||||
</ul>
|
||||
|
||||
But this:
|
||||
|
||||
* Bird
|
||||
|
||||
* Magic
|
||||
|
||||
will turn into:
|
||||
|
||||
<ul>
|
||||
<li><p>Bird</p></li>
|
||||
<li><p>Magic</p></li>
|
||||
</ul>
|
||||
|
||||
List items may consist of multiple paragraphs. Each subsequent
|
||||
paragraph in a list item must be indented by either 4 spaces
|
||||
or one tab:
|
||||
|
||||
1. This is a list item with two paragraphs. Lorem ipsum dolor
|
||||
sit amet, consectetuer adipiscing elit. Aliquam hendrerit
|
||||
mi posuere lectus.
|
||||
|
||||
Vestibulum enim wisi, viverra nec, fringilla in, laoreet
|
||||
vitae, risus. Donec sit amet nisl. Aliquam semper ipsum
|
||||
sit amet velit.
|
||||
|
||||
2. Suspendisse id sem consectetuer libero luctus adipiscing.
|
||||
|
||||
It looks nice if you indent every line of the subsequent
|
||||
paragraphs, but here again, Markdown will allow you to be
|
||||
lazy:
|
||||
|
||||
* This is a list item with two paragraphs.
|
||||
|
||||
This is the second paragraph in the list item. You're
|
||||
only required to indent the first line. Lorem ipsum dolor
|
||||
sit amet, consectetuer adipiscing elit.
|
||||
|
||||
* Another item in the same list.
|
||||
|
||||
To put a blockquote within a list item, the blockquote's `>`
|
||||
delimiters need to be indented:
|
||||
|
||||
* A list item with a blockquote:
|
||||
|
||||
> This is a blockquote
|
||||
> inside a list item.
|
||||
|
||||
To put a code block within a list item, the code block needs
|
||||
to be indented *twice* -- 8 spaces or two tabs:
|
||||
|
||||
* A list item with a code block:
|
||||
|
||||
<code goes here>
|
||||
|
||||
|
||||
It's worth noting that it's possible to trigger an ordered list by
|
||||
accident, by writing something like this:
|
||||
|
||||
1986. What a great season.
|
||||
|
||||
In other words, a *number-period-space* sequence at the beginning of a
|
||||
line. To avoid this, you can backslash-escape the period:
|
||||
|
||||
1986\. What a great season.
|
||||
|
||||
|
||||
|
||||
### Code Blocks
|
||||
|
||||
Pre-formatted code blocks are used for writing about programming or
|
||||
markup source code. Rather than forming normal paragraphs, the lines
|
||||
of a code block are interpreted literally. Markdown wraps a code block
|
||||
in both `<pre>` and `<code>` tags.
|
||||
|
||||
To produce a code block in Markdown, simply indent every line of the
|
||||
block by at least 4 spaces or 1 tab. For example, given this input:
|
||||
|
||||
This is a normal paragraph:
|
||||
|
||||
This is a code block.
|
||||
|
||||
Markdown will generate:
|
||||
|
||||
<p>This is a normal paragraph:</p>
|
||||
|
||||
<pre><code>This is a code block.
|
||||
</code></pre>
|
||||
|
||||
One level of indentation -- 4 spaces or 1 tab -- is removed from each
|
||||
line of the code block. For example, this:
|
||||
|
||||
Here is an example of AppleScript:
|
||||
|
||||
tell application "Foo"
|
||||
beep
|
||||
end tell
|
||||
|
||||
will turn into:
|
||||
|
||||
<p>Here is an example of AppleScript:</p>
|
||||
|
||||
<pre><code>tell application "Foo"
|
||||
beep
|
||||
end tell
|
||||
</code></pre>
|
||||
|
||||
A code block continues until it reaches a line that is not indented
|
||||
(or the end of the article).
|
||||
|
||||
Within a code block, ampersands (`&`) and angle brackets (`<` and `>`)
|
||||
are automatically converted into HTML entities. This makes it very
|
||||
easy to include example HTML source code using Markdown -- just paste
|
||||
it and indent it, and Markdown will handle the hassle of encoding the
|
||||
ampersands and angle brackets. For example, this:
|
||||
|
||||
<div class="footer">
|
||||
© 2004 Foo Corporation
|
||||
</div>
|
||||
|
||||
will turn into:
|
||||
|
||||
<pre><code><div class="footer">
|
||||
&copy; 2004 Foo Corporation
|
||||
</div>
|
||||
</code></pre>
|
||||
|
||||
Regular Markdown syntax is not processed within code blocks. E.g.,
|
||||
asterisks are just literal asterisks within a code block. This means
|
||||
it's also easy to use Markdown to write about Markdown's own syntax.
|
||||
|
||||
|
||||
|
||||
### Horizontal Rules
|
||||
|
||||
You can produce a horizontal rule tag (`<hr />`) by placing three or
|
||||
more hyphens, asterisks, or underscores on a line by themselves. If you
|
||||
wish, you may use spaces between the hyphens or asterisks. Each of the
|
||||
following lines will produce a horizontal rule:
|
||||
|
||||
* * *
|
||||
|
||||
***
|
||||
|
||||
*****
|
||||
|
||||
- - -
|
||||
|
||||
---------------------------------------
|
||||
|
||||
|
||||
## Span Elements
|
||||
|
||||
### Links
|
||||
|
||||
Markdown supports two style of links: *inline* and *reference*.
|
||||
|
||||
In both styles, the link text is delimited by [square brackets].
|
||||
|
||||
To create an inline link, use a set of regular parentheses immediately
|
||||
after the link text's closing square bracket. Inside the parentheses,
|
||||
put the URL where you want the link to point, along with an *optional*
|
||||
title for the link, surrounded in quotes. For example:
|
||||
|
||||
This is [an example](http://example.com/ "Title") inline link.
|
||||
|
||||
[This link](http://example.net/) has no title attribute.
|
||||
|
||||
Will produce:
|
||||
|
||||
<p>This is <a href="http://example.com/" title="Title">
|
||||
an example</a> inline link.</p>
|
||||
|
||||
<p><a href="http://example.net/">This link</a> has no
|
||||
title attribute.</p>
|
||||
|
||||
If you're referring to a local resource on the same server, you can
|
||||
use relative paths:
|
||||
|
||||
See my [About](/about/) page for details.
|
||||
|
||||
Reference-style links use a second set of square brackets, inside
|
||||
which you place a label of your choosing to identify the link:
|
||||
|
||||
This is [an example][id] reference-style link.
|
||||
|
||||
You can optionally use a space to separate the sets of brackets:
|
||||
|
||||
This is [an example] [id] reference-style link.
|
||||
|
||||
Then, anywhere in the document, you define your link label like this,
|
||||
on a line by itself:
|
||||
|
||||
[id]: http://example.com/ "Optional Title Here"
|
||||
|
||||
That is:
|
||||
|
||||
* Square brackets containing the link identifier (optionally
|
||||
indented from the left margin using up to three spaces);
|
||||
* followed by a colon;
|
||||
* followed by one or more spaces (or tabs);
|
||||
* followed by the URL for the link;
|
||||
* optionally followed by a title attribute for the link, enclosed
|
||||
in double or single quotes, or enclosed in parentheses.
|
||||
|
||||
The following three link definitions are equivalent:
|
||||
|
||||
[foo]: http://example.com/ "Optional Title Here"
|
||||
[foo]: http://example.com/ 'Optional Title Here'
|
||||
[foo]: http://example.com/ (Optional Title Here)
|
||||
|
||||
**Note:** There is a known bug in Markdown.pl 1.0.1 which prevents
|
||||
single quotes from being used to delimit link titles.
|
||||
|
||||
The link URL may, optionally, be surrounded by angle brackets:
|
||||
|
||||
[id]: <http://example.com/> "Optional Title Here"
|
||||
|
||||
You can put the title attribute on the next line and use extra spaces
|
||||
or tabs for padding, which tends to look better with longer URLs:
|
||||
|
||||
[id]: http://example.com/longish/path/to/resource/here
|
||||
"Optional Title Here"
|
||||
|
||||
Link definitions are only used for creating links during Markdown
|
||||
processing, and are stripped from your document in the HTML output.
|
||||
|
||||
Link definition names may consist of letters, numbers, spaces, and
|
||||
punctuation -- but they are *not* case sensitive. E.g. these two
|
||||
links:
|
||||
|
||||
[link text][a]
|
||||
[link text][A]
|
||||
|
||||
are equivalent.
|
||||
|
||||
The *implicit link name* shortcut allows you to omit the name of the
|
||||
link, in which case the link text itself is used as the name.
|
||||
Just use an empty set of square brackets -- e.g., to link the word
|
||||
"Google" to the google.com web site, you could simply write:
|
||||
|
||||
[Google][]
|
||||
|
||||
And then define the link:
|
||||
|
||||
[Google]: http://google.com/
|
||||
|
||||
Because link names may contain spaces, this shortcut even works for
|
||||
multiple words in the link text:
|
||||
|
||||
Visit [Daring Fireball][] for more information.
|
||||
|
||||
And then define the link:
|
||||
|
||||
[Daring Fireball]: http://daringfireball.net/
|
||||
|
||||
Link definitions can be placed anywhere in your Markdown document. I
|
||||
tend to put them immediately after each paragraph in which they're
|
||||
used, but if you want, you can put them all at the end of your
|
||||
document, sort of like footnotes.
|
||||
|
||||
Here's an example of reference links in action:
|
||||
|
||||
I get 10 times more traffic from [Google] [1] than from
|
||||
[Yahoo] [2] or [MSN] [3].
|
||||
|
||||
[1]: http://google.com/ "Google"
|
||||
[2]: http://search.yahoo.com/ "Yahoo Search"
|
||||
[3]: http://search.msn.com/ "MSN Search"
|
||||
|
||||
Using the implicit link name shortcut, you could instead write:
|
||||
|
||||
I get 10 times more traffic from [Google][] than from
|
||||
[Yahoo][] or [MSN][].
|
||||
|
||||
[google]: http://google.com/ "Google"
|
||||
[yahoo]: http://search.yahoo.com/ "Yahoo Search"
|
||||
[msn]: http://search.msn.com/ "MSN Search"
|
||||
|
||||
Both of the above examples will produce the following HTML output:
|
||||
|
||||
<p>I get 10 times more traffic from <a href="http://google.com/"
|
||||
title="Google">Google</a> than from
|
||||
<a href="http://search.yahoo.com/" title="Yahoo Search">Yahoo</a>
|
||||
or <a href="http://search.msn.com/" title="MSN Search">MSN</a>.</p>
|
||||
|
||||
For comparison, here is the same paragraph written using
|
||||
Markdown's inline link style:
|
||||
|
||||
I get 10 times more traffic from [Google](http://google.com/ "Google")
|
||||
than from [Yahoo](http://search.yahoo.com/ "Yahoo Search") or
|
||||
[MSN](http://search.msn.com/ "MSN Search").
|
||||
|
||||
The point of reference-style links is not that they're easier to
|
||||
write. The point is that with reference-style links, your document
|
||||
source is vastly more readable. Compare the above examples: using
|
||||
reference-style links, the paragraph itself is only 81 characters
|
||||
long; with inline-style links, it's 176 characters; and as raw HTML,
|
||||
it's 234 characters. In the raw HTML, there's more markup than there
|
||||
is text.
|
||||
|
||||
With Markdown's reference-style links, a source document much more
|
||||
closely resembles the final output, as rendered in a browser. By
|
||||
allowing you to move the markup-related metadata out of the paragraph,
|
||||
you can add links without interrupting the narrative flow of your
|
||||
prose.
|
||||
|
||||
### Emphasis
|
||||
|
||||
Markdown treats asterisks (`*`) and underscores (`_`) as indicators of
|
||||
emphasis. Text wrapped with one `*` or `_` will be wrapped with an
|
||||
HTML `<em>` tag; double `*`'s or `_`'s will be wrapped with an HTML
|
||||
`<strong>` tag. E.g., this input:
|
||||
|
||||
*single asterisks*
|
||||
|
||||
_single underscores_
|
||||
|
||||
**double asterisks**
|
||||
|
||||
__double underscores__
|
||||
|
||||
will produce:
|
||||
|
||||
<em>single asterisks</em>
|
||||
|
||||
<em>single underscores</em>
|
||||
|
||||
<strong>double asterisks</strong>
|
||||
|
||||
<strong>double underscores</strong>
|
||||
|
||||
You can use whichever style you prefer; the lone restriction is that
|
||||
the same character must be used to open and close an emphasis span.
|
||||
|
||||
Emphasis can be used in the middle of a word:
|
||||
|
||||
un*frigging*believable
|
||||
|
||||
But if you surround an `*` or `_` with spaces, it'll be treated as a
|
||||
literal asterisk or underscore.
|
||||
|
||||
To produce a literal asterisk or underscore at a position where it
|
||||
would otherwise be used as an emphasis delimiter, you can backslash
|
||||
escape it:
|
||||
|
||||
\*this text is surrounded by literal asterisks\*
|
||||
|
||||
|
||||
### Code
|
||||
|
||||
To indicate a span of code, wrap it with backtick quotes (`` ` ``).
|
||||
Unlike a pre-formatted code block, a code span indicates code within a
|
||||
normal paragraph. For example:
|
||||
|
||||
Use the `printf()` function.
|
||||
|
||||
will produce:
|
||||
|
||||
<p>Use the <code>printf()</code> function.</p>
|
||||
|
||||
To include a literal backtick character within a code span, you can use
|
||||
multiple backticks as the opening and closing delimiters:
|
||||
|
||||
``There is a literal backtick (`) here.``
|
||||
|
||||
which will produce this:
|
||||
|
||||
<p><code>There is a literal backtick (`) here.</code></p>
|
||||
|
||||
The backtick delimiters surrounding a code span may include spaces --
|
||||
one after the opening, one before the closing. This allows you to place
|
||||
literal backtick characters at the beginning or end of a code span:
|
||||
|
||||
A single backtick in a code span: `` ` ``
|
||||
|
||||
A backtick-delimited string in a code span: `` `foo` ``
|
||||
|
||||
will produce:
|
||||
|
||||
<p>A single backtick in a code span: <code>`</code></p>
|
||||
|
||||
<p>A backtick-delimited string in a code span: <code>`foo`</code></p>
|
||||
|
||||
With a code span, ampersands and angle brackets are encoded as HTML
|
||||
entities automatically, which makes it easy to include example HTML
|
||||
tags. Markdown will turn this:
|
||||
|
||||
Please don't use any `<blink>` tags.
|
||||
|
||||
into:
|
||||
|
||||
<p>Please don't use any <code><blink></code> tags.</p>
|
||||
|
||||
You can write this:
|
||||
|
||||
`—` is the decimal-encoded equivalent of `—`.
|
||||
|
||||
to produce:
|
||||
|
||||
<p><code>&#8212;</code> is the decimal-encoded
|
||||
equivalent of <code>&mdash;</code>.</p>
|
||||
|
||||
|
||||
### Images
|
||||
|
||||
Admittedly, it's fairly difficult to devise a "natural" syntax for
|
||||
placing images into a plain text document format.
|
||||
|
||||
Markdown uses an image syntax that is intended to resemble the syntax
|
||||
for links, allowing for two styles: *inline* and *reference*.
|
||||
|
||||
Inline image syntax looks like this:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
That is:
|
||||
|
||||
* An exclamation mark: `!`;
|
||||
* followed by a set of square brackets, containing the `alt`
|
||||
attribute text for the image;
|
||||
* followed by a set of parentheses, containing the URL or path to
|
||||
the image, and an optional `title` attribute enclosed in double
|
||||
or single quotes.
|
||||
|
||||
Reference-style image syntax looks like this:
|
||||
|
||||
![Alt text][id]
|
||||
|
||||
Where "id" is the name of a defined image reference. Image references
|
||||
are defined using syntax identical to link references:
|
||||
|
||||
[id]: url/to/image "Optional title attribute"
|
||||
|
||||
As of this writing, Markdown has no syntax for specifying the
|
||||
dimensions of an image; if this is important to you, you can simply
|
||||
use regular HTML `<img>` tags.
|
||||
|
||||
|
||||
## Miscellaneous
|
||||
|
||||
### Automatic Links
|
||||
|
||||
Markdown supports a shortcut style for creating "automatic" links for URLs and email addresses: simply surround the URL or email address with angle brackets. What this means is that if you want to show the actual text of a URL or email address, and also have it be a clickable link, you can do this:
|
||||
|
||||
<http://example.com/>
|
||||
|
||||
Markdown will turn this into:
|
||||
|
||||
<a href="http://example.com/">http://example.com/</a>
|
||||
|
||||
Automatic links for email addresses work similarly, except that
|
||||
Markdown will also perform a bit of randomized decimal and hex
|
||||
entity-encoding to help obscure your address from address-harvesting
|
||||
spambots. For example, Markdown will turn this:
|
||||
|
||||
<address@example.com>
|
||||
|
||||
into something like this:
|
||||
|
||||
<a href="mailto:addre
|
||||
ss@example.co
|
||||
m">address@exa
|
||||
mple.com</a>
|
||||
|
||||
which will render in a browser as a clickable link to "address@example.com".
|
||||
|
||||
(This sort of entity-encoding trick will indeed fool many, if not
|
||||
most, address-harvesting bots, but it definitely won't fool all of
|
||||
them. It's better than nothing, but an address published in this way
|
||||
will probably eventually start receiving spam.)
|
||||
|
||||
|
||||
|
||||
### Backslash Escapes
|
||||
|
||||
Markdown allows you to use backslash escapes to generate literal
|
||||
characters which would otherwise have special meaning in Markdown's
|
||||
formatting syntax. For example, if you wanted to surround a word
|
||||
with literal asterisks (instead of an HTML `<em>` tag), you can use
|
||||
backslashes before the asterisks, like this:
|
||||
|
||||
\*literal asterisks\*
|
||||
|
||||
Markdown provides backslash escapes for the following characters:
|
||||
|
||||
\ backslash
|
||||
` backtick
|
||||
* asterisk
|
||||
_ underscore
|
||||
{} curly braces
|
||||
[] square brackets
|
||||
() parentheses
|
||||
# hash mark
|
||||
+ plus sign
|
||||
- minus sign (hyphen)
|
||||
. dot
|
||||
! exclamation mark
|
||||
|
||||
400
simplewiki/views.py
Normal file
@@ -0,0 +1,400 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import types
|
||||
from django.core.urlresolvers import get_callable
|
||||
from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseServerError, HttpResponseForbidden, HttpResponseNotAllowed
|
||||
from django.utils import simplejson
|
||||
from django.shortcuts import get_object_or_404, render_to_response
|
||||
from django.template import RequestContext, Context, loader
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import Q
|
||||
from django.conf import settings
|
||||
|
||||
from models import *
|
||||
from settings import *
|
||||
|
||||
def view(request, wiki_url):
|
||||
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
if err:
|
||||
return err
|
||||
|
||||
perm_err = check_permissions(request, article, check_read=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
c = RequestContext(request, {'wiki_article': article,
|
||||
'wiki_write': article.can_write_l(request.user),
|
||||
'wiki_attachments_write': article.can_attach(request.user),
|
||||
} )
|
||||
return render_to_response('simplewiki_view.html', c)
|
||||
|
||||
def root_redirect(request):
|
||||
"""
|
||||
Reason for redirecting:
|
||||
The root article needs to to have a specific slug
|
||||
in the URL, otherwise pattern matching in urls.py will get confused.
|
||||
I've tried various methods to avoid this, but depending on Django/Python
|
||||
versions, regexps have been greedy in two different ways.. so I just
|
||||
skipped having problematic URLs like '/wiki/_edit' for editing the main page.
|
||||
#benjaoming
|
||||
"""
|
||||
try:
|
||||
root = Article.get_root()
|
||||
except:
|
||||
err = not_found(request, 'mainpage')
|
||||
return err
|
||||
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(root.slug,)))
|
||||
|
||||
def create(request, wiki_url):
|
||||
|
||||
url_path = get_url_path(wiki_url)
|
||||
|
||||
if url_path != [] and url_path[0].startswith('_'):
|
||||
c = RequestContext(request, {'wiki_err_keyword': True,
|
||||
'wiki_url': '/'.join(url_path) })
|
||||
return render_to_response('simplewiki_error.html', c)
|
||||
|
||||
# Lookup path
|
||||
try:
|
||||
# Ensure that the path exists...
|
||||
root = Article.get_root()
|
||||
# Remove root slug if present in path
|
||||
if url_path and root.slug == url_path[0]:
|
||||
url_path = url_path[1:]
|
||||
|
||||
path = Article.get_url_reverse(url_path[:-1], root)
|
||||
if not path:
|
||||
c = RequestContext(request, {'wiki_err_noparent': True,
|
||||
'wiki_url_parent': '/'.join(url_path[:-1]) })
|
||||
return render_to_response('simplewiki_error.html', c)
|
||||
|
||||
perm_err = check_permissions(request, path[-1], check_locked=False, check_write=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
# Ensure doesn't already exist
|
||||
article = Article.get_url_reverse(url_path, root)
|
||||
if article:
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article[-1].get_url(),)))
|
||||
|
||||
# TODO: Somehow this doesnt work...
|
||||
#except ShouldHaveExactlyOneRootSlug, (e):
|
||||
except:
|
||||
if Article.objects.filter(parent=None).count() > 0:
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=('',)))
|
||||
# Root not found...
|
||||
path = []
|
||||
url_path = [""]
|
||||
|
||||
if request.method == 'POST':
|
||||
f = CreateArticleForm(request.POST)
|
||||
if f.is_valid():
|
||||
article = Article()
|
||||
article.slug = url_path[-1]
|
||||
if not request.user.is_anonymous():
|
||||
article.created_by = request.user
|
||||
article.title = f.cleaned_data.get('title')
|
||||
if path != []:
|
||||
article.parent = path[-1]
|
||||
a = article.save()
|
||||
new_revision = f.save(commit=False)
|
||||
if not request.user.is_anonymous():
|
||||
new_revision.revision_user = request.user
|
||||
new_revision.article = article
|
||||
new_revision.save()
|
||||
import django.db as db
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
|
||||
else:
|
||||
f = CreateArticleForm(initial={'title':request.GET.get('wiki_article_name', url_path[-1]),
|
||||
'contents':_('Headline\n===\n\n')})
|
||||
|
||||
c = RequestContext(request, {'wiki_form': f,
|
||||
'wiki_write': True,
|
||||
})
|
||||
|
||||
return render_to_response('simplewiki_create.html', c)
|
||||
|
||||
def edit(request, wiki_url):
|
||||
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
if err:
|
||||
return err
|
||||
|
||||
# Check write permissions
|
||||
perm_err = check_permissions(request, article, check_write=True, check_locked=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
|
||||
if WIKI_ALLOW_TITLE_EDIT:
|
||||
EditForm = RevisionFormWithTitle
|
||||
else:
|
||||
EditForm = RevisionForm
|
||||
|
||||
if request.method == 'POST':
|
||||
f = EditForm(request.POST)
|
||||
if f.is_valid():
|
||||
new_revision = f.save(commit=False)
|
||||
new_revision.article = article
|
||||
# Check that something has actually been changed...
|
||||
if not new_revision.get_diff():
|
||||
return (None, HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),))))
|
||||
if not request.user.is_anonymous():
|
||||
new_revision.revision_user = request.user
|
||||
new_revision.save()
|
||||
if WIKI_ALLOW_TITLE_EDIT:
|
||||
new_revision.article.title = f.cleaned_data['title']
|
||||
new_revision.article.save()
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
|
||||
else:
|
||||
f = EditForm({'contents': article.current_revision.contents, 'title': article.title})
|
||||
c = RequestContext(request, {'wiki_form': f,
|
||||
'wiki_write': True,
|
||||
'wiki_article': article,
|
||||
'wiki_attachments_write': article.can_attach(request.user),
|
||||
})
|
||||
|
||||
return render_to_response('simplewiki_edit.html', c)
|
||||
|
||||
def history(request, wiki_url, page=1):
|
||||
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
if err:
|
||||
return err
|
||||
|
||||
perm_err = check_permissions(request, article, check_read=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
|
||||
page_size = 10
|
||||
|
||||
try:
|
||||
p = int(page)
|
||||
except ValueError:
|
||||
p = 1
|
||||
|
||||
history = Revision.objects.filter(article__exact = article).order_by('-counter')
|
||||
|
||||
if request.method == 'POST':
|
||||
if request.POST.__contains__('revision'):
|
||||
perm_err = check_permissions(request, article, check_write=True, check_locked=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
try:
|
||||
r = int(request.POST['revision'])
|
||||
article.current_revision = Revision.objects.get(id=r)
|
||||
article.save()
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
|
||||
|
||||
page_count = (history.count()+(page_size-1)) / page_size
|
||||
if p > page_count:
|
||||
p = 1
|
||||
beginItem = (p-1) * page_size
|
||||
|
||||
next_page = p + 1 if page_count > p else None
|
||||
prev_page = p - 1 if p > 1 else None
|
||||
|
||||
c = RequestContext(request, {'wiki_page': p,
|
||||
'wiki_next_page': next_page,
|
||||
'wiki_prev_page': prev_page,
|
||||
'wiki_write': article.can_write_l(request.user),
|
||||
'wiki_attachments_write': article.can_attach(request.user),
|
||||
'wiki_article': article,
|
||||
'wiki_history': history[beginItem:beginItem+page_size],})
|
||||
|
||||
return render_to_response('simplewiki_history.html', c)
|
||||
|
||||
def search_articles(request, wiki_url):
|
||||
# blampe: We should check for the presence of other popular django search
|
||||
# apps and use those if possible. Only fall back on this as a last resort.
|
||||
# Adding some context to results (eg where matches were) would also be nice.
|
||||
|
||||
# todo: maybe do some perm checking here
|
||||
|
||||
if request.method == 'POST':
|
||||
querystring = request.POST['value'].strip()
|
||||
if querystring:
|
||||
results = Article.objects.all()
|
||||
for queryword in querystring.split():
|
||||
# Basic negation is as fancy as we get right now
|
||||
if queryword[0] == '-' and len(queryword) > 1:
|
||||
results._search = lambda x: results.exclude(x)
|
||||
queryword = queryword[1:]
|
||||
else:
|
||||
results._search = lambda x: results.filter(x)
|
||||
|
||||
results = results._search(Q(current_revision__contents__icontains = queryword) | \
|
||||
Q(title = queryword))
|
||||
else:
|
||||
# Need to throttle results by splitting them into pages...
|
||||
results = Article.objects.all()
|
||||
|
||||
if results.count() == 1:
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(results[0].get_url(),)))
|
||||
else:
|
||||
c = RequestContext(request, {'wiki_search_results': results,
|
||||
'wiki_search_query': querystring})
|
||||
return render_to_response('simplewiki_searchresults.html', c)
|
||||
|
||||
return view(request, wiki_url)
|
||||
|
||||
def search_add_related(request, wiki_url):
|
||||
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
if err:
|
||||
return err
|
||||
|
||||
perm_err = check_permissions(request, article, check_read=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
|
||||
search_string = request.GET.get('query', None)
|
||||
self_pk = request.GET.get('self', None)
|
||||
if search_string:
|
||||
results = []
|
||||
related = Article.objects.filter(title__istartswith = search_string)
|
||||
others = article.related.all()
|
||||
if self_pk:
|
||||
related = related.exclude(pk=self_pk)
|
||||
if others:
|
||||
related = related.exclude(related__in = others)
|
||||
related = related.order_by('title')[:10]
|
||||
for item in related:
|
||||
results.append({'id': str(item.id),
|
||||
'value': item.title,
|
||||
'info': item.get_url()})
|
||||
else:
|
||||
results = []
|
||||
|
||||
json = simplejson.dumps({'results': results})
|
||||
return HttpResponse(json, mimetype='application/json')
|
||||
|
||||
def add_related(request, wiki_url):
|
||||
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
if err:
|
||||
return err
|
||||
|
||||
perm_err = check_permissions(request, article, check_write=True, check_locked=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
|
||||
try:
|
||||
related_id = request.POST['id']
|
||||
rel = Article.objects.get(id=related_id)
|
||||
has_already = article.related.filter(id=related_id).count()
|
||||
if has_already == 0 and not rel == article:
|
||||
article.related.add(rel)
|
||||
article.save()
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
|
||||
|
||||
def remove_related(request, wiki_url, related_id):
|
||||
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
if err:
|
||||
return err
|
||||
|
||||
perm_err = check_permissions(request, article, check_write=True, check_locked=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
|
||||
try:
|
||||
rel_id = int(related_id)
|
||||
rel = Article.objects.get(id=rel_id)
|
||||
article.related.remove(rel)
|
||||
article.save()
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
|
||||
|
||||
def random_article(request, wiki_url):
|
||||
from random import randint
|
||||
num_arts = Article.objects.count()
|
||||
article = Article.objects.all()[randint(0, num_arts-1)]
|
||||
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
|
||||
|
||||
def encode_err(request, url):
|
||||
return render_to_response('simplewiki_error.html',
|
||||
RequestContext(request, {'wiki_err_encode': True}))
|
||||
|
||||
def not_found(request, wiki_url):
|
||||
"""Generate a NOT FOUND message for some URL"""
|
||||
return render_to_response('simplewiki_error.html',
|
||||
RequestContext(request, {'wiki_err_notfound': True,
|
||||
'wiki_url': wiki_url}))
|
||||
|
||||
def get_url_path(url):
|
||||
"""Return a list of all actual elements of a url, safely ignoring
|
||||
double-slashes (//) """
|
||||
return filter(lambda x: x!='', url.split('/'))
|
||||
|
||||
def fetch_from_url(request, url):
|
||||
"""Analyze URL, returning the article and the articles in its path
|
||||
If something goes wrong, return an error HTTP response"""
|
||||
|
||||
err = None
|
||||
article = None
|
||||
path = None
|
||||
|
||||
url_path = get_url_path(url)
|
||||
|
||||
try:
|
||||
root = Article.get_root()
|
||||
except:
|
||||
err = not_found(request, '')
|
||||
return (article, path, err)
|
||||
|
||||
if url_path and root.slug == url_path[0]:
|
||||
url_path = url_path[1:]
|
||||
|
||||
path = Article.get_url_reverse(url_path, root)
|
||||
if not path:
|
||||
err = not_found(request, '/' + '/'.join(url_path))
|
||||
else:
|
||||
article = path[-1]
|
||||
return (article, path, err)
|
||||
|
||||
|
||||
def check_permissions(request, article, check_read=False, check_write=False, check_locked=False):
|
||||
read_err = check_read and not article.can_read(request.user)
|
||||
write_err = check_write and not article.can_write(request.user)
|
||||
locked_err = check_locked and article.locked
|
||||
|
||||
if read_err or write_err or locked_err:
|
||||
c = RequestContext(request, {'wiki_article': article,
|
||||
'wiki_err_noread': read_err,
|
||||
'wiki_err_nowrite': write_err,
|
||||
'wiki_err_locked': locked_err,})
|
||||
# TODO: Make this a little less jarring by just displaying an error
|
||||
# on the current page? (no such redirect happens for an anon upload yet)
|
||||
# benjaoming: I think this is the nicest way of displaying an error, but
|
||||
# these errors shouldn't occur, but rather be prevented on the other pages.
|
||||
return render_to_response('simplewiki_error.html', c)
|
||||
else:
|
||||
return None
|
||||
|
||||
####################
|
||||
# LOGIN PROTECTION #
|
||||
####################
|
||||
|
||||
if WIKI_REQUIRE_LOGIN_VIEW:
|
||||
view = login_required(view)
|
||||
history = login_required(history)
|
||||
search_related = login_required(search_related)
|
||||
wiki_encode_err = login_required(wiki_encode_err)
|
||||
|
||||
if WIKI_REQUIRE_LOGIN_EDIT:
|
||||
create = login_required(create)
|
||||
edit = login_required(edit)
|
||||
add_related = login_required(add_related)
|
||||
remove_related = login_required(remove_related)
|
||||
|
||||
if WIKI_CONTEXT_PREPROCESSORS:
|
||||
settings.TEMPLATE_CONTEXT_PROCESSORS = settings.TEMPLATE_CONTEXT_PROCESSORS + WIKI_CONTEXT_PREPROCESSORS
|
||||
145
simplewiki/views_attachments.py
Normal file
@@ -0,0 +1,145 @@
|
||||
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, Http404
|
||||
from django.template import loader, Context
|
||||
from django.db.models.fields.files import FieldFile
|
||||
from django.core.servers.basehttp import FileWrapper
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
from settings import *
|
||||
from models import Article, ArticleAttachment, get_attachment_filepath
|
||||
from views import not_found, check_permissions, get_url_path, fetch_from_url
|
||||
|
||||
import os
|
||||
from simplewiki.settings import WIKI_ALLOW_ANON_ATTACHMENTS
|
||||
|
||||
|
||||
def add_attachment(request, wiki_url):
|
||||
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
if err:
|
||||
return err
|
||||
|
||||
perm_err = check_permissions(request, article, check_write=True, check_locked=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
|
||||
if not WIKI_ALLOW_ATTACHMENTS or (not WIKI_ALLOW_ANON_ATTACHMENTS and request.user.is_anonymous()):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
if request.method == 'POST':
|
||||
if request.FILES.__contains__('attachment'):
|
||||
attachment = ArticleAttachment()
|
||||
if not request.user.is_anonymous():
|
||||
attachment.uploaded_by = request.user
|
||||
attachment.article = article
|
||||
|
||||
file = request.FILES['attachment']
|
||||
file_rel_path = get_attachment_filepath(attachment, file.name)
|
||||
chunk_size = request.upload_handlers[0].chunk_size
|
||||
|
||||
filefield = FieldFile(attachment, attachment.file, file_rel_path)
|
||||
attachment.file = filefield
|
||||
|
||||
file_path = WIKI_ATTACHMENTS_ROOT + attachment.file.name
|
||||
|
||||
if not request.POST.__contains__('overwrite') and os.path.exists(file_path):
|
||||
c = Context({'overwrite_warning' : True,
|
||||
'wiki_article': article,
|
||||
'filename': file.name})
|
||||
t = loader.get_template('simplewiki_updateprogressbar.html')
|
||||
return HttpResponse(t.render(c))
|
||||
|
||||
if file.size > WIKI_ATTACHMENTS_MAX:
|
||||
c = Context({'too_big' : True,
|
||||
'max_size': WIKI_ATTACHMENTS_MAX,
|
||||
'wiki_article': article,
|
||||
'file': file})
|
||||
t = loader.get_template('simplewiki_updateprogressbar.html')
|
||||
return HttpResponse(t.render(c))
|
||||
|
||||
def get_extension(fname):
|
||||
return attachment.file.name.split('.')[-2]
|
||||
if WIKI_ATTACHMENTS_ALLOWED_EXTENSIONS and not \
|
||||
get_extension(attachment.file.name) in WIKI_ATTACHMENTS_ALLOWED_EXTENSIONS:
|
||||
c = Context({'extension_err' : True,
|
||||
'extensions': WIKI_ATTACHMENTS_ALLOWED_EXTENSIONS,
|
||||
'wiki_article': article,
|
||||
'file': file})
|
||||
t = loader.get_template('simplewiki_updateprogressbar.html')
|
||||
return HttpResponse(t.render(c))
|
||||
|
||||
# Remove existing attachments
|
||||
# TODO: Move this until AFTER having removed file.
|
||||
# Current problem is that Django's FileField delete() method
|
||||
# automatically deletes files
|
||||
for a in article.attachments():
|
||||
if file_rel_path == a.file.name:
|
||||
a.delete()
|
||||
def receive_file():
|
||||
destination = open(file_path, 'wb+')
|
||||
size = file.size
|
||||
cnt = 0
|
||||
c = Context({'started' : True,})
|
||||
t = loader.get_template('simplewiki_updateprogressbar.html')
|
||||
yield t.render(c)
|
||||
for chunk in file.chunks():
|
||||
cnt += 1
|
||||
destination.write(chunk)
|
||||
c = Context({'progress_width' : (cnt*chunk_size) / size,
|
||||
'wiki_article': article,})
|
||||
t = loader.get_template('simplewiki_updateprogressbar.html')
|
||||
yield t.render(c)
|
||||
c = Context({'finished' : True,
|
||||
'wiki_article': article,})
|
||||
t = loader.get_template('simplewiki_updateprogressbar.html')
|
||||
destination.close()
|
||||
attachment.save()
|
||||
yield t.render(c)
|
||||
|
||||
return HttpResponse(receive_file())
|
||||
|
||||
return HttpResponse('')
|
||||
|
||||
# Taken from http://www.djangosnippets.org/snippets/365/
|
||||
def send_file(request, filepath):
|
||||
"""
|
||||
Send a file through Django without loading the whole file into
|
||||
memory at once. The FileWrapper will turn the file object into an
|
||||
iterator for chunks of 8KB.
|
||||
"""
|
||||
filename = filepath
|
||||
wrapper = FileWrapper(file(filename))
|
||||
response = HttpResponse(wrapper, content_type='text/plain')
|
||||
response['Content-Length'] = os.path.getsize(filename)
|
||||
return response
|
||||
|
||||
def view_attachment(request, wiki_url, file_name):
|
||||
|
||||
(article, path, err) = fetch_from_url(request, wiki_url)
|
||||
if err:
|
||||
return err
|
||||
|
||||
perm_err = check_permissions(request, article, check_read=True)
|
||||
if perm_err:
|
||||
return perm_err
|
||||
|
||||
attachment = None
|
||||
for a in article.attachments():
|
||||
if get_attachment_filepath(a, file_name) == a.file.name:
|
||||
attachment = a
|
||||
|
||||
if attachment:
|
||||
filepath = WIKI_ATTACHMENTS_ROOT + attachment.file.name
|
||||
if os.path.exists(filepath):
|
||||
return send_file(request, filepath)
|
||||
|
||||
raise Http404()
|
||||
|
||||
####################
|
||||
# LOGIN PROTECTION #
|
||||
####################
|
||||
|
||||
if WIKI_REQUIRE_LOGIN_VIEW:
|
||||
view_attachment = login_required(view_attachment)
|
||||
|
||||
if WIKI_REQUIRE_LOGIN_EDIT or not WIKI_ALLOW_ANON_ATTACHMENTS:
|
||||
add_attachment = login_required(add_attachment)
|
||||
1
urls.py
@@ -6,6 +6,7 @@ import django.contrib.auth.views
|
||||
# admin.autodiscover()
|
||||
|
||||
urlpatterns = patterns('',
|
||||
(r'^wiki/', include('simplewiki.urls')),
|
||||
url(r'^courseware/(?P<course>[^/]*)/(?P<chapter>[^/]*)/(?P<section>[^/]*)/$', 'courseware.views.index'),
|
||||
url(r'^courseware/(?P<course>[^/]*)/(?P<chapter>[^/]*)/$', 'courseware.views.index'),
|
||||
url(r'^courseware/(?P<course>[^/]*)/$', 'courseware.views.index'),
|
||||
|
||||