• R/O
  • HTTP
  • SSH
  • HTTPS

コミット

タグ
未設定

よく使われているワード(クリックで追加)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

allura


コミットメタ情報

リビジョンa07e7448376895f998594023b8bafa6a76ec1524 (tree)
日時2010-04-23 08:10:13
作者Jenny Steele <jsteele@geek...>
コミッターJonathan T. Beard

ログメッセージ

[#246] New theme for discussion.

Also noticed there was some missing functionality depicted by the mockups and added that in:
Sidebar links to reply, flag as spam, follow, and tag a thread.
Rss feeds for threads.
Tags for threads
Recently updated threads in sidebar
Help pages for forum permissions (placeholder) and markdown syntax

変更サマリ

差分

--- a/ForgeDiscussion/forgediscussion/controllers/root.py
+++ b/ForgeDiscussion/forgediscussion/controllers/root.py
@@ -56,6 +56,16 @@ class RootController(object):
5656 if results: count=results.hits
5757 return dict(q=q, history=history, results=results or [], count=count)
5858
59+ @expose('forgediscussion.templates.markdown_syntax')
60+ def markdown_syntax(self):
61+ 'Static page explaining markdown.'
62+ return dict()
63+
64+ @expose('forgediscussion.templates.help')
65+ def help(self):
66+ 'Static help page.'
67+ return dict()
68+
5969 @expose()
6070 def _lookup(self, id, *remainder):
6171 return ForumController(id), remainder
--- a/ForgeDiscussion/forgediscussion/forum_main.py
+++ b/ForgeDiscussion/forgediscussion/forum_main.py
@@ -1,6 +1,7 @@
11 #-*- python -*-
22 import logging
33 import Image
4+import pymongo
45
56 # Non-stdlib imports
67 import pkg_resources
@@ -119,12 +120,26 @@ class ForgeDiscussionApp(Application):
119120
120121 def sidebar_menu(self):
121122 try:
122- l = [SitemapEntry('Home', '.')]
123+ l = [SitemapEntry('Home', c.app.url, ui_icon='home')]
124+ # if we are in a thread, provide placeholder links to use in js
125+ if '/thread/' in request.url:
126+ l += [
127+ SitemapEntry('Reply to This', '#', ui_icon='comment', className='sidebar_thread_reply'),
128+ SitemapEntry('Tag This', '#', ui_icon='tag', className='sidebar_thread_tag'),
129+ SitemapEntry('Follow This', 'feed.rss', ui_icon='signal-diag'),
130+ SitemapEntry('Mark as Spam', 'flag_as_spam', ui_icon='flag', className='sidebar_thread_spam')
131+ ]
132+ else:
133+ l.append(SitemapEntry('Search', c.app.url+'search', ui_icon='search'))
123134 if has_artifact_access('admin', app=c.app)():
124- l.append(SitemapEntry('Admin', c.project.url()+'admin/'+self.config.options.mount_point))
125- l.append(SitemapEntry('Search', 'search'))
126- l += [ SitemapEntry(f.name, f.url())
127- for f in self.top_forums if (not f.deleted or has_artifact_access('configure', app=c.app)()) ]
135+ l.append(SitemapEntry('Admin', c.project.url()+'admin/'+self.config.options.mount_point, ui_icon='wrench'))
136+ l.append(SitemapEntry('Recent Topics'))
137+ l += [ SitemapEntry(thread.subject, thread.url(), className='nav_child')
138+ for thread in model.ForumThread.query.find().sort('mod_date', pymongo.DESCENDING).limit(3)
139+ if (not thread.discussion.deleted or has_artifact_access('configure', app=c.app)()) ]
140+ l.append(SitemapEntry('Forum Help'))
141+ l.append(SitemapEntry('Forum Permissions', c.app.url + 'help', className='nav_child'))
142+ l.append(SitemapEntry('Markdown Syntax', c.app.url + 'markdown_syntax', className='nav_child'))
128143 return l
129144 except: # pragma no cover
130145 log.exception('sidebar_menu')
--- /dev/null
+++ b/ForgeDiscussion/forgediscussion/templates/help.html
@@ -0,0 +1,19 @@
1+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3+<html xmlns="http://www.w3.org/1999/xhtml"
4+ xmlns:py="http://genshi.edgewall.org/"
5+ xmlns:xi="http://www.w3.org/2001/XInclude">
6+
7+ <xi:include href="${g.pyforge_templates}/master.html"/>
8+ <xi:include href="lib.html"/>
9+
10+ <head>
11+ <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
12+ <title>Forum Permissions</title>
13+ </head>
14+
15+ <body>
16+ <h1 class="title">Forum Permissions</h1>
17+ <p>Help is coming soon.</p>
18+ </body>
19+</html>
--- /dev/null
+++ b/ForgeDiscussion/forgediscussion/templates/markdown_syntax.html
@@ -0,0 +1,40 @@
1+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3+<html xmlns="http://www.w3.org/1999/xhtml"
4+ xmlns:py="http://genshi.edgewall.org/"
5+ xmlns:xi="http://www.w3.org/2001/XInclude">
6+
7+ <xi:include href="${g.pyforge_templates}/master.html"/>
8+ <xi:include href="lib.html"/>
9+
10+ <head>
11+ <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
12+ <title>Markdown Syntax</title>
13+ </head>
14+
15+ <body>
16+ <h1 class="title">Markdown Syntax</h1>
17+ <p>You can use
18+ <a href="http://daringfireball.net/projects/markdown/">MarkDown</a>
19+ Syntax here</p>
20+ <h4>Example Input</h4>
21+ <pre style="background-color:#fff">
22+# First-level heading
23+
24+Some *emphasized* and **strong** text
25+
26+#### Fourth-level heading
27+
28+</pre>
29+ <h4>Example Rendering</h4>
30+ <div style="background-color:#fff">${Markup(g.markdown.convert('''
31+# First-level heading
32+
33+Some *emphasized* and **strong** text
34+
35+#### Fourth-level heading
36+
37+'''))}</div>
38+ <a href="http://daringfireball.net/projects/markdown/syntax">More Examples</a>
39+ </body>
40+</html>
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
@@ -212,6 +212,16 @@ class TestForum(TestController):
212212 r = self.app.get('/discussion/search')
213213 r = self.app.get('/discussion/search', params=dict(q='foo'))
214214
215+ def test_render_help(self):
216+ summary = 'test render help'
217+ r = self.app.get('/discussion/help')
218+ assert 'Forum Permissions' in r
219+
220+ def test_render_markdown_syntax(self):
221+ summary = 'test render markdown syntax'
222+ r = self.app.get('/discussion/markdown_syntax')
223+ assert 'Markdown Syntax' in r
224+
215225 def test_forum_subscribe(self):
216226 r = self.app.get('/discussion/subscribe', params={
217227 'forum-0.shortname':'TestForum',
@@ -254,10 +264,24 @@ class TestForum(TestController):
254264 def test_sidebar_menu(self):
255265 r = self.app.get('/discussion/')
256266 sidebarmenu = str(r.html.find('ul',{'id':'sidebarmenu'}))
257- assert '<a href="." class=" ">Home</a>' in sidebarmenu
258- assert '<a href="/p/test/admin/discussion" class=" ">Admin</a>' in sidebarmenu
259- assert '<a href="search" class=" ">Search</a>' in sidebarmenu
260- assert '<a href="/p/test/discussion/TestForum/" class=" ">Test Forum</a>' in sidebarmenu
267+ assert '<a href="/p/test/discussion/" class=" "><span class="ui-icon ui-icon-home"></span>Home</a>' in sidebarmenu
268+ assert '<a href="/p/test/admin/discussion" class=" "><span class="ui-icon ui-icon-wrench"></span>Admin</a>' in sidebarmenu
269+ assert '<a href="/p/test/discussion/search" class=" "><span class="ui-icon ui-icon-search"></span>Search</a>' in sidebarmenu
270+ assert '<span class=" nav_head">Forum Help</span>' in sidebarmenu
271+ assert '<a href="/p/test/discussion/help" class="nav_child ">Forum Permissions</a>' in sidebarmenu
272+ assert '<a href="/p/test/discussion/markdown_syntax" class="nav_child ">Markdown Syntax</a>' in sidebarmenu
273+ assert '<a href="#" class="sidebar_thread_reply "><span class="ui-icon ui-icon-comment"></span>Reply to This</a>' not in sidebarmenu
274+ assert '<a href="#" class="sidebar_thread_tag "><span class="ui-icon ui-icon-tag"></span>Tag This</a>' not in sidebarmenu
275+ assert '<a href="feed.rss" class=" "><span class="ui-icon ui-icon-signal-diag"></span>Follow This</a>' not in sidebarmenu
276+ assert '<a href="flag_as_spam" class="sidebar_thread_spam "><span class="ui-icon ui-icon-flag"></span>Mark as Spam</a>' not in sidebarmenu
277+ thread = self.app.get('/discussion/TestForum/post', params=dict(
278+ subject='AAA',
279+ text='aaa')).follow()
280+ thread_sidebarmenu = str(thread.html.find('ul',{'id':'sidebarmenu'}))
281+ assert '<a href="#" class="sidebar_thread_reply "><span class="ui-icon ui-icon-comment"></span>Reply to This</a>' in thread_sidebarmenu
282+ assert '<a href="#" class="sidebar_thread_tag "><span class="ui-icon ui-icon-tag"></span>Tag This</a>' in thread_sidebarmenu
283+ assert '<a href="feed.rss" class=" "><span class="ui-icon ui-icon-signal-diag"></span>Follow This</a>' in thread_sidebarmenu
284+ assert '<a href="flag_as_spam" class="sidebar_thread_spam "><span class="ui-icon ui-icon-flag"></span>Mark as Spam</a>' in thread_sidebarmenu
261285
262286 class TestForumAdmin(TestController):
263287
--- a/pyforge/pyforge/controllers/discuss.py
+++ b/pyforge/pyforge/controllers/discuss.py
@@ -1,8 +1,9 @@
11 from mimetypes import guess_type
22
33 from tg import expose, redirect, validate, request, response, flash
4-from tg.decorators import before_validate
4+from tg.decorators import before_validate, with_trailing_slash, without_trailing_slash
55 from pylons import c
6+from formencode import validators
67 from webob import exc
78
89 from ming.base import Object
@@ -11,6 +12,7 @@ from ming.utils import LazyProperty
1112 from pyforge import model as model
1213 from pyforge.lib import helpers as h
1314 from pyforge.lib.security import require, has_artifact_access
15+from pyforge.lib.helpers import DateTimeConverter
1416
1517 from pyforge.lib.widgets import discuss as DW
1618
@@ -133,11 +135,49 @@ class ThreadController(object):
133135 @expose()
134136 @validate(pass_validator, error_handler=index)
135137 def post(self, **kw):
138+ require(has_artifact_access('post', self.thread))
136139 kw = self.W.edit_post.validate(kw, None)
137140 p = self.thread.add_post(**kw)
138141 flash('Message posted')
139142 redirect(request.referer)
140143
144+ @expose()
145+ def tag(self, labels, **kw):
146+ require(has_artifact_access('post', self.thread))
147+ self.thread.labels = labels.split(',')
148+ redirect(request.referer)
149+
150+ @expose()
151+ def flag_as_spam(self, **kw):
152+ require(has_artifact_access('moderate', self.thread))
153+ self.thread.first_post.status='spam'
154+ flash('Thread flagged as spam.')
155+ redirect(request.referer)
156+
157+ @without_trailing_slash
158+ @expose()
159+ @validate(dict(
160+ since=DateTimeConverter(if_empty=None),
161+ until=DateTimeConverter(if_empty=None),
162+ offset=validators.Int(if_empty=None),
163+ limit=validators.Int(if_empty=None)))
164+ def feed(self, since=None, until=None, offset=None, limit=None):
165+ if request.environ['PATH_INFO'].endswith('.atom'):
166+ feed_type = 'atom'
167+ else:
168+ feed_type = 'rss'
169+ title = 'Recent posts to %s' % self.thread.subject
170+ feed = model.Feed.feed(
171+ {'artifact_reference':self.thread.dump_ref()},
172+ feed_type,
173+ title,
174+ self.thread.url(),
175+ title,
176+ since, until, offset, limit)
177+ response.headers['Content-Type'] = ''
178+ response.content_type = 'application/xml'
179+ return feed.writeString('utf-8')
180+
141181 class PostController(object):
142182 __metaclass__=h.ProxiedAttrMeta
143183 M=h.attrproxy('_discussion_controller', 'M')
--- a/pyforge/pyforge/lib/widgets/discuss.py
+++ b/pyforge/pyforge/lib/widgets/discuss.py
@@ -67,6 +67,21 @@ class PostFilter(ew.SimpleForm):
6767 ])
6868 ]
6969
70+class TagPost(ew.SimpleForm):
71+
72+ # this ickiness is to override the default submit button
73+ def __call__(self, **kw):
74+ result = super(TagPost, self).__call__(**kw)
75+ submit_button = ffw.SubmitButton(label=result['submit_text'])
76+ result['extra_fields'] = [submit_button]
77+ result['buttons'] = [submit_button]
78+ return result
79+
80+ fields=[ffw.LabelEdit(label='Tags',name='labels', className='title')]
81+
82+ def resources(self):
83+ for r in ffw.LabelEdit(name='labels').resources(): yield r
84+
7085 class EditPost(ew.SimpleForm):
7186 show_subject=False
7287
@@ -253,11 +268,12 @@ class Thread(HierWidget):
253268 pagesize=None
254269 total=None
255270 show_subject=False
256- new_post_text="New Post"
271+ new_post_text="+ New Comment"
257272 widgets=dict(
258273 thread_header=ThreadHeader(),
259274 post_thread=PostThread(),
260275 post=Post(),
276+ tag_post=TagPost(),
261277 edit_post=EditPost(submit_text='Submit'))
262278 def resources(self):
263279 for r in super(Thread, self).resources(): yield r
@@ -265,13 +281,50 @@ class Thread(HierWidget):
265281 for r in w.resources():
266282 yield r
267283 yield ew.JSScript('''
268- $(window).load(function(){
269- if($('#new_post_create')){
270- $('#new_post_create').click(function(e){
271- $(e.target).hide();
272- $('#new_post_holder').show();
284+ $(document).ready(function(){
285+ var thread_reply = $('a.sidebar_thread_reply');
286+ var thread_tag = $('a.sidebar_thread_tag');
287+ var thread_spam = $('a.sidebar_thread_spam');
288+ var new_post_holder = $('#new_post_holder');
289+ var new_post_create = $('#new_post_create');
290+ var tag_thread_holder = $('#tag_thread_holder');
291+ var allow_moderate = $('#allow_moderate');
292+ if(new_post_create){
293+ new_post_create.click(function(e){
294+ new_post_create.hide();
295+ new_post_holder.show();
273296 });
274297 }
298+ if(thread_reply){
299+ if(new_post_holder.length){
300+ thread_reply[0].style.display='block';
301+ thread_reply.click(function(e){
302+ new_post_create.hide();
303+ new_post_holder.show();
304+ // focus the submit to scroll to the bottom, then focus the subject for them to start typing
305+ $('input[type="submit"]', new_post_holder).focus();
306+ $('input[type="text"]', new_post_holder).focus();
307+ return false;
308+ });
309+ }
310+ }
311+ if(thread_tag){
312+ if(tag_thread_holder.length){
313+ thread_tag[0].style.display='block';
314+ thread_tag.click(function(e){
315+ tag_thread_holder.show();
316+ // focus the submit to scroll to the bottom, then focus the subject for them to start typing
317+ $('input[type="submit"]', tag_thread_holder).focus();
318+ $('input[type="text"]', tag_thread_holder).focus();
319+ return false;
320+ });
321+ }
322+ }
323+ if(thread_spam){
324+ if(allow_moderate.length){
325+ thread_spam[0].style.display='block';
326+ }
327+ }
275328 });
276329 ''')
277330
--- a/pyforge/pyforge/lib/widgets/templates/thread.html
+++ b/pyforge/pyforge/lib/widgets/templates/thread.html
@@ -15,10 +15,14 @@
1515 </py:for>
1616 </div>
1717 </py:with>
18+ <div id="allow_moderate" py:if="has_artifact_access('moderate', value)()"/>
1819 <py:if test="has_artifact_access('post', value)()">
1920 <input id="new_post_create" type="button" class="ui-state-default ui-button ui-button-text" value="$new_post_text"/>
2021 <div id="new_post_holder" style="display:none">
2122 ${widgets.edit_post.display(submit_text='New Post', action=value.url() + 'post')}
2223 </div>
24+ <div id="tag_thread_holder" style="display:none">
25+ ${widgets.tag_post.display(value=value,submit_text='Tag Post', action=value.url() + 'tag')}
26+ </div>
2327 </py:if>
2428 </div>
--- a/pyforge/pyforge/lib/widgets/templates/threads_table.html
+++ b/pyforge/pyforge/lib/widgets/templates/threads_table.html
@@ -2,7 +2,7 @@
22 xmlns:py="http://genshi.edgewall.org/"
33 xmlns:xi="http://www.w3.org/2001/XInclude">
44 <xi:include href="${g.pyforge_templates}/lib.html" />
5- <table id="forum-list" class="forums clear">
5+ <table id="forum-list" class="clear">
66 <thead>
77 <tr>
88 <th>&nbsp;</th>
--- a/pyforge/pyforge/model/discuss.py
+++ b/pyforge/pyforge/model/discuss.py
@@ -13,7 +13,7 @@ from ming.orm.property import FieldProperty, RelationProperty, ForeignIdProperty
1313
1414 from pyforge.lib import helpers as h
1515 from pyforge.lib.security import require, has_artifact_access
16-from .artifact import Artifact, VersionedArtifact, Snapshot, Message
16+from .artifact import Artifact, VersionedArtifact, Snapshot, Message, Feed
1717 from .filesystem import File
1818 from .types import ArtifactReference, ArtifactReferenceType
1919
@@ -137,6 +137,7 @@ class Thread(Artifact):
137137 self.num_replies += 1
138138 if not self.first_post:
139139 self.first_post_id = p._id
140+ Feed.post(self, title=p.subject, description=p.text)
140141 return p
141142
142143 def post(self, text, message_id=None, parent_id=None, **kw):
--- a/pyforge/pyforge/templates/style.mak
+++ b/pyforge/pyforge/templates/style.mak
@@ -688,7 +688,7 @@ border-bottom: 4px solid rgb(215,215,215);}
688688 .tagEditor li
689689 {
690690 display: inline;
691- background-image: url(https://newforge.sf.geek.net/images/minus_small.png);
691+ background-image: url(/images/minus_small.png);
692692 background-color: #eef;
693693 background-position: right center;
694694 background-repeat: no-repeat;
@@ -832,3 +832,6 @@ small.badge:hover {
832832
833833 #tipcca { text-align: left; height:3em; padding-left: 55px; width: 190px; background-image: url('images/cca.png'); background-position: 5px 50%; background-repeat: no-repeat;}
834834
835+ul#sidebarmenu li a.sidebar_thread_tag,
836+ul#sidebarmenu li a.sidebar_thread_reply,
837+ul#sidebarmenu li a.sidebar_thread_spam{display:none;}
\ No newline at end of file