diff --git a/dist/PublicLab.Editor.js b/dist/PublicLab.Editor.js
index b348f2bb..f3561040 100644
--- a/dist/PublicLab.Editor.js
+++ b/dist/PublicLab.Editor.js
@@ -22892,14 +22892,116 @@ module.exports = function initAutoCenter(_module, wysiwyg) {
$('.wk-commands .woofmark-command-autocenter').click(function() {
wysiwyg.runCommand(function(chunks, mode) {
- if (mode == "wysiwyg") { // first convert then replace
- chunks.selection = ("->"+chunks.selection+"<-");
- var openingTag = /->/g;
- var closingTag = /<-/g;
- chunks.selection = chunks.selection.replace(openingTag, "
");
- chunks.selection = chunks.selection.replace(closingTag, "");
- } else if (mode == "markdown") {
- chunks.selection = _module.wysiwyg.parseHTML(""+chunks.selection+"");
+ if (mode === "wysiwyg") {
+ var tag = "center";
+ var open = '<' + tag;
+ var close = '' + tag.replace(/]*)?>$', 'i');
+ var rtrailing = new RegExp('^' + close + '>', 'i');
+ var ropen = new RegExp(open + '( [^>]*)?>', 'ig');
+ var rclose = new RegExp(close + '( [^>]*)?>', 'ig');
+ chunks.trim();
+ // searches if selected text is center aligned and left aligns it
+ var trail = rtrailing.exec(chunks.before);
+ var lead = rleading.exec(chunks.after);
+ if (lead && trail) {
+ chunks.before = chunks.before.replace(rleading, '');
+ chunks.after = chunks.after.replace(rtrailing, '');
+ } else {
+ // searches if center tag is opened in selected text
+ var opened = ropen.test(chunks.selection);
+ if (opened) {
+ chunks.selection = chunks.selection.replace(ropen, '');
+ if (!surrounded(chunks, tag)) {
+ chunks.before += open + '>';
+ }
+ }
+ // searches if center tag is closed in selected text
+ var closed = rclose.test(chunks.selection);
+ if (closed) {
+ chunks.selection = chunks.selection.replace(rclose, '');
+ if (!surrounded(chunks, tag)) {
+ chunks.after = close + '>' + chunks.after;
+ }
+ }
+ if (surrounded(chunks, tag)) {
+ if (rleading.test(chunks.before)) {
+ chunks.before = chunks.before.replace(rleading, '');
+ } else {
+ chunks.before += close + '>';
+ }
+ if (rtrailing.test(chunks.after)) {
+ chunks.after = chunks.after.replace(rtrailing, '');
+ } else {
+ chunks.after = open + '>' + chunks.after;
+ }
+ } else if (!closebounded(chunks, tag)) {
+ chunks.after = close + '>' + chunks.after;
+ chunks.before += open + '>';
+ }
+ }
+
+ function closebounded(chunks, tag) {
+ var rcloseleft = new RegExp('' + tag.replace(/$', 'i');
+ var ropenright = new RegExp('^<' + tag + '(?: [^>]*)?>', 'i');
+ var bounded = rcloseleft.test(chunks.before) && ropenright.test(chunks.after);
+ if (bounded) {
+ chunks.before = chunks.before.replace(rcloseleft, '');
+ chunks.after = chunks.after.replace(ropenright, '');
+ }
+ return bounded;
+ }
+
+ function surrounded(chunks, tag) {
+ var ropen = new RegExp('<' + tag + '(?: [^>]*)?>', 'ig');
+ var rclose = new RegExp('<\/' + tag.replace(/', 'ig');
+ var opensBefore = count(chunks.before, ropen);
+ var opensAfter = count(chunks.after, ropen);
+ var closesBefore = count(chunks.before, rclose);
+ var closesAfter = count(chunks.after, rclose);
+ var open = opensBefore - closesBefore > 0;
+ var close = closesAfter - opensAfter > 0;
+ return open && close;
+ }
+
+ function count(text, regex) {
+ var match = text.match(regex);
+ if (match) {
+ return match.length;
+ }
+ return 0;
+ }
+ } else if (mode === "markdown") {
+ var open = '->';
+ var close = '<-';
+ var rleading = new RegExp(open + '( [^>]*)?', 'i');
+ var rtrailing = new RegExp('^' + close, 'i');
+ var ropen = new RegExp(open + '( [^>]*)?', 'ig');
+ var rclose = new RegExp(close + '( [^>]*)?', 'ig');
+ chunks.trim();
+ var trail = rleading.exec(chunks.before);
+ var lead = rtrailing.exec(chunks.after);
+
+ if (trail && lead) {
+ chunks.before = chunks.before.replace(rleading, '');
+ chunks.after = chunks.after.replace(rtrailing, '');
+ } else {
+ var opened = ropen.test(chunks.selection);
+ var closed = rclose.test(chunks.selection);
+ if (opened || closed) {
+ if (opened) {
+ chunks.selection = chunks.selection.replace(ropen, '');
+ chunks.before += open;
+ }
+ if (closed) {
+ chunks.selection = chunks.selection.replace(rclose, '');
+ chunks.after = close + chunks.after;
+ }
+ } else {
+ // adds center tag and parses into markdown
+ chunks.selection = _module.wysiwyg.parseHTML("" + chunks.selection + "");
+ }
+ }
}
_module.afterParse();
diff --git a/src/modules/PublicLab.RichTextModule.AutoCenter.js b/src/modules/PublicLab.RichTextModule.AutoCenter.js
index 2f6f9097..f803fe5b 100644
--- a/src/modules/PublicLab.RichTextModule.AutoCenter.js
+++ b/src/modules/PublicLab.RichTextModule.AutoCenter.js
@@ -15,14 +15,116 @@ module.exports = function initAutoCenter(_module, wysiwyg) {
$('.wk-commands .woofmark-command-autocenter').click(function() {
wysiwyg.runCommand(function(chunks, mode) {
- if (mode == "wysiwyg") { // first convert then replace
- chunks.selection = ("->"+chunks.selection+"<-");
- var openingTag = /->/g;
- var closingTag = /<-/g;
- chunks.selection = chunks.selection.replace(openingTag, "");
- chunks.selection = chunks.selection.replace(closingTag, "");
- } else if (mode == "markdown") {
- chunks.selection = _module.wysiwyg.parseHTML(""+chunks.selection+"");
+ if (mode === "wysiwyg") {
+ const tag = "center";
+ const open = '<' + tag;
+ const close = '' + tag.replace(/]*)?>$', 'i');
+ const rtrailing = new RegExp('^' + close + '>', 'i');
+ const ropen = new RegExp(open + '( [^>]*)?>', 'ig');
+ const rclose = new RegExp(close + '( [^>]*)?>', 'ig');
+ chunks.trim();
+ // searches if selected text is center aligned and left aligns it
+ const trail = rtrailing.exec(chunks.before);
+ const lead = rleading.exec(chunks.after);
+ if (lead && trail) {
+ chunks.before = chunks.before.replace(rleading, '');
+ chunks.after = chunks.after.replace(rtrailing, '');
+ } else {
+ // searches if center tag is opened in selected text
+ const opened = ropen.test(chunks.selection);
+ if (opened) {
+ chunks.selection = chunks.selection.replace(ropen, '');
+ if (!surrounded(chunks, tag)) {
+ chunks.before += open + '>';
+ }
+ }
+ // searches if center tag is closed in selected text
+ const closed = rclose.test(chunks.selection);
+ if (closed) {
+ chunks.selection = chunks.selection.replace(rclose, '');
+ if (!surrounded(chunks, tag)) {
+ chunks.after = close + '>' + chunks.after;
+ }
+ }
+ if (surrounded(chunks, tag)) {
+ if (rleading.test(chunks.before)) {
+ chunks.before = chunks.before.replace(rleading, '');
+ } else {
+ chunks.before += close + '>';
+ }
+ if (rtrailing.test(chunks.after)) {
+ chunks.after = chunks.after.replace(rtrailing, '');
+ } else {
+ chunks.after = open + '>' + chunks.after;
+ }
+ } else if (!closebounded(chunks, tag)) {
+ chunks.after = close + '>' + chunks.after;
+ chunks.before += open + '>';
+ }
+ }
+
+ function closebounded(chunks, tag) {
+ const rcloseleft = new RegExp('' + tag.replace(/$', 'i');
+ const ropenright = new RegExp('^<' + tag + '(?: [^>]*)?>', 'i');
+ const bounded = rcloseleft.test(chunks.before) && ropenright.test(chunks.after);
+ if (bounded) {
+ chunks.before = chunks.before.replace(rcloseleft, '');
+ chunks.after = chunks.after.replace(ropenright, '');
+ }
+ return bounded;
+ }
+
+ function surrounded(chunks, tag) {
+ const ropen = new RegExp('<' + tag + '(?: [^>]*)?>', 'ig');
+ const rclose = new RegExp('<\/' + tag.replace(/', 'ig');
+ const opensBefore = count(chunks.before, ropen);
+ const opensAfter = count(chunks.after, ropen);
+ const closesBefore = count(chunks.before, rclose);
+ const closesAfter = count(chunks.after, rclose);
+ const open = opensBefore - closesBefore > 0;
+ const close = closesAfter - opensAfter > 0;
+ return open && close;
+ }
+
+ function count(text, regex) {
+ const match = text.match(regex);
+ if (match) {
+ return match.length;
+ }
+ return 0;
+ }
+ } else if (mode === "markdown") {
+ const open = '->';
+ const close = '<-';
+ const rleading = new RegExp(open + '( [^>]*)?', 'i');
+ const rtrailing = new RegExp('^' + close, 'i');
+ const ropen = new RegExp(open + '( [^>]*)?', 'ig');
+ const rclose = new RegExp(close + '( [^>]*)?', 'ig');
+ chunks.trim();
+ const trail = rleading.exec(chunks.before);
+ const lead = rtrailing.exec(chunks.after);
+
+ if (trail && lead) {
+ chunks.before = chunks.before.replace(rleading, '');
+ chunks.after = chunks.after.replace(rtrailing, '');
+ } else {
+ const opened = ropen.test(chunks.selection);
+ const closed = rclose.test(chunks.selection);
+ if (opened || closed) {
+ if (opened) {
+ chunks.selection = chunks.selection.replace(ropen, '');
+ chunks.before += open;
+ }
+ if (closed) {
+ chunks.selection = chunks.selection.replace(rclose, '');
+ chunks.after = close + chunks.after;
+ }
+ } else {
+ // adds center tag and parses into markdown
+ chunks.selection = _module.wysiwyg.parseHTML("" + chunks.selection + "");
+ }
+ }
}
_module.afterParse();
diff --git a/test/ui-testing/center.test.js b/test/ui-testing/center.test.js
new file mode 100644
index 00000000..b93137e1
--- /dev/null
+++ b/test/ui-testing/center.test.js
@@ -0,0 +1,32 @@
+const timeout = process.env.SLOWMO ? 60000 : 10000;
+const fs = require('fs');
+beforeAll(async () => {
+ path = fs.realpathSync('file://../examples/index.html');
+ await page.goto('file://' + path, {waitUntil: 'domcontentloaded'});
+});
+
+describe('Center Text', () => {
+ test('Centering and decentering text', async () => {
+ // selects a string from textarea in md mode
+ await page.setViewport({width: 1920, height: 1080});
+ await page.waitForSelector('.ple-module-body');
+ await page.click('.woofmark-mode-markdown');
+ await page.focus('.ple-textarea');
+
+ // center aligns the string and checks for presence of '->' and '<-'
+ await page.click('.woofmark-command-autocenter');
+ let opening = await page.evaluate(() => document.querySelector('.ple-textarea').value.includes('->'));
+ let closing = await page.evaluate(() => document.querySelector('.ple-textarea').value.includes('<-'));
+ expect(opening).toBe(true);
+ expect(closing).toBe(true);
+
+ // the selection changes from '-> some text <-' to ->'some text'<-
+ await page.click('.woofmark-command-autocenter');
+ // clicking again removes the center alignment and tests for absence of '->' and '<-'
+ await page.click('.woofmark-command-autocenter');
+ opening = await page.evaluate(() => document.querySelector('.ple-textarea').value.includes('->'));
+ closing = await page.evaluate(() => document.querySelector('.ple-textarea').value.includes('<-'));
+ expect(opening).toBe(false);
+ expect(closing).toBe(false);
+ }, timeout);
+});