Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Issue #554: Top 20 words in post replies

Closes #554, Closes #555
  • Loading branch information...
commit c67f088f11eaa7104bb2830d623cd8a605274d94 1 parent a974d5b
@mwilkie mwilkie authored ginatrapani committed
View
8 webapp/_lib/controller/class.PostController.php
@@ -31,6 +31,12 @@
*
*/
class PostController extends ThinkUpController {
+
+ /**
+ * Replies Top 20 Words number of reply post min to show option in menu...
+ */
+ const TOP_20_WORDS_POST_MIN = 20;
+
/**
* View name
* @var str
@@ -43,6 +49,8 @@ public function control() {
$post_dao = DAOFactory::getDAO('PostDAO');
$this->setPageTitle('Post Details');
$this->setViewTemplate('post.index.tpl');
+ $this->addToView('top_20_post_min', self::TOP_20_WORDS_POST_MIN);
+
$network = (isset($_GET['n']) )?$_GET['n']:'twitter';
if ($this->shouldRefreshCache()) {
if ( isset($_GET['t']) && is_numeric($_GET['t']) ) {
View
7 webapp/_lib/view/_post.tpl
@@ -22,7 +22,8 @@
</div>
<div class="grid_3 right small">
{if $t->network == 'twitter' && $username_link != 'internal'}
- <a href="http://twitter.com/{$t->author_username}">{$t->author_username}</a>
+ <a {if $reply_count && $reply_count > $top_20_post_min}id="post_username-{$smarty.foreach.foo.iteration}" {/if}
+ href="http://twitter.com/{$t->author_username}">{$t->author_username}</a>
{else}
<a href="{$site_root_path}public.php?u={$t->author_username|urlencode}&n={$t->network|urlencode}">
{$t->author_username}
@@ -39,7 +40,11 @@
<div class="post">
{if $t->post_text}
{if $scrub_reply_username}
+ {if $reply_count && $reply_count > $top_20_post_min}
+ <div class="reply_text" id="reply_text-{$smarty.foreach.foo.iteration}">
+ {/if}
{$t->post_text|regex_replace:"/^@[a-zA-Z0-9_]+/":""|link_usernames_to_twitter}
+ {if $reply_count && $reply_count > $top_20_post_min}</div>{/if}
{else}
{$t->post_text|link_usernames_to_twitter}
{/if}
View
33 webapp/_lib/view/_post.word-frequency.tpl
@@ -0,0 +1,33 @@
+<div id="word-frequency-div" style="display: none; margin-right: 100px; border: solid gray 1px; padding: 10px;">
+ <div style="float: left; border: solid black 0px; width: 630px;">
+ <div id="word-frequency-spinner" style="text-align: center;">
+ <img src="{$site_root_path}assets/img/loading.gif" width="31" height="31" />
+ <br />Processing word frequency...
+ </div>
+ <div id="word-frequency-list" style="display: none;">
+ Top 20 Word list:
+ <div class="word-frequency-div" id="word-frequency-words">
+ </div>
+ </div>
+ </div>
+
+ <div style="float: right; width: 27px;">
+ <a href="#" onclick="return false;" id="word-frequency-close">
+ <img src="{$site_root_path}assets/img/close-icon.gif" width="27" height="26" /></a>
+ </div>
+
+ <div id="word-frequency-posts-div" style="display: none; position: absolute; background-color: #fff; z-index: 1000;
+ border: solid black 1px; padding: 10px;">
+ <div style="float: left; margin: 0px 10px 0px 0px; width: 500px;" id="word-frequency-posts">
+
+ </div>
+ <div style="float: right; width: 27px;">
+ <img id="word-frequency-posts-close" src="{$site_root_path}assets/img/close-icon.gif" width="27" height="26"
+ style="cursor: pointer;"/></a>
+ </div>
+ <div style="clear: both;"></div>
+ </div>
+
+ <div style="clear: both;"></div>
+
+</div>
View
32 webapp/_lib/view/post.index.tpl
@@ -6,13 +6,16 @@
<div id="nav-sidebar">
<ul id="top-level-sidenav"><br />
{if $post}
- <ul class="side-subnav">
- <li{if $smarty.get.v eq ''} class="currentview"{/if}><a href="index.php?t={$post->post_id}&n={$post->network}">Post Replies&nbsp;&nbsp;&nbsp;</a></li>
- {if $logged_in_user}
- <li><a href="#" class="grid_search" title="Search" onclick="return false;"><span id="grid_search_icon">Search & Filter Replies</span></a></li>
- {/if}
- <li><a href="{$site_root_path}post/export.php?u={$post->author_username}&n={$post->network}&post_id={$post->post_id}&type=replies">Export Replies (CSV)</a></li>
- </ul></li>
+ <ul class="side-subnav">
+ <li{if $smarty.get.v eq ''} class="currentview"{/if}><a href="index.php?t={$post->post_id}&n={$post->network}">Post Replies&nbsp;&nbsp;&nbsp;</a></li>
+ {if $logged_in_user}
+ <li><a href="#" class="grid_search" title="Search" onclick="return false;"><span id="grid_search_icon">Search & Filter Replies</span></a></li>
+ {/if}
+ <li><a href="{$site_root_path}post/export.php?u={$post->author_username}&n={$post->network}&post_id={$post->post_id}&type=replies">Export Replies (CSV)</a></li>
+ {if $post->reply_count_cache > $top_20_post_min}
+ <li><a href="#" class="grid_search" title="Search" onclick="return false;"><span class="word_frequency">Top 20 Words</span></a></li>
+ {/if}
+ </ul></li>
{/if}
{if $sidebar_menu}
{foreach from=$sidebar_menu key=smkey item=sidebar_menu_item name=smenuloop}
@@ -95,9 +98,18 @@
</div> <!-- end .clearfix -->
{if $replies}
<div class="append_20 clearfix"><br />
- {foreach from=$replies key=tid item=t name=foo}
- {include file="_post.tpl" t=$t sort='no' scrub_reply_username=true}
- {/foreach}
+ {if $post->reply_count_cache > $top_20_post_min}
+ {include file="_post.word-frequency.tpl"}
+ {/if}
+ <div id="post-replies-div">
+ {foreach from=$replies key=tid item=t name=foo}
+ {include file="_post.tpl" t=$t sort='no' scrub_reply_username=true reply_count=$post->reply_count_cache}
+ {/foreach}
+ </div>
+ {if $post->reply_count_cache > $top_20_post_min}
+ {include file="_post.word-frequency.tpl"}
+ <script src="{$site_root_path}assets/js/word_frequency.js" type="text/javascript"></script>
+ {/if}
{if !$logged_in_user && $private_reply_count > 0}
<span style="font-size:12px">Not showing {$private_reply_count} private repl{if $private_reply_count == 1}y{else}ies{/if}.</span>
{/if}
View
27 webapp/assets/css/style.css
@@ -176,11 +176,22 @@ text-align: center;
#nav-sidebar ul.side-subnav li a:hover {color: white;}
#top-level-sidenav {margin-top:7px}
#nav-sidebar ul.side-subnav .currentview a, #nav-sidebar ul.side-subnav .currentview a:hover {
- color: #6184B5;
- background-color: #E6E6E6;
- -moz-border-radius: 3px;-webkit-border-radius: 3px;
- background-color: #e6e6e6;
- background: -moz-linear-gradient(top, #FFFFFF, #E6E6E6);
- background: -webkit-gradient(linear, left top, left bottom, from(#FFFFFF), to(#E6E6E6));
- border: 0px solid #ddd;
-}
+ background-color: #E6E6E6;
+ -moz-border-radius: 3px;-webkit-border-radius: 3px;
+ background-color: #e6e6e6;
+ background: -moz-linear-gradient(top, #FFFFFF, #E6E6E6);
+ background: -webkit-gradient(linear, left top, left bottom, from(#FFFFFF), to(#E6E6E6));
+ border: 0px solid #ddd;
+}
+
+/* word frequency list */
+.word-frequency-div div {
+ float: left;;
+ list-style-type: none;
+ background-color: #2a98de;
+ padding: 1px 4px 1px 4px;
+ margin: 10px 5px 0px 5px;
+}
+.word-frequency-count { font-weight: bold; }
+.word-frequency-word { cursor: pointer; }
View
257 webapp/assets/js/word_frequency.js
@@ -0,0 +1,257 @@
+/**
+ *
+ * ThinkUp/webapp/assets/js/word_frequency.js
+ *
+ * Copyright (c) 2009-2010 Mark Wilkie
+ *
+ * LICENSE:
+ *
+ * This file is part of ThinkUp (http://thinkupapp.com).
+ *
+ * ThinkUp is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any
+ * later version.
+ *
+ * ThinkUp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with ThinkUp. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ *
+ * @author Mark Wilkie <mwilkie[at]gmail[dot]com>
+ * @license http://www.gnu.org/licenses/gpl.html
+ * @copyright 2009-2010 Mark Wilkie
+ */
+
+var TUWordFrequency = function() {
+ /* our word temnplates... */
+ this.word_template = '<div class="word-frequency-word" id="${id}"><span class="word-frequency-count">' +
+ '${count}</span>&nbsp;${word}</div>';
+ this.post_template = '<div style="padding: 10px;">${post} - <a href="http://twitter.com/${author}">${author}</a>';
+
+ /* our stop words... */
+ this.stop_words = new Array('i', 'a', '-', "a's", "able", "about", "above", "according", "accordingly", "across",
+ "actually", "after", "afterwards", "again", "against", "ain't", "all", "allow", "allows", "almost",
+ "alone", "along", "already", "also", "although", "always", "am", "among", "amongst", "an", "and",
+ "another", "any", "anybody", "anyhow", "anyone", "anything", "anyway", "anyways", "anywhere", "apart",
+ "appear", "appreciate", "appropriate", "are", "aren't", "around", "as", "aside", "ask", "asking",
+ "associated", "at", "available", "away", "awfully", "be", "became", "because", "become", "becomes",
+ "becoming", "been", "before", "beforehand", "behind", "being", "believe", "below", "beside", "besides",
+ "best", "better", "between", "beyond", "both", "brief", "but", "by", "c'mon", "c's", "came", "can", "can't",
+ "cannot", "cant", "cause", "causes", "certain", "certainly", "changes", "clearly", "co", "com", "come",
+ "comes", "concerning", "consequently", "consider", "considering", "contain", "containing", "contains",
+ "corresponding", "could", "couldn't", "course", "currently", "definitely", "described", "despite", "did",
+ "didn't", "different", "do", "does", "doesn't", "doing", "don't", "done", "down", "downwards", "during",
+ "each", "edu", "eg", "eight", "either", "else", "elsewhere", "enough", "entirely", "especially", "et",
+ "etc", "even", "ever", "every", "everybody", "everyone", "everything", "everywhere", "ex", "exactly",
+ "example", "except", "far", "few", "fifth", "first", "five", "followed", "following", "follows", "for",
+ "former", "formerly", "forth", "four", "from", "further", "furthermore", "get", "gets", "getting",
+ "given", "gives", "go", "goes", "going", "gone", "got", "gotten", "greetings", "had", "hadn't",
+ "happens", "hardly", "has", "hasn't", "have", "haven't", "having", "he", "he's", "hello", "help",
+ "hence", "her", "here", "here's", "hereafter", "hereby", "herein", "hereupon", "hers", "herself", "hi",
+ "him", "himself", "his", "hither", "hopefully", "how", "howbeit", "however", "i'd", "i'll", "i'm",
+ "i've", "ie", "if", "ignored", "immediate", "in", "inasmuch", "inc", "indeed", "indicate", "indicated",
+ "indicates", "inner", "insofar", "instead", "into", "inward", "is", "isn't", "it", "it'd", "it'll",
+ "it's", "its", "itself", "just", "keep", "keeps", "kept", "know", "knows", "known", "last", "lately",
+ "later", "latter", "latterly", "least", "less", "lest", "let", "let's", "like", "liked", "likely",
+ "little", "look", "looking", "looks", "ltd", "mainly", "many", "may", "maybe", "me", "mean", "meanwhile",
+ "merely", "might", "more", "moreover", "most", "mostly", "much", "must", "my", "myself", "name", "namely",
+ "nd", "near", "nearly", "necessary", "need", "needs", "neither", "never", "nevertheless", "new", "next",
+ "nine", "no", "nobody", "non", "none", "noone", "nor", "normally", "not", "nothing", "novel", "now",
+ "nowhere", "obviously", "of", "off", "often", "oh", "ok", "okay", "old", "on", "once", "one", "ones",
+ "only", "onto", "or", "other", "others", "otherwise", "ought", "our", "ours", "ourselves", "out",
+ "outside", "over", "overall", "own", "particular", "particularly", "per", "perhaps", "placed", "please",
+ "plus", "possible", "presumably", "probably", "provides", "que", "quite", "qv", "rather", "rd", "re",
+ "really", "reasonably", "regarding", "regardless", "regards", "relatively", "respectively", "right",
+ "said", "same", "saw", "say", "saying", "says", "second", "secondly", "see", "seeing", "seem", "seemed",
+ "seeming", "seems", "seen", "self", "selves", "sensible", "sent", "serious", "seriously", "seven",
+ "several", "shall", "she", "should", "shouldn't", "since", "six", "so", "some", "somebody",
+ "somehow", "someone", "something", "sometime", "sometimes", "somewhat", "somewhere", "soon",
+ "sorry", "specified", "specify", "specifying", "still", "sub", "such", "sup", "sure", "t's", "take",
+ "taken", "tell", "tends", "th", "than", "thank", "thanks", "thanx", "that", "that's", "thats", "the",
+ "their", "theirs", "them", "themselves", "then", "thence", "there", "there's", "thereafter", "thereby",
+ "therefore", "therein", "theres", "thereupon", "these", "they", "they'd", "they'll", "they're", "they've",
+ "think", "third", "this", "thorough", "thoroughly", "those", "though", "three", "through", "throughout",
+ "thru", "thus", "to", "together", "too", "took", "toward", "towards", "tried", "tries", "truly", "try",
+ "trying", "twice", "two", "un", "under", "unfortunately", "unless", "unlikely", "until", "unto", "up",
+ "upon", "us", "use", "used", "useful", "uses", "using", "usually", "value", "various", "very", "via",
+ "viz", "vs", "want", "wants", "was", "wasn't", "way", "we", "we'd", "we'll", "we're", "we've", "welcome",
+ "well", "went", "were", "weren't", "what", "what's", "whatever", "when", "whence", "whenever", "where",
+ "where's", "whereafter", "whereas", "whereby", "wherein", "whereupon", "wherever", "whether", "which",
+ "while", "whither", "who", "who's", "whoever", "whole", "whom", "whose", "why", "will", "willing", "wish",
+ "with", "within", "without", "won't", "wonder", "would", "would", "wouldn't", "yes", "yet", "you",
+ "you'd", "you'll", "you're", "you've", "your", "yours", "yourself", "yourselves", "zero");
+
+ /* our stop words hash that will get auto generated on init */
+ this.stop_words_lookup = new Object();
+
+ /* our words object, looks like { words1: 2, word2: 5} */
+ this.words = new Object();
+
+ /* our sorted words */
+ this.sorted_words = new Array();
+
+ /**
+ * init our object...
+ */
+ this.init = function() {
+ // stop words hash...
+ for(i = 0; i < this.stop_words.length; i++) {
+ stopword = this.stop_words[i];
+ this.stop_words_lookup[stopword] = true;
+ }
+ $('.word_frequency').each(function(index) {
+ $(this).click(function() {
+ tu_word_freq.find_words();
+ });
+ });
+
+ // close word freq div...
+ $('#word-frequency-close').click(function() {
+ $('#word-frequency-div').hide();
+ $('#word-frequency-list').hide();
+ $('#word-frequency-spinner').show();
+ });
+
+ // close word-frequency-posts-close div
+ $('#word-frequency-posts-close').click( function() {
+ $('#word-frequency-posts-div').hide();
+ })
+
+ if(document.location.search.match(/wordf=true/)) {
+ this.find_words();
+ }
+ }
+
+ /**
+ * Find words in all posts...
+ */
+ this.find_words = function() {
+ // clear out our words object, sorted words and html...
+ this.words = new Object();
+ this.sorted_words = new Array();
+
+ $('#word-frequency-words').html('&nbsp;');
+
+ // show frequency div
+ $('#word-frequency-div').show();
+ //pull in and clean post texts...
+ var posts = $('.reply_text');
+ for(i = 0; i < posts.length; i++ ) {
+ var post = posts[i];
+ var post_text = post.innerHTML;
+ cleaned_post_text = post_text.replace(/<.*?>/g, '').replace(/@\w+/g, '');
+ var reply_id = 'reply_text-' + (i + 1);
+ var words = this.get_words(cleaned_post_text, reply_id);
+ }
+
+ // sort by count
+ this.sorted_words = this.sort_words();
+
+ // show top 20 words sorted by frequency
+ for(i = 0; i < this.sorted_words.length; i++) {
+ if(i >= 20 ) {
+ break;
+ }
+ var sorted_word = this.sorted_words[i];
+ var litext = this.word_template.replace(/\${count}/, sorted_word['count']);
+ var litext = litext.replace(/\${word}/, sorted_word['word']);
+ var litext = litext.replace(/\${id}/, 'sorted_word' + i);
+ $('#word-frequency-words').append(litext);
+ }
+
+ $('.word-frequency-word').each(function(index) {
+ $(this).click(function() {
+ $('#word-frequency-posts').html(' ');
+ var id = $(this).attr('id');
+ id = id.replace(/sorted_word/, '');
+ var sorted_word = tu_word_freq.sorted_words[id];
+ var word_obj = tu_word_freq.words[sorted_word['word']];
+ var reply_ids = word_obj['reply_ids'];
+ for (var key in reply_ids) {
+ author_id = key.replace(/reply_text/, 'post_username');
+ var post = $('#' + key).html()
+ var author = $('#' + author_id).html();
+ var post = tu_word_freq.post_template.replace(/\${post}/, post);
+ if(author) {
+ var post = post.replace(/\${author}/, author);
+ var post = post.replace(/\${author}/, '@' + author);
+ }
+ var regex = new RegExp(sorted_word['word'], 'ig');
+ post = post.toString().replace(regex, '<strong><i>' + sorted_word['word'] + '</i></strong>');
+ $('#word-frequency-posts').append(post);
+ }
+ $('#word-frequency-posts-div').show();
+ });
+ });
+ // hide spinner and show words...
+ $('#word-frequency-spinner').hide();
+ $('#word-frequency-list').show();
+
+ }
+
+ /**
+ * get words and counts from a post
+ */
+ this.get_words = function(text, reply_id) {
+ var words = text.split(/\s+/g);
+ words.pop(); words.shift();
+ var cleaned_words = Array();
+ for(j = 0; j < words.length; j++) {
+ var tmp_word = words[j].toLowerCase();
+ // clean a bit...
+ tmp_word = tmp_word.replace(/('s|\?|\.|!|,|'s(\.|\?|!))$/g, '');
+ var good_status = true;
+ if(tmp_word.length < 3 || tmp_word.match(/^&/) || this.stop_words_lookup[tmp_word]) {
+ good_status = false;
+ }
+ if(good_status) {
+ cleaned_words[cleaned_words.length] = tmp_word;
+ if(this.words[tmp_word]) {
+ var cnt =
+ this.words[tmp_word]['count']++;
+ } else {
+ this.words[tmp_word] = {count: 1};
+ }
+ }
+
+ // store post ids with the word...
+ if( this.words[tmp_word] ) {
+ if(! this.words[tmp_word]['reply_ids']) {
+ this.words[tmp_word]['reply_ids'] = new Object();
+ }
+ if(! this.words[tmp_word]['reply_ids'][reply_id]) {
+ this.words[tmp_word]['reply_ids'][reply_id] = reply_id;
+ }
+ }
+ }
+ return cleaned_words;
+ }
+
+ /**
+ * sorts words by frequency
+ */
+ this.sort_words = function() {
+ //create an array of word counts form our object for sorting
+ var wordlist = new Array();
+ for (var key in this.words) {
+ wordlist[wordlist.length] = {word: key, count: this.words[key]['count']};
+ }
+
+ // our comparator
+ compare = function(a,b) {
+ if (a.count > b.count)
+ return -1;
+ if (a.count < b.count)
+ return 1;
+ return 0;
+ }
+ wordlist = wordlist.sort(compare);
+ return wordlist;
+ }
+}
+
+var tu_word_freq = new TUWordFrequency();
+tu_word_freq.init();
Please sign in to comment.
Something went wrong with that request. Please try again.