зеркало из https://github.com/mozilla/kitsune.git
[600747] Video hook and render with Flash fallback.
* Videos are identified by title and locale * Depends on an update in py-wikimarkup which adds support for <video> and <source> tags (py-wikimarkup commit cc06e6d264622891b6b018e8670c9ef4bb12d618) * Attaches all the _hook_*s to the WikiParser class, because they need a contextual locale. * Adds locale support for any of the hooks that do document lookup. * Uses SWFobject JS lib to support flash fallback for video. * Adds a migration for unique ('locale', 'title') on gallery_video and gallery_image * Adds a WIKI_VIDEO_WIDTH|HEIGHT constant that may be used as MAX_WIDTH|HEIGHT in the future, once we get video thumbnails.
This commit is contained in:
Родитель
a8816900ee
Коммит
e664ef5c3d
|
@ -24,9 +24,10 @@ class Media(ModelBase):
|
|||
|
||||
class Meta:
|
||||
abstract = True
|
||||
unique_together = ('locale', 'title')
|
||||
|
||||
def __unicode__(self):
|
||||
return self.title + ': ' + self.file.name[30:]
|
||||
return '[%s] %s' % (self.locale, self.title)
|
||||
|
||||
|
||||
class Image(Media):
|
||||
|
|
|
@ -20,6 +20,8 @@ ALLOWED_ATTRIBUTES = {
|
|||
'li': ['class'],
|
||||
'span': ['class'],
|
||||
'img': ['class', 'src', 'alt', 'title', 'height', 'width', 'style'],
|
||||
'video': ['height', 'width', 'controls', 'data-fallback'],
|
||||
'source': ['src', 'type'],
|
||||
}
|
||||
IMAGE_PARAMS = {
|
||||
'align': ('none', 'left', 'center', 'right'),
|
||||
|
@ -33,10 +35,10 @@ def wiki_to_html(wiki_markup):
|
|||
return WikiParser().parse(wiki_markup, show_toc=False)
|
||||
|
||||
|
||||
def _getWikiLink(link):
|
||||
def _getWikiLink(link, locale):
|
||||
"""Checks the page exists, and returns its URL or the URL to create it."""
|
||||
try:
|
||||
d = Document.objects.get(title=link, is_template=False)
|
||||
d = Document.objects.get(locale=locale, title=link, is_template=False)
|
||||
except Document.DoesNotExist:
|
||||
# To avoid circular imports, wiki.models imports wiki_to_html
|
||||
from sumo.helpers import urlparams
|
||||
|
@ -44,36 +46,12 @@ def _getWikiLink(link):
|
|||
return d.get_absolute_url()
|
||||
|
||||
|
||||
def _hook_internal_link(parser, space, name):
|
||||
"""Parses text and returns internal link."""
|
||||
link = text = name
|
||||
|
||||
# Split on pipe -- [[href|name]]
|
||||
if '|' in name:
|
||||
link, text = link.split('|', 1)
|
||||
|
||||
hash = ''
|
||||
if '#' in link:
|
||||
link, hash = link.split('#', 1)
|
||||
|
||||
# Sections use _, page names use +
|
||||
if hash != '':
|
||||
hash = '#' + hash.replace(' ', '_')
|
||||
|
||||
# Links to this page can just contain href="#hash"
|
||||
if link == '' and hash != '':
|
||||
return u'<a href="%s">%s</a>' % (hash, text)
|
||||
|
||||
link = _getWikiLink(link)
|
||||
return u'<a href="%s%s">%s</a>' % (link, hash, text)
|
||||
|
||||
|
||||
def _getImagePath(link):
|
||||
"""Returns an uploaded image's path for image paths in markup."""
|
||||
return settings.WIKI_UPLOAD_URL + urlquote(link)
|
||||
|
||||
|
||||
def _buildImageParams(items):
|
||||
def _buildImageParams(items, locale):
|
||||
"""
|
||||
Builds a list of items and return image-relevant parameters in a dict.
|
||||
"""
|
||||
|
@ -90,7 +68,7 @@ def _buildImageParams(items):
|
|||
params[item] = True
|
||||
|
||||
if 'page' in params and params['page'] is not True:
|
||||
params['link'] = _getWikiLink(params['page'])
|
||||
params['link'] = _getWikiLink(params['page'], locale)
|
||||
|
||||
# Validate params with limited # of values
|
||||
for param_allowed in IMAGE_PARAMS:
|
||||
|
@ -101,35 +79,6 @@ def _buildImageParams(items):
|
|||
return params
|
||||
|
||||
|
||||
def _hook_image_tag(parser, space, name):
|
||||
"""Adds syntax for inserting images."""
|
||||
link = name
|
||||
caption = name
|
||||
params = {}
|
||||
|
||||
# Parse the inner syntax, e.g. [[Image:src|option=val|caption]]
|
||||
separator = name.find('|')
|
||||
items = []
|
||||
if separator != -1:
|
||||
items = link.split('|')
|
||||
link = items[0]
|
||||
# If the last item contains '=', it's not a caption
|
||||
if items[-1].find('=') == -1:
|
||||
caption = items[-1]
|
||||
items = items[1:-1]
|
||||
else:
|
||||
caption = link
|
||||
items = items[1:]
|
||||
|
||||
# parse the relevant items
|
||||
params = _buildImageParams(items)
|
||||
img_path = _getImagePath(link)
|
||||
|
||||
template = jingo.env.get_template('wikiparser/hook_image.html')
|
||||
r_kwargs = {'img_path': img_path, 'caption': caption, 'params': params}
|
||||
return template.render(**r_kwargs)
|
||||
|
||||
|
||||
class WikiParser(Parser):
|
||||
"""Wrapper for wikimarkup which adds Kitsune-specific callbacks and setup.
|
||||
"""
|
||||
|
@ -138,11 +87,72 @@ class WikiParser(Parser):
|
|||
super(WikiParser, self).__init__(base_url)
|
||||
|
||||
# Register default hooks
|
||||
self.registerInternalLinkHook(None, _hook_internal_link)
|
||||
self.registerInternalLinkHook('Image', _hook_image_tag)
|
||||
self.registerInternalLinkHook(None, self._hook_internal_link)
|
||||
self.registerInternalLinkHook('Image', self._hook_image_tag)
|
||||
|
||||
def parse(self, text, show_toc=None, tags=None, attributes=None,
|
||||
locale=settings.WIKI_DEFAULT_LANGUAGE):
|
||||
"""Given wiki markup, return HTML.
|
||||
|
||||
Pass a locale to get all the hooks to look up Documents or Media
|
||||
(Video, Image) for that locale. We key Documents by title and locale,
|
||||
so both are required to identify it for a e.g. link.
|
||||
|
||||
Since py-wikimarkup's hooks don't offer custom paramters for callbacks,
|
||||
we're using self.locale to keep things simple."""
|
||||
self.locale = locale
|
||||
|
||||
def parse(self, text, show_toc=None, tags=None, attributes=None):
|
||||
"""Given wiki markup, return HTML."""
|
||||
parser_kwargs = {'tags': tags} if tags else {}
|
||||
return super(WikiParser, self).parse(text, show_toc=show_toc,
|
||||
attributes=attributes or ALLOWED_ATTRIBUTES, **parser_kwargs)
|
||||
|
||||
def _hook_internal_link(self, parser, space, name):
|
||||
"""Parses text and returns internal link."""
|
||||
link = text = name
|
||||
|
||||
# Split on pipe -- [[href|name]]
|
||||
if '|' in name:
|
||||
link, text = link.split('|', 1)
|
||||
|
||||
hash = ''
|
||||
if '#' in link:
|
||||
link, hash = link.split('#', 1)
|
||||
|
||||
# Sections use _, page names use +
|
||||
if hash != '':
|
||||
hash = '#' + hash.replace(' ', '_')
|
||||
|
||||
# Links to this page can just contain href="#hash"
|
||||
if link == '' and hash != '':
|
||||
return u'<a href="%s">%s</a>' % (hash, text)
|
||||
|
||||
link = _getWikiLink(link, self.locale)
|
||||
return u'<a href="%s%s">%s</a>' % (link, hash, text)
|
||||
|
||||
def _hook_image_tag(self, parser, space, name):
|
||||
"""Adds syntax for inserting images."""
|
||||
link = name
|
||||
caption = name
|
||||
params = {}
|
||||
|
||||
# Parse the inner syntax, e.g. [[Image:src|option=val|caption]]
|
||||
separator = name.find('|')
|
||||
items = []
|
||||
if separator != -1:
|
||||
items = link.split('|')
|
||||
link = items[0]
|
||||
# If the last item contains '=', it's not a caption
|
||||
if items[-1].find('=') == -1:
|
||||
caption = items[-1]
|
||||
items = items[1:-1]
|
||||
else:
|
||||
caption = link
|
||||
items = items[1:]
|
||||
|
||||
# parse the relevant items
|
||||
params = _buildImageParams(items, self.locale)
|
||||
img_path = _getImagePath(link)
|
||||
|
||||
template = jingo.env.get_template('wikiparser/hook_image.html')
|
||||
r_kwargs = {'img_path': img_path, 'caption': caption, 'params': params}
|
||||
return template.render(**r_kwargs)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from functools import partial
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from nose.tools import eq_
|
||||
|
@ -22,6 +24,10 @@ def doc_rev_parser(content, title='Installing Firefox', parser_cls=WikiParser):
|
|||
return (d, r, p)
|
||||
|
||||
|
||||
_buildImageParamsDefault = partial(_buildImageParams,
|
||||
locale=settings.WIKI_DEFAULT_LANGUAGE)
|
||||
|
||||
|
||||
class TestWikiParser(TestCase):
|
||||
fixtures = ['users.json']
|
||||
|
||||
|
@ -42,19 +48,19 @@ class TestWikiParser(TestCase):
|
|||
def test_image_params_page(self):
|
||||
"""_buildImageParams handles wiki pages."""
|
||||
items = ['page=Installing Firefox']
|
||||
params = _buildImageParams(items)
|
||||
params = _buildImageParamsDefault(items)
|
||||
eq_('/en-US/kb/installing-firefox', params['link'])
|
||||
|
||||
def test_image_params_link(self):
|
||||
"""_buildImageParams handles external links."""
|
||||
items = ['link=http://example.com']
|
||||
params = _buildImageParams(items)
|
||||
params = _buildImageParamsDefault(items)
|
||||
eq_('http://example.com', params['link'])
|
||||
|
||||
def test_image_params_page_link(self):
|
||||
"""_buildImageParams - wiki page overrides link."""
|
||||
items = ['page=Installing Firefox', 'link=http://example.com']
|
||||
params = _buildImageParams(items)
|
||||
params = _buildImageParamsDefault(items)
|
||||
eq_('/en-US/kb/installing-firefox', params['link'])
|
||||
|
||||
def test_image_params_align(self):
|
||||
|
@ -62,13 +68,13 @@ class TestWikiParser(TestCase):
|
|||
align_vals = ('none', 'left', 'center', 'right')
|
||||
for align in align_vals:
|
||||
items = ['align=' + align]
|
||||
params = _buildImageParams(items)
|
||||
params = _buildImageParamsDefault(items)
|
||||
eq_(align, params['align'])
|
||||
|
||||
def test_image_params_align_invalid(self):
|
||||
"""Align invalid options."""
|
||||
items = ['align=zzz']
|
||||
params = _buildImageParams(items)
|
||||
params = _buildImageParamsDefault(items)
|
||||
assert not 'align' in params, 'Align is present in params'
|
||||
|
||||
def test_image_params_valign(self):
|
||||
|
@ -77,38 +83,39 @@ class TestWikiParser(TestCase):
|
|||
'middle', 'bottom', 'text-bottom')
|
||||
for valign in valign_vals:
|
||||
items = ['valign=' + valign]
|
||||
params = _buildImageParams(items)
|
||||
params = _buildImageParamsDefault(items)
|
||||
eq_(valign, params['valign'])
|
||||
|
||||
def test_image_params_valign_invalid(self):
|
||||
"""Vertical align invalid options."""
|
||||
items = ['valign=zzz']
|
||||
params = _buildImageParams(items)
|
||||
params = _buildImageParamsDefault(items)
|
||||
assert not 'valign' in params, 'Vertical align is present in params'
|
||||
|
||||
def test_image_params_alt(self):
|
||||
"""Image alt override."""
|
||||
items = ['alt=some alternative text']
|
||||
params = _buildImageParams(items)
|
||||
params = _buildImageParamsDefault(items)
|
||||
eq_('some alternative text', params['alt'])
|
||||
|
||||
def test_image_params_frameless(self):
|
||||
"""Frameless image."""
|
||||
items = ['frameless']
|
||||
params = _buildImageParams(items)
|
||||
params = _buildImageParamsDefault(items)
|
||||
eq_(True, params['frameless'])
|
||||
|
||||
def test_image_params_width_height(self):
|
||||
"""Image width."""
|
||||
items = ['width=10', 'height=20']
|
||||
params = _buildImageParams(items)
|
||||
params = _buildImageParamsDefault(items)
|
||||
eq_('10', params['width'])
|
||||
eq_('20', params['height'])
|
||||
|
||||
def test_get_wiki_link(self):
|
||||
"""Wiki links are properly built for existing pages."""
|
||||
eq_('/en-US/kb/installing-firefox',
|
||||
_getWikiLink('Installing Firefox'))
|
||||
_getWikiLink('Installing Firefox',
|
||||
locale=settings.WIKI_DEFAULT_LANGUAGE))
|
||||
|
||||
def test_showfor(self):
|
||||
"""<showfor> tags should be escaped, not obeyed."""
|
||||
|
|
|
@ -2,13 +2,17 @@ from itertools import count
|
|||
import re
|
||||
from xml.sax.saxutils import quoteattr
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from html5lib import HTMLParser
|
||||
from html5lib.serializer.htmlserializer import HTMLSerializer
|
||||
from html5lib.treebuilders import getTreeBuilder
|
||||
from html5lib.treewalkers import getTreeWalker
|
||||
from lxml.etree import Element
|
||||
|
||||
from tower import ugettext_lazy as _lazy
|
||||
|
||||
from gallery.models import Video
|
||||
import sumo.parser
|
||||
from sumo.parser import ALLOWED_ATTRIBUTES
|
||||
|
||||
|
@ -18,51 +22,22 @@ BLOCK_LEVEL_ELEMENTS = ['table', 'blockquote', 'h1', 'h2', 'h3', 'h4', 'h5',
|
|||
'ol', 'center'] # from Parser.doBlockLevels
|
||||
|
||||
TEMPLATE_ARG_REGEX = re.compile('{{{([^{]+?)}}}')
|
||||
SOURCE_TEMPLATE = '<source src="%(src)s" type="video/%(type)s">'
|
||||
VIDEO_TEXT = _lazy('Watch a video of these instructions.')
|
||||
VIDEO_TEMPLATE = ('<div class="video">'
|
||||
'<a href="#">%(text)s</a>'
|
||||
'<div class="video-wrap">'
|
||||
'<video%(fallback)s height="%(height)s"'
|
||||
'width="%(width)s" controls="">'
|
||||
'%(sources)s'
|
||||
'</video>'
|
||||
'</div>'
|
||||
'</div>')
|
||||
|
||||
|
||||
def wiki_to_html(wiki_markup):
|
||||
def wiki_to_html(wiki_markup, locale=settings.WIKI_DEFAULT_LANGUAGE):
|
||||
"""Wiki Markup -> HTML with the wiki app's enhanced parser"""
|
||||
return WikiParser().parse(wiki_markup, show_toc=False)
|
||||
|
||||
|
||||
def _hook_include(parser, space, title):
|
||||
"""Returns the document's parsed content."""
|
||||
from wiki.models import Document
|
||||
try:
|
||||
return Document.objects.get(title=title).content_parsed
|
||||
except Document.DoesNotExist:
|
||||
return _lazy('The document "%s" does not exist.') % title
|
||||
|
||||
|
||||
# Wiki templates are documents that receive arguments.
|
||||
#
|
||||
# They can be useful when including similar content in multiple places,
|
||||
# with slight variations. For examples and details see:
|
||||
# http://www.mediawiki.org/wiki/Help:Templates
|
||||
#
|
||||
def _hook_template(parser, space, title):
|
||||
"""Handles Template:Template name, formatting the content using given
|
||||
args"""
|
||||
from wiki.models import Document
|
||||
params = title.split('|')
|
||||
short_title = params.pop(0)
|
||||
template_title = 'Template:' + short_title
|
||||
|
||||
try:
|
||||
t = Document.objects.get(title=template_title, is_template=True)
|
||||
except Document.DoesNotExist:
|
||||
return _lazy('The template "%s" does not exist.') % short_title
|
||||
|
||||
c = t.current_revision.content.rstrip()
|
||||
# Note: this completely ignores the allowed attributes passed to the
|
||||
# WikiParser.parse() method, and defaults to ALLOWED_ATTRIBUTES
|
||||
parsed = parser.parse(c, show_toc=False, attributes=ALLOWED_ATTRIBUTES)
|
||||
|
||||
if '\n' not in c:
|
||||
parsed = parsed.replace('<p>', '')
|
||||
parsed = parsed.replace('</p>', '')
|
||||
# Do some string formatting to replace parameters
|
||||
return _format_template_content(parsed, _build_template_params(params))
|
||||
return WikiParser().parse(wiki_markup, show_toc=False, locale=locale)
|
||||
|
||||
|
||||
def _format_template_content(content, params):
|
||||
|
@ -308,9 +283,12 @@ class WikiParser(sumo.parser.WikiParser):
|
|||
super(WikiParser, self).__init__(base_url)
|
||||
|
||||
# The wiki has additional hooks not used elsewhere
|
||||
self.registerInternalLinkHook('Include', _hook_include)
|
||||
self.registerInternalLinkHook('Template', _hook_template)
|
||||
self.registerInternalLinkHook('T', _hook_template)
|
||||
self.registerInternalLinkHook('Include', self._hook_include)
|
||||
self.registerInternalLinkHook('I', self._hook_include)
|
||||
self.registerInternalLinkHook('Template', self._hook_template)
|
||||
self.registerInternalLinkHook('T', self._hook_template)
|
||||
self.registerInternalLinkHook('Video', self._hook_video)
|
||||
self.registerInternalLinkHook('V', self._hook_video)
|
||||
|
||||
def parse(self, text, **kwargs):
|
||||
"""Wrap SUMO's parse() to support additional wiki-only features."""
|
||||
|
@ -333,3 +311,72 @@ class WikiParser(sumo.parser.WikiParser):
|
|||
for_parser.expand_fors()
|
||||
|
||||
return for_parser.to_unicode()
|
||||
|
||||
def _hook_include(self, parser, space, title):
|
||||
"""Returns the document's parsed content."""
|
||||
from wiki.models import Document
|
||||
try:
|
||||
return Document.objects.get(locale=self.locale,
|
||||
title=title).content_parsed
|
||||
except Document.DoesNotExist:
|
||||
return _lazy('The document "%s" does not exist.') % title
|
||||
|
||||
# Wiki templates are documents that receive arguments.
|
||||
#
|
||||
# They can be useful when including similar content in multiple places,
|
||||
# with slight variations. For examples and details see:
|
||||
# http://www.mediawiki.org/wiki/Help:Templates
|
||||
#
|
||||
def _hook_template(self, parser, space, title):
|
||||
"""Handles Template:Template name, formatting the content using given
|
||||
args"""
|
||||
from wiki.models import Document
|
||||
params = title.split('|')
|
||||
short_title = params.pop(0)
|
||||
template_title = 'Template:' + short_title
|
||||
|
||||
try:
|
||||
t = Document.objects.get(locale=self.locale, title=template_title,
|
||||
is_template=True)
|
||||
except Document.DoesNotExist:
|
||||
return _lazy('The template "%s" does not exist.') % short_title
|
||||
|
||||
c = t.current_revision.content.rstrip()
|
||||
# Note: this completely ignores the allowed attributes passed to the
|
||||
# WikiParser.parse() method, and defaults to ALLOWED_ATTRIBUTES
|
||||
parsed = parser.parse(c, show_toc=False, attributes=ALLOWED_ATTRIBUTES)
|
||||
|
||||
# Special case for inline templates
|
||||
if '\n' not in c:
|
||||
parsed = parsed.replace('<p>', '')
|
||||
parsed = parsed.replace('</p>', '')
|
||||
# Do some string formatting to replace parameters
|
||||
return _format_template_content(parsed, _build_template_params(params))
|
||||
|
||||
# Videos are objects that can have one or more files attached to them
|
||||
#
|
||||
# They are keyed by title in the syntax and the locale passed to the
|
||||
# parser.
|
||||
def _hook_video(self, parser, space, title):
|
||||
"""Handles [[Video:video title]] with locale from parser."""
|
||||
try:
|
||||
v = Video.objects.get(locale=self.locale, title=title)
|
||||
except Video.DoesNotExist:
|
||||
return _lazy('The video "%s" does not exist.') % title
|
||||
|
||||
sources = []
|
||||
if v.webm:
|
||||
sources.append(SOURCE_TEMPLATE % {'src': v.webm.url,
|
||||
'type': 'webm'})
|
||||
if v.ogv:
|
||||
sources.append(SOURCE_TEMPLATE % {'src': v.ogv.url,
|
||||
'type': 'ogg'})
|
||||
data_fallback = ''
|
||||
# Flash fallback
|
||||
if v.flv:
|
||||
data_fallback = ' data-fallback="' + v.flv.url + '"'
|
||||
return VIDEO_TEMPLATE % {'fallback': data_fallback,
|
||||
'sources': ''.join(sources),
|
||||
'text': unicode(VIDEO_TEXT),
|
||||
'height': settings.WIKI_VIDEO_HEIGHT,
|
||||
'width': settings.WIKI_VIDEO_WIDTH}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
from nose.tools import eq_
|
||||
from pyquery import PyQuery as pq
|
||||
|
||||
from gallery.models import Video
|
||||
from gallery.tests import video
|
||||
from sumo.tests import TestCase
|
||||
import sumo.tests.test_parser
|
||||
from wiki.parser import (WikiParser, ForParser, PATTERNS,
|
||||
_build_template_params as _btp,
|
||||
_format_template_content as _ftc, _key_split)
|
||||
from wiki.tests import doc_rev
|
||||
|
||||
|
||||
def doc_rev_parser(*args, **kwargs):
|
||||
|
@ -13,8 +16,9 @@ def doc_rev_parser(*args, **kwargs):
|
|||
**kwargs)
|
||||
|
||||
|
||||
def markup_helper(content, markup, title='Template:test'):
|
||||
p = doc_rev_parser(content, title)[2]
|
||||
def doc_parse_markup(content, markup, title='Template:test'):
|
||||
"""Create a doc with given content and parse given markup."""
|
||||
_, _, p = doc_rev_parser(content, title)
|
||||
doc = pq(p.parse(markup))
|
||||
return (doc, p)
|
||||
|
||||
|
@ -71,14 +75,14 @@ class SimpleSyntaxTestCase(TestCase):
|
|||
|
||||
def test_template_inline(self):
|
||||
"""Inline templates are not wrapped in <p>s"""
|
||||
doc, p = markup_helper('<span class="key">{{{1}}}</span>',
|
||||
'[[T:test|Cmd]] + [[T:test|Shift]]')
|
||||
doc, p = doc_parse_markup('<span class="key">{{{1}}}</span>',
|
||||
'[[T:test|Cmd]] + [[T:test|Shift]]')
|
||||
eq_(1, len(doc('p')))
|
||||
|
||||
def test_template_multiline(self):
|
||||
"""Multiline templates are wrapped in <p>s"""
|
||||
doc, p = markup_helper('<span class="key">\n{{{1}}}</span>',
|
||||
'[[T:test|Cmd]]')
|
||||
doc, p = doc_parse_markup('<span class="key">\n{{{1}}}</span>',
|
||||
'[[T:test|Cmd]]')
|
||||
eq_(3, len(doc('p')))
|
||||
|
||||
def test_key_split_callback(self):
|
||||
|
@ -135,7 +139,7 @@ class TestWikiTemplate(TestCase):
|
|||
|
||||
def test_template(self):
|
||||
"""Simple template markup."""
|
||||
doc = markup_helper('Test content', '[[Template:test]]')[0]
|
||||
doc, _ = doc_parse_markup('Test content', '[[Template:test]]')
|
||||
eq_('Test content', doc.text())
|
||||
|
||||
def test_template_does_not_exist(self):
|
||||
|
@ -144,33 +148,51 @@ class TestWikiTemplate(TestCase):
|
|||
doc = pq(p.parse('[[Template:test]]'))
|
||||
eq_('The template "test" does not exist.', doc.text())
|
||||
|
||||
def test_template_locale(self):
|
||||
"""Localized template is returned."""
|
||||
py_doc, p = doc_parse_markup('English content', '[[Template:test]]')
|
||||
d = doc_rev('French content')[0]
|
||||
d.title = 'Template:test'
|
||||
d.locale = 'fr'
|
||||
d.save()
|
||||
eq_('English content', py_doc.text())
|
||||
py_doc = pq(p.parse('[[T:test]]', locale='fr'))
|
||||
eq_('French content', py_doc.text())
|
||||
|
||||
def test_template_locale_not_exist(self):
|
||||
"""If localized template does not exist, say so."""
|
||||
_, p = doc_parse_markup('English content', '[[Template:test]]')
|
||||
doc = pq(p.parse('[[T:test]]', locale='fr'))
|
||||
eq_('The template "test" does not exist.', doc.text())
|
||||
|
||||
def test_template_anonymous_params(self):
|
||||
"""Template markup with anonymous parameters."""
|
||||
doc, p = markup_helper('{{{1}}}:{{{2}}}', '[[Template:test|one|two]]')
|
||||
doc, p = doc_parse_markup('{{{1}}}:{{{2}}}',
|
||||
'[[Template:test|one|two]]')
|
||||
eq_('one:two', doc.text())
|
||||
doc = pq(p.parse('[[T:test|two|one]]'))
|
||||
eq_('two:one', doc.text())
|
||||
|
||||
def test_template_named_params(self):
|
||||
"""Template markup with named parameters."""
|
||||
doc, p = markup_helper('{{{a}}}:{{{b}}}',
|
||||
'[[Template:test|a=one|b=two]]')
|
||||
doc, p = doc_parse_markup('{{{a}}}:{{{b}}}',
|
||||
'[[Template:test|a=one|b=two]]')
|
||||
eq_('one:two', doc.text())
|
||||
doc = pq(p.parse('[[T:test|a=two|b=one]]'))
|
||||
eq_('two:one', doc.text())
|
||||
|
||||
def test_template_numbered_params(self):
|
||||
"""Template markup with numbered parameters."""
|
||||
doc, p = markup_helper('{{{1}}}:{{{2}}}',
|
||||
'[[Template:test|2=one|1=two]]')
|
||||
doc, p = doc_parse_markup('{{{1}}}:{{{2}}}',
|
||||
'[[Template:test|2=one|1=two]]')
|
||||
eq_('two:one', doc.text())
|
||||
doc = pq(p.parse('[[T:test|2=two|1=one]]'))
|
||||
eq_('one:two', doc.text())
|
||||
|
||||
def test_template_wiki_markup(self):
|
||||
"""A template with wiki markup"""
|
||||
doc = markup_helper("{{{1}}}:{{{2}}}\n''wiki''\n'''markup'''",
|
||||
'[[Template:test|2=one|1=two]]')[0]
|
||||
doc, _ = doc_parse_markup("{{{1}}}:{{{2}}}\n''wiki''\n'''markup'''",
|
||||
'[[Template:test|2=one|1=two]]')
|
||||
|
||||
eq_('two:one', doc('p')[1].text.replace('\n', ''))
|
||||
eq_('wiki', doc('em')[0].text)
|
||||
|
@ -178,16 +200,16 @@ class TestWikiTemplate(TestCase):
|
|||
|
||||
def test_template_args_inline_wiki_markup(self):
|
||||
"""Args that contain inline wiki markup are parsed"""
|
||||
doc = markup_helper('{{{1}}}\n\n{{{2}}}',
|
||||
"[[Template:test|'''one'''|''two'']]")[0]
|
||||
doc, _ = doc_parse_markup('{{{1}}}\n\n{{{2}}}',
|
||||
"[[Template:test|'''one'''|''two'']]")
|
||||
|
||||
eq_("<p/><p><strong>one</strong></p><p><em>two</em></p><p/>",
|
||||
doc.html().replace('\n', ''))
|
||||
|
||||
def test_template_args_block_wiki_markup(self):
|
||||
"""Args that contain block level wiki markup aren't parsed"""
|
||||
doc = markup_helper('{{{1}}}\n\n{{{2}}}',
|
||||
"[[Template:test|* ordered|# list]]")[0]
|
||||
doc, _ = doc_parse_markup('{{{1}}}\n\n{{{2}}}',
|
||||
"[[Template:test|* ordered|# list]]")
|
||||
|
||||
eq_("<p/><p>* ordered</p><p># list</p><p/>",
|
||||
doc.html().replace('\n', ''))
|
||||
|
@ -232,16 +254,57 @@ class TestWikiInclude(TestCase):
|
|||
|
||||
def test_revision_include(self):
|
||||
"""Simple include markup."""
|
||||
p = doc_rev_parser('Test content', 'Test title')[2]
|
||||
_, _, p = doc_rev_parser('Test content', 'Test title')
|
||||
|
||||
# Existing title returns document's content
|
||||
doc = pq(p.parse('[[Include:Test title]]'))
|
||||
doc = pq(p.parse('[[I:Test title]]'))
|
||||
eq_('Test content', doc.text())
|
||||
|
||||
# Nonexisting title returns 'Document not found'
|
||||
doc = pq(p.parse('[[Include:Another title]]'))
|
||||
eq_('The document "Another title" does not exist.', doc.text())
|
||||
|
||||
def test_revision_include_locale(self):
|
||||
"""Include finds document in the correct locale."""
|
||||
_, _, p = doc_rev_parser('English content', 'Test title')
|
||||
# Parsing in English should find the French article
|
||||
doc = pq(p.parse('[[Include:Test title]]', locale='en-US'))
|
||||
eq_('English content', doc.text())
|
||||
# If the French article does not exist, notify
|
||||
doc = pq(p.parse('[[I:Test title]]', locale='fr'))
|
||||
eq_('The document "Test title" does not exist.', doc.text())
|
||||
# Create the French article, and test again
|
||||
d = doc_rev('French content')[0]
|
||||
d.title = 'Test title'
|
||||
d.locale = 'fr'
|
||||
d.save()
|
||||
# Parsing in French should find the French article
|
||||
doc = pq(p.parse('[[Include:Test title]]', locale='fr'))
|
||||
eq_('French content', doc.text())
|
||||
|
||||
|
||||
class TestWikiVideo(TestCase):
|
||||
"""Video hook."""
|
||||
fixtures = ['users.json']
|
||||
|
||||
def tearDown(self):
|
||||
Video.objects.all().delete()
|
||||
super(TestWikiVideo, self).tearDown()
|
||||
|
||||
def test_video(self):
|
||||
v = video()
|
||||
d, _, p = doc_rev_parser('[[V:Some title]]')
|
||||
doc = pq(d.html)
|
||||
eq_('video', doc('div.video').attr('class'))
|
||||
eq_(u'<source src="/media/uploads/gallery/videos/test.webm" '
|
||||
u'type="video/webm"><source src="/media/uploads/gallery/'
|
||||
u'videos/test.ogv" type="video/ogg"/></source>',
|
||||
doc('video').html())
|
||||
eq_(1, len(doc('video')))
|
||||
eq_(2, len(doc('source')))
|
||||
data_fallback = doc('video').attr('data-fallback')
|
||||
eq_(v.flv.url, data_fallback)
|
||||
|
||||
|
||||
def parsed_eq(want, to_parse):
|
||||
p = WikiParser()
|
||||
|
@ -323,6 +386,15 @@ class ForWikiTests(TestCase):
|
|||
parsed_eq('<p><span class="for">' + french + '</span></p>',
|
||||
'{for}' + french + '{/for}')
|
||||
|
||||
def test_boolean_attr(self):
|
||||
"""Make sure empty attributes don't raise exceptions."""
|
||||
parsed_eq('<p><video controls height="120">'
|
||||
'<source src="/some/path/file.ogv" type="video/ogv">'
|
||||
'</video></p>',
|
||||
'<p><video controls="" height="120">'
|
||||
'<source src="/some/path/file.ogv" type="video/ogv">'
|
||||
'</video></p>')
|
||||
|
||||
def test_big_swath(self):
|
||||
"""Enclose a big section containing many tags."""
|
||||
parsed_eq('<div class="for"><h1 id="w_h1">H1</h1>'
|
||||
|
|
|
@ -39,7 +39,7 @@ Settings
|
|||
Settings in ``settings.py`` can be overridden in a file named
|
||||
``settings_local.py`` in the same directory. ``settings_local.py`` should
|
||||
start with::
|
||||
|
||||
|
||||
from settings import *
|
||||
|
||||
and below that line, you can override the defaults.
|
||||
|
@ -86,7 +86,7 @@ Concat and Minify
|
|||
|
||||
When running with ``DEBUG=False``, Kitsune will try to use compressed
|
||||
JavaScript and CSS. To generate the compressed files, just run::
|
||||
|
||||
|
||||
./manage.py compress_assests
|
||||
|
||||
and new files will be created in the /media/ directory.
|
||||
|
@ -115,3 +115,6 @@ Here are the currently defined upload settings. All paths are relative to
|
|||
Maximum size for uploaded images (defaults to 1mb) or videos (16mb).
|
||||
``THUMBNAIL_PROGRESS_URL``
|
||||
URL to an image, used to indicate thumbnail generation is in progress.
|
||||
``WIKI_VIDEO_HEIGHT``, ``WIKI_VIDEO_WIDTH``
|
||||
Height and width set on the video tag for videos included in Knowledge
|
||||
Base documents.
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
.video-wrap {
|
||||
display: none;
|
||||
}
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* screencast.js
|
||||
* Scripts for Media, such as <video>
|
||||
*/
|
||||
|
||||
(function () {
|
||||
var VIDEO_ID_PREFIX = 'video-flash-', id_counter = 0,
|
||||
MEDIA_URL = '/media/' // same value as settings.py
|
||||
params = {allowfullscreen: 'true'},
|
||||
flashvars = {
|
||||
autoload: 1,
|
||||
showtime: 1,
|
||||
showvolume: 1
|
||||
};
|
||||
/*
|
||||
* Initializes flash fallback for a video object.
|
||||
*/
|
||||
function initVideoFallback($video) {
|
||||
if ($video[0].tagName !== 'VIDEO') return;
|
||||
|
||||
var formats = {ogg: false, webm: false}, i,
|
||||
width = Number($video.attr('width'))
|
||||
height = Number($video.attr('height')),
|
||||
// Build a unique ID for the object container
|
||||
unique_id = VIDEO_ID_PREFIX + id_counter;
|
||||
id_counter++;
|
||||
|
||||
$video.attr('id', unique_id);
|
||||
|
||||
// Check supported formats
|
||||
$('source', $video).each(function checkSourceFormats() {
|
||||
for (i in formats) {
|
||||
if ($(this).attr('type').indexOf(i) > -1) {
|
||||
formats[i] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (Modernizr.video && // can the browser play video?
|
||||
// do we have a webm it can play?
|
||||
(formats.webm && Modernizr.video.webm) ||
|
||||
// or do we have an ogg it can play?
|
||||
(formats.ogg && Modernizr.video.ogg)) {
|
||||
// good news everyone! No need to fall back!
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the video fallback URL
|
||||
flashvars.flv = $video.attr('data-fallback');
|
||||
|
||||
swfobject.embedSWF(
|
||||
MEDIA_URL + 'swf/screencast.swf', unique_id, width, height,
|
||||
'9.0.0', MEDIA_URL + '/media/swf/expressInstall.swf', flashvars,
|
||||
params);
|
||||
};
|
||||
|
||||
/*
|
||||
* Checks if fallback is necessary and sets objects in place
|
||||
* for the SWF player
|
||||
*/
|
||||
function initFallbackSupport() {
|
||||
$('.video a').click(function showhideVideo() {
|
||||
$video_wrap = $(this).parents('.video').find('.video-wrap');
|
||||
if ($video_wrap.is(':visible')) {
|
||||
$video_wrap = $video_wrap.slideUp();
|
||||
} else {
|
||||
$video_wrap = $video_wrap.slideDown();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
$('.video video').each(function initializeVideo(i) {
|
||||
initVideoFallback($(this));
|
||||
});
|
||||
};
|
||||
|
||||
$(document).ready(function () {
|
||||
initFallbackSupport();
|
||||
});
|
||||
}());
|
Двоичный файл не отображается.
Двоичный файл не отображается.
|
@ -0,0 +1,4 @@
|
|||
CREATE UNIQUE INDEX `gallery_image_locale_title`
|
||||
ON `gallery_image` (`locale`, `title`);
|
||||
CREATE UNIQUE INDEX `gallery_video_locale_title`
|
||||
ON `gallery_video` (`locale`, `title`);
|
|
@ -297,6 +297,7 @@ MINIFY_BUNDLES = {
|
|||
),
|
||||
'wiki': (
|
||||
'css/wiki.css',
|
||||
'css/screencast.css',
|
||||
),
|
||||
'gallery': (
|
||||
'css/gallery.css',
|
||||
|
@ -338,6 +339,8 @@ MINIFY_BUNDLES = {
|
|||
'js/libs/django/prepopulate.js',
|
||||
'js/libs/jquery.cookie.js',
|
||||
'js/browserdetect.js',
|
||||
'js/libs/swfobject.js',
|
||||
'js/screencast.js',
|
||||
'js/wiki.js',
|
||||
),
|
||||
'customercare': (
|
||||
|
@ -399,6 +402,8 @@ REGISTER_URL = '/tiki-register.php'
|
|||
WIKI_CREATE_URL = '/tiki-editpage.php?page=%s'
|
||||
WIKI_EDIT_URL = '/tiki-editpage.php?page=%s'
|
||||
WIKI_UPLOAD_URL = '/img/wiki_up/'
|
||||
WIKI_VIDEO_WIDTH = 640
|
||||
WIKI_VIDEO_HEIGHT = 480
|
||||
|
||||
IMAGE_MAX_FILESIZE = 1048576 # 1 megabyte, in bytes
|
||||
THUMBNAIL_SIZE = 120 # Thumbnail size, in pixels
|
||||
|
|
Загрузка…
Ссылка в новой задаче