[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:
Paul Craciunoiu 2010-09-29 20:04:54 -07:00
Родитель a8816900ee
Коммит e664ef5c3d
13 изменённых файлов: 375 добавлений и 140 удалений

Просмотреть файл

@ -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.

3
media/css/screencast.css Normal file
Просмотреть файл

@ -0,0 +1,3 @@
.video-wrap {
display: none;
}

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

79
media/js/screencast.js Executable file
Просмотреть файл

@ -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();
});
}());

Двоичные данные
media/swf/expressInstall.swf Executable file

Двоичный файл не отображается.

Двоичные данные
media/swf/screencast.swf Executable file

Двоичный файл не отображается.

Просмотреть файл

@ -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