Commit 2b8c7f46 by benjaoming

Adding a plugin for handling links and detecting if they are broken (which will…

Adding a plugin for handling links and detecting if they are broken (which will show a read link in the article text). Also a sidebar for looking up links with typeahead.
parent e237b2ac
......@@ -3,20 +3,18 @@ Not implemented - will be ASAP
* Notification system **Almost done** (email notifications)
* Circuit Editor plugin
* Custom storage engine for attachments
* Implement notifications, revision log messages and user messages thoroughly
* Example plugin
* Spam protection / Bot editing detection. Don't let anyone edit more than once every other minute.
* Key-value meta data
* Index views for urlpaths
* Searching
* Finish all class-based views
* View source for read-only articles + locked status
* Are you sure you wanna leave this page?
* Customize bootstrap to look a bit less like a default installation and add styles for the wiki articles
* Embeddable article template tag
* Expand prepopulated database
* "Fix Wiki URL bug in the footnotes plugin for python-markdown" ?
* Index views for urlpaths **Done**
* Permission system in settings tab **Done**
* Special view for deleted articles w/ restore button **Done**
* Article deletion **Done**
......@@ -24,6 +22,8 @@ Not implemented - will be ASAP
* Attachment plugin **Done**
* Simple user account handling: login/register etc. **Done**
* South migrations **Done**
* Custom storage engine for attachments **Done**
* Handling WikiLinks and detecting broken links in markdown extension **Done**
Ideas
=====
......
......@@ -2,5 +2,5 @@ django>=1.4
South<0.8
Markdown<2.3.0
django-sekizai<0.7
django-mptt>=0.5
django-mptt>=0.5.3
sorl-thumbnail
\ No newline at end of file
......@@ -35,4 +35,4 @@ def load(modname, verbose=False, failfast=False):
get_module(app, modname, verbose, failfast)
def load_wiki_plugins():
load('wiki_plugin', verbose=False)
load('wiki_plugin', verbose=True)
# -*- coding: utf-8 -*-
from django.conf import settings as django_settings
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseNotFound, \
HttpResponseForbidden
from django.shortcuts import redirect, get_object_or_404
from django.template.context import RequestContext
from django.http import HttpResponse, HttpResponseNotFound,\
HttpResponseForbidden
from django.template.loader import render_to_string
from django.utils import simplejson as json
from wiki.core.exceptions import NoRootURL
from django.template.loader import render_to_string
def json_view(func):
def wrap(request, *args, **kwargs):
......
......@@ -62,6 +62,7 @@ class EditForm(forms.Form):
newdata['current_revision'] = self.initial_revision.id
newdata['content'] = simple_merge(self.initial_revision.content,
data.get('content', ""))
newdata['title'] = current_revision.title
kwargs['data'] = newdata
kwargs['initial'] = initial
......
......@@ -40,7 +40,9 @@ class URLPath(MPTTModel):
parent = TreeForeignKey('self', null=True, blank=True, related_name='children')
def __init__(self, *args, **kwargs):
self._tree_manager = URLPath.objects
pass
# Fixed in django-mptt 0.5.3
#self._tree_manager = URLPath.objects
return super(URLPath, self).__init__(*args, **kwargs)
@property
......@@ -152,7 +154,7 @@ class URLPath(MPTTModel):
return parent
def get_absolute_url(self):
return reverse('wiki:get_url', args=(self.path,))
return reverse('wiki:get', kwargs={'path': self.path})
@classmethod
def create_root(cls, site=None, title="Root", **kwargs):
......@@ -211,17 +213,21 @@ def on_article_delete(instance, *args, **kwargs):
parent=URLPath.root(),
site=site)
except URLPath.DoesNotExist:
lost_and_found = URLPath.objects.create(slug=settings.LOST_AND_FOUND_SLUG,
parent=URLPath.root(),
site=site,)
article = Article(group_read = True,
group_write = False,
other_read = False,
other_write = False)
article.add_revision(ArticleRevision(
content=_(u'Articles who lost their parents'
'==============================='),
content=_(u'Articles who lost their parents\n'
'===============================\n\n'
'The children of this article have had their parents deleted. You should probably find a new home for them.'),
title=_(u"Lost and found")))
lost_and_found = URLPath.objects.create(slug=settings.LOST_AND_FOUND_SLUG,
parent=URLPath.root(),
site=site,
article=article)
article.add_object_relation(lost_and_found)
for urlpath in URLPath.objects.filter(articles__article=instance, site=site):
# Delete the children
......
......@@ -16,15 +16,15 @@ class AttachmentPlugin(BasePlugin):
slug = settings.SLUG
urlpatterns = patterns('',
url('^$', views.AttachmentView.as_view(), name='attachments_index'),
url('^search/$', views.AttachmentSearchView.as_view(), name='attachments_search'),
url('^add/(?P<attachment_id>\d+)/$', views.AttachmentAddView.as_view(), name='attachments_add'),
url('^replace/(?P<attachment_id>\d+)/$', views.AttachmentReplaceView.as_view(), name='attachments_replace'),
url('^history/(?P<attachment_id>\d+)/$', views.AttachmentHistoryView.as_view(), name='attachments_history'),
url('^download/(?P<attachment_id>\d+)/$', views.AttachmentDownloadView.as_view(), name='attachments_download'),
url('^delete/(?P<attachment_id>\d+)/$', views.AttachmentDeleteView.as_view(), name='attachments_delete'),
url('^download/(?P<attachment_id>\d+)/revision/(?P<revision_id>\d+)/$', views.AttachmentDownloadView.as_view(), name='attachments_download'),
url('^change/(?P<attachment_id>\d+)/revision/(?P<revision_id>\d+)/$', views.AttachmentChangeRevisionView.as_view(), name='attachments_revision_change'),
url(r'^$', views.AttachmentView.as_view(), name='attachments_index'),
url(r'^search/$', views.AttachmentSearchView.as_view(), name='attachments_search'),
url(r'^add/(?P<attachment_id>\d+)/$', views.AttachmentAddView.as_view(), name='attachments_add'),
url(r'^replace/(?P<attachment_id>\d+)/$', views.AttachmentReplaceView.as_view(), name='attachments_replace'),
url(r'^history/(?P<attachment_id>\d+)/$', views.AttachmentHistoryView.as_view(), name='attachments_history'),
url(r'^download/(?P<attachment_id>\d+)/$', views.AttachmentDownloadView.as_view(), name='attachments_download'),
url(r'^delete/(?P<attachment_id>\d+)/$', views.AttachmentDeleteView.as_view(), name='attachments_delete'),
url(r'^download/(?P<attachment_id>\d+)/revision/(?P<revision_id>\d+)/$', views.AttachmentDownloadView.as_view(), name='attachments_download'),
url(r'^change/(?P<attachment_id>\d+)/revision/(?P<revision_id>\d+)/$', views.AttachmentChangeRevisionView.as_view(), name='attachments_revision_change'),
)
article_tab = (_(u'Attachments'), "icon-file")
......@@ -33,7 +33,7 @@ class AttachmentPlugin(BasePlugin):
# List of notifications to construct signal handlers for. This
# is handled inside the notifications plugin.
notifications = [{'model': models.AttachmentRevision,
'message': lambda obj: _(u"A file was changed: %s") % obj.get_filename(),
'message': lambda obj: (_(u"A file was changed: %s") if not obj.deleted else _(u"A file was deleted: %s")) % obj.get_filename(),
'key': ARTICLE_EDIT,
'created': True,
'get_article': lambda obj: obj.attachment.article}
......
......@@ -41,10 +41,9 @@ class ImagePreprocessor(markdown.preprocessors.Preprocessor):
pass
line = line.replace(m.group(1), "")
elif previous_line_was_image:
print line
if line.startswith(" "):
caption += line[4:]
line = ""
line = None
else:
html = render_to_string("wiki/plugins/images/render.html",
Context({'image': image,
......@@ -53,6 +52,7 @@ class ImagePreprocessor(markdown.preprocessors.Preprocessor):
'align': alignment}))
line = html + line
previous_line_was_image = False
new_text.append(line)
if not line is None:
new_text.append(line)
return new_text
......@@ -3,7 +3,7 @@
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 style="width: 250px; margin: 10px; float: {{ align }};">
<div class="thumbnail">
{% thumbnail revision.image "250x250" as thumb %}
<a href="{{ revision.image.url }}">
......
......@@ -5,7 +5,12 @@
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');
imagetag = '\n[image:'+image_id+' align:'+align+']';
if (! caption == '') {
$('#id_content').insertAtCaret(imagetag+'\n '+caption+'\n\n');
} else {
$('#id_content').insertAtCaret(imagetag+'\n\n');
}
}
</script>
......
......@@ -11,28 +11,34 @@ from wiki.plugins.images.markdown_extensions import ImageExtension
class ImagePlugin(BasePlugin):
slug = settings.SLUG
sidebar = {'headline': _('Images'),
'icon_class': 'icon-picture',
'template': 'wiki/plugins/images/sidebar.html',
'form_class': forms.SidebarForm,
'get_form_kwargs': (lambda a: {'instance': models.Image(article=a)})}
sidebar = {
'headline': _('Images'),
'icon_class': 'icon-picture',
'template': 'wiki/plugins/images/sidebar.html',
'form_class': forms.SidebarForm,
'get_form_kwargs': (lambda a: {'instance': models.Image(article=a)})
}
# List of notifications to construct signal handlers for. This
# is handled inside the notifications plugin.
notifications = [{'model': models.Image,
'message': lambda obj: _(u"An image was added: %s") % obj.current_revision.get_filename(),
'key': ARTICLE_EDIT,
'created': True,
'get_article': lambda obj: obj.article}
]
notifications = [
{'model': models.ImageRevision,
'message': lambda obj: _(u"An image was added: %s") % obj.get_filename(),
'key': ARTICLE_EDIT,
'created': False,
'ignore': lambda revision: bool(revision.previous_revision), # Ignore if there is a previous revision... the image isn't new
'get_article': lambda obj: obj.article}
]
class RenderMedia:
js = [
'wiki/colorbox/colorbox/jquery.colorbox-min.js',
'wiki/js/images.js',
'wiki/colorbox/colorbox/jquery.colorbox-min.js',
'wiki/js/images.js',
]
css = {'screen': 'wiki/colorbox/example1/colorbox.css'}
css = {
'screen': 'wiki/colorbox/example1/colorbox.css'
}
urlpatterns = patterns('',
url('^$', views.ImageView.as_view(), name='images_index'),
......
#!/usr/bin/env python
'''
Wikipath Extension for Python-Markdown
======================================
Converts [Link Name](wiki:ArticleName) to relative links pointing to article. Requires Python-Markdown 2.0+
Basic usage:
>>> import markdown
>>> text = "Some text with a [Link Name](wiki:ArticleName)."
>>> html = markdown.markdown(text, ['wikipath(base_url="/wiki/view/")'])
>>> html
u'<p>Some text with a <a class="wikipath" href="/wiki/view/ArticleName/">Link Name</a>.</p>'
Dependencies:
* [Python 2.3+](http://python.org)
* [Markdown 2.0+](http://www.freewisdom.org/projects/python-markdown/)
'''
import markdown
from os import path as os_path
try:
# Markdown 2.1.0 changed from 2.0.3. We try importing the new version first,
# but import the 2.0.3 version if it fails
from markdown.util import etree #@UnusedImport
except ImportError:
from markdown import etree #@UnresolvedImport @Reimport
class WikiPathExtension(markdown.Extension):
def __init__(self, configs):
# set extension defaults
self.config = {
'base_url' : ['/', 'String to append to beginning of URL.'],
'html_class' : ['wikipath', 'CSS hook. Leave blank for none.']
}
# Override defaults with user settings
for key, value in configs :
# self.config[key][0] = value
self.setConfig(key, value)
print "I was created"
def extendMarkdown(self, md, md_globals):
self.md = md
# append to end of inline patterns
WIKI_RE = r'\[(?P<linkTitle>.+?)\]\(wiki:(?P<wikiTitle>[a-zA-Z\d/_-]*)\)'
wikiPathPattern = WikiPath(WIKI_RE, self.config, markdown_instance=md)
wikiPathPattern.md = md
md.inlinePatterns.add('djangowikipath', wikiPathPattern, "<reference")
class WikiPath(markdown.inlinepatterns.Pattern):
def __init__(self, pattern, config, **kwargs):
markdown.inlinepatterns.Pattern.__init__(self, pattern, **kwargs)
self.config = config
def handleMatch(self, m) :
from wiki import models
article_title = m.group('wikiTitle')
absolute = False
if article_title.startswith("/"):
absolute = True
article_title = article_title.strip("/")
# Use this to calculate some kind of meaningful path
# from the link, regardless of whether or not something can be
# looked up
path_from_link = ""
if absolute:
base_path = self.config['base_url'][0]
path_from_link = os_path.join(base_path, article_title)
try:
urlpath = models.URLPath.get_by_path(path_from_link)
path = urlpath.get_absolute_url()
except models.URLPath.DoesNotExist:
urlpath = None
path = path_from_link
else:
urlpath = models.URLPath.objects.get(article=self.markdown.article)
path_from_link = os_path.join(urlpath.path, article_title)
if urlpath.parent:
lookup = urlpath.parent.get_descendants().filter(slug=article_title)
else:
lookup = urlpath.get_descendants().filter(slug=article_title)
if lookup.count() > 0:
urlpath = lookup[0]
path = urlpath.get_absolute_url()
else:
urlpath = None
path = "/" + path_from_link
label = m.group('linkTitle')
a = etree.Element('a')
a.set('href', path)
if not urlpath:
a.set('class', self.config['html_class'][0] + " linknotfound")
else:
a.set('class', self.config['html_class'][0])
a.text = label
return a
def _getMeta(self):
""" Return meta data or config data. """
base_url = self.config['base_url'][0]
html_class = self.config['html_class'][0]
if hasattr(self.md, 'Meta'):
if self.md.Meta.has_key('wiki_base_url'):
base_url = self.md.Meta['wiki_base_url'][0]
if self.md.Meta.has_key('wiki_html_class'):
html_class = self.md.Meta['wiki_html_class'][0]
return base_url, html_class
def makeExtension(configs=None) :
return WikiPathExtension(configs=configs)
if __name__ == "__main__":
import doctest
doctest.testmod()
\ No newline at end of file
"""A more liberal autolinker
Inspired by Django's urlize function.
Positive examples:
>>> import markdown
>>> md = markdown.Markdown(extensions=['urlize'])
>>> md.convert('http://example.com/')
u'<p><a href="http://example.com/">http://example.com/</a></p>'
>>> md.convert('go to http://example.com')
u'<p>go to <a href="http://example.com">http://example.com</a></p>'
>>> md.convert('example.com')
u'<p><a href="http://example.com">example.com</a></p>'
>>> md.convert('example.net')
u'<p><a href="http://example.net">example.net</a></p>'
>>> md.convert('www.example.us')
u'<p><a href="http://www.example.us">www.example.us</a></p>'
>>> md.convert('(www.example.us/path/?name=val)')
u'<p>(<a href="http://www.example.us/path/?name=val">www.example.us/path/?name=val</a>)</p>'
>>> md.convert('go to <http://example.com> now!')
u'<p>go to <a href="http://example.com">http://example.com</a> now!</p>'
Negative examples:
>>> md.convert('del.icio.us')
u'<p>del.icio.us</p>'
"""
import markdown
# Global Vars
URLIZE_RE = '(%s)' % '|'.join([
r'<(?:f|ht)tps?://[^>]*>',
r'\b(?:f|ht)tps?://[^)<>\s]+[^.,)<>\s]',
r'\bwww\.[^)<>\s]+[^.,)<>\s]',
r'[^(<\s]+\.(?:com|net|org)\b',
])
class UrlizePattern(markdown.inlinepatterns.Pattern):
""" Return a link Element given an autolink (`http://example/com`). """
def handleMatch(self, m):
url = m.group(2)
if url.startswith('<'):
url = url[1:-1]
text = url
if not url.split('://')[0] in ('http','https','ftp'):
if '@' in url and not '/' in url:
url = 'mailto:' + url
else:
url = 'http://' + url
icon = markdown.util.etree.Element("span")
icon.set('class', 'icon-globe')
span_text = markdown.util.etree.Element("span")
span_text.text = markdown.util.AtomicString(" " + text)
el = markdown.util.etree.Element("a")
el.set('href', url)
el.set('target', '_blank')
el.extend([icon, span_text])
return el
class UrlizeExtension(markdown.Extension):
""" Urlize Extension for Python-Markdown. """
def extendMarkdown(self, md, md_globals):
""" Replace autolink with UrlizePattern """
md.inlinePatterns['autolink'] = UrlizePattern(URLIZE_RE, md)
def makeExtension(configs=None):
return UrlizeExtension(configs=configs)
if __name__ == "__main__":
import doctest
doctest.testmod()
\ No newline at end of file
from django.db import models
# Create your models here.
{% load i18n %}
{% load url from future %}
<h4>{% trans "Link to another wiki page" %}</h4>
<p>
{% trans "Type in something from another wiki page's title and auto-complete will help you create a tag for you wiki link. Tags for links look like this:" %}<br />
<code>[Title of link](wiki:ArticleSlug)</code>
</p>
<p>
<input type="text" class="page_title_query" id="links_page_title_query" value="" placeholder="Type to search..." />
<button type="button" class="btn" onclick="wikiInsertLink()">
{% trans "Insert" %}
</button>
</p>
<h4>{% trans "An external link" %}</h4>
<script type="text/javascript" src="{{ STATIC_URL }}wiki/js/bootstrap-typeahead.js"></script>
<script type="text/javascript">
$('.page_title_query').typeahead({
source: function (typeahead, query) {
return $.get('{% url 'wiki:links_query_urlpath' path=urlpath.path article_id=article.id %}', { query: query }, function (data) {
return typeahead.process(data);
});
}
});
function wikiInsertLink() {
$('#id_content').insertAtCaret($('#links_page_title_query').val());
}
</script>
"""
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)
from wiki.decorators import json_view, get_article
from django.views.generic.base import View
from django.utils.decorators import method_decorator
class QueryUrlPath(View):
@method_decorator(json_view)
@method_decorator(get_article(can_read=True))
def dispatch(self, request, article, *args, **kwargs):
max_num = kwargs.pop('max_num', 20)
# TODO: Move this import when
from wiki import models
query = request.GET.get('query', None)
if query:
matches = models.URLPath.objects.can_read(request.user).active().filter(
article__current_revision__title__contains=query,
article__current_revision__deleted=False,
)
matches = matches.select_related_common()
return [("[%s](wiki:%s)") % (m.article.current_revision.title, m.path) for m in matches[:max_num]]
return []
# -*- coding: utf-8 -*-
from django.conf.urls.defaults import patterns, url
from django.utils.translation import ugettext as _
from wiki.core.plugins import registry
from wiki.core.plugins.base import BasePlugin
from wiki.plugins.links import views
from wiki.plugins.links.mdx.urlize import makeExtension
from wiki.plugins.links.mdx.djangowikilinks import WikiPathExtension
from django.core.urlresolvers import reverse_lazy
class LinkPlugin(BasePlugin):
slug = 'links'
urlpatterns = patterns('',
url(r'^json/query-urlpath/$', views.QueryUrlPath.as_view(), name='links_query_urlpath'),
)
sidebar = {'headline': _('Links'),
'icon_class': 'icon-bookmark',
'template': 'wiki/plugins/links/sidebar.html',
'form_class': None,
'get_form_kwargs': (lambda a: {})}
markdown_extensions = [makeExtension(), WikiPathExtension([('base_url', reverse_lazy('wiki:get', kwargs={'path': ''}))])]
def __init__(self):
pass
registry.register(LinkPlugin)
......@@ -60,6 +60,8 @@ for plugin in registry.get_plugins():
notifications = getattr(plugin, 'notifications', [])
for notification_dict in notifications:
def plugin_notification(instance, **kwargs):
if notification_dict.get('ignore', lambda x: False)(instance):
return
if kwargs.get('created', False) == notification_dict.get('created', True):
url = None
if notification_dict.has_key('get_url'):
......
/*
This is not the original bootstrap-typeahead.js but one that
is taken from:
https://gist.github.com/1866577
..because it supports ajax calls.
*/
/* =============================================================
* bootstrap-typeahead.js v2.0.0
* http://twitter.github.com/bootstrap/javascript.html#typeahead
* =============================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================ */
!function( $ ){
"use strict"
var Typeahead = function ( element, options ) {
this.$element = $(element)
this.options = $.extend({}, $.fn.typeahead.defaults, options)
this.matcher = this.options.matcher || this.matcher
this.sorter = this.options.sorter || this.sorter
this.highlighter = this.options.highlighter || this.highlighter
this.$menu = $(this.options.menu).appendTo('body')
this.source = this.options.source
this.onselect = this.options.onselect
this.strings = true
this.shown = false
this.listen()
}
Typeahead.prototype = {
constructor: Typeahead
, select: function () {
var val = JSON.parse(this.$menu.find('.active').attr('data-value'))
, text
if (!this.strings) text = val[this.options.property]
else text = val
this.$element.val(text)
if (typeof this.onselect == "function")
this.onselect(val)
return this.hide()
}
, show: function () {
var pos = $.extend({}, this.$element.offset(), {
height: this.$element[0].offsetHeight
})
this.$menu.css({
top: pos.top + pos.height
, left: pos.left
})
this.$menu.show()
this.shown = true
return this
}
, hide: function () {
this.$menu.hide()
this.shown = false
return this
}
, lookup: function (event) {
var that = this
, items
, q
, value
this.query = this.$element.val()
if (typeof this.source == "function") {
value = this.source(this, this.query)
if (value) this.process(value)
} else {
this.process(this.source)
}
}
, process: function (results) {
var that = this
, items
, q
if (results.length && typeof results[0] != "string")
this.strings = false
this.query = this.$element.val()
if (!this.query) {
return this.shown ? this.hide() : this
}
items = $.grep(results, function (item) {
if (!that.strings)
item = item[that.options.property]
if (that.matcher(item)) return item
})
items = this.sorter(items)
if (!items.length) {
return this.shown ? this.hide() : this
}
return this.render(items.slice(0, this.options.items)).show()
}
, matcher: function (item) {
return ~item.toLowerCase().indexOf(this.query.toLowerCase())
}
, sorter: function (items) {
var beginswith = []
, caseSensitive = []
, caseInsensitive = []
, item
, sortby
while (item = items.shift()) {
if (this.strings) sortby = item
else sortby = item[this.options.property]
if (!sortby.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
else if (~sortby.indexOf(this.query)) caseSensitive.push(item)
else caseInsensitive.push(item)
}
return beginswith.concat(caseSensitive, caseInsensitive)
}
, highlighter: function (item) {
return item.replace(new RegExp('(' + this.query + ')', 'ig'), function ($1, match) {
return '<strong>' + match + '</strong>'
})
}
, render: function (items) {
var that = this
items = $(items).map(function (i, item) {
i = $(that.options.item).attr('data-value', JSON.stringify(item))
if (!that.strings)
item = item[that.options.property]
i.find('a').html(that.highlighter(item))
return i[0]
})
items.first().addClass('active')
this.$menu.html(items)
return this
}
, next: function (event) {
var active = this.$menu.find('.active').removeClass('active')
, next = active.next()
if (!next.length) {
next = $(this.$menu.find('li')[0])
}
next.addClass('active')
}
, prev: function (event) {
var active = this.$menu.find('.active').removeClass('active')
, prev = active.prev()
if (!prev.length) {
prev = this.$menu.find('li').last()
}
prev.addClass('active')
}
, listen: function () {
this.$element
.on('blur', $.proxy(this.blur, this))
.on('keypress', $.proxy(this.keypress, this))
.on('keyup', $.proxy(this.keyup, this))
if ($.browser.webkit || $.browser.msie) {
this.$element.on('keydown', $.proxy(this.keypress, this))
}
this.$menu
.on('click', $.proxy(this.click, this))
.on('mouseenter', 'li', $.proxy(this.mouseenter, this))
}
, keyup: function (e) {
e.stopPropagation()
e.preventDefault()
switch(e.keyCode) {
case 40: // down arrow
case 38: // up arrow
break
case 9: // tab
case 13: // enter
if (!this.shown) return
this.select()
break
case 27: // escape
this.hide()
break
default:
this.lookup()
}
}
, keypress: function (e) {
e.stopPropagation()
if (!this.shown) return
switch(e.keyCode) {
case 9: // tab
case 13: // enter
case 27: // escape
e.preventDefault()
break
case 38: // up arrow
e.preventDefault()
this.prev()
break
case 40: // down arrow
e.preventDefault()
this.next()
break
}
}
, blur: function (e) {
var that = this
e.stopPropagation()
e.preventDefault()
setTimeout(function () { that.hide() }, 150)
}
, click: function (e) {
e.stopPropagation()
e.preventDefault()
this.select()
}
, mouseenter: function (e) {
this.$menu.find('.active').removeClass('active')
$(e.currentTarget).addClass('active')
}
}
/* TYPEAHEAD PLUGIN DEFINITION
* =========================== */
$.fn.typeahead = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('typeahead')
, options = typeof option == 'object' && option
if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
if (typeof option == 'string') data[option]()
})
}
$.fn.typeahead.defaults = {
source: []
, items: 8
, menu: '<ul class="typeahead dropdown-menu"></ul>'
, item: '<li><a href="#"></a></li>'
, onselect: null
, property: 'value'
}
$.fn.typeahead.Constructor = Typeahead
/* TYPEAHEAD DATA-API
* ================== */
$(function () {
$('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
var $this = $(this)
if ($this.data('typeahead')) return
e.preventDefault()
$this.typeahead($this.data())
})
})
}( window.jQuery );
......@@ -33,7 +33,7 @@ mySettings = {
{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:'(!(`)!)'},
{name:'Code Block / Code', openWith:'(!( |!|`)!)', closeWith:'(!(`)!)'},
]
}
......
......@@ -13,7 +13,12 @@
<div class="tabbable tabs-top" style="margin-top: 20px;">
<ul class="nav nav-tabs">
<li style="float: left">
<h1 style="margin-top: -10px;">{{ article.current_revision.title }}</h1>
<h1 style="margin-top: -10px;">
{{ article.current_revision.title }}
{% if urlpath and urlpath.parent %}
<small style="font-size: 14px;"><a href="{% url 'wiki:get' path=urlpath.path %}">{{ urlpath.slug }} <span class="icon-bookmark"></span></a></small>
{% endif %}
</h1>
</li>
{% include "wiki/includes/article_menu.html" %}
</ul>
......
......@@ -35,6 +35,12 @@
border: 1px solid #CCC;
}
.wiki-article a.linknotfound {color: #C00;}
.wiki-article pre {
max-width: 700px;
}
input[type=file] {float: none; width: auto;}
.asteriskField { font-size: 20px; margin-left: 5px;}
......
......@@ -9,6 +9,19 @@
{% addtoblock "js" %}
<script type="text/javascript" src="{{ STATIC_URL }}admin/js/urlify.js "></script>
<script type="text/javascript">
// Replacement of django's URLify that doesn't remove any words.
function URLify(s, num_chars) {
s = downcode(s);
removelist = [];
r = new RegExp('\\b(' + removelist.join('|') + ')\\b', 'gi');
s = s.replace(r, '');
// if downcode doesn't hit, the char will be stripped here
s = s.replace(/[^-\w\s]/g, ''); // remove unneeded chars
s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces
s = s.replace(/[-\s]+/g, '-'); // convert spaces to hyphens
s = s.toLowerCase(); // convert to lowercase
return s.substring(0, num_chars);// trim to first num_chars chars
}
//<![CDATA[
(function($) {
$(document).ready(function (){
......@@ -17,7 +30,7 @@
if(!e._changed) {
slug = URLify(this.value, 64);
wikislug = slug.replace(/\-/g, " ");
wikislug = wikislug.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1);});
wikislug = wikislug.replace(/\w\S*/gi, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1);});
wikislug = wikislug.replace(/\s*/g, "");
e.value = wikislug;
}
......
......@@ -60,5 +60,7 @@ class Login(FormView):
if django_settings.LOGIN_REDIRECT_URL:
return redirect(django_settings.LOGIN_REDIRECT_URL)
else:
if not self.referer:
return redirect('wiki:get', path='')
return redirect(self.referer)
\ No newline at end of file
......@@ -400,7 +400,10 @@ class Dir(ListView, ArticleMixin):
paginate_by = 30
def get_queryset(self):
return self.urlpath.get_children().can_read(self.request.user).select_related_common().order_by('slug')
children = self.urlpath.get_children().can_read(self.request.user).select_related_common().order_by('article__current_revision__title')
if not self.request.user.has_perm('wiki.moderator'):
children = children.active()
return children
def get_context_data(self, **kwargs):
kwargs_article = ArticleMixin.get_context_data(self, **kwargs)
......@@ -534,8 +537,10 @@ class Preview(ArticleMixin, TemplateView):
return super(Preview, self).get(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
self.title = self.revision.title
self.content = self.revision.content
if self.revision and not self.title:
self.title = self.revision.title
if self.revision and not self.content:
self.content = self.revision.content
return super(Preview, self).get( request, *args, **kwargs)
def get_context_data(self, **kwargs):
......@@ -543,7 +548,7 @@ class Preview(ArticleMixin, TemplateView):
kwargs['revision'] = self.revision
kwargs['content'] = self.content
kwargs['preview'] = self.preview
return super(Preview, self).get_context_data(**kwargs)
return ArticleMixin.get_context_data(self, **kwargs)
@json_view
......
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