Commit 92ab1327 by Bridger Maxwell

Merge branch 'master' of github.com:benjaoming/django-wiki

parents 2edd8d37 59881309
Not implemented - will be ASAP Not implemented - will be ASAP
============================== ==============================
* Permission system in settings tab **Almost done** (option to apply changes recursively and to change owner if user has a grant permission)
* Notification system **Almost done** (email notifications) * Notification system **Almost done** (email notifications)
* Image plugin **In progress**
* Circuit Editor plugin * Circuit Editor plugin
* Custom storage engine for attachments * Custom storage engine for attachments
* Implement notifications, revision log messages and user messages thoroughly * Implement notifications, revision log messages and user messages thoroughly
...@@ -19,8 +17,10 @@ Not implemented - will be ASAP ...@@ -19,8 +17,10 @@ Not implemented - will be ASAP
* Embeddable article template tag * Embeddable article template tag
* Expand prepopulated database * Expand prepopulated database
* "Fix Wiki URL bug in the footnotes plugin for python-markdown" ? * "Fix Wiki URL bug in the footnotes plugin for python-markdown" ?
* Permission system in settings tab **Done**
* Special view for deleted articles w/ restore button **Done** * Special view for deleted articles w/ restore button **Done**
* Article deletion **Done** * Article deletion **Done**
* Image plugin **Done**
* Attachment plugin **Done** * Attachment plugin **Done**
* Simple user account handling: login/register etc. **Done** * Simple user account handling: login/register etc. **Done**
* South migrations **Done** * South migrations **Done**
......
...@@ -109,6 +109,7 @@ INSTALLED_APPS = ( ...@@ -109,6 +109,7 @@ INSTALLED_APPS = (
'sorl.thumbnail', 'sorl.thumbnail',
'wiki', 'wiki',
'wiki.plugins.images', 'wiki.plugins.images',
'wiki.plugins.help',
'wiki.plugins.attachments', 'wiki.plugins.attachments',
'wiki.plugins.notifications', 'wiki.plugins.notifications',
'mptt', 'mptt',
......
...@@ -13,6 +13,24 @@ WIKI_LANGUAGE = 'markdown' ...@@ -13,6 +13,24 @@ WIKI_LANGUAGE = 'markdown'
# extend the built-in editor and customize it.... # extend the built-in editor and customize it....
EDITOR = getattr(django_settings, 'WIKI_EDITOR', 'wiki.editors.markitup.MarkItUp') EDITOR = getattr(django_settings, 'WIKI_EDITOR', 'wiki.editors.markitup.MarkItUp')
# If you want to write an extension, you should use the plugin API as you
# will get an article model instance to play with. These are just the
# builtin markdown extensions
# Notice that this should be a callable which accepts the article as an argument
def get_extensions(article):
from wiki.models import URLPath
try:
urlpath = URLPath.objects.get(article=article)
url = reverse_lazy('wiki:get', kwargs={'path': urlpath.path})
except URLPath.DoesNotExist:
url = reverse_lazy('wiki:get', kwargs={'article_id': article.id})
return ['extra', 'wikilinks(base_url=%s)' % url, 'codehilite(force_linenos=True)', 'toc']
MARKDOWN_EXTENSIONS = getattr(
django_settings,
'WIKI_MARKDOWN_EXTENSIONS',
get_extensions
)
# This slug is used in URLPath if an article has been deleted. The children of the # This slug is used in URLPath if an article has been deleted. The children of the
# URLPath of that article are moved to lost and found. They keep their permissions # URLPath of that article are moved to lost and found. They keep their permissions
# and all their content. # and all their content.
......
...@@ -22,9 +22,7 @@ def send_file(request, filepath, last_modified=None, filename=None): ...@@ -22,9 +22,7 @@ def send_file(request, filepath, last_modified=None, filename=None):
response["Last-Modified"] = http_date(statobj.st_mtime) response["Last-Modified"] = http_date(statobj.st_mtime)
else: else:
if isinstance(last_modified, datetime): if isinstance(last_modified, datetime):
print last_modified
last_modified = float(dateformat.format(last_modified, 'U')) last_modified = float(dateformat.format(last_modified, 'U'))
print last_modified
response["Last-Modified"] = http_date(epoch_seconds=last_modified) response["Last-Modified"] = http_date(epoch_seconds=last_modified)
response["Content-Length"] = statobj.st_size response["Content-Length"] = statobj.st_size
......
...@@ -31,7 +31,9 @@ class BasePlugin(object): ...@@ -31,7 +31,9 @@ class BasePlugin(object):
markdown_extensions = [] markdown_extensions = []
pass class RenderMedia:
js = []
css = {}
class PluginSidebarFormMixin(object): class PluginSidebarFormMixin(object):
......
# -*- coding: utf-8 -*-
"""
Credits to ojii, functions get_module and load are from:
https://github.com/ojii/django-load.
Thanks for the technique!
"""
from django.conf import settings
from django.utils.importlib import import_module
def get_module(app, modname, verbose, failfast):
"""
Internal function to load a module from a single app.
"""
module_name = '%s.%s' % (app, modname)
try:
module = import_module(module_name)
except ImportError, e:
if failfast:
raise e
elif verbose:
print "Could not load %r from %r: %s" % (modname, app, e)
return None
if verbose:
print "Loaded %r from %r" % (modname, app)
return module
def load(modname, verbose=False, failfast=False):
"""
Loads all modules with name 'modname' from all installed apps.
If verbose is True, debug information will be printed to stdout.
If failfast is True, import errors will not be surpressed.
"""
for app in settings.INSTALLED_APPS:
get_module(app, modname, verbose, failfast)
def load_wiki_plugins():
load('wiki_plugin', verbose=False)
# -*- coding: utf-8 -*-
from django.utils.importlib import import_module
_cache = {}
_settings_forms = []
_markdown_extensions = []
_article_tabs = []
_sidebar = []
def register(PluginClass):
"""
Register a plugin class. This function will call back your plugin's
constructor.
"""
if PluginClass in _cache.keys():
raise Exception("Plugin class already registered")
plugin = PluginClass()
_cache[PluginClass] = plugin
settings_form = getattr(PluginClass, 'settings_form', None)
if settings_form:
if isinstance(settings_form, basestring):
klassname = settings_form.split(".")[-1]
modulename = ".".join(settings_form.split(".")[:-1])
form_module = import_module(modulename)
settings_form = getattr(form_module, klassname)
_settings_forms.append(settings_form)
if getattr(PluginClass, 'article_tab', None):
_article_tabs.append(plugin)
if getattr(PluginClass, 'sidebar', None):
_sidebar.append(plugin)
_markdown_extensions.extend(getattr(PluginClass, 'markdown_extensions', []))
def get_plugins():
return _cache
def get_markdown_extensions():
return _markdown_extensions
def get_article_tabs():
"""Returns plugin classes that should connect to the article tab menu"""
return _article_tabs
def get_sidebar():
"""Returns plugin classes that should connect to the sidebar"""
return _sidebar
...@@ -13,6 +13,7 @@ from wiki.editors import getEditor ...@@ -13,6 +13,7 @@ from wiki.editors import getEditor
from wiki.core.diff import simple_merge from wiki.core.diff import simple_merge
from django.forms.widgets import HiddenInput from django.forms.widgets import HiddenInput
from wiki.core.plugins.base import PluginSettingsFormMixin from wiki.core.plugins.base import PluginSettingsFormMixin
from django.contrib.auth.models import User
class CreateRootForm(forms.Form): class CreateRootForm(forms.Form):
...@@ -69,7 +70,7 @@ class EditForm(forms.Form): ...@@ -69,7 +70,7 @@ class EditForm(forms.Form):
def clean(self): def clean(self):
cd = self.cleaned_data cd = self.cleaned_data
if self.no_clean: if self.no_clean or self.preview:
return cd return cd
if not str(self.initial_revision.id) == str(self.presumed_revision): if not str(self.initial_revision.id) == str(self.presumed_revision):
raise forms.ValidationError(_(u'While you were editing, someone else changed the revision. Your contents have been automatically merged with the new contents. Please review the text below.')) raise forms.ValidationError(_(u'While you were editing, someone else changed the revision. Your contents have been automatically merged with the new contents. Please review the text below.'))
...@@ -231,12 +232,17 @@ class PermissionsForm(PluginSettingsFormMixin, forms.ModelForm): ...@@ -231,12 +232,17 @@ class PermissionsForm(PluginSettingsFormMixin, forms.ModelForm):
settings_order = 5 settings_order = 5
settings_write_access = False settings_write_access = False
owner_username = forms.CharField(required=False, label=_(u'Owner'),
help_text=_(u'Enter the username of the owner.'))
group = forms.ModelChoiceField(models.Group.objects.all(), widget=SelectWidgetBootstrap(), group = forms.ModelChoiceField(models.Group.objects.all(), widget=SelectWidgetBootstrap(),
empty_label=_(u'(none)'), required=False) empty_label=_(u'(none)'), required=False)
recursive = forms.BooleanField(label=_(u'Inherit permissions'), help_text=_(u'Check here to apply the above permissions recursively to articles under this one.'),
required=False)
def get_usermessage(self): def get_usermessage(self):
if self.changed_data: if self.changed_data:
return _('Your permission settings were updated.') return _('Permission settings for the article were updated.')
else: else:
return _('Your permission settings were unchanged, so nothing saved.') return _('Your permission settings were unchanged, so nothing saved.')
...@@ -245,13 +251,54 @@ class PermissionsForm(PluginSettingsFormMixin, forms.ModelForm): ...@@ -245,13 +251,54 @@ class PermissionsForm(PluginSettingsFormMixin, forms.ModelForm):
self.user = user self.user = user
kwargs['instance'] = article kwargs['instance'] = article
super(PermissionsForm, self).__init__(*args, **kwargs) super(PermissionsForm, self).__init__(*args, **kwargs)
print self.data self.can_change_groups = True
if user.has_perm("wiki.admin"): self.can_assign = False
if user.has_perm("wiki.assign"):
self.can_assign = True
self.fields['group'].queryset = models.Group.objects.all() self.fields['group'].queryset = models.Group.objects.all()
else: else:
self.fields['group'].queryset = models.Group.objects.filter(user=user) self.fields['owner_username'].widget = forms.HiddenInput()
self.fields['recursive'].widget = forms.HiddenInput()
groups = models.Group.objects.filter(user=user)
self.fields['group'].queryset = groups
# Sanity: If somehow the article belongs to a group that the
# owner is not a member of, don't let the owner make any decisions
# for group permissions.
if article.group and not user in article.group.user_set.all():
self.can_change_groups = False
self.fields['group'].widget = forms.HiddenInput()
self.fields['group_read'].widget = forms.HiddenInput()
self.fields['group_write'].widget = forms.HiddenInput()
self.fields['owner_username'].initial = article.owner.username if article.owner else ""
def clean_owner_username(self):
if self.can_assign:
username = self.cleaned_data['owner_username']
if username:
try:
user = User.objects.get(username=username)
except models.User.DoesNotExist:
raise forms.ValidationError(_(u'No user with that username'))
else:
user = None
else:
user = self.article.owner
return user
def save(self, commit=True):
article = super(PermissionsForm, self).save(commit=False)
article.owner = self.cleaned_data['owner_username']
if not self.can_change_groups:
article.group = self.article.group
article.group_read = self.article.group_read
article.group_write = self.article.group_write
if self.can_assign and self.cleaned_data['recursive']:
article.set_permissions_recursive()
article.save()
class Meta: class Meta:
model = models.Article model = models.Article
fields = ('group', 'group_read', 'group_write', 'other_read', 'other_write') fields = ('owner_username', 'group', 'group_read', 'group_write', 'other_read', 'other_write',
'recursive')
...@@ -69,11 +69,11 @@ class Article(models.Model): ...@@ -69,11 +69,11 @@ class Article(models.Model):
return True return True
return False return False
def decendant_objects(self): def descendant_objects(self):
"""NB! This generator is expensive, so use it with care!!""" """NB! This generator is expensive, so use it with care!!"""
for obj in self.articleforobject_set.filter(is_mptt=True): for obj in self.articleforobject_set.filter(is_mptt=True):
for decendant in obj.content_object.get_decendants(): for descendant in obj.content_object.get_descendants():
yield decendant yield descendant
def get_children(self, max_num=None, **kwargs): def get_children(self, max_num=None, **kwargs):
"""NB! This generator is expensive, so use it with care!!""" """NB! This generator is expensive, so use it with care!!"""
...@@ -84,25 +84,28 @@ class Article(models.Model): ...@@ -84,25 +84,28 @@ class Article(models.Model):
if max_num and cnt > max_num: return if max_num and cnt > max_num: return
yield child yield child
# All recursive permission methods will use decendant_objects to access # All recursive permission methods will use descendant_objects to access
# generic relations and check if they are using MPTT and have INHERIT_PERMISSIONS=True # generic relations and check if they are using MPTT and have INHERIT_PERMISSIONS=True
def set_permissions_recursive(self): def set_permissions_recursive(self):
for decendant in self.decendant_objects(): for descendant in self.descendant_objects():
if decendant.INHERIT_PERMISSIONS: if descendant.INHERIT_PERMISSIONS:
decendant.group_read = self.group_read descendant.group_read = self.group_read
decendant.group_write = self.group_write descendant.group_write = self.group_write
decendant.other_read = self.other_read descendant.other_read = self.other_read
decendant.other_write = self.other_write descendant.other_write = self.other_write
descendant.save()
def set_group_recursive(self): def set_group_recursive(self):
for decendant in self.decendant_objects(): for descendant in self.descendant_objects():
if decendant.INHERIT_PERMISSIONS: if descendant.INHERIT_PERMISSIONS:
decendant.group = self.group descendant.group = self.group
descendant.save()
def set_owner_recursive(self): def set_owner_recursive(self):
for decendant in self.decendant_objects(): for descendant in self.descendant_objects():
if decendant.INHERIT_PERMISSIONS: if descendant.INHERIT_PERMISSIONS:
decendant.owner = self.owner descendant.owner = self.owner
descendant.save()
def add_revision(self, new_revision, save=True): def add_revision(self, new_revision, save=True):
""" """
...@@ -158,6 +161,7 @@ class Article(models.Model): ...@@ -158,6 +161,7 @@ class Article(models.Model):
else: else:
content = self.current_revision.content content = self.current_revision.content
extensions = plugin_registry.get_markdown_extensions() extensions = plugin_registry.get_markdown_extensions()
extensions += settings.MARKDOWN_EXTENSIONS(self)
return mark_safe(article_markdown(content, self, extensions=extensions)) return mark_safe(article_markdown(content, self, extensions=extensions))
......
...@@ -25,7 +25,7 @@ class AttachmentPreprocessor(markdown.preprocessors.Preprocessor): ...@@ -25,7 +25,7 @@ class AttachmentPreprocessor(markdown.preprocessors.Preprocessor):
if m: if m:
attachment_id = m.group('id').strip() attachment_id = m.group('id').strip()
try: try:
attachment = models.Attachment.objects.get(article=self.markdown.article, attachment = models.Attachment.objects.get(articles=self.markdown.article,
id=attachment_id) id=attachment_id)
url = reverse('wiki:attachments_download', kwargs={'article_id': self.markdown.article.id, url = reverse('wiki:attachments_download', kwargs={'article_id': self.markdown.article.id,
'attachment_id':attachment.id,}) 'attachment_id':attachment.id,})
......
from django.db import models
# Create your models here.
{% load i18n %}<h4>{% trans "Adding new articles" %}</h4>
<p>
{% trans "To create a new wiki article, create a link to it. Clicking the link gives you the creation page." %}
</p>
<pre>[Article Name](wiki:ArticleName)</pre>
<h4>{% trans "An external link" %}</h4>
<pre>[Link](http://google.com)</pre>
<h4>{% trans "Headers" %}</h4>
<p>{% trans "Use these codes for headers and to automatically generate Tables of Contents." %}</p>
<pre>Hugest header
===========
Huger header
------------
# Hugest Header
## Huger header
### Huge header
#### Small header
[[ TOC ]]
</pre>
<h4>{% trans "Typography" %}</h4>
<pre>*emphasis* or _emphasis_</pre>
<pre>**strong** or __strong__</pre>
<h4>{% trans "Lists" %}</h4>
<pre>- Unordered List
- Sub Item 1
- Sub Item 2</pre>
<pre>1. Ordered
2. List</pre>
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
Replace this 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.assertEqual(1 + 1, 2)
# Create your views here.
# -*- coding: utf-8 -*-
from django.conf.urls.defaults import patterns
from django.utils.translation import ugettext as _
from wiki.core.plugins import registry
from wiki.core.plugins.base import BasePlugin
class HelpPlugin(BasePlugin):
slug = 'help'
urlpatterns = patterns('',)
sidebar = {'headline': _('Help'),
'icon_class': 'icon-question-sign',
'template': 'wiki/plugins/help/sidebar.html',
'form_class': None,
'get_form_kwargs': (lambda a: {})}
markdown_extensions = []
def __init__(self):
pass
registry.register(HelpPlugin)
...@@ -42,6 +42,8 @@ class RevisionForm(forms.ModelForm): ...@@ -42,6 +42,8 @@ class RevisionForm(forms.ModelForm):
kwargs['commit'] = False kwargs['commit'] = False
revision = super(RevisionForm, self).save(*args, **kwargs) revision = super(RevisionForm, self).save(*args, **kwargs)
revision.set_from_request(self.request) revision.set_from_request(self.request)
revision.set_from_request(self.request)
revision.save()
self.image.add_revision(self.instance, save=True) self.image.add_revision(self.instance, save=True)
return revision return revision
return super(SidebarForm, self).save(*args, **kwargs) return super(SidebarForm, self).save(*args, **kwargs)
......
import markdown
import re
from django.template.loader import render_to_string
from django.template import Context
from wiki.core import article_markdown
IMAGE_RE = re.compile(r'.*(\[image\:(?P<id>\d+)\s+align\:(?P<align>right|left|center)\s*\]).*',
re.IGNORECASE)
from wiki.plugins.images import models
class ImageExtension(markdown.Extension):
""" Images plugin markdown extension for django-wiki. """
def extendMarkdown(self, md, md_globals):
""" Insert ImagePreprocessor before ReferencePreprocessor. """
md.preprocessors.add('dw-images', ImagePreprocessor(md), '>html_block')
class ImagePreprocessor(markdown.preprocessors.Preprocessor):
"""django-wiki image preprocessor - parse text for [image:id align:left|right|center] references. """
def run(self, lines):
new_text = []
previous_line_was_image = False
image = None
image_id = None
alignment = None
caption = ""
for line in lines:
m = IMAGE_RE.match(line)
if m:
previous_line_was_image = True
image_id = m.group('id').strip()
alignment = m.group('align').strip()
try:
image = models.Image.objects.get(article=self.markdown.article,
id=image_id,
current_revision__deleted=False)
except models.Image.DoesNotExist:
pass
line = line.replace(m.group(1), "")
elif previous_line_was_image:
print line
if line.startswith(" "):
caption += line[4:]
line = ""
else:
html = render_to_string("wiki/plugins/images/render.html",
Context({'image': image,
'caption': article_markdown(caption, self.markdown.article,
extensions=self.markdown.registeredExtensions),
'align': alignment}))
line = html + line
previous_line_was_image = False
new_text.append(line)
return new_text
// ColorBox v1.3.20 - jQuery lightbox plugin
// (c) 2012 Jack Moore - jacklmoore.com
// License: http://www.opensource.org/licenses/mit-license.php
(function(e,t,n){function G(n,r,i){var o=t.createElement(n);return r&&(o.id=s+r),i&&(o.style.cssText=i),e(o)}function Y(e){var t=T.length,n=(U+e)%t;return n<0?t+n:n}function Z(e,t){return Math.round((/%/.test(e)?(t==="x"?N.width():N.height())/100:1)*parseInt(e,10))}function et(e){return B.photo||/\.(gif|png|jp(e|g|eg)|bmp|ico)((#|\?).*)?$/i.test(e)}function tt(){var t,n=e.data(R,i);n==null?(B=e.extend({},r),console&&console.log&&console.log("Error: cboxElement missing settings object")):B=e.extend({},n);for(t in B)e.isFunction(B[t])&&t.slice(0,2)!=="on"&&(B[t]=B[t].call(R));B.rel=B.rel||R.rel||"nofollow",B.href=B.href||e(R).attr("href"),B.title=B.title||R.title,typeof B.href=="string"&&(B.href=e.trim(B.href))}function nt(t,n){e.event.trigger(t),n&&n.call(R)}function rt(){var e,t=s+"Slideshow_",n="click."+s,r,i,o;B.slideshow&&T[1]?(r=function(){M.text(B.slideshowStop).unbind(n).bind(f,function(){if(B.loop||T[U+1])e=setTimeout(J.next,B.slideshowSpeed)}).bind(a,function(){clearTimeout(e)}).one(n+" "+l,i),g.removeClass(t+"off").addClass(t+"on"),e=setTimeout(J.next,B.slideshowSpeed)},i=function(){clearTimeout(e),M.text(B.slideshowStart).unbind([f,a,l,n].join(" ")).one(n,function(){J.next(),r()}),g.removeClass(t+"on").addClass(t+"off")},B.slideshowAuto?r():i()):g.removeClass(t+"off "+t+"on")}function it(t){V||(R=t,tt(),T=e(R),U=0,B.rel!=="nofollow"&&(T=e("."+o).filter(function(){var t=e.data(this,i),n;return t&&(n=t.rel||this.rel),n===B.rel}),U=T.index(R),U===-1&&(T=T.add(R),U=T.length-1)),W||(W=X=!0,g.show(),B.returnFocus&&e(R).blur().one(c,function(){e(this).focus()}),m.css({opacity:+B.opacity,cursor:B.overlayClose?"pointer":"auto"}).show(),B.w=Z(B.initialWidth,"x"),B.h=Z(B.initialHeight,"y"),J.position(),d&&N.bind("resize."+v+" scroll."+v,function(){m.css({width:N.width(),height:N.height(),top:N.scrollTop(),left:N.scrollLeft()})}).trigger("resize."+v),nt(u,B.onOpen),H.add(A).hide(),P.html(B.close).show()),J.load(!0))}function st(){!g&&t.body&&(Q=!1,N=e(n),g=G(K).attr({id:i,"class":p?s+(d?"IE6":"IE"):""}).hide(),m=G(K,"Overlay",d?"position:absolute":"").hide(),y=G(K,"Wrapper"),b=G(K,"Content").append(C=G(K,"LoadedContent","width:0; height:0; overflow:hidden"),L=G(K,"LoadingOverlay").add(G(K,"LoadingGraphic")),A=G(K,"Title"),O=G(K,"Current"),_=G(K,"Next"),D=G(K,"Previous"),M=G(K,"Slideshow").bind(u,rt),P=G(K,"Close")),y.append(G(K).append(G(K,"TopLeft"),w=G(K,"TopCenter"),G(K,"TopRight")),G(K,!1,"clear:left").append(E=G(K,"MiddleLeft"),b,S=G(K,"MiddleRight")),G(K,!1,"clear:left").append(G(K,"BottomLeft"),x=G(K,"BottomCenter"),G(K,"BottomRight"))).find("div div").css({"float":"left"}),k=G(K,!1,"position:absolute; width:9999px; visibility:hidden; display:none"),H=_.add(D).add(O).add(M),e(t.body).append(m,g.append(y,k)))}function ot(){return g?(Q||(Q=!0,j=w.height()+x.height()+b.outerHeight(!0)-b.height(),F=E.width()+S.width()+b.outerWidth(!0)-b.width(),I=C.outerHeight(!0),q=C.outerWidth(!0),g.css({"padding-bottom":j,"padding-right":F}),_.click(function(){J.next()}),D.click(function(){J.prev()}),P.click(function(){J.close()}),m.click(function(){B.overlayClose&&J.close()}),e(t).bind("keydown."+s,function(e){var t=e.keyCode;W&&B.escKey&&t===27&&(e.preventDefault(),J.close()),W&&B.arrowKey&&T[1]&&(t===37?(e.preventDefault(),D.click()):t===39&&(e.preventDefault(),_.click()))}),e("."+o,t).live("click",function(e){e.which>1||e.shiftKey||e.altKey||e.metaKey||(e.preventDefault(),it(this))})),!0):!1}var r={transition:"elastic",speed:300,width:!1,initialWidth:"600",innerWidth:!1,maxWidth:!1,height:!1,initialHeight:"450",innerHeight:!1,maxHeight:!1,scalePhotos:!0,scrolling:!0,inline:!1,html:!1,iframe:!1,fastIframe:!0,photo:!1,href:!1,title:!1,rel:!1,opacity:.9,preloading:!0,current:"image {current} of {total}",previous:"previous",next:"next",close:"close",xhrError:"This content failed to load.",imgError:"This image failed to load.",open:!1,returnFocus:!0,reposition:!0,loop:!0,slideshow:!1,slideshowAuto:!0,slideshowSpeed:2500,slideshowStart:"start slideshow",slideshowStop:"stop slideshow",onOpen:!1,onLoad:!1,onComplete:!1,onCleanup:!1,onClosed:!1,overlayClose:!0,escKey:!0,arrowKey:!0,top:!1,bottom:!1,left:!1,right:!1,fixed:!1,data:undefined},i="colorbox",s="cbox",o=s+"Element",u=s+"_open",a=s+"_load",f=s+"_complete",l=s+"_cleanup",c=s+"_closed",h=s+"_purge",p=!e.support.opacity&&!e.support.style,d=p&&!n.XMLHttpRequest,v=s+"_IE6",m,g,y,b,w,E,S,x,T,N,C,k,L,A,O,M,_,D,P,H,B,j,F,I,q,R,U,z,W,X,V,$,J,K="div",Q;if(e.colorbox)return;e(st),J=e.fn[i]=e[i]=function(t,n){var s=this;t=t||{},st();if(ot()){if(!s[0]){if(s.selector)return s;s=e("<a/>"),t.open=!0}n&&(t.onComplete=n),s.each(function(){e.data(this,i,e.extend({},e.data(this,i)||r,t))}).addClass(o),(e.isFunction(t.open)&&t.open.call(s)||t.open)&&it(s[0])}return s},J.position=function(e,t){function f(e){w[0].style.width=x[0].style.width=b[0].style.width=e.style.width,b[0].style.height=E[0].style.height=S[0].style.height=e.style.height}var n,r=0,i=0,o=g.offset(),u,a;N.unbind("resize."+s),g.css({top:-9e4,left:-9e4}),u=N.scrollTop(),a=N.scrollLeft(),B.fixed&&!d?(o.top-=u,o.left-=a,g.css({position:"fixed"})):(r=u,i=a,g.css({position:"absolute"})),B.right!==!1?i+=Math.max(N.width()-B.w-q-F-Z(B.right,"x"),0):B.left!==!1?i+=Z(B.left,"x"):i+=Math.round(Math.max(N.width()-B.w-q-F,0)/2),B.bottom!==!1?r+=Math.max(N.height()-B.h-I-j-Z(B.bottom,"y"),0):B.top!==!1?r+=Z(B.top,"y"):r+=Math.round(Math.max(N.height()-B.h-I-j,0)/2),g.css({top:o.top,left:o.left}),e=g.width()===B.w+q&&g.height()===B.h+I?0:e||0,y[0].style.width=y[0].style.height="9999px",n={width:B.w+q,height:B.h+I,top:r,left:i},e===0&&g.css(n),g.dequeue().animate(n,{duration:e,complete:function(){f(this),X=!1,y[0].style.width=B.w+q+F+"px",y[0].style.height=B.h+I+j+"px",B.reposition&&setTimeout(function(){N.bind("resize."+s,J.position)},1),t&&t()},step:function(){f(this)}})},J.resize=function(e){W&&(e=e||{},e.width&&(B.w=Z(e.width,"x")-q-F),e.innerWidth&&(B.w=Z(e.innerWidth,"x")),C.css({width:B.w}),e.height&&(B.h=Z(e.height,"y")-I-j),e.innerHeight&&(B.h=Z(e.innerHeight,"y")),!e.innerHeight&&!e.height&&(C.css({height:"auto"}),B.h=C.height()),C.css({height:B.h}),J.position(B.transition==="none"?0:B.speed))},J.prep=function(t){function o(){return B.w=B.w||C.width(),B.w=B.mw&&B.mw<B.w?B.mw:B.w,B.w}function u(){return B.h=B.h||C.height(),B.h=B.mh&&B.mh<B.h?B.mh:B.h,B.h}if(!W)return;var n,r=B.transition==="none"?0:B.speed;C.remove(),C=G(K,"LoadedContent").append(t),C.hide().appendTo(k.show()).css({width:o(),overflow:B.scrolling?"auto":"hidden"}).css({height:u()}).prependTo(b),k.hide(),e(z).css({"float":"none"}),d&&e("select").not(g.find("select")).filter(function(){return this.style.visibility!=="hidden"}).css({visibility:"hidden"}).one(l,function(){this.style.visibility="inherit"}),n=function(){function y(){p&&g[0].style.removeAttribute("filter")}var t,n,o=T.length,u,a="frameBorder",l="allowTransparency",c,d,v,m;if(!W)return;c=function(){clearTimeout($),L.hide(),nt(f,B.onComplete)},p&&z&&C.fadeIn(100),A.html(B.title).add(C).show();if(o>1){typeof B.current=="string"&&O.html(B.current.replace("{current}",U+1).replace("{total}",o)).show(),_[B.loop||U<o-1?"show":"hide"]().html(B.next),D[B.loop||U?"show":"hide"]().html(B.previous),B.slideshow&&M.show();if(B.preloading){t=[Y(-1),Y(1)];while(n=T[t.pop()])m=e.data(n,i),m&&m.href?(d=m.href,e.isFunction(d)&&(d=d.call(n))):d=n.href,et(d)&&(v=new Image,v.src=d)}}else H.hide();B.iframe?(u=G("iframe")[0],a in u&&(u[a]=0),l in u&&(u[l]="true"),u.name=s+ +(new Date),B.fastIframe?c():e(u).one("load",c),u.src=B.href,B.scrolling||(u.scrolling="no"),e(u).addClass(s+"Iframe").appendTo(C).one(h,function(){u.src="//about:blank"})):c(),B.transition==="fade"?g.fadeTo(r,1,y):y()},B.transition==="fade"?g.fadeTo(r,0,function(){J.position(0,n)}):J.position(r,n)},J.load=function(t){var n,r,i=J.prep;X=!0,z=!1,R=T[U],t||tt(),nt(h),nt(a,B.onLoad),B.h=B.height?Z(B.height,"y")-I-j:B.innerHeight&&Z(B.innerHeight,"y"),B.w=B.width?Z(B.width,"x")-q-F:B.innerWidth&&Z(B.innerWidth,"x"),B.mw=B.w,B.mh=B.h,B.maxWidth&&(B.mw=Z(B.maxWidth,"x")-q-F,B.mw=B.w&&B.w<B.mw?B.w:B.mw),B.maxHeight&&(B.mh=Z(B.maxHeight,"y")-I-j,B.mh=B.h&&B.h<B.mh?B.h:B.mh),n=B.href,$=setTimeout(function(){L.show()},100),B.inline?(G(K).hide().insertBefore(e(n)[0]).one(h,function(){e(this).replaceWith(C.children())}),i(e(n))):B.iframe?i(" "):B.html?i(B.html):et(n)?(e(z=new Image).addClass(s+"Photo").error(function(){B.title=!1,i(G(K,"Error").html(B.imgError))}).load(function(){var e;z.onload=null,B.scalePhotos&&(r=function(){z.height-=z.height*e,z.width-=z.width*e},B.mw&&z.width>B.mw&&(e=(z.width-B.mw)/z.width,r()),B.mh&&z.height>B.mh&&(e=(z.height-B.mh)/z.height,r())),B.h&&(z.style.marginTop=Math.max(B.h-z.height,0)/2+"px"),T[1]&&(B.loop||T[U+1])&&(z.style.cursor="pointer",z.onclick=function(){J.next()}),p&&(z.style.msInterpolationMode="bicubic"),setTimeout(function(){i(z)},1)}),setTimeout(function(){z.src=n},1)):n&&k.load(n,B.data,function(t,n,r){i(n==="error"?G(K,"Error").html(B.xhrError):e(this).contents())})},J.next=function(){!X&&T[1]&&(B.loop||T[U+1])&&(U=Y(1),J.load())},J.prev=function(){!X&&T[1]&&(B.loop||U)&&(U=Y(-1),J.load())},J.close=function(){W&&!V&&(V=!0,W=!1,nt(l,B.onCleanup),N.unbind("."+s+" ."+v),m.fadeTo(200,0),g.stop().fadeTo(300,0,function(){g.add(m).css({opacity:1,cursor:"auto"}).hide(),nt(h),C.remove(),setTimeout(function(){V=!1,nt(c,B.onClosed)},1)}))},J.remove=function(){e([]).add(g).add(m).remove(),g=null,e("."+o).removeData(i).removeClass(o).die()},J.element=function(){return e(R)},J.settings=r})(jQuery,document,this);
\ No newline at end of file
<div id='homer' style="background:url(../content/homer.jpg) right center no-repeat #ececec; height:135px; width:280px; padding:30px 10px;">
<strong>Homer</strong><br/>
<em>\noun\</em><br/>
<strong>1.</strong> American bonehead<br/>
<strong>2. Pull a Homer-</strong><br/>
to succeed despite<br/>
idiocy
</div>
<script>
$('#homer strong').css({color:'red'});
</script>
\ No newline at end of file
<div style="width:504px; height:412px; overflow:hidden;">
<object>
<param name="allowfullscreen" value="true" /><param name="wmode" value="opaque" />
<param name="allowscriptaccess" value="always" />
<param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=2285902&amp;server=vimeo.com&amp;show_title=0&amp;show_byline=0&amp;show_portrait=0&amp;color=00ADEF&amp;fullscreen=1" />
<embed src="http://vimeo.com/moogaloop.swf?clip_id=2285902&amp;server=vimeo.com&amp;show_title=0&amp;show_byline=0&amp;show_portrait=0&amp;color=00ADEF&amp;fullscreen=1" type="application/x-shockwave-flash" allowfullscreen="true" wmode="opaque" allowscriptaccess="always" width="504" height="412"></embed>
</object>
</div>
\ No newline at end of file
/*
ColorBox Core Style:
The following CSS is consistent between example themes and should not be altered.
*/
#colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999; overflow:hidden;}
#cboxOverlay{position:fixed; width:100%; height:100%;}
#cboxMiddleLeft, #cboxBottomLeft{clear:left;}
#cboxContent{position:relative;}
#cboxLoadedContent{overflow:auto;}
#cboxTitle{margin:0;}
#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:0; left:0; width:100%; height:100%;}
#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;}
.cboxPhoto{float:left; margin:auto; border:0; display:block; max-width:none;}
.cboxIframe{width:100%; height:100%; display:block; border:0;}
#colorbox, #cboxContent, #cboxLoadedContent{box-sizing:content-box;}
/*
User Style:
Change the following styles to modify the appearance of ColorBox. They are
ordered & tabbed in a way that represents the nesting of the generated HTML.
*/
#cboxOverlay{background:url(images/overlay.png) repeat 0 0;}
#colorbox{}
#cboxTopLeft{width:21px; height:21px; background:url(images/controls.png) no-repeat -101px 0;}
#cboxTopRight{width:21px; height:21px; background:url(images/controls.png) no-repeat -130px 0;}
#cboxBottomLeft{width:21px; height:21px; background:url(images/controls.png) no-repeat -101px -29px;}
#cboxBottomRight{width:21px; height:21px; background:url(images/controls.png) no-repeat -130px -29px;}
#cboxMiddleLeft{width:21px; background:url(images/controls.png) left top repeat-y;}
#cboxMiddleRight{width:21px; background:url(images/controls.png) right top repeat-y;}
#cboxTopCenter{height:21px; background:url(images/border.png) 0 0 repeat-x;}
#cboxBottomCenter{height:21px; background:url(images/border.png) 0 -29px repeat-x;}
#cboxContent{background:#fff; overflow:hidden;}
.cboxIframe{background:#fff;}
#cboxError{padding:50px; border:1px solid #ccc;}
#cboxLoadedContent{margin-bottom:28px;}
#cboxTitle{position:absolute; bottom:4px; left:0; text-align:center; width:100%; color:#949494;}
#cboxCurrent{position:absolute; bottom:4px; left:58px; color:#949494;}
#cboxSlideshow{position:absolute; bottom:4px; right:30px; color:#0092ef;}
#cboxPrevious{position:absolute; bottom:0; left:0; background:url(images/controls.png) no-repeat -75px 0; width:25px; height:25px; text-indent:-9999px;}
#cboxPrevious:hover{background-position:-75px -25px;}
#cboxNext{position:absolute; bottom:0; left:27px; background:url(images/controls.png) no-repeat -50px 0; width:25px; height:25px; text-indent:-9999px;}
#cboxNext:hover{background-position:-50px -25px;}
#cboxLoadingOverlay{background:url(images/loading_background.png) no-repeat center center;}
#cboxLoadingGraphic{background:url(images/loading.gif) no-repeat center center;}
#cboxClose{position:absolute; bottom:0; right:0; background:url(images/controls.png) no-repeat -25px 0; width:25px; height:25px; text-indent:-9999px;}
#cboxClose:hover{background-position:-25px -25px;}
/*
The following fixes a problem where IE7 and IE8 replace a PNG's alpha transparency with a black fill
when an alpha filter (opacity change) is set on the element or ancestor element. This style is not applied to or needed in IE9.
See: http://jacklmoore.com/notes/ie-transparency-problems/
*/
.cboxIE #cboxTopLeft,
.cboxIE #cboxTopCenter,
.cboxIE #cboxTopRight,
.cboxIE #cboxBottomLeft,
.cboxIE #cboxBottomCenter,
.cboxIE #cboxBottomRight,
.cboxIE #cboxMiddleLeft,
.cboxIE #cboxMiddleRight {
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#00FFFFFF,endColorstr=#00FFFFFF);
}
/*
The following provides PNG transparency support for IE6
Feel free to remove this and the /ie6/ directory if you have dropped IE6 support.
*/
.cboxIE6 #cboxTopLeft{background:url(images/ie6/borderTopLeft.png);}
.cboxIE6 #cboxTopCenter{background:url(images/ie6/borderTopCenter.png);}
.cboxIE6 #cboxTopRight{background:url(images/ie6/borderTopRight.png);}
.cboxIE6 #cboxBottomLeft{background:url(images/ie6/borderBottomLeft.png);}
.cboxIE6 #cboxBottomCenter{background:url(images/ie6/borderBottomCenter.png);}
.cboxIE6 #cboxBottomRight{background:url(images/ie6/borderBottomRight.png);}
.cboxIE6 #cboxMiddleLeft{background:url(images/ie6/borderMiddleLeft.png);}
.cboxIE6 #cboxMiddleRight{background:url(images/ie6/borderMiddleRight.png);}
.cboxIE6 #cboxTopLeft,
.cboxIE6 #cboxTopCenter,
.cboxIE6 #cboxTopRight,
.cboxIE6 #cboxBottomLeft,
.cboxIE6 #cboxBottomCenter,
.cboxIE6 #cboxBottomRight,
.cboxIE6 #cboxMiddleLeft,
.cboxIE6 #cboxMiddleRight {
_behavior: expression(this.src = this.src ? this.src : this.currentStyle.backgroundImage.split('"')[1], this.style.background = "none", this.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src=" + this.src + ", sizingMethod='scale')");
}
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'/>
<title>ColorBox Examples</title>
<style>
body{font:12px/1.2 Verdana, sans-serif; padding:0 10px;}
a:link, a:visited{text-decoration:none; color:#416CE5; border-bottom:1px solid #416CE5;}
h2{font-size:13px; margin:15px 0 0 0;}
</style>
<link rel="stylesheet" href="colorbox.css" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="../colorbox/jquery.colorbox.js"></script>
<script>
$(document).ready(function(){
//Examples of how to assign the ColorBox event to elements
$(".group1").colorbox({rel:'group1'});
$(".group2").colorbox({rel:'group2', transition:"fade"});
$(".group3").colorbox({rel:'group3', transition:"none", width:"75%", height:"75%"});
$(".group4").colorbox({rel:'group4', slideshow:true});
$(".ajax").colorbox();
$(".youtube").colorbox({iframe:true, innerWidth:425, innerHeight:344});
$(".iframe").colorbox({iframe:true, width:"80%", height:"80%"});
$(".inline").colorbox({inline:true, width:"50%"});
$(".callbacks").colorbox({
onOpen:function(){ alert('onOpen: colorbox is about to open'); },
onLoad:function(){ alert('onLoad: colorbox has started to load the targeted content'); },
onComplete:function(){ alert('onComplete: colorbox has displayed the loaded content'); },
onCleanup:function(){ alert('onCleanup: colorbox has begun the close process'); },
onClosed:function(){ alert('onClosed: colorbox has completely closed'); }
});
//Example of preserving a JavaScript event for inline calls.
$("#click").click(function(){
$('#click').css({"background-color":"#f00", "color":"#fff", "cursor":"inherit"}).text("Open this window again and this message will still be here.");
return false;
});
});
</script>
</head>
<body>
<h1>ColorBox Demonstration</h1>
<h2>Elastic Transition</h2>
<p><a class="group1" href="../content/ohoopee1.jpg" title="Me and my grandfather on the Ohoopee.">Grouped Photo 1</a></p>
<p><a class="group1" href="../content/ohoopee2.jpg" title="On the Ohoopee as a child">Grouped Photo 2</a></p>
<p><a class="group1" href="../content/ohoopee3.jpg" title="On the Ohoopee as an adult">Grouped Photo 3</a></p>
<h2>Fade Transition</h2>
<p><a class="group2" href="../content/ohoopee1.jpg" title="Me and my grandfather on the Ohoopee">Grouped Photo 1</a></p>
<p><a class="group2" href="../content/ohoopee2.jpg" title="On the Ohoopee as a child">Grouped Photo 2</a></p>
<p><a class="group2" href="../content/ohoopee3.jpg" title="On the Ohoopee as an adult">Grouped Photo 3</a></p>
<h2>No Transition + fixed width and height (75% of screen size)</h2>
<p><a class="group3" href="../content/ohoopee1.jpg" title="Me and my grandfather on the Ohoopee.">Grouped Photo 1</a></p>
<p><a class="group3" href="../content/ohoopee2.jpg" title="On the Ohoopee as a child">Grouped Photo 2</a></p>
<p><a class="group3" href="../content/ohoopee3.jpg" title="On the Ohoopee as an adult">Grouped Photo 3</a></p>
<h2>Slideshow</h2>
<p><a class="group4" href="../content/ohoopee1.jpg" title="Me and my grandfather on the Ohoopee.">Grouped Photo 1</a></p>
<p><a class="group4" href="../content/ohoopee2.jpg" title="On the Ohoopee as a child">Grouped Photo 2</a></p>
<p><a class="group4" href="../content/ohoopee3.jpg" title="On the Ohoopee as an adult">Grouped Photo 3</a></p>
<h2>Other Content Types</h2>
<p><a class='ajax' href="../content/ajax.html" title="Homer Defined">Outside HTML (Ajax)</a></p>
<p><a class='ajax' href="../content/flash.html" title="Royksopp: Remind Me">Flash / Video (Ajax/Embedded)</a></p>
<p><a class='youtube' href="http://www.youtube.com/embed/617ANIA5Rqs?rel=0&amp;wmode=transparent" title="The Knife: We Share Our Mother's Health">Flash / Video (Iframe/Direct Link To YouTube)</a></p>
<p><a class='iframe' href="http://threadless.com">Outside Webpage (Iframe)</a></p>
<p><a class='inline' href="#inline_content">Inline HTML</a></p>
<h2>Demonstration of using callbacks</h2>
<p><a class='callbacks' href="../content/marylou.jpg" title="Marylou on Cumberland Island">Example with alerts</a>. Callbacks and event-hooks allow users to extend functionality without having to rewrite parts of the plugin.</p>
<!-- This contains the hidden content for inline calls -->
<div style='display:none'>
<div id='inline_content' style='padding:10px; background:#fff;'>
<p><strong>This content comes from a hidden element on this page.</strong></p>
<p>The inline option preserves bound JavaScript events and changes, and it puts the content back where it came from when it is closed.</p>
<p><a id="click" href="#" style='padding:5px; background:#ccc;'>Click me, it will be preserved!</a></p>
<p><strong>If you try to open a new ColorBox while it is already open, it will update itself with the new content.</strong></p>
<p>Updating Content Example:<br />
<a class="ajax" href="../content/flash.html">Click here to load new content</a></p>
</div>
</div>
</body>
</html>
\ No newline at end of file
$(document).ready(function() {
$('.wiki-article .thumbnail a').colorbox();
});
{% load thumbnail i18n %}
{% comment %}
This template is used for the markdown extension that renders images and captions
{% endcomment %}
{% with image.current_revision.imagerevision as revision %}
<div class="pull-{{ align }}" style="width: 250px; margin: 10px;">
<div class="thumbnail">
{% thumbnail revision.image "250x250" as thumb %}
<a href="{{ revision.image.url }}">
<img src="{{ thumb.url }}" alt="{{ revision.get_filename }}" />
</a>
{% empty %}
<div class="caption">
<em>{% trans "Image not found" %}</em>
</div>
{% endthumbnail %}
<div class="caption">
{{ caption|safe }}
</div>
</div>
</div>
{% endwith %}
{% load i18n wiki_tags wiki_images_tags humanize thumbnail %} {% load i18n wiki_tags wiki_images_tags humanize thumbnail %}
{% load url from future %} {% load url from future %}
<script type="text/javascript">
function insert_image(image_id) {
align=prompt('Enter an alignment. \'left\', \'right\' or \'center\':', 'right');
caption=prompt('Enter a caption text. You may use markdown. You can edit the text after...');
$('#id_content').insertAtCaret('\n[image:'+image_id+' align:'+align+']\n '+caption+'\n\n');
}
</script>
{% with article|images_for_article as images %} {% with article|images_for_article as images %}
<style type="text/css"> <style type="text/css">
#image-list tr:first-child td {border:0;} #image-list tr:first-child td {border:0;}
...@@ -12,9 +20,9 @@ ...@@ -12,9 +20,9 @@
{% thumbnail revision.image "50x50" crop="center" as thumb %} {% thumbnail revision.image "50x50" crop="center" as thumb %}
<tr> <tr>
<td style="white-space: nowrap;"> <td style="white-space: nowrap;">
<p>{{ revision.get_filename|truncatechars:30 }}</p> <p>{% trans "Image id" %}: {{ image.id }}</code></p>
<p> <p>
<a href=""><span class="icon-edit"></span> {% trans "Insert" %}</a><br /> <a href="javascript:void(insert_image({{ image.id }}))"><span class="icon-edit"></span> {% trans "Insert" %}</a><br />
{% if image|can_write:user %} {% if image|can_write:user %}
<a href="{% url 'wiki:images_add_revision' path=urlpath.path article_id=article.id image_id=image.id %}"><span class="icon-edit"></span> {% trans "Replace" %}</a> <a href="{% url 'wiki:images_add_revision' path=urlpath.path article_id=article.id image_id=image.id %}"><span class="icon-edit"></span> {% trans "Replace" %}</a>
{% endif %} {% endif %}
...@@ -99,7 +107,10 @@ ...@@ -99,7 +107,10 @@
{% trans "How to use images" %} {% trans "How to use images" %}
</h4> </h4>
<p>{% trans "After uploading an image, it is attached to this particular artice and can be used only here. Other users may replace the image, but older versions are kept. You probably want to show the image with a nice caption. To achieve this, press the Insert button and fill in the caption fields and possibly choose to have you image floating right or left of the content. You can use Markdown in the caption. The markdown code syntax for images looks like this:" %}<br /><code>[image:id alignment caption text]</code></p> <p>{% trans "After uploading an image, it is attached to this particular artice and can be used only here. Other users may replace the image, but older versions are kept. You probably want to show the image with a nice caption. To achieve this, press the Insert button and fill in the caption fields and possibly choose to have you image floating right or left of the content. You can use Markdown in the caption. The markdown code syntax for images looks like this, possible values for align are left/center/right:" %}<br />
<pre>[image:id align:right]
caption indented by 4 spaces</pre>
</p>
{% endwith %} {% endwith %}
...@@ -6,16 +6,11 @@ from wiki.core.plugins import registry ...@@ -6,16 +6,11 @@ from wiki.core.plugins import registry
from wiki.core.plugins.base import BasePlugin from wiki.core.plugins.base import BasePlugin
from wiki.plugins.images import views, models, settings, forms from wiki.plugins.images import views, models, settings, forms
from wiki.plugins.notifications import ARTICLE_EDIT from wiki.plugins.notifications import ARTICLE_EDIT
from wiki.plugins.images.markdown_extensions import ImageExtension
class ImagePlugin(BasePlugin): class ImagePlugin(BasePlugin):
#settings_form = 'wiki.plugins.notifications.forms.SubscriptionForm'
slug = settings.SLUG slug = settings.SLUG
urlpatterns = patterns('',
url('^$', views.ImageView.as_view(), name='images_index'),
)
sidebar = {'headline': _('Images'), sidebar = {'headline': _('Images'),
'icon_class': 'icon-picture', 'icon_class': 'icon-picture',
'template': 'wiki/plugins/images/sidebar.html', 'template': 'wiki/plugins/images/sidebar.html',
...@@ -31,6 +26,14 @@ class ImagePlugin(BasePlugin): ...@@ -31,6 +26,14 @@ class ImagePlugin(BasePlugin):
'get_article': lambda obj: obj.article} 'get_article': lambda obj: obj.article}
] ]
class RenderMedia:
js = [
'wiki/colorbox/colorbox/jquery.colorbox-min.js',
'wiki/js/images.js',
]
css = {'screen': 'wiki/colorbox/example1/colorbox.css'}
urlpatterns = patterns('', urlpatterns = patterns('',
url('^$', views.ImageView.as_view(), name='images_index'), url('^$', views.ImageView.as_view(), name='images_index'),
url('^delete/(?P<image_id>\d+)/$', views.DeleteView.as_view(), name='images_delete'), url('^delete/(?P<image_id>\d+)/$', views.DeleteView.as_view(), name='images_delete'),
...@@ -39,7 +42,7 @@ class ImagePlugin(BasePlugin): ...@@ -39,7 +42,7 @@ class ImagePlugin(BasePlugin):
url('^(?P<image_id>\d+)/revision/add/$', views.RevisionAddView.as_view(), name='images_add_revision'), url('^(?P<image_id>\d+)/revision/add/$', views.RevisionAddView.as_view(), name='images_add_revision'),
) )
#markdown_extensions = [AttachmentExtension()] markdown_extensions = [ImageExtension()]
def __init__(self): def __init__(self):
#print "I WAS LOADED!" #print "I WAS LOADED!"
......
$.fn.extend({
insertAtCaret: function(myValue){
return this.each(function(i) {
if (document.selection) {
//For browsers like Internet Explorer
this.focus();
sel = document.selection.createRange();
sel.text = myValue;
this.focus();
}
else if (this.selectionStart || this.selectionStart == '0') {
//For browsers like Firefox and Webkit based
var startPos = this.selectionStart;
var endPos = this.selectionEnd;
var scrollTop = this.scrollTop;
this.value = this.value.substring(0, startPos)+myValue+this.value.substring(endPos,this.value.length);
this.focus();
this.selectionStart = startPos + myValue.length;
this.selectionEnd = startPos + myValue.length;
this.scrollTop = scrollTop;
} else {
this.value += myValue;
this.focus();
}
})
}
});
...@@ -27,6 +27,14 @@ ...@@ -27,6 +27,14 @@
#attachment_form #id_description #attachment_form #id_description
{ width: 95% } { width: 95% }
.wiki-article div.toc {
margin: 10px 0;
background: #f9f9f9;
padding: 10px;
width: 300px;
border: 1px solid #CCC;
}
input[type=file] {float: none; width: auto;} input[type=file] {float: none; width: auto;}
.asteriskField { font-size: 20px; margin-left: 5px;} .asteriskField { font-size: 20px; margin-left: 5px;}
......
{% load sekizai_tags %} {% load sekizai_tags %}
{% addtoblock "js" %} {% addtoblock "js" %}
<script type="text/javascript" src="{{ STATIC_URL }}wiki/js/editor.js"></script>
{% for js in editor.Media.js %} {% for js in editor.Media.js %}
<script type="text/javascript" src="{{ STATIC_URL }}{{ js }}"></script> <script type="text/javascript" src="{{ STATIC_URL }}{{ js }}"></script>
{% endfor %} {% endfor %}
......
{% load wiki_tags i18n cache %} {% load wiki_tags i18n cache %}
{% block wiki_contents %} {% block wiki_contents %}
{% for plugin in plugins %}
{% if plugin.RenderMedia.css %}
{% for media, url in plugin.RenderMedia.css.items %}
<link rel="stylesheet" href="{{ STATIC_URL }}{{ url }}" />
{% endfor %}
{% endif %}
{% if plugin.RenderMedia.js %}
{% for url in plugin.RenderMedia.js %}
<script type="text/javascript" src="{{ STATIC_URL }}{{ url }}"></script>
{% endfor %}
{% endif %}
{% endfor %}
<div class="wiki-article">
{% if not preview %} {% if not preview %}
{% if article.current_revision %}
{% cache 1 article article.current_revision.id %} {% if article.current_revision %}
{{ article.render }} {% cache 6000 article article.current_revision.id %}
{% endcache %} {{ article.render }}
{% endif %} {% endcache %}
{% endif %}
{% else %} {% else %}
{{ content|default:"" }} {{ content|default:"" }}
{% endif %} {% endif %}
</div>
{% endblock %} {% endblock %}
from django.conf import settings as django_settings
from django import template from django import template
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db.models import Model from django.db.models import Model
...@@ -6,6 +7,7 @@ from django.forms import BaseForm ...@@ -6,6 +7,7 @@ from django.forms import BaseForm
register = template.Library() register = template.Library()
from wiki import models from wiki import models
from wiki.core.plugins import registry as plugin_registry
# Cache for looking up objects for articles... article_for_object is # Cache for looking up objects for articles... article_for_object is
# called more than once per page in multiple template blocks. # called more than once per page in multiple template blocks.
...@@ -39,6 +41,8 @@ def wiki_render(article, preview_content=None): ...@@ -39,6 +41,8 @@ def wiki_render(article, preview_content=None):
'article': article, 'article': article,
'content': content, 'content': content,
'preview': not preview_content is None, 'preview': not preview_content is None,
'plugins': plugin_registry.get_plugins(),
'STATIC_URL': django_settings.STATIC_URL,
} }
@register.inclusion_tag('wiki/includes/form.html', takes_context=True) @register.inclusion_tag('wiki/includes/form.html', takes_context=True)
......
...@@ -21,7 +21,7 @@ if settings.ACCOUNT_HANDLING: ...@@ -21,7 +21,7 @@ if settings.ACCOUNT_HANDLING:
urlpatterns += patterns('', urlpatterns += patterns('',
# This one doesn't work because it don't know where to redirect after... # This one doesn't work because it don't know where to redirect after...
url('^_revision/change/(?P<article_id>\d+)/(?P<revision_id>\d+)/$', 'wiki.views.article.change_revision', name='change_revision'), url('^_revision/change/(?P<article_id>\d+)/(?P<revision_id>\d+)/$', 'wiki.views.article.change_revision', name='change_revision'),
url('^_revision/preview/(?P<article_id>\d+)/$', 'wiki.views.article.preview', name='preview_revision'), url('^_revision/preview/(?P<article_id>\d+)/$', article.Preview.as_view(), name='preview_revision'),
url('^_revision/merge/(?P<article_id>\d+)/(?P<revision_id>\d+)/preview/$', 'wiki.views.article.merge', name='merge_revision_preview', kwargs={'preview': True}), url('^_revision/merge/(?P<article_id>\d+)/(?P<revision_id>\d+)/preview/$', 'wiki.views.article.merge', name='merge_revision_preview', kwargs={'preview': True}),
# Paths decided by article_ids # Paths decided by article_ids
...@@ -29,7 +29,7 @@ urlpatterns += patterns('', ...@@ -29,7 +29,7 @@ urlpatterns += patterns('',
url('^(?P<article_id>\d+)/delete/$', article.Delete.as_view(), name='delete'), url('^(?P<article_id>\d+)/delete/$', article.Delete.as_view(), name='delete'),
url('^(?P<article_id>\d+)/deleted/$', article.Deleted.as_view(), name='deleted'), url('^(?P<article_id>\d+)/deleted/$', article.Deleted.as_view(), name='deleted'),
url('^(?P<article_id>\d+)/edit/$', article.Edit.as_view(), name='edit'), url('^(?P<article_id>\d+)/edit/$', article.Edit.as_view(), name='edit'),
url('^(?P<article_id>\d+)/preview/$', 'wiki.views.article.preview', name='preview'), url('^(?P<article_id>\d+)/preview/$', article.Preview.as_view(), name='preview'),
url('^(?P<article_id>\d+)/history/$', article.History.as_view(), name='history'), url('^(?P<article_id>\d+)/history/$', article.History.as_view(), name='history'),
url('^(?P<article_id>\d+)/settings/$', article.Settings.as_view(), name='settings'), url('^(?P<article_id>\d+)/settings/$', article.Settings.as_view(), name='settings'),
url('^(?P<article_id>\d+)/revision/change/(?P<revision_id>\d+)/$', 'wiki.views.article.change_revision', name='change_revision'), url('^(?P<article_id>\d+)/revision/change/(?P<revision_id>\d+)/$', 'wiki.views.article.change_revision', name='change_revision'),
...@@ -53,7 +53,7 @@ urlpatterns += patterns('', ...@@ -53,7 +53,7 @@ urlpatterns += patterns('',
url('^(?P<path>.+/|)_delete/$', article.Delete.as_view(), name='delete'), url('^(?P<path>.+/|)_delete/$', article.Delete.as_view(), name='delete'),
url('^(?P<path>.+/|)_deleted/$', article.Deleted.as_view(), name='deleted'), url('^(?P<path>.+/|)_deleted/$', article.Deleted.as_view(), name='deleted'),
url('^(?P<path>.+/|)_edit/$', article.Edit.as_view(), name='edit'), url('^(?P<path>.+/|)_edit/$', article.Edit.as_view(), name='edit'),
url('^(?P<path>.+/|)_preview/$', 'wiki.views.article.preview', name='preview'), url('^(?P<path>.+/|)_preview/$', article.Preview.as_view(), name='preview'),
url('^(?P<path>.+/|)_history/$', article.History.as_view(), name='history'), url('^(?P<path>.+/|)_history/$', article.History.as_view(), name='history'),
url('^(?P<path>.+/|)_list/$', article.List.as_view(), name='list'), url('^(?P<path>.+/|)_list/$', article.List.as_view(), name='list'),
url('^(?P<path>.+/|)_settings/$', article.Settings.as_view(), name='settings'), url('^(?P<path>.+/|)_settings/$', article.Settings.as_view(), name='settings'),
......
...@@ -224,7 +224,7 @@ class Edit(FormView, ArticleMixin): ...@@ -224,7 +224,7 @@ class Edit(FormView, ArticleMixin):
@method_decorator(get_article(can_write=True)) @method_decorator(get_article(can_write=True))
def dispatch(self, request, article, *args, **kwargs): def dispatch(self, request, article, *args, **kwargs):
self.sidebar_plugins = plugin_registry.get_sidebar() self.sidebar_plugins = plugin_registry.get_sidebar()
self.sidebar_forms = [] self.sidebar = []
return super(Edit, self).dispatch(request, article, *args, **kwargs) return super(Edit, self).dispatch(request, article, *args, **kwargs)
def get_form(self, form_class): def get_form(self, form_class):
...@@ -233,7 +233,7 @@ class Edit(FormView, ArticleMixin): ...@@ -233,7 +233,7 @@ class Edit(FormView, ArticleMixin):
otherwise removes the 'data' and 'files' kwargs from form initialisation. otherwise removes the 'data' and 'files' kwargs from form initialisation.
""" """
kwargs = self.get_form_kwargs() kwargs = self.get_form_kwargs()
if self.request.POST.get('save', '') != '1': if self.request.POST.get('save', '') != '1' and self.request.POST.get('preview') != '1':
kwargs['data'] = None kwargs['data'] = None
kwargs['files'] = None kwargs['files'] = None
kwargs['no_clean'] = True kwargs['no_clean'] = True
...@@ -245,38 +245,44 @@ class Edit(FormView, ArticleMixin): ...@@ -245,38 +245,44 @@ class Edit(FormView, ArticleMixin):
to identify which form is being saved.""" to identify which form is being saved."""
form_classes = {} form_classes = {}
for cnt, plugin in enumerate(self.sidebar_plugins): for cnt, plugin in enumerate(self.sidebar_plugins):
form_classes['form%d' % cnt] = plugin.sidebar.get('form_class', None) form_classes['form%d' % cnt] = (plugin, plugin.sidebar.get('form_class', None))
return form_classes return form_classes
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
# Generate sidebar forms # Generate sidebar forms
self.sidebar_forms = [] self.sidebar_forms = []
for form_id, Form in self.get_sidebar_form_classes().items(): for form_id, (plugin, Form) in self.get_sidebar_form_classes().items():
form = Form(self.article, self.request.user) if Form:
setattr(form, 'form_id', form_id) form = Form(self.article, self.request.user)
self.sidebar_forms.append(form) setattr(form, 'form_id', form_id)
else:
form = None
self.sidebar.append((plugin, form))
return super(Edit, self).get(request, *args, **kwargs) return super(Edit, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
# Generate sidebar forms # Generate sidebar forms
self.sidebar_forms = [] self.sidebar_forms = []
for form_id, Form in self.get_sidebar_form_classes().items(): for form_id, (plugin, Form) in self.get_sidebar_form_classes().items():
if form_id == self.request.GET.get('f', None): if Form:
form = Form(self.article, self.request, data=self.request.POST, files=self.request.FILES) if form_id == self.request.GET.get('f', None):
if form.is_valid(): form = Form(self.article, self.request, data=self.request.POST, files=self.request.FILES)
form.save() if form.is_valid():
usermessage = form.get_usermessage() form.save()
if usermessage: usermessage = form.get_usermessage()
messages.success(self.request, usermessage) if usermessage:
else: messages.success(self.request, usermessage)
messages.success(self.request, _(u'Your changes were saved.')) else:
if self.urlpath: messages.success(self.request, _(u'Your changes were saved.'))
return redirect('wiki:edit', path=self.urlpath.path) if self.urlpath:
return redirect('wiki:edit', article_id=self.article.id) return redirect('wiki:edit', path=self.urlpath.path)
return redirect('wiki:edit', article_id=self.article.id)
else:
form = Form(self.article, self.request)
setattr(form, 'form_id', form_id)
else: else:
form = Form(self.article, self.request) form = None
setattr(form, 'form_id', form_id) self.sidebar.append((plugin, form))
self.sidebar_forms.append(form)
return super(Edit, self).post(request, *args, **kwargs) return super(Edit, self).post(request, *args, **kwargs)
def form_valid(self, form): def form_valid(self, form):
...@@ -303,7 +309,7 @@ class Edit(FormView, ArticleMixin): ...@@ -303,7 +309,7 @@ class Edit(FormView, ArticleMixin):
kwargs['edit_form'] = kwargs.pop('form', None) kwargs['edit_form'] = kwargs.pop('form', None)
kwargs['editor'] = editors.getEditor() kwargs['editor'] = editors.getEditor()
kwargs['selected_tab'] = 'edit' kwargs['selected_tab'] = 'edit'
kwargs['sidebar'] = zip(self.sidebar_plugins, self.sidebar_forms) kwargs['sidebar'] = self.sidebar
return super(Edit, self).get_context_data(**kwargs) return super(Edit, self).get_context_data(**kwargs)
...@@ -498,33 +504,42 @@ def change_revision(request, article, revision_id=None, urlpath=None): ...@@ -498,33 +504,42 @@ def change_revision(request, article, revision_id=None, urlpath=None):
else: else:
return redirect('wiki:history', article_id=article.id) return redirect('wiki:history', article_id=article.id)
# TODO: Throw in a class-based view class Preview(ArticleMixin, TemplateView):
@get_article(can_read=True)
def preview(request, article, urlpath=None, template_file="wiki/preview_inline.html"):
content = article.current_revision.content template_name="wiki/preview_inline.html"
title = article.current_revision.title
revision_id = request.GET.get('r', None) @method_decorator(get_article(can_read=True))
revision = None def dispatch(self, request, article, *args, **kwargs):
revision_id = request.GET.get('r', None)
self.title = None
self.content = None
self.preview = False
if revision_id:
self.revision = get_object_or_404(models.ArticleRevision, article=article, id=revision_id)
else:
self.revision = None
return super(Preview, self).dispatch(request, article, *args, **kwargs)
if request.method == 'POST': def post(self, request, *args, **kwargs):
edit_form = forms.EditForm(article.current_revision, request.POST, preview=True) edit_form = forms.EditForm(self.article.current_revision, request.POST, preview=True)
if edit_form.is_valid(): if edit_form.is_valid():
title = edit_form.cleaned_data['title'] self.title = edit_form.cleaned_data['title']
content = edit_form.cleaned_data['content'] self.content = edit_form.cleaned_data['content']
self.preview = True
elif revision_id: return super(Preview, self).get(request, *args, **kwargs)
revision = get_object_or_404(models.ArticleRevision, article=article, id=revision_id)
title = revision.title def get(self, request, *args, **kwargs):
content = revision.content self.title = self.revision.title
self.content = self.revision.content
c = RequestContext(request, {'urlpath': urlpath, return super(Preview, self).get( request, *args, **kwargs)
'article': article,
'title': title, def get_context_data(self, **kwargs):
'revision': revision, kwargs['title'] = self.title
'content': content}) kwargs['revision'] = self.revision
return render_to_response(template_file, context_instance=c) kwargs['content'] = self.content
kwargs['preview'] = self.preview
return super(Preview, self).get_context_data(**kwargs)
@json_view @json_view
def diff(request, revision_id, other_revision_id=None): def diff(request, revision_id, other_revision_id=None):
......
...@@ -24,5 +24,6 @@ class ArticleMixin(TemplateResponseMixin): ...@@ -24,5 +24,6 @@ class ArticleMixin(TemplateResponseMixin):
kwargs['article_tabs'] = registry.get_article_tabs() kwargs['article_tabs'] = registry.get_article_tabs()
kwargs['children_slice'] = self.children_slice[:20] kwargs['children_slice'] = self.children_slice[:20]
kwargs['children_slice_more'] = len(self.children_slice) > 20 kwargs['children_slice_more'] = len(self.children_slice) > 20
kwargs['plugins'] = registry.get_plugins()
return kwargs return kwargs
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment