diff --git a/app/assets/javascripts/discourse/tests/unit/lib/pretty-text-test.js b/app/assets/javascripts/discourse/tests/unit/lib/pretty-text-test.js index 840eb6fdb29..33215f44cdb 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/pretty-text-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/pretty-text-test.js @@ -1493,6 +1493,45 @@ var bar = 'bar'; ); }); + test("customizing markdown-it rules", function (assert) { + assert.cookedOptions( + "**bold**", + { markdownItRules: [] }, + "
**bold**
", + "does not apply bold markdown when rule is not enabled" + ); + + assert.cookedOptions( + "**bold**", + { markdownItRules: ["emphasis"] }, + "bold
", + "applies bold markdown when rule is enabled" + ); + }); + + test("features override", function (assert) { + assert.cookedOptions( + ":grin: @sam", + { featuresOverride: [] }, + ":grin: @sam
", + "does not cook emojis when Discourse markdown engines are disabled" + ); + + assert.cookedOptions( + ":grin: @sam", + { featuresOverride: ["emoji"] }, + ' @sam
:grin: @sam
`, + "cooks mentions when only the mentions markdown engine is enabled" + ); + }); + test("emoji", function (assert) { assert.cooked( ":smile:", diff --git a/app/assets/javascripts/pretty-text/addon/engines/discourse-markdown-it.js b/app/assets/javascripts/pretty-text/addon/engines/discourse-markdown-it.js index 80e5f3338a9..5d9ca251bd5 100644 --- a/app/assets/javascripts/pretty-text/addon/engines/discourse-markdown-it.js +++ b/app/assets/javascripts/pretty-text/addon/engines/discourse-markdown-it.js @@ -353,6 +353,12 @@ export function setup(opts, siteSettings, state) { } }); + if (opts.featuresOverride) { + Object.keys(opts.features).forEach((feature) => { + opts.features[feature] = opts.featuresOverride.includes(feature); + }); + } + let copy = {}; Object.keys(opts).forEach((entry) => { copy[entry] = opts[entry]; @@ -371,14 +377,22 @@ export function setup(opts, siteSettings, state) { enableDiffhtmlPreview: siteSettings.enable_diffhtml_preview, }; - opts.engine = window.markdownit({ + const markdownitOpts = { discourse: opts.discourse, html: true, breaks: !siteSettings.traditional_markdown_linebreaks, xhtmlOut: false, linkify: siteSettings.enable_markdown_linkify, typographer: siteSettings.enable_markdown_typographer, - }); + }; + + if (opts.discourse.markdownItRules !== undefined) { + opts.engine = window + .markdownit("zero", markdownitOpts) // Preset for "zero", https://github.com/markdown-it/markdown-it/blob/master/lib/presets/zero.js + .enable(opts.discourse.markdownItRules); + } else { + opts.engine = window.markdownit(markdownitOpts); + } const quotation_marks = siteSettings.markdown_typographer_quotation_marks; if (quotation_marks) { diff --git a/app/assets/javascripts/pretty-text/addon/pretty-text.js b/app/assets/javascripts/pretty-text/addon/pretty-text.js index 12edf332954..6654f9cc244 100644 --- a/app/assets/javascripts/pretty-text/addon/pretty-text.js +++ b/app/assets/javascripts/pretty-text/addon/pretty-text.js @@ -38,6 +38,8 @@ export function buildOptions(state) { customEmojiTranslation, watchedWordsReplace, watchedWordsLink, + featuresOverride, + markdownItRules, } = state; let features = {}; @@ -76,6 +78,8 @@ export function buildOptions(state) { disableEmojis, watchedWordsReplace, watchedWordsLink, + featuresOverride, + markdownItRules, }; // note, this will mutate options due to the way the API is designed diff --git a/lib/pretty_text.rb b/lib/pretty_text.rb index fd0b26e8ba0..9f08e803001 100644 --- a/lib/pretty_text.rb +++ b/lib/pretty_text.rb @@ -156,6 +156,17 @@ module PrettyText end end + # Acceptable options: + # + # disable_emojis - Disables the emoji markdown engine. + # features - A hash where the key is the markdown feature name and the value is a boolean to enable/disable the markdown feature. + # The hash is merged into the default features set in pretty-text.js which can be used to add new features or disable existing features. + # features_override - An array of markdown feature names to override the default markdown feature set. Currently used by plugins to customize what features should be enabled + # when rendering markdown. + # markdown_it_rules - An array of markdown rule names which will be applied to the markdown-it engine. Currently used by plugins to customize what markdown-it rules should be + # enabled when rendering markdown. + # topic_id - Topic id for the post being cooked. + # user_id - User id for the post being cooked. def self.markdown(text, opts = {}) # we use the exact same markdown converter as the client # TODO: use the same extensions on both client and server (in particular the template for mentions) @@ -175,6 +186,8 @@ module PrettyText __paths = #{paths_json}; __optInput.getURL = __getURL; #{"__optInput.features = #{opts[:features].to_json};" if opts[:features]} + #{"__optInput.featuresOverride = #{opts[:features_override].to_json};" if opts[:features_override]} + #{"__optInput.markdownItRules = #{opts[:markdown_it_rules].to_json};" if opts[:markdown_it_rules]} __optInput.getCurrentUser = __getCurrentUser; __optInput.lookupAvatar = __lookupAvatar; __optInput.lookupPrimaryUserGroup = __lookupPrimaryUserGroup; @@ -190,8 +203,8 @@ module PrettyText __optInput.watchedWordsLink = #{WordWatcher.word_matcher_regexps(:link).to_json}; JS - if opts[:topicId] - buffer << "__optInput.topicId = #{opts[:topicId].to_i};\n" + if opts[:topic_id] + buffer << "__optInput.topicId = #{opts[:topic_id].to_i};\n" end if opts[:user_id] @@ -279,10 +292,6 @@ module PrettyText def self.cook(text, opts = {}) options = opts.dup - - # we have a minor inconsistency - options[:topicId] = opts[:topic_id] - working_text = text.dup sanitized = markdown(working_text, options) diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb index 740d6de4b16..7760458d03a 100644 --- a/spec/components/pretty_text_spec.rb +++ b/spec/components/pretty_text_spec.rb @@ -2072,4 +2072,32 @@ HTML expect(cooked).to match_html(html) end + + context "customizing markdown-it rules" do + it 'customizes the markdown-it rules correctly' do + cooked = PrettyText.cook('This is some text **bold**', markdown_it_rules: []) + + expect(cooked).to eq("This is some text **bold**
") + + cooked = PrettyText.cook('This is some text **bold**', markdown_it_rules: ["emphasis"]) + + expect(cooked).to eq("This is some text bold
") + end + end + + context "enabling/disabling features" do + it "allows features to be overriden" do + cooked = PrettyText.cook(':grin: @mention', features_override: []) + + expect(cooked).to eq(":grin: @mention
") + + cooked = PrettyText.cook(':grin: @mention', features_override: ["emoji"]) + + expect(cooked).to eq(" @mention
:grin: @mention
") + end + end end