1
# -*- coding: utf-8 -*-
3
MoinMoin - SlideShow action
5
Treat a wiki page as a set of slides. Displays a single slide at a
6
time, along with a navigation aid.
8
@copyright: 2005 Jim Clark,
10
2008 MoinMoin:ThomasWaldmann,
11
2009 MoinMoin:ReimarBauer
12
@license: GNU GPL, see COPYING for details.
17
from MoinMoin import config, wikiutil, i18n, error
18
from MoinMoin.Page import Page
20
Dependencies = ['language']
23
class Error(error.Error):
24
""" Raised for errors in this module """
26
# This could be delivered in a separate plugin, but
27
# it is more convenient to have everything in one module.
29
class WikiSlideParser(object):
30
""" Parse slides using wiki format
33
for title, start, end in WikiSlideParser().parse(text):
34
slides.append((title, start, end))
36
If you want to override this parser, you can add 'slideshow_wiki'
37
parser plugin, that provides a SlideParser class.
39
_heading_pattern = re.compile(r"""
40
# TODO: check, mhz found bug here
41
(?P<skip>{{{(?:.*\n)+?}}}) |
42
# Match headings level 1
43
(?P<heading>^=\s(?P<text>.*)\s=$\n?)
44
""", re.MULTILINE | re.UNICODE | re.VERBOSE)
46
def parse(self, text):
47
""" Parse slide data in text
49
Wiki slides are defined by the headings, ignoring the text
50
before the first heading. This parser finds all headings,
51
skipping headings in preformatted code areas.
53
Returns an iterator over slide data. For each slide, a tuple
54
(title, bodyStart, bodyEnd) is returned. bodyStart and bodyEnd
55
are indexes into text.
57
matches = [match for match in self._heading_pattern.finditer(text)
58
if match.start('skip') == -1]
60
for i in range(len(matches)):
61
title = matches[i].group('text').strip()
62
bodyStart = matches[i].end('heading')
64
bodyEnd = matches[i + 1].start('heading')
67
yield title, bodyStart, bodyEnd
70
class SlidePage(Page):
71
""" A wiki page containing a slideshow
73
The slides are parsed according to the page #format xxx processing
74
instruction. This module implements only a wiki format slide parser.
76
To support other formats like rst, add a 'slideshow_rst' parser
77
plugin, providing SlideParser class, implementing the SlideParser
78
protocol. See WikiSlideParser for details.
80
defaultFormat = 'wiki'
81
defaultParser = WikiSlideParser
83
def __init__(self, request, name, **keywords):
84
Page.__init__(self, request, name, **keywords)
85
self._slideIndex = None
89
""" Return the slide count """
90
return len(self.slideIndex())
95
# Slide accessing methods map 1 based slides to 0 based index.
97
def titleAt(self, number):
98
""" Return the title of slide number """
100
return self.slideIndex()[number - 1][0]
104
def bodyAt(self, number):
105
""" Return the body of slide number """
107
start, end = self.slideIndex()[number - 1][1:]
108
return self.get_raw_body()[start:end]
110
return self.get_raw_body()
112
# Private ----------------------------------------------------------------
114
def slideIndex(self):
115
if self._slideIndex is None:
117
return self._slideIndex
119
def parseSlides(self):
120
body = self.get_raw_body()
121
self._slideIndex = []
122
parser = self.createSlideParser()
123
for title, bodyStart, bodyEnd in parser.parse(body):
124
self._slideIndex.append((title, bodyStart, bodyEnd))
126
def createSlideParser(self):
127
""" Import plugin and return parser class
129
If plugin is not found, and format is not defaultFormat, raise an error.
130
For defaultFormat, use builtin defaultParser in this module.
132
format = self.pi['format']
133
plugin = 'slideshow_' + format
135
Parser = wikiutil.importPlugin(self.request.cfg, 'parser', plugin, 'SlideParser')
136
except wikiutil.PluginMissingError:
137
if format != self.defaultFormat:
138
raise Error('SlideShow does not support %s format.' % format)
139
Parser = self.defaultParser
143
class SlideshowAction:
148
def __init__(self, request, pagename, template):
149
self.request = request
150
self.page = SlidePage(self.request, pagename)
151
self.template = template
153
# Cache values used many times
154
self.pageURL = self.page.url(request)
157
_ = self.request.getText
159
self.setSlideNumber()
160
language = self.page.pi['language']
161
self.request.content_type = "text/html; charset=%s" % (config.charset, )
162
self.request.setContentLanguage(language)
163
self.request.write(self.template % self)
165
self.request.theme.add_msg(wikiutil.escape(unicode(err)), "error")
166
self.page.send_page()
168
# Private ----------------------------------------------------------------
170
def setSlideNumber(self):
172
slideNumber = int(self.request.values.get('n', 1))
173
if not 1 <= slideNumber <= len(self.page):
177
self.slideNumber = slideNumber
179
def createParser(self, format, text):
181
format = 'text_moin_wiki'
183
Parser = wikiutil.importPlugin(self.request.cfg, 'parser', format,
185
except wikiutil.PluginMissingError:
186
from MoinMoin.parser.text import Parser
187
parser = Parser(text, self.request)
190
def createFormatter(self, format):
192
Formatter = wikiutil.importPlugin(self.request.cfg, 'formatter',
194
except wikiutil.PluginMissingError:
195
from MoinMoin.formatter.text_plain import Formatter
197
formatter = Formatter(self.request)
198
self.request.formatter = formatter
199
formatter.page = self.page
202
def languageAttributes(self, lang):
203
return ' lang="%s" dir="%s"' % (lang, i18n.getDirection(lang))
205
def linkToPage(self, text, query='', **attributes):
206
""" Return a link to current page """
208
url = '%s?%s' % (self.pageURL, query)
211
return self.formatLink(url, text, **attributes)
213
def linkToSlide(self, number, text, **attributes):
214
""" Return a link to current page """
215
if number == self.slideNumber:
216
return self.disabledLink(text, **attributes)
218
url = '%s?action=%s&n=%s' % (self.pageURL, self.name, number)
219
return self.formatLink(url, text, **attributes)
221
def disabledLink(self, text, **attributes):
222
return '<span%s>%s</span>' % (self.formatAttributes(attributes), text)
224
def formatLink(self, url, text, **attributes):
225
return '<a href="%(url)s"%(attributes)s>%(text)s</a>' % {
226
'url': wikiutil.escape(url),
227
'attributes': self.formatAttributes(attributes),
228
'text': wikiutil.escape(text),
231
def formatAttributes(self, attributes):
232
""" Return formatted attributes string """
233
formattedPairs = [' %s="%s"' % (k, v) for k, v in attributes.items()]
234
return ''.join(formattedPairs)
236
def adaptToLanguage(self, direction):
237
# In RTL, directional items should be switched
238
if i18n.getDirection(self.request.lang) == 'rtl':
242
def forwardIcon(self, forward=True):
243
return [u'\u2190', u'\u2192'][self.adaptToLanguage(forward)]
246
return self.forwardIcon(False)
248
# Key codes constants
252
def slideLinksRange(self):
253
""" Return range of slides to display, current centered """
254
other = self.maxSlideLinks - 1 # other slides except current
255
first, last = self.first_slide(), self.last_slide()
256
start = max(first, self.slideNumber - other / 2)
257
end = min(start + other, last)
258
start = max(first, end - other)
259
return range(start, end + 1)
261
def first_slide(self):
264
def next_slide(self):
265
return min(self.slideNumber + 1, self.last_slide())
267
def previous_slide(self):
268
return max(self.slideNumber - 1, self.first_slide())
270
def last_slide(self):
271
return max(len(self.page), 1)
273
# Replacing methods ------------------------------------------------------
275
def __getitem__(self, name):
276
item = getattr(self, 'item_' + name)
282
def item_language_attribtues(self):
283
return self.languageAttributes(self.request.content_lang)
285
def item_theme_url(self):
286
return '%s/%s' % (self.request.cfg.url_prefix_static, self.request.theme.name)
288
item_action_name = name
290
def item_title(self):
291
return wikiutil.escape(self.page.page_name)
293
def item_slide_title(self):
294
return wikiutil.escape(self.page.titleAt(self.slideNumber))
296
def item_slide_body(self):
297
text = self.page.bodyAt(self.slideNumber)
298
format = self.page.pi['format']
299
parser = self.createParser(format, text)
300
formatter = self.createFormatter('text_html')
301
return self.request.redirectedOutput(parser.format, formatter)
303
def item_navigation_language_attributes(self):
304
return self.languageAttributes(self.request.lang)
306
def item_navigation_edit(self):
307
_ = self.request.getText
309
if self.request.user.may.write(self.page.page_name):
310
return self.linkToPage(text, 'action=edit', title=_('Edit slide show'))
311
return self.disabledLink(text, title=_("You are not allowed to edit this page."))
313
def item_navigation_quit(self):
314
_ = self.request.getText
315
return self.linkToPage(_('Quit'), title=_('Quit slide show'))
317
def item_navigation_start(self):
318
_ = self.request.getText
319
number = self.first_slide()
320
return self.linkToSlide(number, '|', title=_('Show first slide (up arrow)'))
322
def item_navigation_end(self):
323
_ = self.request.getText
324
number = self.last_slide()
325
return self.linkToSlide(number, '|', title=_('Show last slide (down arrow)'))
327
def item_navigation_back(self):
328
_ = self.request.getText
329
number = self.previous_slide()
330
return self.linkToSlide(number, text=self.backIcon(), title=_('Show previous slide (left arrow)'))
332
def item_navigation_forward(self):
333
_ = self.request.getText
334
number = self.next_slide()
335
return self.linkToSlide(number, self.forwardIcon(), title=_('Show next slide (right arrow)'))
337
def item_forward_key(self, forward=True):
338
return (self.leftArrowKey, self.rightArrowKey)[self.adaptToLanguage(forward)]
340
def item_back_key(self):
341
return self.item_forward_key(False)
343
def item_navigation_slides(self):
345
for i in self.slideLinksRange():
346
attributes = {'title': self.page.titleAt(i)}
347
if i == self.slideNumber:
348
attributes = {'class': 'current'}
349
items.append(self.linkToSlide(i, i, **attributes))
350
items = ['<li>%s</li>' % item for item in items]
351
return '\n'.join(items)
353
def item_slide_link_base(self):
354
return wikiutil.escape(self.pageURL) + '?action=%s&n=' % self.name
356
item_next_slide = next_slide
357
item_previous_slide = previous_slide
358
item_first_slide = first_slide
359
item_last_slide = last_slide
362
return wikiutil.escape(self.request.getPragma('date', defval=''))
364
def item_author(self):
365
return wikiutil.escape(self.request.getPragma('author', defval=''))
367
def item_counter(self):
368
return "%d|%d" % (self.slideNumber, self.last_slide())
370
# This is quite stupid template, but it cleans most of the code from
371
# html. With smarter templates, there will be no html in the action code.
373
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
374
"http://www.w3.org/TR/html4/strict.dtd">
376
<html%(language_attribtues)s>
378
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
379
<meta name="robots" content="noindex,nofollow">
380
<title>%(title)s</title>
382
<script type="text/javascript">
384
// Support multiple browsers stupidity
390
// Standards compliant
391
if (e.altKey || e.ctrlKey) {
400
window.location="%(slide_link_base)s" + slide;
403
function onkeydown(e) {
405
// presenter maybe rather wants to use up/down for scrolling content!
406
// case 38: go('%(first_slide)s'); break; // up arrow
407
// case 40: go('%(last_slide)s'); break; // down arrow
408
case %(forward_key)s: go('%(next_slide)s'); break;
409
case %(back_key)s: go('%(previous_slide)s'); break;
410
default: return true; // pass event to browser
412
// Return false to consume the event
416
document.onkeydown = onkeydown
419
<link rel="stylesheet" type="text/css" charset="utf-8" media="all"
420
href="%(theme_url)s/css/%(action_name)s.css">
424
<h1>%(slide_title)s</h1>
430
<div id="navigation"%(navigation_language_attributes)s>
432
<li>%(navigation_edit)s</li>
433
<li>%(navigation_quit)s</li>
434
<li>%(navigation_start)s</li>
435
<li>%(navigation_back)s</li>
436
%(navigation_slides)s
437
<li>%(navigation_forward)s</li>
438
<li>%(navigation_end)s</li>
442
<ul id="date">%(date)s</ul>
443
<ul id="author">%(author)s</ul>
444
<ul id="counter">%(counter)s</ul>
447
<p><a href="http://validator.w3.org/check?uri=referer">
456
def execute(pagename, request):
457
""" Glue to current plugin system """
458
SlideshowAction(request, pagename, template).execute()