WikiCalendarMacro: WikiCalendarMacro.2.py

File WikiCalendarMacro.2.py, 9.0 kB (added by Andy, 6 months ago)

Refactored: uses CSS, Javascript - tested with Trac 0.11b1, Python 2.5, Firefox 2.0

Line 
1 # Copyright (C) 2005 Matthew Good <trac@matt-good.net>
2 # Copyright (C) 2005 Jan Finell <finell@cenix-bioscience.com>
3 #
4 # "THE BEER-WARE LICENSE" (Revision 42):
5 # <trac@matt-good.net> wrote this file.  As long as you retain this notice you
6 # can do whatever you want with this stuff.  If we meet some day, and you think
7 # this stuff is worth it, you can buy me a beer in return.  Matthew Good
8 # (Beer-ware license written by Poul-Henning Kamp
9 #  http://people.freebsd.org/~phk/)
10 #
11 # Author: Matthew Good <trac@matt-good.net>
12 # Month/Year navigation by: Jan Finell <finell@cenix-bioscience.com>
13 # trac 0.11 compatibility by: Vlad Sukhoy <vladimir.sukhoy@gmail.com>
14 # refactored; CSS, Javascript added: Andy Schlaikjer <andrew.schlaikjer@gmail.com>
15
16 import time
17 import calendar
18 from cStringIO import StringIO
19 from trac.wiki.api import WikiSystem
20 from trac.wiki.macros import WikiMacroBase
21 from trac.util import *
22
23 class WikiCalendarMacro(WikiMacroBase):
24    
25     """Inserts a small calendar where each day links to a wiki page whose name
26     matches `wiki-page-format`. The current day is highlighted, and days with
27     Milestones are marked in bold. This version requires Javascript and makes
28     heavy use of CSS for formatting. Tested in Firefox 2.0 only.
29     
30     Usage:
31     {{{
32     [[WikiCalendar([year, [month, [show-buttons, [wiki-page-format]]]])]]
33     }}}
34     
35     Arguments:
36      1. `year` (4-digit year) - defaults to `*` (current year)
37      1. `month` (2-digit month) - defaults to `*` (current month)
38      1. `show-buttons` (boolean) - defaults to `true`
39      1. `wiki-page-format` (string) - defaults to `%Y-%m-%d`
40     
41     Examples:
42     {{{
43     [[WikiCalendar(2006,07)]]
44     [[WikiCalendar(2006,07,false)]]
45     [[WikiCalendar(*,*,true,Meeting-%Y-%m-%d)]]
46     [[WikiCalendar(2006,07,false,Meeting-%Y-%m-%d)]]
47     }}}
48     """
49    
50     def expand_macro(self, formatter, name, content):
51         today = time.localtime()
52         # VS: The hdf is gone in 0.11, using request object instead
53         http_param_year = formatter.req.args.get('year', '')
54         http_param_month = formatter.req.args.get('month', '')
55         if content == "":
56             args = []
57         else:
58             args = content.split(',', 3)
59        
60         # find out whether use http param, current or macro param year/month
61        
62         if http_param_year == "":
63             # not clicked on a prev or next button
64             if len(args) >= 1 and args[0] <> "*":
65                 # year given in macro parameters
66                 year = int(args[0])
67             else:
68                 # use current year
69                 year = today.tm_year
70         else:
71             # year in http params (clicked by user) overrides everything
72             year = int(http_param_year)
73        
74         if http_param_month == "":
75             # not clicked on a prev or next button
76             if len(args) >= 2 and args[1] <> "*":
77                 # month given in macro parameters
78                 month = int(args[1])
79             else:
80                 # use current month
81                 month = today.tm_mon
82         else:
83             # month in http params (clicked by user) overrides everything
84             month = int(http_param_month)
85        
86         showbuttons = 1
87         if len(args) >= 3:
88             showbuttons = bool(args[2]=="True" or args[2]=="true" or args[2]=="no" or args[2]=="0")
89        
90         wiki_page_format = "%Y-%m-%d"
91         if len(args) >= 4:
92             wiki_page_format = args[3]
93        
94         curr_day = None
95         if year == today.tm_year and month == today.tm_mon:
96             curr_day = today.tm_mday
97        
98         # url to the current page (used in the navigation links)
99         # VS: hdf is gone in 0.11, using the new "Context" object instead
100         # AS: trac.web.Href object used instead of basic strings
101         thispageURL = formatter.context.href
102         # for the prev/next navigation links
103         prevMonth = month-1
104         prevYear  = year
105         nextMonth = month+1
106         nextYear  = year
107         # check for year change (KISS version)
108         if prevMonth == 0:
109             prevMonth = 12
110             prevYear -= 1
111         if nextMonth == 13:
112             nextMonth = 1
113             nextYear += 1
114        
115         # 9-tuple for use with time.* functions requiring a struct_time
116         # argument. The ninth value of -1 signals "do the right thing"
117         # w.r.t. daylight savings time.
118         date = [0] * 8 + [-1]
119        
120         # building the output
121         buff = StringIO()
122         buff.write('''\
123 <style type="text/css">
124 <!--
125 table.wiki-calendar {border-collapse: separate; border-spacing: 0px;}
126 table.wiki-calendar caption {font-size: 120%; white-space: nowrap;}
127 table.wiki-calendar span.button {color: #b00; cursor: pointer;}
128 table.wiki-calendar span.button:hover {background-color: #eee;}
129 table.wiki-calendar th {width: 2em; border-bottom: 2px solid #000; text-align: center; font-weight: bold;}
130 table.wiki-calendar td {width: 2em; border: 2px solid transparent; text-align: right; color: #888; cursor: pointer;}
131 table.wiki-calendar td:hover {background-color: #eee; color: #000;}
132 table.wiki-calendar td.empty {background-color: #fafafa; cursor: default;}
133 table.wiki-calendar td.milestone {border-color: #888; font-weight: bold;}
134 table.wiki-calendar td.haspage {color: #b00;}
135 table.wiki-calendar td.haspage:hover {color: #b00;}
136 table.wiki-calendar td.today {border-color: #b00;}
137 //-->
138 </style>
139 <table class="wiki-calendar"><caption>
140         ''')
141        
142         if showbuttons:
143             # prev year link
144             date[0:2] = [year-1, month]
145             buff.write('<span class="button prev" onclick="javascript:self.location=\'%s\'" title="%s">&lt;&lt;</span>' % (
146                 thispageURL(month=month, year=year-1),
147                 time.strftime('%B %Y', tuple(date))
148                 ))
149             # prev month link
150             date[0:2] = [prevYear, prevMonth]
151             buff.write('<span class="button prev" onclick="javascript:self.location=\'%s\'" title="%s">&nbsp;&nbsp;&lt;&nbsp;&nbsp;</span>' % (
152                 thispageURL(month=prevMonth, year=prevYear),
153                 time.strftime('%B %Y', tuple(date))
154                 ))
155        
156         # the caption
157         date[0:2] = [year, month]
158         buff.write(time.strftime('%B %Y', tuple(date)))
159        
160         if showbuttons:
161             # next month link
162             date[0:2] = [nextYear, nextMonth]
163             buff.write('<span class="button next" onclick="javascript:self.location=\'%s\'" title="%s">&nbsp;&nbsp;&gt;&nbsp;&nbsp;</span>' % (
164                 thispageURL(month=nextMonth, year=nextYear),
165                 time.strftime('%B %Y', tuple(date))
166                 ))
167             # next year link
168             date[0:2] = [year+1, month]
169             buff.write('<span class="button next" onclick="javascript:self.location=\'%s\'" title="%s">&gt;&gt;</span>' % (
170                 thispageURL(month=month, year=year+1),
171                 time.strftime('%B %Y', tuple(date))
172                 ))
173            
174         buff.write('</caption>\n<thead><tr class="header">')
175         for day in calendar.weekheader(2).split():
176             buff.write('<th scope="col">%s</th>' % day)
177         buff.write('</tr></thead>\n<tbody>\n')
178         date[0:2] = [year, month]
179         for row in calendar.monthcalendar(year, month):
180             buff.write('<tr>')
181             for day in row:
182                 date[2] = day
183                 if not day:
184                     buff.write('<td class="empty">&nbsp;</td>')
185                     continue
186                 classes = day == curr_day and 'today' or ''
187                 url = ''
188                 title = ''
189                 # first check for milestone on that day
190                 db = self.env.get_db_cnx()
191                 cursor = db.cursor()
192                 duedate = time.mktime(tuple(date))
193                 cursor.execute("SELECT name,due FROM milestone WHERE due=%s", (duedate,))
194                 row = cursor.fetchone()
195                 if row:
196                     # found a milestone
197                     milestone_name = row[0]
198                     classes += ' milestone'
199                     url = self.env.href.milestone(milestone_name)
200                     title = milestone_name + ' - '
201                 # secondly check for wikipage with name specified in 'wiki_page_format'
202                 wiki = time.strftime(wiki_page_format, tuple(date))
203                 url = self.env.href.wiki(wiki)
204                 if WikiSystem(self.env).has_page(wiki):
205                     classes += ' haspage'
206                     title += 'Go to page %s' % wiki
207                 else:
208                     url += "?action=edit"
209                     title += 'Create page %s' % wiki
210                 buff.write('<td class="%(classes)s" onclick="javascript:self.location=\'%(url)s\'" title="%(title)s">%(day)s</td>' % {
211                     'classes': classes.strip(),
212                     'url': url,
213                     'title': title,
214                     'day': day
215                     })
216             buff.write('</tr>\n')
217         buff.write('</tbody>\n</table>')
218         table = buff.getvalue()
219         buff.close()
220         return table