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 = ']*)?>$', '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('$', '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 = ']*)?>$', '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('$', '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); +});