diff --git a/examples/index.html b/examples/index.html index 9b3e6215e..a407e05d0 100644 --- a/examples/index.html +++ b/examples/index.html @@ -54,7 +54,30 @@ "Image", "Video", "Tweet" - ] + ], + textFormatting: { + bold: true, + italic: true, + underline: true, + strikethrough: true, + link: true, + h1: true, + h2: true, + list: true, + blockquote: true, + configure: function(scribe, scribeConfiguration){ + scribeConfiguration.formatBarCommands.push({ + name: "Superscript", + cmd: "superscript", + text: "^", + title: "superscript" + }); + scribeConfiguration.sanitize.sup = {}; + scribeConfiguration.shortcuts.superscript = function (event) { + return (event.metaKey || event.ctrlKey) && event.keyCode === 80; // p + }; + } + } }); $('form').bind('submit', function () { diff --git a/package.json b/package.json index c5c0138ea..c71e92beb 100644 --- a/package.json +++ b/package.json @@ -49,10 +49,15 @@ "node-bourbon": "^4.2.1-beta1", "object.assign": "^1.1.1", "scribe-editor": "1.4.15", + "scribe-plugin-blockquote-command": "^0.1.0", "scribe-plugin-formatter-plain-text-convert-new-lines-to-html": "^0.1.1", + "scribe-plugin-heading-command": "^0.1.0", + "scribe-plugin-intelligent-unlink-command": "^0.1.6", + "scribe-plugin-keyboard-shortcuts": "^0.1.1", "scribe-plugin-link-prompt-command": "^0.2.0", "scribe-plugin-sanitizer": "^0.1.9", "selenium-webdriver": "2.47.0", + "scribe-plugin-smart-lists": "^0.1.8", "spin.js": "^2.0.1", "webpack": "^1.8.4", "webpack-dev-server": "^1.8.0" diff --git a/spec/app/index.html b/spec/app/index.html index 7603fb91f..c55e3edec 100644 --- a/spec/app/index.html +++ b/spec/app/index.html @@ -26,6 +26,17 @@ diff --git a/spec/e2e/format-bar.spec.js b/spec/e2e/format-bar.spec.js index c32922be8..e05e2dd54 100644 --- a/spec/e2e/format-bar.spec.js +++ b/spec/e2e/format-bar.spec.js @@ -13,7 +13,7 @@ describe('Format Bar', function() { { "type": "text", "data": { - "text": "Bold, Italic, Link" + "text": "Bold, Italic, Link, Underline, Strikethrough\\n\\nH1\\n\\nH2\\n\\nUL\\n\\nOL\\n\\nQuote" } } ] @@ -31,8 +31,10 @@ describe('Format Bar', function() { it('should allow formatting of text', function(done) { var testUrl = 'http://www.example.com'; - var expectedHtmlWithoutLink = '

Bold, Italic, Link

'; - var expectedFullHtml = '

Bold, Italic, Link

'; + var expectedHtmlUpToLink = '

Bold, Italic, Link, Underline, Strikethrough

H1

H2

UL

OL

Quote

'; + var expectedHtmlWithLinkRemoved = '

Bold, Italic, Link, Underline, Strikethrough

H1

H2

UL

OL

Quote

'; + var expectedFinalFirefoxHtml = '

Bold, Italic, Link, Underline, Strikethrough

H1

H2

  1. OL

Quote

'; + var expectedFinalChromeHtml = '

Bold, Italic, Link, Underline, Strikethrough

H1

H2

  1. OL

Quote

'; function selectBoldText() { return helpers.browser.actions() @@ -67,18 +69,132 @@ describe('Format Bar', function() { function selectLinkText() { return helpers.browser.actions() - .sendKeys(driver.Key.ARROW_RIGHT) - .sendKeys(driver.Key.ARROW_RIGHT) - .sendKeys(driver.Key.ARROW_RIGHT) - .keyDown(driver.Key.SHIFT) - .sendKeys(driver.Key.ARROW_RIGHT) - .sendKeys(driver.Key.ARROW_RIGHT) - .sendKeys(driver.Key.ARROW_RIGHT) - .sendKeys(driver.Key.ARROW_RIGHT) - .keyUp(driver.Key.SHIFT) - .perform().then( function() { - return helpers.findElementByCss('.st-format-btn--Link').click(); - }); + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .keyDown(driver.Key.SHIFT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .keyUp(driver.Key.SHIFT) + .perform().then( function() { + return helpers.findElementByCss('.st-format-btn--Link').click(); + }); + } + + function selectUnderlineText() { + return helpers.browser.actions() + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .keyDown(driver.Key.SHIFT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .keyUp(driver.Key.SHIFT) + .perform().then( function() { + return helpers.findElementByCss('.st-format-btn--Underline').click(); + }); + } + + function selectStrikethroughText() { + return helpers.browser.actions() + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .keyDown(driver.Key.SHIFT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .keyUp(driver.Key.SHIFT) + .perform().then( function() { + return helpers.findElementByCss('.st-format-btn--Strikethrough').click(); + }); + } + + function selectH1Text() { + return helpers.browser.actions() + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .keyDown(driver.Key.SHIFT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .keyUp(driver.Key.SHIFT) + .perform().then( function() { + return helpers.findElementByCss('.st-format-btn--Heading1').click(); + }); + } + + function selectH2Text() { + return helpers.browser.actions() + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .keyDown(driver.Key.SHIFT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .keyUp(driver.Key.SHIFT) + .perform().then( function() { + return helpers.findElementByCss('.st-format-btn--Heading2').click(); + }); + } + + function selectUnorderedListText() { + return helpers.browser.actions() + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .keyDown(driver.Key.SHIFT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .keyUp(driver.Key.SHIFT) + .perform().then( function() { + return helpers.findElementByCss('.st-format-btn--UnorderedList').click(); + }); + } + + function selectOrderedListText() { + return helpers.browser.actions() + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .keyDown(driver.Key.SHIFT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .keyUp(driver.Key.SHIFT) + .perform().then( function() { + return helpers.findElementByCss('.st-format-btn--OrderedList').click(); + }); + } + + function selectQuoteText() { + return helpers.browser.actions() + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .keyDown(driver.Key.SHIFT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .sendKeys(driver.Key.ARROW_RIGHT) + .keyUp(driver.Key.SHIFT) + .perform().then( function() { + return helpers.findElementByCss('.st-format-btn--Blockquote').click(); + }); } var parent; @@ -96,12 +212,24 @@ describe('Format Bar', function() { }).then( function() { return parent.getInnerHtml(); }).then( function(html) { - expect(html).toBe(expectedFullHtml); + expect(html).toBe(expectedHtmlUpToLink); return helpers.findElementByCss('.st-format-btn--Unlink').click(); }).then( function() { return parent.getInnerHtml(); }).then( function(html) { - expect(html).toBe(expectedHtmlWithoutLink); + expect(html).toBe(expectedHtmlWithLinkRemoved); + }).then(selectUnderlineText) + .then(selectStrikethroughText) + .then(selectH1Text) + .then(selectH2Text) + .then(selectUnorderedListText) + .then(selectOrderedListText) + .then(selectQuoteText) + .then( function() { + return parent.getInnerHtml(); + }).then( function(html) { + var result = html === expectedFinalChromeHtml || html === expectedFinalFirefoxHtml; + expect(result).toBe(true); done(); }); }); diff --git a/src/block-manager.js b/src/block-manager.js index 20fa05171..d6dcc7a0f 100644 --- a/src/block-manager.js +++ b/src/block-manager.js @@ -8,7 +8,7 @@ var EventBus = require('./event-bus'); var Blocks = require('./blocks'); var BLOCK_OPTION_KEYS = ['convertToMarkdown', 'convertFromMarkdown', - 'formatBar']; + 'formatBar', 'textFormatting']; var BlockManager = function(options, editorInstance, mediator) { this.options = options; diff --git a/src/block.js b/src/block.js index 6b2cbcdcf..34fca52d1 100644 --- a/src/block.js +++ b/src/block.js @@ -73,8 +73,6 @@ Object.assign(Block.prototype, SimpleBlock.fn, require('./block-validations'), { paste_options: {}, upload_options: {}, - formattable: true, - _previousSelection: '', initialize: function() {}, @@ -115,6 +113,9 @@ Object.assign(Block.prototype, SimpleBlock.fn, require('./block-validations'), { this.$inputs = input_html; } + // This is also used by multi-editable mixin. + this.textFormatting = Object.assign({}, this.options.textFormatting, this.textFormatting); + if (this.hasTextBlock()) { this._initTextBlocks(); } this.availableMixins.forEach(function(mixin) { @@ -123,8 +124,6 @@ Object.assign(Block.prototype, SimpleBlock.fn, require('./block-validations'), { } }, this); - if (this.formattable) { this._initFormatting(); } - this._blockPrepare(); return this; @@ -303,41 +302,6 @@ Object.assign(Block.prototype, SimpleBlock.fn, require('./block-validations'), { this.onBlur(); }, - _initFormatting: function() { - - // Enable formatting keyboard input - var block = this; - - if (!this.options.formatBar) { - return; - } - - this.options.formatBar.commands.forEach(function(cmd) { - if (_.isUndefined(cmd.keyCode)) { - return; - } - - var ctrlDown = false; - - block.$el - .on('keyup','.st-text-block', function(ev) { - if(ev.which === 17 || ev.which === 224 || ev.which === 91) { - ctrlDown = false; - } - }) - .on('keydown','.st-text-block', {formatter: cmd}, function(ev) { - if(ev.which === 17 || ev.which === 224 || ev.which === 91) { - ctrlDown = true; - } - - if(ev.which === ev.data.formatter.keyCode && ctrlDown) { - ev.preventDefault(); - block.execTextBlockCommand(ev.data.formatter.cmd); - } - }); - }); - }, - _initTextBlocks: function() { this.getTextBlock() .bind('keyup', this.getSelectionForFormatter) @@ -349,9 +313,13 @@ Object.assign(Block.prototype, SimpleBlock.fn, require('./block-validations'), { var configureScribe = _.isFunction(this.configureScribe) ? this.configureScribe.bind(this) : null; - this._scribe = ScribeInterface.initScribeInstance( - textBlock, this.scribeOptions, configureScribe + + var scribeData = ScribeInterface.initScribeInstance( + textBlock, this.scribeOptions, this.textFormatting, configureScribe ); + + this._scribe = scribeData.scribe; + this.formatBarCommands = scribeData.formatBarCommands; } }, diff --git a/src/block_mixins/multi-editable.js b/src/block_mixins/multi-editable.js index 60268c0e4..352ff4b0c 100644 --- a/src/block_mixins/multi-editable.js +++ b/src/block_mixins/multi-editable.js @@ -23,10 +23,14 @@ module.exports = { var configureScribe = _.isFunction(this.configureScribe) ? this.configureScribe.bind(this) : null; - var scribe = ScribeInterface.initScribeInstance( - editor, this.scribeOptions, configureScribe + + var scribeData = ScribeInterface.initScribeInstance( + editor, this.scribeOptions, this.textFormatting, configureScribe ); + var scribe = scribeData.scribe; + this.formatBarCommands = scribeData.formatBarCommands; + scribe.setContent(content); var editorObject = { diff --git a/src/blocks/heading.js b/src/blocks/heading.js index 53748372d..349762152 100644 --- a/src/blocks/heading.js +++ b/src/blocks/heading.js @@ -15,13 +15,21 @@ module.exports = Block.extend({ editorHTML: '

', - scribeOptions: { + scribeOptions: { allowBlockElements: false, tags: { p: false } }, + textFormatting: { + bold: false, + h1: false, + h2: false, + list: false, + blockquote: false + }, + icon_name: 'heading', loadData: function(data){ diff --git a/src/blocks/list.js b/src/blocks/list.js index 136a90b0f..5f9408fab 100644 --- a/src/blocks/list.js +++ b/src/blocks/list.js @@ -55,13 +55,20 @@ module.exports = Block.extend({ icon_name: 'list', multi_editable: true, - scribeOptions: { + scribeOptions: { allowBlockElements: false, tags: { p: false } }, + textFormatting: { + h1: false, + h2: false, + list: false, + blockquote: false + }, + configureScribe: function(scribe) { scribe.use(new ScribeListBlockPlugin(this)); }, diff --git a/src/blocks/quote.js b/src/blocks/quote.js index 7703a6fba..f32ce0a15 100644 --- a/src/blocks/quote.js +++ b/src/blocks/quote.js @@ -28,6 +28,10 @@ module.exports = Block.extend({ return template(this); }, + textFormatting: { + blockquote: false + }, + loadData: function(data){ if (this.options.convertFromMarkdown && data.format !== "html") { this.setTextBlockHTML(stToHTML(data.text, this.type)); diff --git a/src/config.js b/src/config.js index 88af8ce61..349b7ce87 100644 --- a/src/config.js +++ b/src/config.js @@ -49,7 +49,7 @@ module.exports = { Block: { drop_options: drop_options, paste_options: paste_options, - upload_options: upload_options, + upload_options: upload_options }, blockLimit: 0, blockTypeLimits: {}, @@ -58,37 +58,16 @@ module.exports = { baseImageUrl: '/sir-trevor-uploads/', errorsContainer: undefined, convertFromMarkdown: true, - formatBar: { - commands: [ - { - name: "Bold", - title: "bold", - cmd: "bold", - keyCode: 66, - text : "B" - }, - { - name: "Italic", - title: "italic", - cmd: "italic", - keyCode: 73, - text : "i" - }, - { - name: "Link", - title: "link", - iconName: "link", - cmd: "linkPrompt", - text : "link", - }, - { - name: "Unlink", - title: "unlink", - iconName: "link", - cmd: "unlink", - text : "link", - }, - ], - }, + textFormatting: { + bold: true, + italic: true, + underline: false, + strikethrough: false, + link: true, + h1: false, + h2: false, + list: false, + blockquote: false + } } }; diff --git a/src/format-bar.js b/src/format-bar.js index e280b0ac7..d90707a01 100644 --- a/src/format-bar.js +++ b/src/format-bar.js @@ -16,7 +16,7 @@ var utils = require('./utils'); var FormatBar = function(options, mediator, editor) { this.editor = editor; this.options = Object.assign({}, config.defaults.formatBar, options || {}); - this.commands = this.options.commands; + this.commands = null; this.mediator = mediator; this._ensureElement(); @@ -41,20 +41,6 @@ Object.assign(FormatBar.prototype, require('./function-bind'), require('./mediat }, initialize: function() { - this.$btns = []; - - this.commands.forEach(function(format) { - var btn = $("