diff --git a/app/assets/javascripts/discourse/controllers/topic-bulk-actions.js.es6 b/app/assets/javascripts/discourse/controllers/topic-bulk-actions.js.es6 index 4c0ddcfde..3d5285017 100644 --- a/app/assets/javascripts/discourse/controllers/topic-bulk-actions.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic-bulk-actions.js.es6 @@ -1,11 +1,39 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality'; +const _buttons = []; + +function addBulkButton(action, key) { + _buttons.push({ action: action, label: "topics.bulk." + key }); +} + +// Default buttons +addBulkButton('showChangeCategory', 'change_category'); +addBulkButton('deleteTopics', 'delete'); +addBulkButton('closeTopics', 'close_topics'); +addBulkButton('archiveTopics', 'archive_topics'); +addBulkButton('showNotificationLevel', 'notification_level'); +addBulkButton('resetRead', 'reset_read'); + // Modal for performing bulk actions on topics export default Ember.ArrayController.extend(ModalFunctionality, { needs: ['discovery/topics'], + buttonRows: null, onShow: function() { this.set('controllers.modal.modalClass', 'topic-bulk-actions-modal small'); + + const buttonRows = []; + let row = []; + _buttons.forEach(function(b) { + row.push(b); + if (row.length === 4) { + buttonRows.push(row); + row = []; + } + }); + if (row.length) { buttonRows.push(row); } + + this.set('buttonRows', buttonRows); }, perform: function(operation) { @@ -89,3 +117,5 @@ export default Ember.ArrayController.extend(ModalFunctionality, { } } }); + +export { addBulkButton }; diff --git a/app/assets/javascripts/discourse/dialects/code_dialect.js b/app/assets/javascripts/discourse/dialects/code_dialect.js index 9131775a3..0eb5c738a 100644 --- a/app/assets/javascripts/discourse/dialects/code_dialect.js +++ b/app/assets/javascripts/discourse/dialects/code_dialect.js @@ -10,7 +10,7 @@ var acceptableCodeClasses = "perl", "php", "profile", "python", "r", "rib", "rsl", "ruby", "rust", "scala", "smalltalk", "sql", "tex", "text", "vala", "vbscript", "vhdl"]; -var textCodeClasses = ["text", "pre", "plain"]; +var textCodeClasses = ["text", "pre"]; function flattenBlocks(blocks) { var result = ""; @@ -39,17 +39,6 @@ Discourse.Dialect.replaceBlock({ } }); -Discourse.Dialect.replaceBlock({ - start: /(]*\>)([\s\S]*)/igm, - stop: /<\/pre>/igm, - rawContents: true, - skipIfTradtionalLinebreaks: true, - - emitter: function(blockContents) { - return ['p', ['pre', flattenBlocks(blockContents)]]; - } -}); - // Ensure that content in a code block is fully escaped. This way it's not white listed // and we can use HTML and Javascript examples. Discourse.Dialect.on('parseNode', function (event) { @@ -62,6 +51,7 @@ Discourse.Dialect.on('parseNode', function (event) { if (path && path[path.length-1] && path[path.length-1][0] && path[path.length-1][0] === "pre") { regexp = / +$/g; + } else { regexp = /^ +| +$/g; } @@ -69,6 +59,17 @@ Discourse.Dialect.on('parseNode', function (event) { } }); +Discourse.Dialect.replaceBlock({ + start: /(]*\>)([\s\S]*)/igm, + stop: /<\/pre>/igm, + rawContents: true, + skipIfTradtionalLinebreaks: true, + + emitter: function(blockContents) { + return ['p', ['pre', flattenBlocks(blockContents)]]; + } +}); + // Whitelist the language classes var regexpSource = "^lang-(" + acceptableCodeClasses.join('|') + ")$"; Discourse.Markdown.whiteListTag('code', 'class', new RegExp(regexpSource, "i")); diff --git a/app/assets/javascripts/discourse/dialects/dialect.js b/app/assets/javascripts/discourse/dialects/dialect.js index 245e52391..66b72994b 100644 --- a/app/assets/javascripts/discourse/dialects/dialect.js +++ b/app/assets/javascripts/discourse/dialects/dialect.js @@ -12,8 +12,7 @@ var parser = window.BetterMarkdown, initialized = false, emitters = [], hoisted, - preProcessors = [], - escape = Handlebars.Utils.escapeExpression; + preProcessors = []; /** Initialize our dialects for processing. @@ -163,10 +162,6 @@ function hoister(t, target, replacement) { return t; } -function outdent(t) { - return t.replace(/^[ ]{4}/gm, ""); -} - /** An object used for rendering our dialects. @@ -188,46 +183,14 @@ Discourse.Dialect = { cook: function(text, opts) { if (!initialized) { initializeDialects(); } - dialect.options = opts; - // Helps us hoist out HTML hoisted = {}; - // pre-hoist all code-blocks - - //
...
blocks - text = text.replace(/(\n*)
([\s\S]*?)<\/pre>/ig, function(_, before, m) {
-      var hash = md5(m);
-      hoisted[hash] = escape(m.trim());
-      return before + "
" + hash + "
"; - }); - - // fenced blocks - text = text.replace(/(\n*)```([a-z0-9\-]*)\n([\s\S]*?)\n```/g, function(_, before, language, m) { - var hash = md5(m); - hoisted[hash] = escape(m.trim()); - return before + "```" + language + "\n" + hash + "\n```"; - }); - - // inline - text = text.replace(/(^|[^`])`([^`]*?)`([^`]|$)/g, function(_, before, m, after) { - var hash = md5(m); - hoisted[hash] = escape(m); - return before + "`" + hash + "`" + after; - }); - - // markdown blocks - text = text.replace(/(\n*)((?:(?:[ ]{4}).*\n+)+)/g, function(_, before, m) { - var hash = md5(m); - hoisted[hash] = escape(outdent(m).trim()); - return before + " " + hash + "\n"; - }); - - // pre-processors preProcessors.forEach(function(p) { text = p(text, hoister); }); + dialect.options = opts; var tree = parser.toHTMLTree(text, 'Discourse'), result = parser.renderJsonML(parseTree(tree)); @@ -240,11 +203,12 @@ Discourse.Dialect = { // If we hoisted out anything, put it back var keys = Object.keys(hoisted); if (keys.length) { - keys.forEach(function(key) { - result = result.replace(new RegExp(key, "g"), hoisted[key]); + keys.forEach(function(k) { + result = result.replace(new RegExp(k,"g"), hoisted[k]); }); } + hoisted = {}; return result.trim(); }, diff --git a/app/assets/javascripts/discourse/templates/modal/bulk_actions_buttons.hbs b/app/assets/javascripts/discourse/templates/modal/bulk_actions_buttons.hbs index 40ace4bc3..6cd4580a5 100644 --- a/app/assets/javascripts/discourse/templates/modal/bulk_actions_buttons.hbs +++ b/app/assets/javascripts/discourse/templates/modal/bulk_actions_buttons.hbs @@ -1,10 +1,7 @@ -

- - - - -

-

- - -

+{{#each row in buttonRows}} +

+ {{#each button in row}} + {{d-button action=button.action label=button.label}} + {{/each}} +

+{{/each}} diff --git a/app/assets/javascripts/discourse/templates/modal/bulk_change_category.hbs b/app/assets/javascripts/discourse/templates/modal/bulk_change_category.hbs index e67173ff7..6239773fd 100644 --- a/app/assets/javascripts/discourse/templates/modal/bulk_change_category.hbs +++ b/app/assets/javascripts/discourse/templates/modal/bulk_change_category.hbs @@ -1,10 +1,7 @@ -

Choose the new category for the topics:

+

{{i18n "topics.bulk.choose_new_category"}}

{{category-chooser value=newCategoryId}}

-{{#if loading}} -
{{i18n 'loading'}}
-{{else}} - -{{/if}} - +{{#loading-spinner condition=loading}} + {{d-button action="changeCategory" label="topics.bulk.change_category"}} +{{/loading-spinner}} diff --git a/app/assets/javascripts/discourse/views/topic-bulk-actions.js.es6 b/app/assets/javascripts/discourse/views/topic-bulk-actions.js.es6 new file mode 100644 index 000000000..dd6490a71 --- /dev/null +++ b/app/assets/javascripts/discourse/views/topic-bulk-actions.js.es6 @@ -0,0 +1,4 @@ +export default Discourse.ModalBodyView.extend({ + templateName: 'modal/topic-bulk-actions', + title: I18n.t('topics.bulk.actions') +}); diff --git a/app/assets/javascripts/discourse/views/topic_bulk_actions_view.js b/app/assets/javascripts/discourse/views/topic_bulk_actions_view.js deleted file mode 100644 index a72348415..000000000 --- a/app/assets/javascripts/discourse/views/topic_bulk_actions_view.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - Handles the view for the topic bulk actions modal - - @class TopicBulkActionsView - @extends Discourse.ModalBodyView - @namespace Discourse - @module Discourse -**/ -Discourse.TopicBulkActionsView = Discourse.ModalBodyView.extend({ - templateName: 'modal/topic_bulk_actions', - title: I18n.t('topics.bulk.actions') -}); diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 04b0dd324..79e67bb94 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -814,6 +814,7 @@ en: close_topics: "Close Topics" archive_topics: "Archive Topics" notification_level: "Change Notification Level" + choose_new_category: "Choose the new category for the topics:" selected: one: "You have selected 1 topic." other: "You have selected {{count}} topics." diff --git a/lib/topics_bulk_action.rb b/lib/topics_bulk_action.rb index 71defb0e3..337c32eac 100644 --- a/lib/topics_bulk_action.rb +++ b/lib/topics_bulk_action.rb @@ -8,7 +8,12 @@ class TopicsBulkAction end def self.operations - %w(change_category close archive change_notification_level reset_read dismiss_posts delete) + @operations ||= %w(change_category close archive change_notification_level reset_read dismiss_posts delete) + end + + def self.register_operation(name, &block) + operations << name + define_method(name, &block) end def perform! diff --git a/test/javascripts/lib/markdown-test.js.es6 b/test/javascripts/lib/markdown-test.js.es6 index 79dd7f31d..7c6cf4e40 100644 --- a/test/javascripts/lib/markdown-test.js.es6 +++ b/test/javascripts/lib/markdown-test.js.es6 @@ -345,12 +345,12 @@ test("Code Blocks", function() { "

{hello: 'world'}

\n\n

trailing

", "It does not truncate text after a code block."); - cooked("```json\nline 1\n\nline 2\n\n\nline 3\n```", - "

line 1\n\nline 2\n\n\nline 3

", + cooked("```json\nline 1\n\nline 2\n\n\nline3\n```", + "

line 1\n\nline 2\n\n\nline3

", "it maintains new lines inside a code block."); - cooked("hello\nworld\n```json\nline 1\n\nline 2\n\n\nline 3\n```", - "

hello
world

\n\n

line 1\n\nline 2\n\n\nline 3

", + cooked("hello\nworld\n```json\nline 1\n\nline 2\n\n\nline3\n```", + "

hello
world

\n\n

line 1\n\nline 2\n\n\nline3

", "it maintains new lines inside a code block with leading content."); cooked("```ruby\n
hello
\n```",