Attachment 'Blog_MoinMoin1.5.py'
Download 1 """
2 MoinMoin - Blog macro
3
4 (c) 2003 Mark Proctor, athico.com
5
6 (c) 2006 Carsten Grohmann
7
8 Overview
9 ========
10 This is a simply Blog macro that utilises a javascript calendar and the
11 Include macro to hack together a pseudo bwiki (blog/wiki).
12 The calendar is used to choose the entries to show, the number of visible
13 entries is controlled by the select control "max entries".
14 The button to the left of "max entries" allows you to toggle between the
15 two modes "Show All" and "Show Published".
16 - "Show Published" only shows those days that contain entries up to the
17 given "max entries", from the chosen calendar date.
18 - "Show All" Show all the dates, previous to the chosen calendar date,
19 up to a maximum of "max entries".
20
21 This is the mode you will need to use to enter new blog entries
22
23 Dependencies
24 ============
25 - C{Include}
26
27 To install
28 ==========
29 - Save this macro in your macros directory
30
31 To Use
32 ======
33 - C{<date>} : in the format of yyyy-mm-dd
34 - C{<showAll>} : 1 or 0, where 1 shows all and 0 shows published
35 - C{<entries>} : the maximum visible number of entries
36 - C{<maxEntriesInOptionList>} : the maximum value that C{<entries>} can be, this is used to restrict the web gui
37 - C{<startDay>} : The start Day for the calendar
38 - values : C{Su}, C{Mo}, C{Tu}, C{We}, C{Th}, C{Fr}, C{Sa}
39
40 Defaults Values
41 ===============
42 - C{<date>} : C{today}
43 - C{<showAll>} : C{0}
44 - C{<entries>} : C{5}
45 - C{<maxEntriesInOptionList>} : C{20}
46 - C{<startDay>} : C{Mo}
47
48 Example::
49 [[Blog[<date>, <showAll>, <entries>, <maxEntriesInOptionList>]]
50 [[Blog(, 1, 7)]] - Shows all days, up to 7 days, from todays date.
51 [[Blog(2003-05-23, 0, 5)]] - Shows upto 5 published entries from the given date.
52 [[Blog( , , , 10)]] - Shows upto 5(default) published(default) entries from todays date(default), but does not allow the user to speficy max entries to be more than 10.
53 [[Blog( , , , , We)]] - Shows upto 5(default) published(default) entries from todays date(default), maxEntriesInOptionList(20) with start calendar day Wednesday.
54 [[Blog(2003-05-23, 0, 5, ,Sa)]] - Shows upto 5 published entries from the given date, with start calendar day Saturday
55
56 $Id: Blog.py,v 1.4 2006/06/15 17:39:04 carsten Exp $
57
58 @license: Licensed under GNU GPL - see COPYING for details.
59 @version: 1.1
60 @var re_entries: Precompiled regular expression to match the entries option
61 @var re_date: Precompiled regular expression to match the date option
62 """
63
64 from MoinMoin.Page import Page
65 import datetime, re
66 import MoinMoin.macro.Include
67
68 Dependencies = []
69
70 # compile regular expressions once at start time
71 re_entries=re.compile(r'(?P<entries>\d+)')
72 re_date=re.compile(r'(?P<year>\d\d\d\d)-(?P<month>\d?\d)-(?P<day>\d?\d)')
73
74 class Blog:
75 """
76 This macro provides an html based blog for MoinMoin.
77
78 Blog entries are per day possible and named like the day
79
80 @cvar calendarHTML: HTML code of the calendar part
81 @cvar cssStyle: The CSS declaration
82 @cvar errorMessage: Preformatted html error message
83 @cvar headingLevel: The level of depth of the heading of missing pages
84 @cvar inputHeader: Header of the html imput form
85 @cvar javaScriptFunctions: Set of java script function to have a
86 more comfortable use of the blog calendar
87 @cvar oneDay: Timedelta object with one day length
88
89 @ivar macro: Reference to the macro instance
90 @ivar maxEntriesInOptionList: Maximum numbers of entries in the option list
91 @ivar re_blogEntries: Page specific regular expression to catch all blog
92 entries (subpages)
93 @ivar startDay: First day of the week; valid values are
94 "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"
95 @ivar showAll: Integer flag to show all entries or the existing only
96 @ivar text: The macro arguments
97 @ivar thisPageName: The of the current page
98 """
99
100 calendarHTML = """
101 <TABLE CELLPADDING=0 CELLSPACING=0 BORDER=0 >
102 <TR><TD>
103 <CENTER>
104 <FORM NAME="calControl" onsubmit="return false;" >
105 <SELECT class="calendarButton" NAME="month" onchange="selectDate()">
106 <OPTION>January</OPTION>
107 <OPTION>February</OPTION>
108 <OPTION>March</OPTION>
109 <OPTION>April</OPTION>
110 <OPTION>May</OPTION>
111 <OPTION>June</OPTION>
112 <OPTION>July</OPTION>
113 <OPTION>August</OPTION>
114 <OPTION>September</OPTION>
115 <OPTION>October</OPTION>
116 <OPTION>November</OPTION>
117 <OPTION>December</OPTION>
118 </SELECT>
119 <INPUT NAME="year" class="calendarButton" TYPE=TEXT SIZE=4 MAXLENGTH=4>
120 <INPUT TYPE="button" class="calendarButton" NAME="Go" value="Update Year" onClick="selectDate()">
121 </FORM>
122 </CENTER>
123 </TD></TR>
124 <TR><TD id="calendar" align="center" onsubmit="return false;" ></TD></TR>
125 <TR><TD>
126 <CENTER>
127 <FORM NAME="calButtons">
128 <INPUT class='calendarButton' TYPE=BUTTON NAME="previousYear" VALUE=" << " onClick="setPreviousYear()">
129 <INPUT class='calendarButton' TYPE=BUTTON NAME="previousYear" VALUE=" < " onClick="setPreviousMonth()">
130 <INPUT class='calendarButton' TYPE=BUTTON NAME="previousYear" VALUE="Today" onClick="setToday()">
131 <INPUT class='calendarButton' TYPE=BUTTON NAME="previousYear" VALUE=" > " onClick="setNextMonth()">
132 <INPUT class='calendarButton' TYPE=BUTTON NAME="previousYear" VALUE=" >> " onClick="setNextYear()">
133 </FORM>
134 </CENTER>
135 </TD></TR>
136 </TABLE>
137 <script>
138 onload=function() {
139 var date = global.date.split("-");
140 document.calControl.month.selectedIndex = date[1]-1;
141 document.calControl.year.value = date[0];
142 makeCalendar();
143 updateCalendar(date[1]-1, date[0]);
144 };
145 </script>
146 """
147
148 cssStyle = """
149 <style type="text/css">
150 .calendarButton {
151 font-size:10;
152 cursor:pointer;
153 cursor:hand;
154 }
155
156 .calendarHeader {
157 background-color:#C0DED1;
158 font-size:12;
159 text-decoration:none;
160 cursor:pointer;
161 cursor:hand;
162 }
163
164 .calendarValue {
165 background-color:#FDFAD1;
166 font-size:10;
167 font-color:black;
168 cursor:pointer;
169 cursor:hand;
170 }
171
172 .calendarValueSelected {
173 background-color:#FD0000;
174 font-size:10;
175 font-color:black;
176 cursor:pointer;
177 cursor:hand;
178 }
179 </style>
180 """
181
182 errorMessage = '<p><strong class="error">%s</strong></p>'
183
184 headingLevel = 1
185
186 inputHeader = """
187 <input class='calendarButton' type=button value='%s' onclick='global.showAll=%s;updateBlog();'>
188 max entries:
189 <select class='calendarButton'
190 onchange='if (this.value&&(this.value != "")) {global.entries=this.value; updateBlog();}'>
191 """
192
193 javaScriptFunctions = """
194 <script>
195
196 var global = {};
197 global.page = "%s";
198 global.date = "%s";
199 global.entries = "%s";
200 global.showAll = "%s";
201
202 global.daysLookup = {"Su":0, "Mo":1, "Tu":2, "We":3, "Th":4, "Fr":5, "Sa":6};
203 global.days = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
204 global.startDay = "%s";
205
206 function gotoDate() {
207 if (!this.date) return;
208 global.date = this.date;
209 updateBlog();
210 }
211
212 function updateBlog() {
213 window.location.href = "/"+global.page+"?date="+global.date+"&entries="+global.entries+"&showAll="+global.showAll
214 }
215
216 function setToday() {
217 var now = new Date();
218 var day = now.getDate();
219 var month = now.getMonth();
220 var year = now.getYear();
221 if (year < 2000) // Y2K Fix, Isaac Powell
222 year = year + 1900; // http://onyx.idbsu.edu/~ipowell
223 this.focusDay = day;
224 document.calControl.month.selectedIndex = month;
225 document.calControl.year.value = year;
226 updateCalendar(month, year);
227 }
228
229 function isFourDigitYear(year) {
230 if (year.length != 4) {
231 alert ("Sorry, the year must be four-digits in length.");
232 document.calControl.year.select();
233 document.calControl.year.focus();
234 return false;
235 } else {
236 return true;
237 }
238 }
239
240 function selectDate() {
241 var year = document.calControl.year.value;
242 if (isFourDigitYear(year)) {
243 var day = 0;
244 var month = document.calControl.month.selectedIndex;
245 updateCalendar(month, year);
246 }
247 }
248
249 function setPreviousYear() {
250 var year = document.calControl.year.value;
251 if (isFourDigitYear(year)) {
252 var day = 0;
253 var month = document.calControl.month.selectedIndex;
254 year--;
255 document.calControl.year.value = year;
256 updateCalendar(month, year);
257 }
258 }
259 function setPreviousMonth() {
260 var year = document.calControl.year.value;
261 if (isFourDigitYear(year)) {
262 var day = 0;
263 var month = document.calControl.month.selectedIndex;
264 if (month == 0) {
265 month = 11;
266 if (year > 1000) {
267 year--;
268 document.calControl.year.value = year;
269 }
270 } else {
271 month--;
272 }
273 document.calControl.month.selectedIndex = month;
274 updateCalendar(month, year);
275 }
276 }
277 function setNextMonth() {
278 var year = document.calControl.year.value;
279 if (isFourDigitYear(year)) {
280 var day = 0;
281 var month = document.calControl.month.selectedIndex;
282 if (month == 11) {
283 month = 0;
284 year++;
285 document.calControl.year.value = year;
286 } else {
287 month++;
288 }
289 document.calControl.month.selectedIndex = month;
290 updateCalendar(month, year);
291 }
292 }
293 function setNextYear() {
294 var year = document.calControl.year.value;
295 if (isFourDigitYear(year)) {
296 var day = 0;
297 var month = document.calControl.month.selectedIndex;
298 year++;
299 document.calControl.year.value = year;
300 updateCalendar(month, year);
301 }
302 }
303
304 function makeCalendar() {
305 var cal = document.getElementById("calendarTbody");
306 if (cal) return;
307 var i;
308 var table = document.createElement("table");
309 table.style.cssText = "border-left:1px solid black; border-top:1px solid black";
310 table.cellSpacing = 0;
311 table.cellPadding = 2;
312 var thead = document.createElement("thead");
313
314 var cell;
315 var row = document.createElement("tr");
316
317 var titleDays = [];
318 var startDay = global.daysLookup[global.startDay];
319
320 for (i=startDay;i<7;i++) {
321 titleDays.push(global.days[i]);
322 }
323
324 for (i=0;i<startDay;i++) {
325 titleDays.push(global.days[i]);
326 }
327
328 for (i=0;i<titleDays.length;i++) {
329 cell = document.createElement("td");
330 cell.style.cssText = "border-right:1px solid black; border-bottom:1px solid black";
331 cell.className = "calendarHeader";
332 cell.innerHTML = titleDays[i];
333 row.appendChild(cell);
334 }
335 thead.appendChild(row);
336 table.appendChild(thead);
337
338 var tbody = document.createElement("tbody");
339 tbody.id = "calendarTbody";
340 row = document.createElement("tr");
341 for (i=0; i<42; i++) {
342 if ( i%%7 == 0 ) { //start new line
343 tbody.appendChild(row);
344 row = document.createElement("tr");
345 }
346 cell = document.createElement("td");
347 cell.className = "calendarValue";
348 cell.style.cssText = "border-right:1px solid black; border-bottom:1px solid black";
349 cell.innerHTML = " ";
350 cell.date = null;
351 cell.onclick = gotoDate
352 row.appendChild(cell);
353 }
354 tbody.appendChild(row);
355 table.appendChild(tbody);
356
357 var calendar = document.getElementById("calendar");
358 calendar.appendChild(table);
359 }
360
361 function updateCalendar(month, year) {
362 month = parseInt(month);
363 year = parseInt(year);
364 var i = 0;
365 var days = getDaysInMonth(month+1,year);
366 var startDay = global.daysLookup[global.startDay];
367 var firstOfMonth = new Date (year, month, 1).getDay();
368 var startingPos = (firstOfMonth >= startDay) ? firstOfMonth - startDay : 7 - (startDay - firstOfMonth);
369 days += startingPos;
370
371 var cal = document.getElementById("calendarTbody");
372
373 var cells = cal.getElementsByTagName("td");
374 var cell;
375 for (i = 0; i < startingPos; i++) {
376 cell = cells[i];
377 cell.innerHTML = " ";
378 cell.date = null;
379 cell.className = "calendarValue";
380 }
381
382 var value;
383 month++;
384 if (month<10) month = "0" + month;
385 date = year+"-"+month+"-";
386 for (i = startingPos; i < days; i++) {
387 cell = cells[i];
388 value = "";
389 value = i-startingPos+1;
390 if (value < 10) value = "0" + value;
391 cell.date = year+'-'+(month)+'-'+value;
392 cell.innerHTML = value;
393 if (global.date != date+value) cell.className = "calendarValue";
394 else cell.className = "calendarValueSelected";
395 }
396
397 for (i = days; i < 42; i++) {
398 cell = cells[i];
399 cell.date = null;
400 cell.className = "calendarValue";
401 cell.innerHTML = " ";
402 }
403 }
404
405 function getDaysInMonth(month,year) {
406 var days;
407 if (month==1 || month==3 || month==5 || month==7 || month==8 || month==10 || month==12) days=31;
408 else if (month==4 || month==6 || month==9 || month==11) days=30;
409 else if (month==2) {
410 if (isLeapYear(year)) { days=29; }
411 else { days=28; }
412 }
413 return (days);
414 }
415
416 function isLeapYear (Year) {
417 if (((Year %% 4)==0) && ((Year %% 100)!=0) || ((Year %% 400)==0)) {
418 return (true);
419 } else { return (false); }
420 }
421 </script>
422 """
423
424 oneDay = datetime.timedelta(days = 1)
425
426 def __init__(self, macro, text):
427 """
428 Constructor to initialise this class with default values
429
430 @param macro: Instance of the class Macro
431 @param text: The macro arguments
432 """
433 self.macro = macro
434 self.text = text
435 self.thisPageName = self.macro.formatter.page.page_name
436
437 # set default values
438 self.maxEntriesInOptionList = 20
439 self.startDay = "Mo"
440 self.showAll = "0"
441
442 # compile regular expression to catch own sub pages
443 self.re_blogEntries = re.compile(
444 r'^%s/' # parent page
445 r'BlogEntry-'
446 r'(?P<year>\d{4,4})-(?P<month>\d{2,2})-(?P<day>\d{2,2})' # date of the entry
447 % self.thisPageName ).match
448
449 def dispatch(self):
450 """
451 Main function
452
453 Process all stuff and format the content
454 """
455
456 #get incoming macro args, else set to []
457 if self.text:
458 args = self.text.split(",")
459 else:
460 args = []
461
462 #remove all leading and trailing spaces
463 args = map(lambda line: line.strip(), args)
464
465 #set date
466 if self.macro.form.has_key('date'):
467 date = self.macro.form['date'][0]
468 elif (len(args) > 0) and (args[0]):
469 date = args[0]
470 else:
471 date = ""
472
473 #set showAll
474 if self.macro.form.has_key('showAll'):
475 self.showAll = self.macro.form['showAll'][0]
476 elif (len(args) > 1) and (args[1]):
477 self.showAll = args[1]
478
479 #set entries
480 if self.macro.form.has_key('entries'):
481 self.entries = self.macro.form['entries'][0]
482 elif (len(args) > 2) and (args[2]):
483 self.entries = args[2]
484 else:
485 self.entries = None
486
487 #set max entries
488 if (len(args) > 3) and (args[3]):
489 self.maxEntriesInOptionList = int(args[3])
490 if self.maxEntriesInOptionList < 0:
491 self.maxEntriesInOptionList = 20
492
493 #set start day
494 if (len(args) > 4) and (args[4]):
495 self.startDay = args[4]
496
497 #set the number of visible entries
498 if self.entries:
499 args = re_entries.match(self.entries)
500 if not args:
501 return (self.errorMessage %('Invalid entries "%s"!')) % (self.macro.form['beforeDate'][0])
502 self.entries = int(self.macro.form['entries'][0])
503 if self.entries > self.maxEntriesInOptionList:
504 self.entries = self.maxEntriesInOptionList
505 if self.entries < 0:
506 self.entries = 5
507 else:
508 self.entries = 5
509
510 # get the date
511 if not date == "":
512 args = re_date.match(date)
513 if not args:
514 return (self.errorMessage %('Invalid date "%s"!')) % (self.macro.form['date'][0])
515 try:
516 self.blogDate = datetime.date(
517 int(args.group('year')),
518 int(args.group('month')),
519 int(args.group('day'))
520 )
521 except ValueError:
522 self.blogDate = datetime.date.today()
523 else:
524 self.blogDate = datetime.date.today()
525
526 content = self.getCSSStyle()
527 content += self.getJavaScript()
528 content += self.getCalendar()
529
530 # get the entries to display
531 if (self.showAll == '1'):
532 content += self.getShowAll()
533 else:
534 content += self.getShowEntered()
535
536 return content
537
538 def getCalendar(self):
539 """"
540 Returns the calendar HTML block
541
542 @see: L{self.calendarHTML}
543 """
544 return self.calendarHTML
545
546 def getCSSStyle(self):
547 """
548 Returns the CSS declaration
549
550 @see: L{self.cssStyle}
551 """
552 return self.cssStyle
553
554 def getJavaScript(self):
555 """
556 Returns the java script code of all calendar functions
557
558 @see: L{self.javaScriptFunctions}
559 """
560
561 return self.javaScriptFunctions % (
562 self.thisPageName,
563 "%d-%02d-%02d" % (
564 self.blogDate.year,
565 self.blogDate.month,
566 self.blogDate.day
567 ),
568 self.entries,
569 self.showAll,
570 self.startDay)
571
572 def getShowAll(self):
573 """
574 Shows the existing entries and link to non-existing pages
575 """
576
577 entryDate = self.blogDate
578 formatter = self.macro.formatter
579
580 content = []
581 content.append(self.inputHeader % ("Show Published", "0"))
582 content.append(self._createMaxEntriesOptionList())
583
584 for i in range(self.entries):
585 entryTitle = '%d-%02d-%02d' % (
586 entryDate.year,
587 entryDate.month,
588 entryDate.day
589 )
590 fullEntryPath = '%s/BlogEntry-%s' % (
591 self.thisPageName,
592 entryTitle
593 )
594
595 # we need this only to check the existence of a page
596 dummyPage = Page(self.macro.request, fullEntryPath, formatter = formatter)
597
598 # include existing pages using the Include() macro
599 if dummyPage.exists():
600 content.append(MoinMoin.macro.Include.execute(
601 self.macro,
602 '%s, "%s", 1' % (
603 fullEntryPath,
604 entryTitle
605 )))
606 else:
607 # create an heading for the empty page
608 content.append(
609 formatter.heading(1, self.headingLevel) +
610 formatter.pagelink(1, fullEntryPath, generated=1) +
611 formatter.text(entryTitle) +
612 formatter.pagelink(0,fullEntryPath) +
613 formatter.heading(0, self.headingLevel)
614 )
615
616 # decrease date
617 entryDate -= self.oneDay
618
619 return "\n".join(content)
620
621 def getShowEntered(self):
622 """
623 Shows the existing entries only.
624
625 @note: This function uses the Include macro.
626 @return: HTML page as string
627 """
628 content = []
629 content.append(self.inputHeader % ("Show All", "1"))
630 content.append(self._createMaxEntriesOptionList())
631
632 # use precompiled regular expression to found all children
633 child_page_list = self.macro.request.rootpage.getPageList(
634 filter = self.re_blogEntries
635 )
636
637 child_page_list.sort()
638 child_page_list.reverse()
639 selectedDate = int( "%d%02d%02d" % (
640 self.blogDate.year,
641 self.blogDate.month,
642 self.blogDate.day))
643 pageCount = 0
644
645 # process all child pages
646 for pageName in child_page_list:
647
648 # extract date from pagename
649 formattedDate = pageName[-10:]
650
651 pageDate = int("%s%s%s" % (
652 formattedDate[:4],
653 formattedDate[5:7],
654 formattedDate[8:10]
655 ))
656
657 # skip too young pages
658 if (pageDate > selectedDate):
659 continue
660
661 # include child page
662 includeParams = """%s, "%s", 1""" % (pageName, formattedDate)
663 content.append(MoinMoin.macro.Include.execute(self.macro, includeParams))
664
665 # leave loop on maximum number of pages to show
666 pageCount += 1
667 if (pageCount >= self.entries):
668 break
669
670 return "\n".join(content)
671
672 def _createMaxEntriesOptionList(self):
673 """
674 Creates an html option list to select the number of entries that will be shown
675
676 @return: Option list as string
677 """
678 # open option list
679 content = """<option value=''>--Entries--</option>"""
680
681 # create an entry for each value till maxEntriesInOptionList and make the current as selected
682 for i in xrange(1, self.maxEntriesInOptionList +1):
683 if i == self.entries:
684 selected = "selected"
685 else:
686 selected = ""
687 content += '<option %s value="%d">%d</option>' % (selected, i, i)
688
689 # add closing tag
690 content += "</select>"
691
692 return content
693
694 def execute(macro, args):
695 """
696 Execute macro
697 """
698
699 return Blog(macro, args).dispatch()
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.