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 a02cf063b22..86e2fd72d75 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 @@ -1601,4 +1601,74 @@ var bar = 'bar'; assert.cookedOptions(" -->asd ", enabledTypographer, "
–>asd
"); assert.cookedOptions(" -->asd", enabledTypographer, "–>asd
"); }); + + test("default typhographic replacements", function (assert) { + const enabledTypographer = { + siteSettings: { enable_markdown_typographer: true }, + }; + + assert.cookedOptions("(bad)", enabledTypographer, "(bad)
"); + assert.cookedOptions("+-5", enabledTypographer, "±5
"); + assert.cookedOptions( + "test.. test... test..... test?..... test!....", + enabledTypographer, + "test… test… test… test?.. test!..
" + ); + assert.cookedOptions( + "!!!!!! ???? ,,", + enabledTypographer, + "!!! ??? ,
" + ); + assert.cookedOptions( + "!!!!!! ???? ,,", + enabledTypographer, + "!!! ??? ,
" + ); + assert.cookedOptions("(tm) (TM)", enabledTypographer, "™ ™
"); + }); + + test("default typhographic replacements - dashes", function (assert) { + const enabledTypographer = { + siteSettings: { enable_markdown_typographer: true }, + }; + + assert.cookedOptions( + "---markdownit --- super---", + enabledTypographer, + "—markdownit — super—
" + ); + assert.cookedOptions( + "markdownit---awesome", + enabledTypographer, + "markdownit—awesome
" + ); + assert.cookedOptions("abc ----", enabledTypographer, "abc ----
"); + assert.cookedOptions( + "--markdownit -- super--", + enabledTypographer, + "–markdownit – super–
" + ); + assert.cookedOptions( + "markdownit--awesome", + enabledTypographer, + "markdownit–awesome
" + ); + assert.cookedOptions("1---2---3", enabledTypographer, "1—2—3
"); + assert.cookedOptions("1--2--3", enabledTypographer, "1–2–3
"); + assert.cookedOptions( + "1 – – 3
", + enabledTypographer, + "1 – – 3
" + ); + }); + + test("disabled typhographic replacements", function (assert) { + const enabledTypographer = { + siteSettings: { enable_markdown_typographer: true }, + }; + + assert.cookedOptions("(c) (C)", enabledTypographer, "(c) (C)
"); + assert.cookedOptions("(r) (R)", enabledTypographer, "(r) (R)
"); + assert.cookedOptions("(p) (P)", enabledTypographer, "(p) (P)
"); + }); }); diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown/custom-typographer-replacements.js b/app/assets/javascripts/pretty-text/engines/discourse-markdown/custom-typographer-replacements.js new file mode 100644 index 00000000000..220e75fe275 --- /dev/null +++ b/app/assets/javascripts/pretty-text/engines/discourse-markdown/custom-typographer-replacements.js @@ -0,0 +1,95 @@ +// Simple typographic replacements +// +// (tm) (TM) → ™ +// +- → ± +// ... → … (also ?.... → ?.., !.... → !..) +// ???????? → ???, !!!!! → !!!, `,,` → `,` +// -- → –, --- → — +// --> <-- -> <- to → ← → ← +// +// Disabled replacements: +// +// (c) (C) → © +// (r) (R) → ® +// (p) (P) -> § + +let RARE_RE = /\+-|\.\.|\?\?\?\?|!!!!|,,|--|-->|<--|->|<-/; +let SCOPED_ABBR_RE = /\((tm)\)/gi; + +function replaceScoped(inlineTokens) { + let i, token; + + for (i = inlineTokens.length - 1; i >= 0; i--) { + token = inlineTokens[i]; + if (token.type === "text") { + token.content = token.content.replace(SCOPED_ABBR_RE, () => { + return "™"; + }); + } + } +} + +function replaceRare(inlineTokens) { + let i, + token, + inside_autolink = 0; + + for (i = inlineTokens.length - 1; i >= 0; i--) { + token = inlineTokens[i]; + + if (token.type === "text" && !inside_autolink) { + if (RARE_RE.test(token.content)) { + token.content = token.content + .replace(/\+-/g, "±") + // Custom arrows + .replace(/(^|\s)-{1,2}>(\s|$)/gm, "\u0020\u2192\u0020") + .replace(/(^|\s)<-{1,2}(\s|$)/gm, "\u0020\u2190\u0020") + // .., ..., ....... -> … + // but ?..... & !..... -> ?.. & !.. + .replace(/\.{2,}/g, "…") + .replace(/([?!])…/g, "$1..") + .replace(/([?!]){4,}/g, "$1$1$1") + .replace(/,{2,}/g, ",") + // em-dash + .replace(/(^|[^-])---(?=[^-]|$)/gm, "$1\u2014") + // en-dash + .replace(/(^|\s)--(?=\s|$)/gm, "$1\u2013") + .replace(/(^|[^-\s])--(?=[^-\s]|$)/gm, "$1\u2013"); + } + } + + if (token.type === "link_open" && token.info === "auto") { + inside_autolink--; + } + + if (token.type === "link_close" && token.info === "auto") { + inside_autolink++; + } + } +} + +function replace(state) { + let blkIdx; + + for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { + if (state.tokens[blkIdx].type !== "inline") { + continue; + } + + if (SCOPED_ABBR_RE.test(state.tokens[blkIdx].content)) { + replaceScoped(state.tokens[blkIdx].children); + } + + if (RARE_RE.test(state.tokens[blkIdx].content)) { + replaceRare(state.tokens[blkIdx].children); + } + } +} + +export function setup(helper) { + helper.registerPlugin((md) => { + if (md.options.typographer) { + md.core.ruler.at("replacements", replace); + } + }); +} diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown/typographer-arrows.js b/app/assets/javascripts/pretty-text/engines/discourse-markdown/typographer-arrows.js deleted file mode 100644 index b63f52f8969..00000000000 --- a/app/assets/javascripts/pretty-text/engines/discourse-markdown/typographer-arrows.js +++ /dev/null @@ -1,32 +0,0 @@ -function replaceArrows(state) { - for (let i = 0; i < state.tokens.length; i++) { - let token = state.tokens[i]; - - if (token.type !== "inline") { - continue; - } - - const arrowsRegexp = /-->|<--|->|<-/; - if (arrowsRegexp.test(token.content)) { - for (let ci = 0; ci < token.children.length; ci++) { - let child = token.children[ci]; - - if (child.type === "text") { - if (arrowsRegexp.test(child.content)) { - child.content = child.content - .replace(/(^|\s)-{1,2}>(\s|$)/gm, "\u0020\u2192\u0020") - .replace(/(^|\s)<-{1,2}(\s|$)/gm, "\u0020\u2190\u0020"); - } - } - } - } - } -} - -export function setup(helper) { - helper.registerPlugin((md) => { - if (md.options.typographer) { - md.core.ruler.before("replacements", "typographer-arrow", replaceArrows); - } - }); -} diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb index c0ae2ac6a05..e154b54bb9f 100644 --- a/spec/components/pretty_text_spec.rb +++ b/spec/components/pretty_text_spec.rb @@ -1442,10 +1442,10 @@ HTML it 'supports typographer' do SiteSetting.enable_markdown_typographer = true - expect(PrettyText.cook('(tm)')).to eq('™
') + expect(PrettyText.cook('->')).to eq('→
') SiteSetting.enable_markdown_typographer = false - expect(PrettyText.cook('(tm)')).to eq('(tm)
') + expect(PrettyText.cook('->')).to eq('->
') end it 'uses quotation marks from site settings' do