From bb908d59135095502ba954ab1196942bd01978ac Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 21 Jun 2013 11:36:33 -0400 Subject: [PATCH] Email parsing uses Traditional Markdown Linebreaks by default. Added JS tests for line breaks. --- .../discourse/components/markdown.js | 3 ++- app/models/post.rb | 4 +-- lib/email/receiver.rb | 3 ++- lib/post_creator.rb | 24 ++++++++++-------- spec/components/email/receiver_spec.rb | 16 ++++-------- spec/components/post_creator_spec.rb | 14 +++++++++++ test/javascripts/components/markdown_test.js | 25 +++++++++++++++++-- 7 files changed, 62 insertions(+), 27 deletions(-) diff --git a/app/assets/javascripts/discourse/components/markdown.js b/app/assets/javascripts/discourse/components/markdown.js index 0fc5f0a19a3..8f67853f53f 100644 --- a/app/assets/javascripts/discourse/components/markdown.js +++ b/app/assets/javascripts/discourse/components/markdown.js @@ -122,7 +122,8 @@ Discourse.Markdown = { }); // newline prediction in trivial cases - if (!Discourse.SiteSettings.traditional_markdown_linebreaks) { + var linebreaks = opts.traditional_markdown_linebreaks || Discourse.SiteSettings.traditional_markdown_linebreaks; + if (!linebreaks) { converter.hooks.chain("preConversion", function(text) { return text.replace(/(^[\w<][^\n]*\n+)/gim, function(t) { if (t.match(/\n{2}/gim)) return t; diff --git a/app/models/post.rb b/app/models/post.rb index 95b4070b5f0..3751b0949c3 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -34,8 +34,8 @@ class Post < ActiveRecord::Base validates_with ::Validators::PostValidator - # We can pass a hash of image sizes when saving to prevent crawling those images - attr_accessor :image_sizes, :quoted_post_numbers, :no_bump, :invalidate_oneboxes + # We can pass several creating options to a post via attributes + attr_accessor :image_sizes, :quoted_post_numbers, :no_bump, :invalidate_oneboxes, :cooking_options SHORT_POST_CHARS = 1200 diff --git a/lib/email/receiver.rb b/lib/email/receiver.rb index fb43d77f594..0ddc2a2ad13 100644 --- a/lib/email/receiver.rb +++ b/lib/email/receiver.rb @@ -82,7 +82,8 @@ module Email creator = PostCreator.new(email_log.user, raw: @body, topic_id: @email_log.topic_id, - reply_to_post_number: @email_log.post.post_number) + reply_to_post_number: @email_log.post.post_number, + cooking_options: {traditional_markdown_linebreaks: true}) creator.create end diff --git a/lib/post_creator.rb b/lib/post_creator.rb index f8a45276ad0..2507ec4a3c1 100644 --- a/lib/post_creator.rb +++ b/lib/post_creator.rb @@ -32,11 +32,13 @@ class PostCreator # target_usernames - comma delimited list of usernames for membership (private message) # target_group_names - comma delimited list of groups for membership (private message) # meta_data - Topic meta data hash + # cooking_options - Options for rendering the text + # def initialize(user, opts) # TODO: we should reload user in case it is tainted, should take in a user_id as opposed to user # If we don't do this we introduce a rather risky dependency @user = user - @opts = opts + @opts = opts || {} @spam = false end @@ -87,14 +89,17 @@ class PostCreator end post.post_number ||= Topic.next_post_number(post.topic_id, post.reply_to_post_number.present?) - post.cooked ||= post.cook(post.raw, topic_id: post.topic_id) + + cooking_options = post.cooking_options || {} + cooking_options[:topic_id] = post.topic_id + + post.cooked ||= post.cook(post.raw, cooking_options) post.sort_order = post.post_number DiscourseEvent.trigger(:before_create_post, post) post.last_version_at ||= Time.now end def self.after_create_tasks(post) - Rails.logger.info (">" * 30) + "#{post.no_bump} #{post.created_at}" # 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 @@ -183,14 +188,13 @@ class PostCreator user: @user, reply_to_post_number: @opts[:reply_to_post_number]) - post.post_type = @opts[:post_type] if @opts[:post_type].present? - post.no_bump = @opts[:no_bump] if @opts[:no_bump].present? - post.extract_quoted_post_numbers - post.acting_user = @opts[:acting_user] if @opts[:acting_user].present? - post.created_at = Time.zone.parse(@opts[:created_at].to_s) if @opts[:created_at].present? + # Attributes we pass through to the post instance if present + [:post_type, :no_bump, :cooking_options, :image_sizes, :acting_user, :invalidate_oneboxes].each do |a| + post.send("#{a}=", @opts[a]) if @opts[a].present? + end - post.image_sizes = @opts[:image_sizes] if @opts[:image_sizes].present? - post.invalidate_oneboxes = @opts[:invalidate_oneboxes] if @opts[:invalidate_oneboxes].present? + post.extract_quoted_post_numbers + post.created_at = Time.zone.parse(@opts[:created_at].to_s) if @opts[:created_at].present? @post = post end diff --git a/spec/components/email/receiver_spec.rb b/spec/components/email/receiver_spec.rb index 15acc2e327e..ab6758b441f 100644 --- a/spec/components/email/receiver_spec.rb +++ b/spec/components/email/receiver_spec.rb @@ -40,16 +40,6 @@ stripped from my reply?") end end - describe "with a content boundary" do - let(:bounded_email) { File.read("#{Rails.root}/spec/fixtures/emails/boundary.eml") } - let(:receiver) { Email::Receiver.new(bounded_email) } - - it "does something" do - receiver.process - expect(receiver.body).to eq("I'll look into it, thanks!") - end - end - describe "with a valid email" do let(:reply_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" } let(:valid_reply) { File.read("#{Rails.root}/spec/fixtures/emails/valid_reply.eml") } @@ -83,7 +73,11 @@ greatest show ever created. Everyone should watch it. EmailLog.expects(:for).with(reply_key).returns(email_log) creator = mock - PostCreator.expects(:new).with(instance_of(User), has_entry(raw: reply_body)).returns(creator) + PostCreator.expects(:new).with(instance_of(User), + has_entries(raw: reply_body, + cooking_options: {traditional_markdown_linebreaks: true})) + .returns(creator) + creator.expects(:create) end diff --git a/spec/components/post_creator_spec.rb b/spec/components/post_creator_spec.rb index a3b550f60cd..84c2a64e860 100644 --- a/spec/components/post_creator_spec.rb +++ b/spec/components/post_creator_spec.rb @@ -253,6 +253,20 @@ describe PostCreator do end + context "cooking options" do + let(:raw) { "this is my awesome message body hello world" } + + it "passes the cooking options through correctly" do + creator = PostCreator.new(user, + title: 'hi there welcome to my topic', + raw: raw, + cooking_options: { traditional_markdown_linebreaks: true }) + + Post.any_instance.expects(:cook).with(raw, has_key(:traditional_markdown_linebreaks)).returns(raw) + creator.create + end + end + # integration test ... minimise db work context 'private message' do let(:target_user1) { Fabricate(:coding_horror) } diff --git a/test/javascripts/components/markdown_test.js b/test/javascripts/components/markdown_test.js index 4096e24fb7c..97b49175865 100644 --- a/test/javascripts/components/markdown_test.js +++ b/test/javascripts/components/markdown_test.js @@ -1,6 +1,10 @@ /*global sanitizeHtml:true */ -module("Discourse.Markdown"); +module("Discourse.Markdown", { + setup: function() { + Discourse.SiteSettings.traditional_markdown_linebreaks = false; + } +}); var cooked = function(input, expected, text) { equal(Discourse.Markdown.cook(input, {mentionLookup: false }), expected, text); @@ -12,7 +16,24 @@ var cookedOptions = function(input, opts, expected, text) { test("basic cooking", function() { cooked("hello", "

hello

", "surrounds text with paragraphs"); - cooked("1\n2\n3", "

1
\n2
\n3

", "automatically handles trivial newlines"); + +}); + +test("Line Breaks", function() { + + var input = "1\n2\n3"; + cooked(input, "

1
\n2
\n3

", "automatically handles trivial newlines"); + + var traditionalOutput = "

1\n2\n3

"; + + cookedOptions(input, + {traditional_markdown_linebreaks: true}, + traditionalOutput, + "It supports traditional markdown via an option") + + Discourse.SiteSettings.traditional_markdown_linebreaks = true; + cooked(input, traditionalOutput, "It supports traditional markdown via a Site Setting") + }); test("Links", function() {