From 6853f37bee465f0cb896f06ce620877818b045c0 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 10 Dec 2013 13:47:07 -0500 Subject: [PATCH] Show estimated reading time near summarize button. --- .../javascripts/discourse/models/topic.js | 9 +++++++++ .../components/toggle-summary.js.handlebars | 7 ++++++- app/serializers/topic_view_serializer.rb | 1 + config/locales/client.en.yml | 1 + .../20131210163702_add_word_count_to_posts.rb | 11 ++++++++++ .../20131210181901_migrate_word_counts.rb | 20 +++++++++++++++++++ lib/post_creator.rb | 2 ++ lib/post_revisor.rb | 8 ++++++++ spec/components/post_creator_spec.rb | 12 +++++++++++ spec/components/post_revisor_spec.rb | 10 ++++++++-- 10 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20131210163702_add_word_count_to_posts.rb create mode 100644 db/migrate/20131210181901_migrate_word_counts.rb diff --git a/app/assets/javascripts/discourse/models/topic.js b/app/assets/javascripts/discourse/models/topic.js index be6df660e..cef10580b 100644 --- a/app/assets/javascripts/discourse/models/topic.js +++ b/app/assets/javascripts/discourse/models/topic.js @@ -160,6 +160,15 @@ Discourse.Topic = Discourse.Model.extend({ return I18n.t(this.get('favoriteTooltipKey')); }.property('favoriteTooltipKey'), + estimatedReadingTime: function() { + var wordCount = this.get('word_count'); + if (!wordCount) return; + + // Avg for 250 words per minute. + var minutes = Math.floor(wordCount / 250.0); + return minutes; + }.property('word_count'), + toggleStar: function() { var topic = this; topic.toggleProperty('starred'); diff --git a/app/assets/javascripts/discourse/templates/components/toggle-summary.js.handlebars b/app/assets/javascripts/discourse/templates/components/toggle-summary.js.handlebars index 9b9e8749e..f60565061 100644 --- a/app/assets/javascripts/discourse/templates/components/toggle-summary.js.handlebars +++ b/app/assets/javascripts/discourse/templates/components/toggle-summary.js.handlebars @@ -2,6 +2,11 @@

{{{i18n summary.enabled_description}}}

{{else}} -

{{{i18n summary.description count="topic.posts_count"}}}

+ {{#if topic.estimatedReadingTime}} +

{{{i18n summary.description_time count="topic.posts_count" readingTime="topic.estimatedReadingTime"}}}

+ {{else}} +

{{{i18n summary.description count="topic.posts_count"}}}

+ {{/if}} + {{/if}} diff --git a/app/serializers/topic_view_serializer.rb b/app/serializers/topic_view_serializer.rb index 94cea11cc..f0c895907 100644 --- a/app/serializers/topic_view_serializer.rb +++ b/app/serializers/topic_view_serializer.rb @@ -22,6 +22,7 @@ class TopicViewSerializer < ApplicationSerializer :archetype, :slug, :category_id, + :word_count, :deleted_at] end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 3fea4b4ec..2c93fa628 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -384,6 +384,7 @@ en: summary: enabled_description: "You're viewing a summary of this topic. To see all posts again, click below." description: "There are {{count}} replies. Save reading time by displaying only the most relevant replies?" + description_time: "There are {{count}} replies with an estimated read time of {{readingTime}} minutes. Save reading time by displaying only the most relevant replies?" enable: 'Summarize This Topic' disable: 'Show All Posts' diff --git a/db/migrate/20131210163702_add_word_count_to_posts.rb b/db/migrate/20131210163702_add_word_count_to_posts.rb new file mode 100644 index 000000000..f0436ef83 --- /dev/null +++ b/db/migrate/20131210163702_add_word_count_to_posts.rb @@ -0,0 +1,11 @@ +class AddWordCountToPosts < ActiveRecord::Migration + def up + add_column :posts, :word_count, :integer + add_column :topics, :word_count, :integer + end + + def down + remove_column :posts, :word_count, :integer + remove_column :topics, :word_count, :integer + end +end diff --git a/db/migrate/20131210181901_migrate_word_counts.rb b/db/migrate/20131210181901_migrate_word_counts.rb new file mode 100644 index 000000000..35a2c6c39 --- /dev/null +++ b/db/migrate/20131210181901_migrate_word_counts.rb @@ -0,0 +1,20 @@ +class MigrateWordCounts < ActiveRecord::Migration + def up + + disable_ddl_transaction + + post_ids = execute("SELECT id FROM posts WHERE word_count IS NULL LIMIT 500").map {|r| r['id'].to_i } + while post_ids.length > 0 + execute "UPDATE posts SET word_count = array_length(regexp_split_to_array(raw, ' '),1) WHERE id IN (#{post_ids.join(',')})" + post_ids = execute("SELECT id FROM posts WHERE word_count IS NULL LIMIT 500").map {|r| r['id'].to_i } + end + + topic_ids = execute("SELECT id FROM topics WHERE word_count IS NULL LIMIT 500").map {|r| r['id'].to_i } + while topic_ids.length > 0 + execute "UPDATE topics SET word_count = (SELECT SUM(COALESCE(posts.word_count, 0)) FROM posts WHERE posts.topic_id = topics.id) WHERE topics.id IN (#{topic_ids.join(',')})" + topic_ids = execute("SELECT id FROM topics WHERE word_count IS NULL LIMIT 500").map {|r| r['id'].to_i } + end + + end + +end diff --git a/lib/post_creator.rb b/lib/post_creator.rb index 0be025581..aa555d5dd 100644 --- a/lib/post_creator.rb +++ b/lib/post_creator.rb @@ -96,6 +96,7 @@ class PostCreator post.reply_to_user_id ||= Post.select(:user_id).where(topic_id: post.topic_id, post_number: post.reply_to_post_number).first.try(:user_id) end + post.word_count = post.raw.scan(/\w+/).size post.post_number ||= Topic.next_post_number(post.topic_id, post.reply_to_post_number.present?) cooking_options = post.cooking_options || {} @@ -198,6 +199,7 @@ class PostCreator # Update attributes on the topic - featured users and last posted. attrs = {last_posted_at: @post.created_at, last_post_user_id: @post.user_id} attrs[:bumped_at] = @post.created_at unless @post.no_bump + attrs[:word_count] = (@topic.word_count || 0) + @post.word_count @topic.update_attributes(attrs) end diff --git a/lib/post_revisor.rb b/lib/post_revisor.rb index 4d3039326..1395cc70a 100644 --- a/lib/post_revisor.rb +++ b/lib/post_revisor.rb @@ -16,6 +16,7 @@ class PostRevisor revise_post update_category_description post_process_post + update_topic_word_counts @post.advance_draft_sequence true end @@ -66,8 +67,15 @@ class PostRevisor end end + def update_topic_word_counts + Topic.exec_sql("UPDATE topics SET word_count = (SELECT SUM(COALESCE(posts.word_count, 0)) + FROM posts WHERE posts.topic_id = :topic_id) + WHERE topics.id = :topic_id", topic_id: @post.topic_id) + end + def update_post @post.raw = @new_raw + @post.word_count = @new_raw.scan(/\w+/).size @post.updated_by = @user @post.last_editor_id = @user.id @post.edit_reason = @opts[:edit_reason] if @opts[:edit_reason] diff --git a/spec/components/post_creator_spec.rb b/spec/components/post_creator_spec.rb index 7b6fe2e41..994b8cd2a 100644 --- a/spec/components/post_creator_spec.rb +++ b/spec/components/post_creator_spec.rb @@ -394,5 +394,17 @@ describe PostCreator do creator.errors.should be_nil end end + + describe "word_count" do + it "has a word count" do + creator = PostCreator.new(user, title: 'some inspired poetry for a rainy day', raw: 'mary had a little lamb, little lamb, little lamb. mary had a little lamb') + post = creator.create + post.word_count.should == 14 + + post.topic.reload + post.topic.word_count.should == 14 + end + end + end diff --git a/spec/components/post_revisor_spec.rb b/spec/components/post_revisor_spec.rb index 4dadaa2f1..c20a55f9b 100644 --- a/spec/components/post_revisor_spec.rb +++ b/spec/components/post_revisor_spec.rb @@ -222,14 +222,14 @@ describe PostRevisor do describe 'with a new body' do let(:changed_by) { Fabricate(:coding_horror) } - let!(:result) { subject.revise!(changed_by, 'updated body') } + let!(:result) { subject.revise!(changed_by, "lets update the body") } it 'returns true' do result.should be_true end it 'updates the body' do - post.raw.should == 'updated body' + post.raw.should == "lets update the body" end it 'sets the invalidate oneboxes attribute' do @@ -252,6 +252,12 @@ describe PostRevisor do post.versions.first.user.should be_present end + it "updates the word count" do + post.word_count.should == 4 + post.topic.reload + post.topic.word_count.should == 4 + end + context 'second poster posts again quickly' do before do SiteSetting.expects(:ninja_edit_window).returns(1.minute.to_i)