| 1 | from trac.core import * |
|---|
| 2 | from trac.web.main import IRequestHandler |
|---|
| 3 | from trac.web.chrome import ITemplateProvider, INavigationContributor |
|---|
| 4 | from trac.util import Markup |
|---|
| 5 | from StringIO import StringIO |
|---|
| 6 | from trac.wiki.web_ui import WikiModule |
|---|
| 7 | from trac.wiki.formatter import wiki_to_oneliner |
|---|
| 8 | import re |
|---|
| 9 | try: |
|---|
| 10 | set = set |
|---|
| 11 | except: |
|---|
| 12 | from sets import Set as set |
|---|
| 13 | |
|---|
| 14 | _tag_split = re.compile('[,\s]+') |
|---|
| 15 | |
|---|
| 16 | class TagsWikiModule(WikiModule): |
|---|
| 17 | """ Replacement for the default Wiki module. Tag editing is much more |
|---|
| 18 | intuitive now, as it no longer requires the TagIt macro and JavaScript |
|---|
| 19 | magic. """ |
|---|
| 20 | |
|---|
| 21 | def _do_save(self, req, db, page): |
|---|
| 22 | # This method is overridden so the user doesn't get "Page not modified" |
|---|
| 23 | # exceptions when updating tags but not wiki content. |
|---|
| 24 | from tractags.api import TagEngine |
|---|
| 25 | if 'tags' in req.args: |
|---|
| 26 | newtags = set([t.strip() for t in |
|---|
| 27 | _tag_split.split(req.args.get('tags')) if t.strip()]) |
|---|
| 28 | wikitags = TagEngine(self.env).tagspace.wiki |
|---|
| 29 | oldtags = wikitags.get_tags([page.name]) |
|---|
| 30 | |
|---|
| 31 | if oldtags != newtags: |
|---|
| 32 | wikitags.replace_tags(req, page.name, newtags) |
|---|
| 33 | # No changes, just redirect |
|---|
| 34 | if req.args.get('text') == page.text: |
|---|
| 35 | req.redirect(self.env.href.wiki(page.name)) |
|---|
| 36 | return |
|---|
| 37 | return WikiModule._do_save(self, req, db, page) |
|---|
| 38 | |
|---|
| 39 | def process_request(self, req): |
|---|
| 40 | from tractags.api import TagEngine |
|---|
| 41 | from trac.web.chrome import add_stylesheet |
|---|
| 42 | |
|---|
| 43 | add_stylesheet(req, 'tags/css/tractags.css') |
|---|
| 44 | |
|---|
| 45 | pagename = req.args.get('page', 'WikiStart') |
|---|
| 46 | action = req.args.get('action', 'view') |
|---|
| 47 | |
|---|
| 48 | engine = TagEngine(self.env) |
|---|
| 49 | wikitags = engine.tagspace.wiki |
|---|
| 50 | tags = list(wikitags.get_tags([pagename])) |
|---|
| 51 | tags.sort() |
|---|
| 52 | |
|---|
| 53 | if action == 'edit': |
|---|
| 54 | req.hdf['tags'] = req.args.get('tags', ', '.join(tags)) |
|---|
| 55 | elif action == 'view': |
|---|
| 56 | hdf_tags = [] |
|---|
| 57 | for tag in tags: |
|---|
| 58 | href, title = engine.get_tag_link(tag) |
|---|
| 59 | hdf_tags.append({'name': tag, |
|---|
| 60 | 'href': href, |
|---|
| 61 | 'title': title}) |
|---|
| 62 | req.hdf['tags'] = hdf_tags |
|---|
| 63 | result = WikiModule.process_request(self, req) |
|---|
| 64 | if result is None: |
|---|
| 65 | return None |
|---|
| 66 | if result[0] == 'wiki.cs': |
|---|
| 67 | return 'tagswiki.cs', None |
|---|
| 68 | return result |
|---|
| 69 | |
|---|
| 70 | class TagsModule(Component): |
|---|
| 71 | """ Serve a /tags namespace. Top-level displays tag cloud, sub-levels |
|---|
| 72 | display output of ListTagged(tag). |
|---|
| 73 | |
|---|
| 74 | The following configuration options are supported: |
|---|
| 75 | |
|---|
| 76 | [tags] |
|---|
| 77 | # Use a tag list or cloud for the main index |
|---|
| 78 | index = cloud|list |
|---|
| 79 | # The keyword arguments to pass to the TagCloud or ListTags macros that |
|---|
| 80 | # is being used for the index. |
|---|
| 81 | index.args = ... |
|---|
| 82 | # Keyword arguments to pass to the listing for each tag under the |
|---|
| 83 | # /tags/ URL space. |
|---|
| 84 | listing.args = ... |
|---|
| 85 | """ |
|---|
| 86 | implements(IRequestHandler, INavigationContributor, ITemplateProvider) |
|---|
| 87 | |
|---|
| 88 | def _prepare_wiki(self, req): |
|---|
| 89 | from tractags.api import TagEngine |
|---|
| 90 | page = req.path_info[6:] or 'WikiStart' |
|---|
| 91 | engine = TagEngine(self.env) |
|---|
| 92 | wikitags = engine.tagspace.wiki |
|---|
| 93 | tags = list(wikitags.get_tags(page)) |
|---|
| 94 | tags.sort() |
|---|
| 95 | |
|---|
| 96 | action = req.args.get('action', 'view') |
|---|
| 97 | if action == 'edit': |
|---|
| 98 | req.hdf['tags'] = req.args.get('tags', ', '.join(tags)) |
|---|
| 99 | elif action == 'view': |
|---|
| 100 | hdf_tags = [] |
|---|
| 101 | for tag in tags: |
|---|
| 102 | href, title = engine.get_tag_link(tag) |
|---|
| 103 | hdf_tags.append({'name': tag, |
|---|
| 104 | 'href': href, |
|---|
| 105 | 'title': title}) |
|---|
| 106 | req.hdf['tags'] = hdf_tags |
|---|
| 107 | |
|---|
| 108 | # ITemplateProvider methods |
|---|
| 109 | def get_templates_dirs(self): |
|---|
| 110 | """ |
|---|
| 111 | Return the absolute path of the directory containing the provided |
|---|
| 112 | ClearSilver templates. |
|---|
| 113 | """ |
|---|
| 114 | from pkg_resources import resource_filename |
|---|
| 115 | return [resource_filename(__name__, 'templates')] |
|---|
| 116 | |
|---|
| 117 | def get_htdocs_dirs(self): |
|---|
| 118 | """Return the absolute path of a directory containing additional |
|---|
| 119 | static resources (such as images, style sheets, etc). |
|---|
| 120 | """ |
|---|
| 121 | from pkg_resources import resource_filename |
|---|
| 122 | return [('tags', resource_filename(__name__, 'htdocs'))] |
|---|
| 123 | |
|---|
| 124 | # INavigationContributor methods |
|---|
| 125 | def get_active_navigation_item(self, req): |
|---|
| 126 | return 'tags' |
|---|
| 127 | |
|---|
| 128 | def get_navigation_items(self, req): |
|---|
| 129 | from trac.web.chrome import Chrome |
|---|
| 130 | yield ('metanav', 'tags', |
|---|
| 131 | Markup('<a href="%s" accesskey="T">Tag Index</a>', |
|---|
| 132 | self.env.href.tags())) |
|---|
| 133 | |
|---|
| 134 | # IRequestHandler methods |
|---|
| 135 | def match_request(self, req): |
|---|
| 136 | return req.path_info.startswith('/tags') |
|---|
| 137 | |
|---|
| 138 | def process_request(self, req): |
|---|
| 139 | from tractags.macros import TagMacros |
|---|
| 140 | from tractags.parseargs import parseargs |
|---|
| 141 | from trac.web.chrome import add_stylesheet |
|---|
| 142 | |
|---|
| 143 | add_stylesheet(req, 'tags/css/tractags.css') |
|---|
| 144 | req.hdf['trac.href.tags'] = self.env.href.tags() |
|---|
| 145 | |
|---|
| 146 | def update_from_req(args): |
|---|
| 147 | for k in req.args.keys(): |
|---|
| 148 | args[str(k)] = str(req.args.get(k)) |
|---|
| 149 | |
|---|
| 150 | if req.path_info == '/tags': |
|---|
| 151 | index = self.env.config.get('tags', 'index', 'cloud') |
|---|
| 152 | index_kwargs = {'smallest': 10, 'biggest': 30} |
|---|
| 153 | _, config_kwargs = parseargs(self.env.config.get('tags', 'index.args', '')) |
|---|
| 154 | index_kwargs.update(config_kwargs) |
|---|
| 155 | update_from_req(index_kwargs) |
|---|
| 156 | |
|---|
| 157 | if index == 'cloud': |
|---|
| 158 | req.hdf['tag.body'] = Markup( |
|---|
| 159 | TagMacros(self.env).render_tagcloud(req, **index_kwargs)) |
|---|
| 160 | elif index == 'list': |
|---|
| 161 | req.hdf['tag.body'] = Markup( |
|---|
| 162 | TagMacros(self.env).render_listtagged(req, **index_kwargs)) |
|---|
| 163 | else: |
|---|
| 164 | raise TracError("Invalid index style '%s'" % index) |
|---|
| 165 | else: |
|---|
| 166 | _, args = parseargs(self.env.config.get('tags', 'listing.args', '')) |
|---|
| 167 | update_from_req(args) |
|---|
| 168 | tag = req.path_info[6:] |
|---|
| 169 | tags = tag.split(',') |
|---|
| 170 | req.hdf['tag.name'] = tag |
|---|
| 171 | req.hdf['tag.body'] = Markup( |
|---|
| 172 | TagMacros(self.env).render_listtagged(req, *tags, **args)) |
|---|
| 173 | return 'tags.cs', None |
|---|
| 174 | |
|---|
| 175 | # XXX I think this is planned for some AJAX goodness, commenting out for now. (Alec) XXX |
|---|
| 176 | #class TagsLi(Component): |
|---|
| 177 | # implements(IRequestHandler) |
|---|
| 178 | # |
|---|
| 179 | # # IRequestHandler methods |
|---|
| 180 | # def match_request(self, req): |
|---|
| 181 | # return req.path_info == '/tagli' |
|---|
| 182 | # |
|---|
| 183 | # def process_request(self, req): |
|---|
| 184 | # db = self.env.get_db_cnx() |
|---|
| 185 | # cursor = db.cursor() |
|---|
| 186 | # cs = db.cursor() |
|---|
| 187 | # tag = req.args.get('tag') |
|---|
| 188 | # req.send_response(200) |
|---|
| 189 | # req.send_header('Content-Type', 'text/plain') |
|---|
| 190 | # req.end_headers() |
|---|
| 191 | # buf = StringIO() |
|---|
| 192 | # if tag: |
|---|
| 193 | # buf.write('WHERE tag LIKE \'%s%s\'' % (tag,'%')) |
|---|
| 194 | # |
|---|
| 195 | # cursor.execute('SELECT DISTINCT tag FROM tags %s ORDER BY tag' % (buf.getvalue())) |
|---|
| 196 | # |
|---|
| 197 | # msg = StringIO() |
|---|
| 198 | # |
|---|
| 199 | # msg.write('<ul>') |
|---|
| 200 | # while 1: |
|---|
| 201 | # row = cursor.fetchone() |
|---|
| 202 | # if row == None: |
|---|
| 203 | # break |
|---|
| 204 | # |
|---|
| 205 | # t = row[0] |
|---|
| 206 | # msg.write('<li>') |
|---|
| 207 | # msg.write(t) |
|---|
| 208 | # msg.write('</li>') |
|---|
| 209 | # |
|---|
| 210 | # msg.write('</ul>') |
|---|
| 211 | # req.write(msg.getvalue()) |
|---|