Support for Fancy topic titles

This commit is contained in:
Robin Ward 2013-02-19 16:08:23 -05:00
parent c0371ff427
commit 836c3a7379
14 changed files with 2139 additions and 12 deletions

View file

@ -8,6 +8,7 @@ gem 'message_bus', path: 'vendor/gems/message_bus'
gem 'rails_multisite', path: 'vendor/gems/rails_multisite' gem 'rails_multisite', path: 'vendor/gems/rails_multisite'
gem 'simple_handlebars_rails', path: 'vendor/gems/simple_handlebars_rails' gem 'simple_handlebars_rails', path: 'vendor/gems/simple_handlebars_rails'
gem 'redcarpet', require: false
gem 'activerecord-postgres-hstore' gem 'activerecord-postgres-hstore'
gem 'acts_as_paranoid' gem 'acts_as_paranoid'
gem 'active_attr' # until we get ActiveModel::Model with Rails 4 gem 'active_attr' # until we get ActiveModel::Model with Rails 4

View file

@ -332,6 +332,7 @@ GEM
ffi (>= 0.5.0) ffi (>= 0.5.0)
rdoc (3.12.1) rdoc (3.12.1)
json (~> 1.4) json (~> 1.4)
redcarpet (2.2.2)
redis (3.0.2) redis (3.0.2)
redis-actionpack (3.2.3) redis-actionpack (3.2.3)
actionpack (~> 3.2.3) actionpack (~> 3.2.3)
@ -494,6 +495,7 @@ DEPENDENCIES
rake rake
rb-fsevent rb-fsevent
rb-inotify (~> 0.8.8) rb-inotify (~> 0.8.8)
redcarpet
redis redis
redis-rails redis-rails
rest-client rest-client

View file

@ -20,7 +20,9 @@ Handlebars.registerHelper 'shorten', (property, options) ->
Handlebars.registerHelper 'topicLink', (property, options) -> Handlebars.registerHelper 'topicLink', (property, options) ->
topic = Ember.Handlebars.get(this, property, options) topic = Ember.Handlebars.get(this, property, options)
"<a href='#{topic.get('lastReadUrl')}' class='title excerptable'>#{Handlebars.Utils.escapeExpression(topic.get('title'))}</a>"
title = topic.get('fancy_title') || topic.get('title')
"<a href='#{topic.get('lastReadUrl')}' class='title excerptable'>#{title}</a>"
Handlebars.registerHelper 'categoryLink', (property, options) -> Handlebars.registerHelper 'categoryLink', (property, options) ->
category = Ember.Handlebars.get(this, property, options) category = Ember.Handlebars.get(this, property, options)

View file

