Commit 458bbd21 by benjaoming

Initial plugin and editor structure and base classes for extending

MarkItUp editor in admin
Creating images and attachments as plugins
parent 2940142e
...@@ -85,6 +85,8 @@ INSTALLED_APPS = ( ...@@ -85,6 +85,8 @@ INSTALLED_APPS = (
'django.contrib.admindocs', 'django.contrib.admindocs',
'south', 'south',
'wiki', 'wiki',
'wiki.plugins.images',
'wiki.plugins.attachments',
'mptt', 'mptt',
) )
......
Attachments and images should be a plugin?
Management script: Management script:
Cleanup deleted Image's image files Cleanup deleted Image's image files
......
from django.contrib import admin from django.contrib import admin
from django.contrib.contenttypes.generic import GenericTabularInline from django.contrib.contenttypes.generic import GenericTabularInline
from django.utils.translation import ugettext_lazy as _
from mptt.admin import MPTTModelAdmin from mptt.admin import MPTTModelAdmin
import models
from django import forms from django import forms
from django.forms.widgets import HiddenInput from django.forms.widgets import HiddenInput
from django.core.urlresolvers import get_callable
import models
from conf import settings
class ArticleObjectAdmin(GenericTabularInline): class ArticleObjectAdmin(GenericTabularInline):
model = models.ArticleForObject model = models.ArticleForObject
extra = 1 extra = 1
max_num = 1 max_num = 1
class ArticleRevisionForm(forms.ModelForm):
class Meta:
model = models.ArticleRevision
def __init__(self, *args, **kwargs):
super(ArticleRevisionForm, self).__init__(*args, **kwargs)
EditorClass = get_callable(settings.EDITOR)
Editor = EditorClass(instance=self.instance)
self.fields['content'].widget = Editor.get_admin_widget()
class ArticleRevisionAdmin(admin.ModelAdmin):
form = ArticleRevisionForm
class Media:
js = get_callable(settings.EDITOR).AdminMedia.js
css = get_callable(settings.EDITOR).AdminMedia.css
class ArticleRevisionInline(admin.TabularInline):
model = models.ArticleRevision
form = ArticleRevisionForm
fk_name = 'article'
extra = 1
fields = ('content', 'title', 'deleted', 'locked',)
class Media:
js = get_callable(settings.EDITOR).AdminMedia.js
css = get_callable(settings.EDITOR).AdminMedia.css
class ArticleForm(forms.ModelForm): class ArticleForm(forms.ModelForm):
class Meta: class Meta:
...@@ -24,12 +56,6 @@ class ArticleForm(forms.ModelForm): ...@@ -24,12 +56,6 @@ class ArticleForm(forms.ModelForm):
else: else:
self.fields['current_revision'].queryset = models.ArticleRevision.objects.get_empty_query_set() self.fields['current_revision'].queryset = models.ArticleRevision.objects.get_empty_query_set()
self.fields['current_revision'].widget = HiddenInput() self.fields['current_revision'].widget = HiddenInput()
class ArticleRevisionInline(admin.TabularInline):
model = models.ArticleRevision
fk_name = 'article'
extra = 1
fields = ('content', 'title', 'user', 'user_message', 'deleted', 'locked', 'redirect')
class ArticleAdmin(admin.ModelAdmin): class ArticleAdmin(admin.ModelAdmin):
inlines = [ArticleRevisionInline] inlines = [ArticleRevisionInline]
...@@ -38,13 +64,14 @@ class ArticleAdmin(admin.ModelAdmin): ...@@ -38,13 +64,14 @@ class ArticleAdmin(admin.ModelAdmin):
class URLPathAdmin(MPTTModelAdmin): class URLPathAdmin(MPTTModelAdmin):
inlines = [ArticleObjectAdmin] inlines = [ArticleObjectAdmin]
list_filter = ('site', 'articles__article__current_revision__deleted', list_filter = ('site', 'articles__article__current_revision__deleted',
'articles__article__current_revision__created', 'articles__article__created',
'articles__article__modified') 'articles__article__modified')
list_display = ('__unicode__', 'article', 'created') list_display = ('__unicode__', 'article', 'get_created')
class ArticleRevisionAdmin(admin.ModelAdmin): def get_created(self, instance):
pass return instance.article.created
get_created.short_description = _(u'created')
admin.site.register(models.URLPath, URLPathAdmin) admin.site.register(models.URLPath, URLPathAdmin)
admin.site.register(models.Article, ArticleAdmin) admin.site.register(models.Article, ArticleAdmin)
admin.site.register(models.ArticleRevision, ArticleRevisionAdmin) admin.site.register(models.ArticleRevision, ArticleRevisionAdmin)
\ No newline at end of file
...@@ -2,31 +2,18 @@ ...@@ -2,31 +2,18 @@
from django.conf import settings as django_settings from django.conf import settings as django_settings
# Should urls be case sensitive? # Should urls be case sensitive?
URL_CASE_SENSITIVE = getattr(django_settings, "WIKI_URL_CASE_SENSITIVE", False) URL_CASE_SENSITIVE = getattr(django_settings, 'WIKI_URL_CASE_SENSITIVE', False)
# Non-configurable (at the moment)
APP_LABEL = 'wiki' APP_LABEL = 'wiki'
WIKI_LANGUAGE = 'markdown'
EDITOR = getattr(django_settings, 'WIKI_EDITOR', 'wiki.editors.MarkItUp')
# 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.
LOST_AND_FOUND_SLUG = getattr(django_settings, "WIKI_LOST_AND_FOUND_SLUG", 'lost-and-found') LOST_AND_FOUND_SLUG = getattr(django_settings, 'WIKI_LOST_AND_FOUND_SLUG', 'lost-and-found')
# Where to store article attachments, relative to MEDIA_ROOT
UPLOAD_PATH = getattr(django_settings, "WIKI_UPLOAD_PATH", 'wiki/uploads/%aid/')
# Should the upload path be obscurified? If so, a random hash will be added to the path
# such that someone can not guess the location of files (if you have
# restricted permissions and the files are still located within the web server's
UPLOAD_PATH_OBSCURIFY = getattr(django_settings, "WIKI_UPLOAD_PATH_OBSCURIFY", True)
# Allowed non-image extensions. Empty to disallow completely.
# No files are saved without appending ".upload" to the file to ensure that
# your web server never actually executes some script.
# Case insensitive.
FILE_EXTENTIONS = getattr(django_settings, "WIKI_FILE_EXTENTIONS", ['pdf', 'doc', 'odt', 'docx', 'txt'])
# Where to store images
IMAGE_PATH = getattr(django_settings, "WIKI_IMAGE_PATH", 'wiki/images/%aid/')
#################### ####################
...@@ -34,23 +21,23 @@ IMAGE_PATH = getattr(django_settings, "WIKI_IMAGE_PATH", 'wiki/images/%aid/') ...@@ -34,23 +21,23 @@ IMAGE_PATH = getattr(django_settings, "WIKI_IMAGE_PATH", 'wiki/images/%aid/')
#################### ####################
# Maximum revisions to keep for an article, 0=unlimited # Maximum revisions to keep for an article, 0=unlimited
MAX_REVISIONS = getattr(django_settings, "WIKI_MAX_REVISIONS", 100) MAX_REVISIONS = getattr(django_settings, 'WIKI_MAX_REVISIONS', 100)
# Maximum age of revisions in days, 0=unlimited # Maximum age of revisions in days, 0=unlimited
MAX_REVISION_AGE = getattr(django_settings, "MAX_REVISION_AGE", 365) MAX_REVISION_AGE = getattr(django_settings, 'MAX_REVISION_AGE', 365)
LOG_IPS_ANONYMOUS = getattr(django_settings, "WIKI_LOG_IPS_ANONYMOUS", True) LOG_IPS_ANONYMOUS = getattr(django_settings, 'WIKI_LOG_IPS_ANONYMOUS', True)
LOG_IPS_USERS = getattr(django_settings, "WIKI_LOG_IPS_USERS", False) LOG_IPS_USERS = getattr(django_settings, 'WIKI_LOG_IPS_USERS', False)
# Maximum allowed revisions per minute for any given user or IP # Maximum allowed revisions per minute for any given user or IP
REVISIONS_PER_MINUTE = getattr(django_settings, "WIKI_REVISIONS_PER_MINUTE", 3) REVISIONS_PER_MINUTE = getattr(django_settings, 'WIKI_REVISIONS_PER_MINUTE', 3)
# Allow others to upload # Allow others to upload
UPLOAD_OTHERS = getattr(django_settings, "WIKI_UPLOAD_OTHERS", True) UPLOAD_OTHERS = getattr(django_settings, 'WIKI_UPLOAD_OTHERS', True)
# Treat anonymous (non logged in) users as the "other" user group # Treat anonymous (non logged in) users as the "other" user group
ANONYMOUS = getattr(django_settings, "WIKI_ANONYMOUS", True) ANONYMOUS = getattr(django_settings, 'WIKI_ANONYMOUS', True)
# Globally enable write access for anonymous users, if true anonymous users will be treated # Globally enable write access for anonymous users, if true anonymous users will be treated
# as the others_write boolean field on models.Article. # as the others_write boolean field on models.Article.
ANONYMOUS_WRITE = getattr(django_settings, "WIKI_ANONYMOUS_WRITE", False) ANONYMOUS_WRITE = getattr(django_settings, 'WIKI_ANONYMOUS_WRITE', False)
...@@ -6,7 +6,3 @@ class NoRootURL(Exception): ...@@ -6,7 +6,3 @@ class NoRootURL(Exception):
# If there is more than one... # If there is more than one...
class MultipleRootURLs(Exception): class MultipleRootURLs(Exception):
pass pass
class IllegalFileExtension(Exception):
"""File extension on upload is not allowed"""
pass
from django import forms
from django.utils.safestring import mark_safe
from django.utils.html import conditional_escape
from django.utils.encoding import force_unicode
from django.forms.util import flatatt
class BaseEditor():
# The editor id can be used for conditional testing. If you write your
# own editor class, you can use the same editor_id as some editor
editor_id = 'plaintext'
media_admin = ()
media_frontend = ()
def __init__(self, instance=None):
self.instance = instance
def get_admin_widget(self):
return forms.Textarea()
class AdminMedia:
css = {}
js = ()
class MarkItUpWidget(forms.Widget):
def __init__(self, attrs=None):
# The 'rows' and 'cols' attributes are required for HTML correctness.
default_attrs = {'class': 'markItUp',
'rows': '10', 'cols': '40',}
if attrs:
default_attrs.update(attrs)
super(MarkItUpWidget, self).__init__(default_attrs)
def render(self, name, value, attrs=None):
if value is None: value = ''
final_attrs = self.build_attrs(attrs, name=name)
return mark_safe(u'<textarea%s>%s</textarea>' % (flatatt(final_attrs),
conditional_escape(force_unicode(value))))
class MarkItUp(BaseEditor):
editor_id = 'markitup'
def get_admin_widget(self, instance=None):
return MarkItUpWidget()
class AdminMedia:
css = {
'all': ("wiki/markitup/skins/simple/style.css",
"wiki/markitup/sets/admin/style.css",)
}
js = ("wiki/markitup/admin.init.js",
"wiki/markitup/jquery.markitup.js",
"wiki/markitup/sets/admin/set.js",
)
...@@ -19,6 +19,7 @@ class Article(models.Model): ...@@ -19,6 +19,7 @@ class Article(models.Model):
help_text=_(u'The revision being displayed for this article. If you need to do a roll-back, simply change the value of this field.'), help_text=_(u'The revision being displayed for this article. If you need to do a roll-back, simply change the value of this field.'),
) )
created = models.DateTimeField(auto_now_add=True, verbose_name=_(u'created'),)
modified = models.DateTimeField(auto_now=True, verbose_name=_(u'modified'), modified = models.DateTimeField(auto_now=True, verbose_name=_(u'modified'),
help_text=_(u'Article properties last modified')) help_text=_(u'Article properties last modified'))
...@@ -35,8 +36,6 @@ class Article(models.Model): ...@@ -35,8 +36,6 @@ class Article(models.Model):
other_read = models.BooleanField(default=True, verbose_name=_(u'others read access')) other_read = models.BooleanField(default=True, verbose_name=_(u'others read access'))
other_write = models.BooleanField(default=True, verbose_name=_(u'others write access')) other_write = models.BooleanField(default=True, verbose_name=_(u'others write access'))
attachments = models.ManyToManyField('Attachment', blank=True, verbose_name=_(u'attachments'))
def can_read(self, user=None, group=None): def can_read(self, user=None, group=None):
if self.other_read: if self.other_read:
return True return True
...@@ -177,6 +176,8 @@ class BaseRevision(models.Model): ...@@ -177,6 +176,8 @@ class BaseRevision(models.Model):
super(BaseRevision, self).save(*args, **kwargs) super(BaseRevision, self).save(*args, **kwargs)
class ArticleRevision(BaseRevision): class ArticleRevision(BaseRevision):
"""This is where main revision data is stored. To make it easier to
copy, do NEVER create m2m relationships."""
article = models.ForeignKey('Article', on_delete=models.CASCADE, article = models.ForeignKey('Article', on_delete=models.CASCADE,
verbose_name=_(u'article')) verbose_name=_(u'article'))
...@@ -203,14 +204,18 @@ class ArticleRevision(BaseRevision): ...@@ -203,14 +204,18 @@ class ArticleRevision(BaseRevision):
def __unicode__(self): def __unicode__(self):
return "%s (%d)" % (self.article.title, self.revision_number) return "%s (%d)" % (self.article.title, self.revision_number)
def inherit_predecessor(self, revision): def inherit_predecessor(self, article):
""" """
Inherit certain properties from predecessor because it's very Inherit certain properties from predecessor because it's very
convenient. Remember to always call this method before setting properties :)""" convenient. Remember to always call this method before
self.title = revision.title setting properties :)"""
self.deleted = revision.deleted predecessor = article.current_revision
self.locked = revision.locked self.article = predecessor.article
self.redirect = revision.redirect self.content = predecessor.content
self.title = predecessor.title
self.deleted = predecessor.deleted
self.locked = predecessor.locked
self.redirect = predecessor.redirect
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
super(ArticleRevision, self).save(*args, **kwargs) super(ArticleRevision, self).save(*args, **kwargs)
...@@ -221,65 +226,3 @@ class ArticleRevision(BaseRevision): ...@@ -221,65 +226,3 @@ class ArticleRevision(BaseRevision):
if not self.title: if not self.title:
self.title = self.article.title self.title = self.article.title
def upload_path(instance, filename):
from os import path
try:
extension = filename.split(".")[-1]
except IndexError:
raise exceptions.IllegalFileExtension()
if not extension.lower() in map(lambda x: x.lower(), settings.FILE_EXTENTIONS):
raise exceptions.IllegalFileExtension()
upload_path = settings.UPLOAD_PATH
upload_path = upload_path.replace('%aid', str(instance.original_article.id))
if settings.UPLOAD_PATH_OBSCURIFY:
import random, hashlib
m=hashlib.md5(str(random.randint(0,100000000000000)))
upload_path = path.join(upload_path, m.hexdigest())
return path.join(upload_path, filename + '.upload')
class Attachment(models.Model):
# The article on which the file was originally uploaded.
# Used to apply permissions.
original_article = models.ForeignKey('Article', on_delete=models.SET_NULL,
verbose_name=_(u'original article'), null=True, blank=True,
related_name='original_attachment_set')
current_revision = models.OneToOneField('AttachmentRevision',
verbose_name=_(u'current revision'),
blank=True, null=True, related_name='current_set',
help_text=_(u'The revision of this attachment currently in use (on all articles using the attachment)'),
)
class Meta:
app_label = settings.APP_LABEL
class AttachmentRevision(BaseRevision):
attachment = models.ForeignKey('Attachment')
file = models.FileField(upload_to=upload_path, #@ReservedAssignment
verbose_name=_(u'file'))
original_filename = models.CharField(max_length=256, verbose_name=_(u'original filename'))
overwritten = models.BooleanField(default=False)
class Meta:
app_label = settings.APP_LABEL
class Image(models.Model):
article = models.ForeignKey('Article', on_delete=models.CASCADE,
verbose_name=_(u'article'))
image = models.ImageField(upload_to=settings.IMAGE_PATH)
caption = models.CharField(max_length=2056)
def render_caption(self):
"""Returns a rendered version of the caption. Should only use a
subset of the rendering machine."""
pass
\ No newline at end of file
from django.db import models
from django.utils.translation import ugettext_lazy as _
"""
There are two kinds of plugin objects:
1) ArticlePlugin - an object associated with an article. Does not reference
a Revision.
2) RevisionPlugin - an object associated with a revision. This object
is automatically referenced to each new revision, and if you create
a new object, a new revision will be created.
3) ReusablePlugin - a plugin that can be used on many articles. Please note
that the logics for keeping revisions on such plugins are complicated, so you
have to implement that on your own.
"""
from article import Article, ArticleRevision
class ArticlePlugin(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE,
verbose_name=_(u"article"))
created = models.DateTimeField(auto_now_add=True, verbose_name=_(u"created"))
modified = models.DateTimeField(auto_now=True, verbose_name=_(u"created"))
class Meta:
abstract = True
class ReusablePlugin(models.Model):
# The article on which the plugin was originally created.
# Used to apply permissions.
original_article = models.ForeignKey(Article, on_delete=models.SET_NULL,
verbose_name=_(u'original article'), null=True, blank=True,
related_name='original_plugin_set',
help_text=_(u'Permissions are inherited from this article'))
articles = models.ManyToManyField(Article)
created = models.DateTimeField(auto_now_add=True, verbose_name=_(u"created"))
modified = models.DateTimeField(auto_now=True, verbose_name=_(u"created"))
# Permission methods - you may override these, if they don't fit your logic.
def can_read(self, *args, **kwargs):
if self.original_article:
return self.original_article.can_read(*args, **kwargs)
return False
def can_write(self, *args, **kwargs):
if self.original_article:
return self.original_article.can_write(*args, **kwargs)
return False
class Meta:
abstract = True
def save(self, *args, **kwargs):
# Automatically make the original article the first one in the added set
if not self.original_article:
articles = self.articles.all()
if articles.count() == 0:
self.original_article = articles[0]
super(ReusablePlugin, self).save(*args, **kwargs)
class RevisionPluginCreateError(Exception): pass
class RevisionPlugin(models.Model):
"""
Inherit from this model and make sure to specify an article when
saving a new instance. This way, a new revision will be created, and
users are able to roll back to the a previous revision (containing some
other instance of your plugin).
Usage:
class YourPlugin(RevisionPlugin):
...
Creating new plugins:
YourPlugin(article=article_instance, ...) or
YourPlugin.objects.create(article=article_instance, ...)
"""
revision = models.ForeignKey(ArticleRevision, on_delete=models.CASCADE)
def __init__(self, *args, **kwargs):
super(RevisionPlugin, self).__init__(*args, **kwargs)
if not self.id and not 'article' in kwargs:
raise RevisionPluginCreateError("Keyword argument 'article' expected.")
self.article = kwargs['article']
def get_logmessage(self):
return _(u"A plugin was changed")
def save(self, *args, **kwargs):
if not self.id:
if not self.article.current_revision:
raise RevisionPluginCreateError("Article does not have a current_revision set.")
new_revision = ArticleRevision()
new_revision.inherit_predecessor(self.article)
new_revision.automatic_log = self.get_logmessage()
new_revision.save()
self.revision = new_revision
class Meta:
abstract = True
def get_editor_media(self, editor):
if editor == 'markitup':
pass
if editor == 'markitup':
pass
from django.db import models
from django.utils.translation import ugettext_lazy as _
import settings
from wiki.conf import settings as wiki_settings
from wiki.models.pluginbase import ReusablePlugin
from wiki.models.article import BaseRevision
class IllegalFileExtension(Exception):
"""File extension on upload is not allowed"""
pass
class Attachment(ReusablePlugin):
current_revision = models.OneToOneField('AttachmentRevision',
verbose_name=_(u'current revision'),
blank=True, null=True, related_name='current_set',
help_text=_(u'The revision of this attachment currently in use (on all articles using the attachment)'),
)
class Meta:
verbose_name = _(u'attachment')
verbose_name_plural = _(u'attachments')
app_label = wiki_settings.APP_LABEL
def upload_path(instance, filename):
from os import path
try:
extension = filename.split(".")[-1]
except IndexError:
raise IllegalFileExtension()
if not extension.lower() in map(lambda x: x.lower(), settings.FILE_EXTENTIONS):
raise IllegalFileExtension()
upload_path = settings.UPLOAD_PATH
upload_path = upload_path.replace('%aid', str(instance.original_article.id))
if settings.UPLOAD_PATH_OBSCURIFY:
import random, hashlib
m=hashlib.md5(str(random.randint(0,100000000000000)))
upload_path = path.join(upload_path, m.hexdigest())
return path.join(upload_path, filename + '.upload')
class AttachmentRevision(BaseRevision):
attachment = models.ForeignKey('Attachment')
file = models.FileField(upload_to=upload_path, #@ReservedAssignment
verbose_name=_(u'file'))
original_filename = models.CharField(max_length=256, verbose_name=_(u'original filename'))
overwritten = models.BooleanField(default=False)
class Meta:
verbose_name = _(u'attachment revision')
verbose_name_plural = _(u'attachment revisions')
get_latest_by = ('revision_number',)
app_label = wiki_settings.APP_LABEL
from django.conf import settings as django_settings
# Where to store article attachments, relative to MEDIA_ROOT
UPLOAD_PATH = getattr(django_settings, 'WIKI_UPLOAD_PATH', 'wiki/uploads/%aid/')
# Should the upload path be obscurified? If so, a random hash will be added to the path
# such that someone can not guess the location of files (if you have
# restricted permissions and the files are still located within the web server's
UPLOAD_PATH_OBSCURIFY = getattr(django_settings, 'WIKI_UPLOAD_PATH_OBSCURIFY', True)
# Allowed non-image extensions. Empty to disallow completely.
# No files are saved without appending ".upload" to the file to ensure that
# your web server never actually executes some script.
# Case insensitive.
FILE_EXTENTIONS = getattr(django_settings, 'WIKI_FILE_EXTENTIONS', ['pdf', 'doc', 'odt', 'docx', 'txt'])
from django.contrib import admin
import models
class ImageAdmin(admin.ModelAdmin):
# Do not let images be added in the admin. An image can only be added
# from the article admin due to the automatic revision system.
def has_add_permission(self, request):
return False
admin.site.register(models.Image, ImageAdmin)
\ No newline at end of file
from django.db import models
from django.utils.translation import ugettext_lazy as _
import settings
from wiki.conf import settings as wiki_settings
from wiki.models.pluginbase import RevisionPlugin
class Image(RevisionPlugin):
image = models.ImageField(upload_to=settings.IMAGE_PATH)
caption = models.CharField(max_length=2056, null=True, blank=True)
def render_caption(self):
"""Returns a rendered version of the caption. Should only use a
subset of the rendering machine."""
pass
class Meta:
verbose_name = _(u'image')
verbose_name_plural = _(u'images')
app_label = wiki_settings.APP_LABEL
from django.conf import settings as django_settings
# Where to store images
IMAGE_PATH = getattr(django_settings, "WIKI_IMAGE_PATH", 'wiki/images/%aid/')
jQuery = django.jQuery;
django.jQuery(document).ready(function() {
django.jQuery(".markItUp").markItUp(mySettings);
});
Markup language:
Markdown
Description:
A basic Markdown markup set with Headings, Bold, Italic, Picture, Link, List, Quotes, Code, Preview button.
Install:
- Download the zip file
- Unzip it in your markItUp! sets folder
- Modify your JS link to point at this set.js
- Modify your CSS link to point at this style.css
\ No newline at end of file
// -------------------------------------------------------------------
// markItUp!
// -------------------------------------------------------------------
// Copyright (C) 2008 Jay Salvat
// http://markitup.jaysalvat.com/
// -------------------------------------------------------------------
// MarkDown tags example
// http://en.wikipedia.org/wiki/Markdown
// http://daringfireball.net/projects/markdown/
// -------------------------------------------------------------------
// Feel free to add more tags
// -------------------------------------------------------------------
mySettings = {
previewParserPath: '',
onShiftEnter: {keepDefault:false, openWith:'\n\n'},
markupSet: [
{name:'First Level Heading', key:'1', placeHolder:'Your title here...', closeWith:function(markItUp) { return miu.markdownTitle(markItUp, '=') } },
{name:'Second Level Heading', key:'2', placeHolder:'Your title here...', closeWith:function(markItUp) { return miu.markdownTitle(markItUp, '-') } },
{name:'Heading 3', key:'3', openWith:'### ', placeHolder:'Your title here...' },
{name:'Heading 4', key:'4', openWith:'#### ', placeHolder:'Your title here...' },
{name:'Heading 5', key:'5', openWith:'##### ', placeHolder:'Your title here...' },
{name:'Heading 6', key:'6', openWith:'###### ', placeHolder:'Your title here...' },
{separator:'---------------' },
{name:'Bold', key:'B', openWith:'**', closeWith:'**'},
{name:'Italic', key:'I', openWith:'_', closeWith:'_'},
{separator:'---------------' },
{name:'Bulleted List', openWith:'- ' },
{name:'Numeric List', openWith:function(markItUp) {
return markItUp.line+'. ';
}},
{separator:'---------------' },
{name:'Picture', key:'P', replaceWith:'![[![Alternative text]!]]([![Url:!:http://]!] "[![Title]!]")'},
{name:'Link', key:'L', openWith:'[', closeWith:']([![Url:!:http://]!] "[![Title]!]")', placeHolder:'Your text to link here...' },
{separator:'---------------'},
{name:'Quotes', openWith:'> '},
{name:'Code Block / Code', openWith:'(!(\t|!|`)!)', closeWith:'(!(`)!)'},
{separator:'---------------'},
{name:'Preview', call:'preview', className:"preview"}
]
}
// mIu nameSpace to avoid conflict.
miu = {
markdownTitle: function(markItUp, char) {
heading = '';
n = django.jQuery.trim(markItUp.selection||markItUp.placeHolder).length;
for(i = 0; i < n; i++) {
heading += char;
}
return '\n'+heading;
}
}
/* -------------------------------------------------------------------
// markItUp!
// By Jay Salvat - http://markitup.jaysalvat.com/
// ------------------------------------------------------------------*/
/*label[for=id_content] {float: none; clear: both; width: 100%; display: block;}*/
textarea.markItUp {width: 500px; padding: 0;}
.inline-related textarea.markItUp {width: 450px; padding: 0;}
.inline-related .markItUp {width: 450px;}
.inline-related .markItUpContainer {float: none;}
.markItUpContainer {float: left;}
.markItUpHeader ul {margin-left: 0 !important; padding-left: 0 !important;}
.markItUp .markItUpButton1 a {
background-image:url(images/h1.png);
}
.markItUp .markItUpButton2 a {
background-image:url(images/h2.png);
}
.markItUp .markItUpButton3 a {
background-image:url(images/h3.png);
}
.markItUp .markItUpButton4 a {
background-image:url(images/h4.png);
}
.markItUp .markItUpButton5 a {
background-image:url(images/h5.png);
}
.markItUp .markItUpButton6 a {
background-image:url(images/h6.png);
}
.markItUp .markItUpButton7 a {
background-image:url(images/bold.png);
}
.markItUp .markItUpButton8 a {
background-image:url(images/italic.png);
}
.markItUp .markItUpButton9 a {
background-image:url(images/list-bullet.png);
}
.markItUp .markItUpButton10 a {
background-image:url(images/list-numeric.png);
}
.markItUp .markItUpButton11 a {
background-image:url(images/picture.png);
}
.markItUp .markItUpButton12 a {
background-image:url(images/link.png);
}
.markItUp .markItUpButton13 a {
background-image:url(images/quotes.png);
}
.markItUp .markItUpButton14 a {
background-image:url(images/code.png);
}
.markItUp .preview a {
background-image:url(images/preview.png);
}
// ----------------------------------------------------------------------------
// markItUp!
// ----------------------------------------------------------------------------
// Copyright (C) 2011 Jay Salvat
// http://markitup.jaysalvat.com/
// ----------------------------------------------------------------------------
// Html tags
// http://en.wikipedia.org/wiki/html
// ----------------------------------------------------------------------------
// Basic set. Feel free to add more tags
// ----------------------------------------------------------------------------
var mySettings = {
onShiftEnter: {keepDefault:false, replaceWith:'<br />\n'},
onCtrlEnter: {keepDefault:false, openWith:'\n<p>', closeWith:'</p>'},
onTab: {keepDefault:false, replaceWith:' '},
markupSet: [
{name:'Bold', key:'B', openWith:'(!(<strong>|!|<b>)!)', closeWith:'(!(</strong>|!|</b>)!)' },
{name:'Italic', key:'I', openWith:'(!(<em>|!|<i>)!)', closeWith:'(!(</em>|!|</i>)!)' },
{name:'Stroke through', key:'S', openWith:'<del>', closeWith:'</del>' },
{separator:'---------------' },
{name:'Bulleted List', openWith:' <li>', closeWith:'</li>', multiline:true, openBlockWith:'<ul>\n', closeBlockWith:'\n</ul>'},
{name:'Numeric List', openWith:' <li>', closeWith:'</li>', multiline:true, openBlockWith:'<ol>\n', closeBlockWith:'\n</ol>'},
{separator:'---------------' },
{name:'Picture', key:'P', replaceWith:'<img src="[![Source:!:http://]!]" alt="[![Alternative text]!]" />' },
{name:'Link', key:'L', openWith:'<a href="[![Link:!:http://]!]"(!( title="[![Title]!]")!)>', closeWith:'</a>', placeHolder:'Your text to link...' },
{separator:'---------------' },
{name:'Clean', className:'clean', replaceWith:function(markitup) { return markitup.selection.replace(/<(.*?)>/g, "") } },
{name:'Preview', className:'preview', call:'preview'}
]
}
/* -------------------------------------------------------------------
// markItUp!
// By Jay Salvat - http://markitup.jaysalvat.com/
// ------------------------------------------------------------------*/
.markItUp .markItUpButton1 a {
background-image:url(images/bold.png);
}
.markItUp .markItUpButton2 a {
background-image:url(images/italic.png);
}
.markItUp .markItUpButton3 a {
background-image:url(images/stroke.png);
}
.markItUp .markItUpButton4 a {
background-image:url(images/list-bullet.png);
}
.markItUp .markItUpButton5 a {
background-image:url(images/list-numeric.png);
}
.markItUp .markItUpButton6 a {
background-image:url(images/picture.png);
}
.markItUp .markItUpButton7 a {
background-image:url(images/link.png);
}
.markItUp .markItUpButton8 a {
background-image:url(images/clean.png);
}
.markItUp .preview a {
background-image:url(images/preview.png);
}
/* -------------------------------------------------------------------
// markItUp! Universal MarkUp Engine, JQuery plugin
// By Jay Salvat - http://markitup.jaysalvat.com/
// ------------------------------------------------------------------*/
.markItUp * {
margin:0px; padding:0px;
outline:none;
}
.markItUp a:link,
.markItUp a:visited {
color:#000;
text-decoration:none;
}
.markItUp {
width:700px;
margin:5px 0 5px 0;
border:5px solid #F5F5F5;
}
.markItUpContainer {
border:1px solid #3C769D;
background:#FFF url(images/bg-container.png) repeat-x top left;
padding:5px 5px 2px 5px;
font:11px Verdana, Arial, Helvetica, sans-serif;
}
.markItUpEditor {
font:12px 'Courier New', Courier, monospace;
padding:5px 5px 5px 35px;
border:3px solid #3C769D;
width:643px;
height:320px;
background:#FFF url(images/bg-editor.png) no-repeat;
clear:both;
line-height:18px;
overflow:auto;
}
.markItUpPreviewFrame {
overflow:auto;
background-color:#FFFFFF;
border:1px solid #3C769D;
width:99.9%;
height:300px;
margin:5px 0;
}
.markItUpFooter {
width:100%;
cursor:n-resize;
}
.markItUpResizeHandle {
overflow:hidden;
width:22px; height:5px;
margin-left:auto;
margin-right:auto;
background-image:url(images/handle.png);
cursor:n-resize;
}
/***************************************************************************************/
/* first row of buttons */
.markItUpHeader ul li {
list-style:none;
float:left;
position:relative;
}
.markItUpHeader ul li ul{
display:none;
}
.markItUpHeader ul li:hover > ul{
display:block;
}
.markItUpHeader ul .markItUpDropMenu {
background:transparent url(images/menu.png) no-repeat 115% 50%;
margin-right:5px;
}
.markItUpHeader ul .markItUpDropMenu li {
margin-right:0px;
}
.markItUpHeader ul .markItUpSeparator {
margin:0 10px;
width:1px;
height:16px;
overflow:hidden;
background-color:#CCC;
}
.markItUpHeader ul ul .markItUpSeparator {
width:auto; height:1px;
margin:0px;
}
/* next rows of buttons */
.markItUpHeader ul ul {
display:none;
position:absolute;
top:18px; left:0px;
background:#F5F5F5;
border:1px solid #3C769D;
height:inherit;
}
.markItUpHeader ul ul li {
float:none;
border-bottom:1px solid #3C769D;
}
.markItUpHeader ul ul .markItUpDropMenu {
background:#F5F5F5 url(images/submenu.png) no-repeat 100% 50%;
}
/* next rows of buttons */
.markItUpHeader ul ul ul {
position:absolute;
top:-1px; left:150px;
}
.markItUpHeader ul ul ul li {
float:none;
}
.markItUpHeader ul a {
display:block;
width:16px; height:16px;
text-indent:-10000px;
background-repeat:no-repeat;
padding:3px;
margin:0px;
}
.markItUpHeader ul ul a {
display:block;
padding-left:0px;
text-indent:0;
width:120px;
padding:5px 5px 5px 25px;
background-position:2px 50%;
}
.markItUpHeader ul ul a:hover {
color:#FFF;
background-color:#3C769D;
}
/***************************************************************************************/
.html .markItUpEditor {
background-image:url(images/bg-editor-html.png);
}
.markdown .markItUpEditor {
background-image:url(images/bg-editor-markdown.png);
}
.textile .markItUpEditor {
background-image:url(images/bg-editor-textile.png);
}
.bbcode .markItUpEditor {
background-image:url(images/bg-editor-bbcode.png);
}
.wiki .markItUpEditor,
.dotclear .markItUpEditor {
background-image:url(images/bg-editor-wiki.png);
}
\ No newline at end of file
Skin:
Simple and neutral Skin
Install:
- Download the zip file
- Unzip it in your markItUp! skins folder
- Modify your CSS link to point at this skin
\ No newline at end of file
/* -------------------------------------------------------------------
// markItUp! Universal MarkUp Engine, JQuery plugin
// By Jay Salvat - http://markitup.jaysalvat.com/
// ------------------------------------------------------------------*/
.markItUp * {
margin:0px; padding:0px;
outline:none;
}
.markItUp a:link,
.markItUp a:visited {
color:#000;
text-decoration:none;
}
.markItUp {
width:700px;
margin:5px 0 5px 0;
}
.markItUpContainer {
font:11px Verdana, Arial, Helvetica, sans-serif;
}
.markItUpEditor {
font:12px 'Courier New', Courier, monospace;
padding:5px;
width:690px;
height:320px;
clear:both; display:block;
line-height:18px;
overflow:auto;
}
.markItUpPreviewFrame {
overflow:auto;
background-color:#FFFFFF;
width:99.9%;
height:350px;
margin:5px 0;
}
.markItUpFooter {
width:100%;
}
.markItUpResizeHandle {
overflow:hidden;
width:22px; height:5px;
margin-left:auto;
margin-right:auto;
background-image:url(images/handle.png);
cursor:n-resize;
}
/***************************************************************************************/
/* first row of buttons */
.markItUpHeader ul li {
list-style:none;
float:left;
position:relative;
}
.markItUpHeader ul li:hover > ul{
display:block;
}
.markItUpHeader ul .markItUpDropMenu {
background:transparent url(images/menu.png) no-repeat 115% 50%;
margin-right:5px;
}
.markItUpHeader ul .markItUpDropMenu li {
margin-right:0px;
}
/* next rows of buttons */
.markItUpHeader ul ul {
display:none;
position:absolute;
top:18px; left:0px;
background:#FFF;
border:1px solid #000;
}
.markItUpHeader ul ul li {
float:none;
border-bottom:1px solid #000;
}
.markItUpHeader ul ul .markItUpDropMenu {
background:#FFF url(images/submenu.png) no-repeat 100% 50%;
}
.markItUpHeader ul .markItUpSeparator {
margin:0 10px;
width:1px;
height:16px;
overflow:hidden;
background-color:#CCC;
}
.markItUpHeader ul ul .markItUpSeparator {
width:auto; height:1px;
margin:0px;
}
/* next rows of buttons */
.markItUpHeader ul ul ul {
position:absolute;
top:-1px; left:150px;
}
.markItUpHeader ul ul ul li {
float:none;
}
.markItUpHeader ul a {
display:block;
width:16px; height:16px;
text-indent:-10000px;
background-repeat:no-repeat;
padding:3px;
margin:0px;
}
.markItUpHeader ul ul a {
display:block;
padding-left:0px;
text-indent:0;
width:120px;
padding:5px 5px 5px 25px;
background-position:2px 50%;
}
.markItUpHeader ul ul a:hover {
color:#FFF;
background-color:#000;
}
\ No newline at end of file
/* preview style examples */
body {
background-color:#EFEFEF;
font:70% Verdana, Arial, Helvetica, sans-serif;
}
\ No newline at end of file
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>markItUp! preview template</title>
<link rel="stylesheet" type="text/css" href="~/templates/preview.css" />
</head>
<body>
<!-- content -->
</body>
</html>
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