REFACTOR: Remove cases other than [], [x], and [X] (#23)

REFACTOR: Remove cases other than , , and (#23)

diff --git a/assets/javascripts/discourse/initializers/checklist.js.es6 b/assets/javascripts/discourse/initializers/checklist.js.es6
index cbaf2ea..02c9808 100644
--- a/assets/javascripts/discourse/initializers/checklist.js.es6
+++ b/assets/javascripts/discourse/initializers/checklist.js.es6
@@ -26,7 +26,10 @@ export function checklistSyntax($elem, postDecorator) {
   $boxes.each((idx, val) => {
     $(val).click(ev => {
       const $box = $(ev.currentTarget);
-      const newValue = $box.hasClass("checked") ? "[ ]" : "[\\*]";
+
+      if ($box.hasClass("permanent")) return;
+
+      const newValue = $box.hasClass("checked") ? "[ ]" : "[x]";
 
       $box.after(iconHTML("spinner", { class: "fa-spin" })).hide();
 
diff --git a/assets/javascripts/lib/discourse-markdown/checklist.js.es6 b/assets/javascripts/lib/discourse-markdown/checklist.js.es6
index d4d4349..7b9a430 100644
--- a/assets/javascripts/lib/discourse-markdown/checklist.js.es6
+++ b/assets/javascripts/lib/discourse-markdown/checklist.js.es6
@@ -1,15 +1,11 @@
-const REGEX = /\[(\s?|_|-|x|\*)\]/gi;
+const REGEX = /\[(\s?|x|X)\]/g;
 
 function getClasses(str) {
-  switch (str.toLowerCase()) {
+  switch (str) {
     case "x":
-      return "checked fa fa-check-square fa-fw";
-    case "*":
       return "checked fa fa-check-square-o fa-fw";
-    case "-":
-      return "fa fa-minus-square-o fa-fw";
-    case "_":
-      return "fa fa-square fa-fw";
+    case "X":
+      return "checked permanent fa fa-check-square fa-fw";
     default:
       return "fa fa-square-o fa-fw";
   }
@@ -98,10 +94,8 @@ export function setup(helper) {
   helper.whiteList([
     "span.chcklst-stroked",
     "span.chcklst-box fa fa-square-o fa-fw",
-    "span.chcklst-box fa fa-square fa-fw",
-    "span.chcklst-box fa fa-minus-square-o fa-fw",
-    "span.chcklst-box checked fa fa-check-square fa-fw",
-    "span.chcklst-box checked fa fa-check-square-o fa-fw"
+    "span.chcklst-box checked fa fa-check-square-o fa-fw",
+    "span.chcklst-box checked permanent fa fa-check-square fa-fw"
   ]);
 
   helper.registerPlugin(md =>
diff --git a/assets/stylesheets/checklist.scss b/assets/stylesheets/checklist.scss
index 2e9e81a..def9d76 100644
--- a/assets/stylesheets/checklist.scss
+++ b/assets/stylesheets/checklist.scss
@@ -27,11 +27,6 @@ span.chcklst-box {
         "M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48z"
       );
     }
-    &.fa-minus-square-o {
-      @include checklist-svg-icon(
-        "M108 284c-6.6 0-12-5.4-12-12v-32c0-6.6 5.4-12 12-12h232c6.6 0 12 5.4 12 12v32c0 6.6-5.4 12-12 12H108zM448 80v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V80c0-26.5 21.5-48 48-48h352c26.5 0 48 21.5 48 48zm-48 346V86c0-3.3-2.7-6-6-6H54c-3.3 0-6 2.7-6 6v340c0 3.3 2.7 6 6 6h340c3.3 0 6-2.7 6-6z"
-      );
-    }
   }
 
   &.checked {
diff --git a/lib/checklist_syntax_migrator.rb b/lib/checklist_syntax_migrator.rb
new file mode 100644
index 0000000..a815bcc
--- /dev/null
+++ b/lib/checklist_syntax_migrator.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+class ChecklistSyntaxMigrator
+  CHECKBOX_REGEX = /^( {0,3})\[(_|-|\*|\\\*)\]/
+  CODE_BLOCK_REGEX = /^ {0,3}`‍``/
+  QUOTE_START_REGEX = /^ {0,3}\[quote/
+  QUOTE_END_REGEX = /^ {0,3}\[\/quote\]/
+
+  def initialize(post)
+    @post = post
+  end
+
+  def update_syntax!
+    lines = @post.raw.split("\n")
+    in_code = false
+    in_quote = false
+    lines.each_with_index do |line, index|
+      if line.match(CODE_BLOCK_REGEX)
+        in_code = !in_code
+      elsif line.match(QUOTE_START_REGEX)
+        in_quote = true
+      elsif line.match(QUOTE_END_REGEX)
+        in_quote = false
+      else
+        next if in_code || in_quote
+
+        lines[index] = line.gsub(CHECKBOX_REGEX) { "#{$1}[x]" }
+      end
+    end
+    new_raw = lines.join("\n")
+
+    return if new_raw == @post.raw
+    @post.raw = new_raw
+    @post.save!
+  end
+end
diff --git a/lib/tasks/checklist.rake b/lib/tasks/checklist.rake
new file mode 100644
index 0000000..1f436e5
--- /dev/null
+++ b/lib/tasks/checklist.rake
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+desc "Convert old style checkbox markdown to new style"
+task 'discourse_checklist:migrate_old_syntax' => :environment do |t|
+  puts "Updating checklist syntax on all posts..."
+
+  Post.raw_match("[").find_each(batch_size: 50) do |post|
+    ChecklistSyntaxMigrator.new(post).update_syntax!
+  end
+
+  puts "", "Done!"
+end
diff --git a/plugin.rb b/plugin.rb
index 275b961..74f4fa6 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -11,3 +11,9 @@ enabled_site_setting :checklist_enabled
 register_asset 'stylesheets/checklist.scss'
 
 register_svg_icon 'spinner' if respond_to?(:register_svg_icon)
+
+after_initialize do
+  [
+    "../lib/checklist_syntax_migrator.rb"
+  ].each { |path| load File.expand_path(path, __FILE__) }
+end
diff --git a/spec/lib/checklist_syntax_migrator_spec.rb b/spec/lib/checklist_syntax_migrator_spec.rb
new file mode 100644
index 0000000..dbf5534
--- /dev/null
+++ b/spec/lib/checklist_syntax_migrator_spec.rb
@@ -0,0 +1,118 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe ChecklistSyntaxMigrator do
+  before do
+    SiteSetting.allow_uncategorized_topics = true
+  end
+  let(:topic) { Fabricate(:topic) }
+  let(:post_args) do
+    { user: topic.user, topic: topic }
+  end
+
+  def post_with_body(body)
+    args = post_args.merge(raw: body)
+    Fabricate.build(:post, args)
+  end
+
+  it "replaces instances of the old checkbox instance, with the new syntax" do
+    body = "[-] 1\n[_] 2\n[*] 3\n[\\*] 4"
+    post = post_with_body(body)
+
+    ChecklistSyntaxMigrator.new(post).update_syntax!
+
+    expected = "[x] 1\n[x] 2\n[x] 3\n[x] 4"
+    expect(post.reload.raw).to eq(expected)
+  end
+
+  it "does not replace if more than 3 spaces are before a checkbox" do
+    body = "    [\*]\n      [-]"
+    post = post_with_body(body)
+    post.save
+
+    ChecklistSyntaxMigrator.new(post).update_syntax!
+    expect(post.reload.raw).to eq(body)
+  end
+
+  it "does not replace checkboxes after text" do
+    body = "what about this? [\*]"
+    post = post_with_body(body)
+    post.save
+
+    ChecklistSyntaxMigrator.new(post).update_syntax!
+    expect(post.reload.raw).to eq(body)
+  end
+
+  it "handles each line independently" do
+    body = "[-] replace that, \n this wont be changed! [\*]"
+    post = post_with_body(body)
+
+    ChecklistSyntaxMigrator.new(post).update_syntax!
+
+    expected = "[x] replace that, \n this wont be changed! [\*]"
+    expect(post.reload.raw).to eq(expected)
+  end
+
+  it "allows spaces 0,1,2 and 3 spaces before" do
+    body = "[-] 0 spaces\n [-] 1 space\n  [-] 2 spaces\n   [-] 3 spaces\n    [-] 4 spaces"
+    post = post_with_body(body)
+
+    ChecklistSyntaxMigrator.new(post).update_syntax!
+
+    expected = "[x] 0 spaces\n [x] 1 space\n  [x] 2 spaces\n   [x] 3 spaces\n    [-] 4 spaces"
+    expect(post.reload.raw).to eq(expected)
+  end
+
+  it "does not convert checkboxes in code blocks" do
+    body = [
+      "`‍``",
+      "[\*] This won't be converted",
+      "`‍``",
+      "[\*] That will",
+      "`‍``",
+      "[\*] Again this won't",
+      "`‍``"
+    ].join("\n")
+    post = post_with_body(body)
+
+    ChecklistSyntaxMigrator.new(post).update_syntax!
+
+    expected = [
+      "`‍``",
+      "[\*] This won't be converted",
+      "`‍``",
+      "[x] That will",
+      "`‍``",
+      "[\*] Again this won't",
+      "`‍``"
+    ].join("\n")
+    expect(post.reload.raw).to eq(expected)
+  end
+
+  it "does not convert checkboxes in block quotes" do
+    body = [
+      '[quote="markvanlan, post:10, topic:10"]',
+      "[\*] This won't be converted",
+      "[/quote]",
+      "[\*] That will",
+      '[quote="markvanlan, post:11, topic:10"]',
+      "[\*] Again this won't",
+      "[/quote]"
+    ].join("\n")
+    post = post_with_body(body)
+
+    ChecklistSyntaxMigrator.new(post).update_syntax!
+

[... diff too long, it was truncated ...]

GitHub sha: d997eb14