Index: branches/liquidthreads/skins/monobook/main.css |
— | — | @@ -33,6 +33,17 @@ |
34 | 34 | margin-bottom: .1em; |
35 | 35 | }*/ |
36 | 36 | |
| 37 | +.lqt_archive_widget { |
| 38 | + /* floats to the right of the main h1 page title. */ |
| 39 | + float:right; |
| 40 | + position:relative; |
| 41 | + top:-3.2em; |
| 42 | +} |
| 43 | + |
| 44 | +h1.firstHeading { |
| 45 | + padding-right: 4.5em; |
| 46 | +} |
| 47 | + |
37 | 48 | #lqt_archive_hide_show { |
38 | 49 | display: block; |
39 | 50 | } |
Index: branches/liquidthreads/extensions/LqtExtension.php |
— | — | @@ -185,6 +185,8 @@ |
186 | 186 | if ( $e->didSave && $subject != '' ) { |
187 | 187 | $thread->setSubject( Sanitizer::stripAllTags($subject) ); |
188 | 188 | } |
| 189 | + |
| 190 | + if ($e->didSave) $thread->touch(); // TODO reduntent if above $thread->setX called. |
189 | 191 | } |
190 | 192 | |
191 | 193 | function scratchTitle() { |
— | — | @@ -245,6 +247,11 @@ |
246 | 248 | $this->output->addWikitext( $sig, false ); |
247 | 249 | $this->output->addHTML( wfCloseElement( 'li' ) ); |
248 | 250 | |
| 251 | + $this->output->addHTML( wfOpenElement( 'li' ) ); |
| 252 | + $d = new Date($thread->touched()); |
| 253 | + $this->output->addHTML( $d->lastMonth()->text() ); |
| 254 | + $this->output->addHTML( wfCloseElement( 'li' ) ); |
| 255 | + |
249 | 256 | $commands = array( 'Edit' => $this->lqtTalkpageUrl( $this->title, 'lqt_edit_post', $thread ), |
250 | 257 | 'Reply' => $this->lqtTalkpageUrl( $this->title, 'lqt_reply_to', $thread ), |
251 | 258 | 'Permalink' => $this->permalinkUrl( $thread ) ); |
— | — | @@ -338,23 +345,57 @@ |
339 | 346 | logged-in users, don't really fit the metaphor. What to do, what to do? |
340 | 347 | */ |
341 | 348 | } |
342 | | - function show() { |
343 | | - global $wgHooks; |
344 | | - $wgHooks['SkinTemplateTabs'][] = array($this, 'customizeTabs'); |
345 | | - |
346 | | - $this->output->setPageTitle( "Talk:" . $this->title->getText() ); |
347 | | - |
| 349 | + |
| 350 | + function showArchive($month) { |
| 351 | + $threads = Thread::threadsOfArticleInMonth( $this->article, $month ); |
| 352 | + foreach($threads as $t) { |
| 353 | + $this->showThread($t); |
| 354 | + } |
| 355 | + } |
| 356 | + |
| 357 | + function showLatest() { |
348 | 358 | if( $this->request->getBool('lqt_new_thread_form') ) { |
349 | 359 | $this->showNewThreadForm(); |
350 | 360 | } else { |
351 | 361 | $url = $this->lqtTalkpageUrl( $this->title, 'lqt_new_thread_form' ); |
352 | 362 | $this->output->addHTML("<strong><a href=\"$url\">Start a Discussion</a></strong>"); |
353 | 363 | } |
354 | | - $threads = Thread::latestNThreadsOfArticle($this->article, 10); |
| 364 | + |
| 365 | + $threads = Thread::latestNThreadsOfArticle($this->article, 10); |
355 | 366 | foreach($threads as $t) { |
356 | 367 | $this->showThread($t); |
357 | 368 | } |
358 | 369 | } |
| 370 | + |
| 371 | + function showArchiveWidget($month) { |
| 372 | + global $wgLang; // TODO global. |
| 373 | + |
| 374 | + $options = Thread::monthsWhereArticleHasThreads($this->article); |
| 375 | + array_unshift($options, 'Last 30 days' ); # prepend. |
| 376 | + |
| 377 | + $this->openDiv('lqt_archive_widget'); |
| 378 | + $this->output->addHTML('<form><select>'); |
| 379 | + foreach( $options as $o ) { |
| 380 | + $this->output->addHTML("<option>$o"); |
| 381 | + } |
| 382 | + $this->output->addHTML('</select></form>'); |
| 383 | + $this->closeDiv(); |
| 384 | + } |
| 385 | + |
| 386 | + function show() { |
| 387 | + global $wgHooks; |
| 388 | + $wgHooks['SkinTemplateTabs'][] = array($this, 'customizeTabs'); |
| 389 | + |
| 390 | + $this->output->setPageTitle( "Talk:" . $this->title->getText() ); |
| 391 | + |
| 392 | + $month = $this->request->getVal('lqt_archive_month'); |
| 393 | + $this->showArchiveWidget($month); |
| 394 | + if ( $month ) { |
| 395 | + $this->showArchive($month); |
| 396 | + } else { |
| 397 | + $this->showLatest(); |
| 398 | + } |
| 399 | + } |
359 | 400 | } |
360 | 401 | |
361 | 402 | class ThreadPermalinkView extends LqtView { |
Index: branches/liquidthreads/extensions/LqtModel.php |
— | — | @@ -2,6 +2,41 @@ |
3 | 3 | |
4 | 4 | require_once('Article.php'); |
5 | 5 | |
| 6 | +// TODO if we're gonna have a Date class we should really do it. |
| 7 | +class Date { |
| 8 | + public $year, $month, $day, $hour, $minute, $second; |
| 9 | + |
| 10 | + // ex. "20070530033751" |
| 11 | + function __construct( $text ) { |
| 12 | + if ( !strlen( $text ) == 14 || !ctype_digit($text) ) { |
| 13 | + $this->isValid = false; |
| 14 | + return null; |
| 15 | + } |
| 16 | + $this->year = intval( substr( $text, 0, 4 ) ); |
| 17 | + $this->month = intval( substr( $text, 4, 2 ) ); |
| 18 | + $this->day = intval( substr( $text, 6, 2 ) ); |
| 19 | + $this->hour = intval( substr( $text, 8, 2 ) ); |
| 20 | + $this->minute = intval( substr( $text, 10, 2 ) ); |
| 21 | + $this->second = intval( substr( $text, 12, 2 ) ); |
| 22 | + } |
| 23 | + function lastMonth() { |
| 24 | + $d = clone $this; |
| 25 | + $d->month -= 1; |
| 26 | + return $d; |
| 27 | + } |
| 28 | +/* function monthString() { |
| 29 | + return sprintf( '%04d%02d', $this->year, $this->month ); |
| 30 | + }*/ |
| 31 | + static function monthString($text) { |
| 32 | + return substr($text, 0, 6); |
| 33 | + } |
| 34 | + static function beginningOfMonth($yyyymm) { return $yyyymm . '00000000'; } |
| 35 | + static function endOfMonth($yyyymm) { return $yyyymm . '31235959'; } |
| 36 | + function text() { |
| 37 | + return sprintf( '%04d%02d%02d%02d%02d%02d', $this->year, $this->month, $this->day, |
| 38 | + $this->hour, $this->minute, $this->second ); |
| 39 | + } |
| 40 | +} |
6 | 41 | |
7 | 42 | class Post extends Article { |
8 | 43 | /** |
— | — | @@ -25,8 +60,6 @@ |
26 | 61 | |
27 | 62 | } |
28 | 63 | |
29 | | -// TODO when exactly do we update thraed_touched? |
30 | | - |
31 | 64 | class Thread { |
32 | 65 | |
33 | 66 | /* ID references to other objects that are loaded on demand: */ |
— | — | @@ -108,6 +141,17 @@ |
109 | 142 | array('ORDER BY' => 'thread_touched') ); |
110 | 143 | } |
111 | 144 | |
| 145 | + function touch() { |
| 146 | + $this->updateRecord(); // TODO side-effect, ugly, etc. |
| 147 | + if ( $this->superthread() ) { |
| 148 | + $this->superthread()->touch(); |
| 149 | + } |
| 150 | + } |
| 151 | + |
| 152 | + function touched() { |
| 153 | + return $this->touched; |
| 154 | + } |
| 155 | + |
112 | 156 | protected function updateRecord() { |
113 | 157 | $dbr =& wfGetDB( DB_MASTER ); |
114 | 158 | $res = $dbr->update( 'lqt_thread', |
— | — | @@ -116,7 +160,7 @@ |
117 | 161 | 'thread_subthread_of' => $this->superthreadId, |
118 | 162 | 'thread_summary_page' => $this->summaryId, |
119 | 163 | 'thread_subject' => $this->subject, |
120 | | - 'thread_touched' => $this->touched ), |
| 164 | + 'thread_touched' => wfTimestampNow() ), |
121 | 165 | /* WHERE */ array( 'thread_id' => $this->id, ), |
122 | 166 | __METHOD__); |
123 | 167 | } |
— | — | @@ -149,6 +193,17 @@ |
150 | 194 | return Thread::newFromId( $dbr->insertId() ); |
151 | 195 | } |
152 | 196 | |
| 197 | + /** List of months in which there are >0 threads, suitable for threadsOfArticleInMonth. */ |
| 198 | + static function monthsWhereArticleHasThreads( $article ) { |
| 199 | + $threads = Thread::allThreadsOfArticle( $article ); |
| 200 | + $months = array(); |
| 201 | + foreach( $threads as $t ) { |
| 202 | + $m = substr( $t->touched(), 0, 6 ); |
| 203 | + if (!in_array( $m, $months )) $months[] = $m; |
| 204 | + } |
| 205 | + return $months; |
| 206 | + } |
| 207 | + |
153 | 208 | static function latestNThreadsOfArticle( $article, $n ) { |
154 | 209 | return Thread::threadsWhere( array('thread_article' => $article->getID(), |
155 | 210 | 'thread_subthread_of is null'), |
— | — | @@ -159,16 +214,31 @@ |
160 | 215 | static function allThreadsOfArticle( $article ) { |
161 | 216 | return Thread::threadsWhere( array('thread_article' => $article->getID(), |
162 | 217 | 'thread_subthread_of is null'), |
163 | | - array('ORDER BY' => 'thread_touched DESC') ); |
| 218 | + array('ORDER BY' => 'thread_touched DESC') ); |
164 | 219 | } |
165 | 220 | |
166 | | - static function threadsOfPost( $post ) { |
| 221 | + static function threadsOfArticleInMonth( $article, $yyyymm ) { |
| 222 | + return Thread::threadsWhere( array('thread_article' => $article->getID(), |
| 223 | + 'thread_subthread_of is null', |
| 224 | + 'thread_touched >= "'.Date::beginningOfMonth($yyyymm).'"', |
| 225 | + 'thread_touched <= "'.Date::endOfMonth($yyyymm).'"'), |
| 226 | + array('ORDER BY' => 'thread_touched DESC') ); |
| 227 | + } |
| 228 | + |
| 229 | + /* |
| 230 | + static function threadsOfArticleInLastNDays( $article, $n ) { |
| 231 | + return Thread::threadsWhere( array('thread_article' => $article->getID(), |
| 232 | + 'thread_subthread_of is null', |
| 233 | + 'thread_touched > ' . 'foo' ), |
| 234 | + array('ORDER BY' => 'thread_touched DESC' ); |
| 235 | + }*/ |
| 236 | + |
| 237 | + static function threadsWhoseRootPostIs( $post ) { |
167 | 238 | return Thread::threadsWhere( array('thread_root_post' => $post->getID()) ); |
168 | 239 | } |
169 | 240 | |
170 | 241 | static function threadsWhere( $where_clause, $options = array() ) { |
171 | 242 | $dbr =& wfGetDB( DB_SLAVE ); |
172 | | - |
173 | 243 | $res = $dbr->select( array('lqt_thread'), |
174 | 244 | array('*'), |
175 | 245 | $where_clause, |