diff --git a/app/assets/javascripts/discourse/dialects/dialect.js b/app/assets/javascripts/discourse/dialects/dialect.js
index e5b90484010..62febca1a7a 100644
--- a/app/assets/javascripts/discourse/dialects/dialect.js
+++ b/app/assets/javascripts/discourse/dialects/dialect.js
@@ -475,7 +475,7 @@ Discourse.Dialect = {
**/
replaceBlock: function(args) {
- this.registerBlock(args.start.toString(), function(block, next) {
+ var fn = function(block, next) {
var linebreaks = dialect.options.traditional_markdown_linebreaks ||
Discourse.SiteSettings.traditional_markdown_linebreaks;
@@ -565,7 +565,13 @@ Discourse.Dialect = {
var emitterResult = args.emitter.call(this, contentBlocks, match, dialect.options);
if (emitterResult) { result.push(emitterResult); }
return result;
- });
+ };
+
+ if (args.priority) {
+ fn.priority = args.priority;
+ }
+
+ this.registerBlock(args.start.toString(), fn);
},
/**
diff --git a/app/assets/javascripts/discourse/dialects/table_dialect.js b/app/assets/javascripts/discourse/dialects/table_dialect.js
new file mode 100644
index 00000000000..d73393023e2
--- /dev/null
+++ b/app/assets/javascripts/discourse/dialects/table_dialect.js
@@ -0,0 +1,52 @@
+var flattenBlocks = function(blocks) {
+ var result = "";
+ blocks.forEach(function(b) {
+ result += b;
+ if (b.trailing) { result += b.trailing; }
+ });
+
+ // bypass newline insertion
+ return result.replace(/[\n\r]/g, " ");
+};
+
+var emitter = function(contents) {
+ // TODO event should be fired when sanitizer loads
+ if (window.html4 && window.html4.ELEMENTS.td !== 1) {
+ window.html4.ELEMENTS.table = 0;
+ window.html4.ELEMENTS.tbody = 1;
+ window.html4.ELEMENTS.td = 1;
+ window.html4.ELEMENTS.thead = 1;
+ window.html4.ELEMENTS.th = 1;
+ window.html4.ELEMENTS.tr = 1;
+ }
+ return ['table', {"class": "md-table"}, flattenBlocks.apply(this, [contents])];
+};
+
+var tableBlock = {
+ start: /(
)([\S\s]*)/igm,
+ stop: /<\/table>/igm,
+ rawContents: true,
+ emitter: emitter,
+ priority: 1
+};
+
+var init = function(){
+ if (Discourse.SiteSettings.allow_html_tables) {
+ Discourse.Markdown.whiteListTag("table");
+ Discourse.Markdown.whiteListTag("table", "class", "md-table");
+ Discourse.Markdown.whiteListTag("tbody");
+ Discourse.Markdown.whiteListTag("thead");
+ Discourse.Markdown.whiteListTag("tr");
+ Discourse.Markdown.whiteListTag("th");
+ Discourse.Markdown.whiteListTag("td");
+ Discourse.Dialect.replaceBlock(tableBlock);
+
+ }
+};
+
+if (Discourse.SiteSettings) {
+ init();
+} else {
+ Discourse.initializer({initialize: init, name: 'enable-html-tables'});
+}
+
diff --git a/app/assets/stylesheets/common/base/topic-post.scss b/app/assets/stylesheets/common/base/topic-post.scss
index 7e2444b2cc3..ff80230d2d2 100644
--- a/app/assets/stylesheets/common/base/topic-post.scss
+++ b/app/assets/stylesheets/common/base/topic-post.scss
@@ -243,4 +243,20 @@ blockquote > *:last-child {
}
+table.md-table {
+ thead {
+ border-bottom: 2px solid lighten($primary, 80%);
+ th {
+ text-align: left;
+ padding-bottom: 2px;
+ }
+ }
+
+ td,th {
+ padding: 3px 3px 3px 10px;
+ }
+ tr {
+ border-bottom: 1px solid lighten($primary, 80%);
+ }
+}
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 4b01b8b3e81..f4d2c718ca5 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -842,6 +842,7 @@ en:
flag_sockpuppets: "If a new user replies to a topic from the same IP address as the new user who started the topic, flag both of their posts as potential spam."
traditional_markdown_linebreaks: "Use traditional linebreaks in Markdown, which require two trailing spaces for a linebreak."
+ allow_html_tables: "Allow tables to be entered in Markdown using HTML tags, TABLE, THEAD, TD, TR, TH are whiteliseted (requires full rebake on all old posts containing tables)"
post_undo_action_window_mins: "Number of minutes users are allowed to undo recent actions on a post (like, flag, etc)."
must_approve_users: "Staff must approve all new user accounts before they are allowed to access the site. WARNING: enabling this for a live site will revoke access for existing non-staff users!"
ga_tracking_code: "Google analytics (ga.js) tracking code code, eg: UA-12345678-9; see http://google.com/analytics"
diff --git a/config/site_settings.yml b/config/site_settings.yml
index eb5755b1d99..716a89332a9 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -386,6 +386,9 @@ posting:
traditional_markdown_linebreaks:
client: true
default: false
+ allow_html_tables:
+ client: true
+ default: false
suppress_reply_directly_below:
client: true
default: true
diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb
index 9416d0a681d..de5e3fbb9cc 100644
--- a/spec/components/pretty_text_spec.rb
+++ b/spec/components/pretty_text_spec.rb
@@ -331,4 +331,30 @@ describe PrettyText do
expect(PrettyText.cook(raw)).to match_html(cooked)
end
+ describe 'tables' do
+ before do
+ PrettyText.reset_context
+ end
+
+ after do
+ PrettyText.reset_context
+ end
+
+ it 'allows table html' do
+ SiteSetting.allow_html_tables = true
+ PrettyText.reset_context
+ table = ""
+ match = ""
+ expect(PrettyText.cook(table)).to match_html(match)
+
+ end
+
+ it 'allows no tables when not enabled' do
+ SiteSetting.allow_html_tables = false
+ table = ""
+ expect(PrettyText.cook(table)).to match_html("")
+ end
+
+ end
+
end
diff --git a/vendor/assets/javascripts/better_markdown.js b/vendor/assets/javascripts/better_markdown.js
index 7a9e74b8c23..a445aacec5c 100644
--- a/vendor/assets/javascripts/better_markdown.js
+++ b/vendor/assets/javascripts/better_markdown.js
@@ -318,13 +318,26 @@
// Build default order from insertion order.
Markdown.buildBlockOrder = function(d) {
- var ord = [];
+ var ord = [[]];
for ( var i in d ) {
if ( i === "__order__" || i === "__call__" )
continue;
- ord.push( i );
+
+ var priority = d[i].priority || 0;
+ ord[priority] = ord[priority] || [];
+ ord[priority].push( i );
}
- d.__order__ = ord;
+
+ var flattend = [];
+ for (i=ord.length-1; i>=0; i--){
+ if (ord[i]) {
+ for (var j=0; j