@ -15,9 +15,9 @@
<button class='btn btn-small' {{action cancelEdit target="view"}}><i class='icon-remove'></i></button> <button class='btn btn-small' {{action cancelEdit target="view"}}><i class='icon-remove'></i></button>
{{else}} {{else}}
<h1> <h1>
{{#if view.topic.title}} {{#if view.topic.fancy_title}}
{{view Discourse.TopicStatusView topicBinding="view.topic"}} {{view Discourse.TopicStatusView topicBinding="view.topic"}}
<a href='{{unbound view.topic.url}}'>{{unbound view.topic.title}}</a> <a href='{{unbound view.topic.url}}'>{{{unbound view.topic.fancy_title}}}</a>
{{else}} {{else}}
{{#if view.topic.missing}} {{#if view.topic.missing}}
{{i18n topic.not_found.title}} {{i18n topic.not_found.title}}

View file

@ -280,7 +280,9 @@ window.Discourse.TopicView = Ember.View.extend Discourse.Scrolling,
finishedEdit: -> finishedEdit: ->
if @get('editingTopic') if @get('editingTopic')
topic = @get('topic') topic = @get('topic')
topic.set('title', $('#edit-title').val()) new_val = $('#edit-title').val()
topic.set('title', new_val)
topic.set('fancy_title', new_val)
topic.save() topic.save()
@set('editingTopic', false) @set('editingTopic', false)

View file

@ -138,6 +138,8 @@ class SiteSetting < ActiveRecord::Base
setting(:new_user_period_days, 2) setting(:new_user_period_days, 2)
setting(:title_fancy_entities, true)
client_setting(:educate_until_posts, 2) client_setting(:educate_until_posts, 2)
def self.call_discourse_hub? def self.call_discourse_hub?

View file

@ -5,6 +5,7 @@ require_dependency 'rate_limiter'
require_dependency 'text_sentinel' require_dependency 'text_sentinel'
class Topic < ActiveRecord::Base class Topic < ActiveRecord::Base
include ActionView::Helpers
include RateLimiter::OnCreateRecord include RateLimiter::OnCreateRecord
MAX_SORT_ORDER = 2147483647 MAX_SORT_ORDER = 2147483647
@ -67,7 +68,10 @@ class Topic < ActiveRecord::Base
end end
before_validation do before_validation do
self.title.strip! if self.title.present? if self.title.present?
self.title = sanitize(self.title)
self.title.strip!
end
end end
before_create do before_create do
@ -115,6 +119,14 @@ class Topic < ActiveRecord::Base
errors.add(:title, I18n.t(:has_already_been_used)) if finder.exists? errors.add(:title, I18n.t(:has_already_been_used)) if finder.exists?
end end
def fancy_title
return title unless SiteSetting.title_fancy_entities?
# We don't always have to require this, if fancy is disabled
require 'redcarpet'
Redcarpet::Render::SmartyPants.render(title)
end
def title_quality def title_quality
# We don't care about quality on private messages # We don't care about quality on private messages

View file

@ -3,8 +3,20 @@ require_dependency 'age_words'
class BasicTopicSerializer < ApplicationSerializer class BasicTopicSerializer < ApplicationSerializer
include ActionView::Helpers include ActionView::Helpers
attributes :id, :title, :reply_count, :posts_count, :highest_post_number, :image_url, :created_at, attributes :id,
:last_posted_at, :age, :unseen, :last_read_post_number, :unread, :new_posts :title,
:fancy_title,
:reply_count,
:posts_count,
:highest_post_number,
:image_url,
:created_at,
:last_posted_at,
:age,
:unseen,
:last_read_post_number,
:unread,
:new_posts
def age def age
AgeWords.age_words(Time.now - (object.created_at || Time.now)) AgeWords.age_words(Time.now - (object.created_at || Time.now))

View file

@ -1,6 +1,12 @@
class TopicLinkSerializer < ApplicationSerializer class TopicLinkSerializer < ApplicationSerializer
attributes :url, :title, :internal, :reflection, :clicks, :user_id attributes :url,
:title,
:fancy_title,
:internal,
:reflection,
:clicks,
:user_id
def url def url
object['url'] object['url']
@ -10,6 +16,10 @@ class TopicLinkSerializer < ApplicationSerializer
object['title'] object['title']
end end
def fancy_title
object['fancy_title']
end
def internal def internal
object['internal'] == 't' object['internal'] == 't'
end end
@ -25,6 +35,7 @@ class TopicLinkSerializer < ApplicationSerializer
def user_id def user_id
object['user_id'].to_i object['user_id'].to_i
end end
def include_user_id? def include_user_id?
object['user_id'].present? object['user_id'].present?
end end

View file

@ -1,6 +1,16 @@
class TopicListItemSerializer < BasicTopicSerializer class TopicListItemSerializer < BasicTopicSerializer
attributes :views, :like_count, :visible, :pinned, :closed, :archived, :last_post_age, :starred, :has_best_of, :archetype, :slug attributes :views,
:like_count,
:visible,
:pinned,
:closed,
:archived,
:last_post_age,
:starred,
:has_best_of,
:archetype,
:slug
has_one :category has_one :category
has_many :posters, serializer: TopicPosterSerializer, embed: :objects has_many :posters, serializer: TopicPosterSerializer, embed: :objects

View file

@ -4,6 +4,7 @@ class TopicViewSerializer < ApplicationSerializer
def self.topic_attributes def self.topic_attributes
[:id, [:id,
:title, :title,
:fancy_title,
:posts_count, :posts_count,
:created_at, :created_at,
:views, :views,

View file

@ -342,12 +342,11 @@ en:
email_time_window_mins: "How many minutes we wait before sending a user mail, to give them a chance to see it first." email_time_window_mins: "How many minutes we wait before sending a user mail, to give them a chance to see it first."
flush_timings_secs: "How frequently we flush timing data to the server, in seconds." flush_timings_secs: "How frequently we flush timing data to the server, in seconds."
max_word_length: "The maximum word length in a topic title" max_word_length: "The maximum word length in a topic title"
title_min_entropy: "The minimum entropy for a topic title" title_min_entropy: "The minimum entropy for a topic title"
body_min_entropy: "The minimum entropy for post body" body_min_entropy: "The minimum entropy for post body"
new_user_period_days: "How long a user is highlighted as being new, in days." new_user_period_days: "How long a user is highlighted as being new, in days."
title_fancy_entities: "Convert fancy HTML entities in topic titles"
notification_types: notification_types:
mentioned: "%{display_username} mentioned you in %{link}" mentioned: "%{display_username} mentioned you in %{link}"

File diff suppressed because it is too large Load diff

View file

@ -109,6 +109,41 @@ describe Topic do
end end
context 'html in title' do
let(:topic) { Fabricate(:topic, title: "<script>alert('title')</script> is my topic title" ) }
it "should escape the HTML" do
topic.title.should == "is my topic title"
end
end
context 'fancy title' do
let(:topic) { Fabricate(:topic, title: "\"this topic\" -- has ``fancy stuff''" ) }
context 'title_fancy_entities disabled' do
before do
SiteSetting.stubs(:title_fancy_entities).returns(false)
end
it "doesn't change the title to add entities" do
topic.fancy_title.should == topic.title
end
end
context 'title_fancy_entities enabled' do
before do
SiteSetting.stubs(:title_fancy_entities).returns(true)
end
it "converts the title to have fancy entities" do
topic.fancy_title.should == "&ldquo;this topic&rdquo; &ndash; has &ldquo;fancy stuff&rdquo;"
end
end
end
context 'message bus' do context 'message bus' do
it 'calls the message bus observer after create' do it 'calls the message bus observer after create' do