diff --git a/src/formats/bbcode.js b/src/formats/bbcode.js index fc89cd0b7..341fc1a37 100644 --- a/src/formats/bbcode.js +++ b/src/formats/bbcode.js @@ -785,17 +785,38 @@ allowsEmpty: true, tags: { iframe: { - 'data-youtube-id': null + 'data-youtube-id': null, + 'data-youtube-start': null } }, format: function (element, content) { - element = attr(element, 'data-youtube-id'); + var id = attr(element, 'data-youtube-id'); + var start = attr(element, 'data-youtube-start'); - return element ? '[youtube]' + element + '[/youtube]' : content; + if (start > 0) { + return id ? '[youtube=' + start + ']' + id + + '[/youtube]' : content; + } else { + return id ? '[youtube]' + id + '[/youtube]' : content; + } }, - html: '' + html: function (token, attrs, content) { + var pOpts = this.opts; + var id = content; + var start = 0; + + if (attrs.defaultattr) { + start = escapeEntities(attrs.defaultattr); + } + + return ''; + } }, // END_COMMAND diff --git a/src/formats/xhtml.js b/src/formats/xhtml.js index e59347fa0..252f95be0 100644 --- a/src/formats/xhtml.js +++ b/src/formats/xhtml.js @@ -180,17 +180,20 @@ youtube: { txtExec: function (caller) { var editor = this; + var pOpts = editor.opts.parserOptions; getEditorCommand('youtube')._dropDown( editor, caller, - function (id, time) { + function (id, start) { editor.insertText( - '' + 'data-youtube-start="' + start + '">' + + '' ); } ); diff --git a/src/icons/material.js b/src/icons/material.js index 9c464eef5..b79f262de 100644 --- a/src/icons/material.js +++ b/src/icons/material.js @@ -74,7 +74,8 @@ 'undo': '', // Austin Andrews @Templarian - https://materialdesignicons.com/ 'unlink': '', - 'youtube': '' + 'youtube': '', + 'video': '' }; sceditor.icons.material = function () { diff --git a/src/icons/monocons.js b/src/icons/monocons.js index 0e852cab8..4186eb75b 100644 --- a/src/icons/monocons.js +++ b/src/icons/monocons.js @@ -58,7 +58,8 @@ 'underline': 'U', 'undo': '', 'unlink': '', - 'youtube': '' + 'youtube': '', + 'video': '' }; sceditor.icons.monocons = function () { diff --git a/src/lib/SCEditor.js b/src/lib/SCEditor.js index d2b67f312..53eab02ed 100644 --- a/src/lib/SCEditor.js +++ b/src/lib/SCEditor.js @@ -407,6 +407,15 @@ export default function SCEditor(original, userOptions) { isRequired = original.required; original.required = false; + // Plugins should be initiated before the formatters since plugin + // may add/change the handlers and format init should create cache + // with those changes + initPlugins(); + + // Plugins might change the commands, should re-build it + base.commands = utils + .extend(true, {}, (userOptions.commands || defaultCommands)); + var FormatCtor = SCEditor.formats[options.format]; format = FormatCtor ? new FormatCtor() : {}; if ('init' in format) { @@ -414,7 +423,6 @@ export default function SCEditor(original, userOptions) { } // create the editor - initPlugins(); initEmoticons(); initToolBar(); initEditor(); diff --git a/src/lib/defaultCommands.js b/src/lib/defaultCommands.js index a38932d6e..e6ed97861 100644 --- a/src/lib/defaultCommands.js +++ b/src/lib/defaultCommands.js @@ -777,11 +777,13 @@ var defaultCmds = { }, exec: function (btn) { var editor = this; + var pOpts = editor.opts.parserOptions; defaultCmds.youtube._dropDown(editor, btn, function (id, time) { editor.wysiwygEditorInsertHtml(_tmpl('youtube', { id: id, - time: time + time: time, + params: pOpts.youtubeParameters })); }); }, diff --git a/src/lib/defaultOptions.js b/src/lib/defaultOptions.js index bff9a0237..c6afd6750 100644 --- a/src/lib/defaultOptions.js +++ b/src/lib/defaultOptions.js @@ -317,7 +317,7 @@ export default { * * @type {string} */ - plugins: '', + plugins: 'video', /** * z-index to set the editor container to. Needed for jQuery UI dialog. @@ -350,7 +350,24 @@ export default { * * @type {Object} */ - parserOptions: { }, + parserOptions: { + /** + * Parameters that will be added to YouTube frame tag + * + * @type {string} + */ + youtubeParameters: 'width="560" height="315" frameborder="0" ' + + 'allowfullscreen', + + /** + * Parameters that will be added to Facebook frame tag + * + * @type {string} + */ + facebookParameters: 'width="560" height="315" ' + + 'style="border:none;overflow:hidden" scrolling="no" ' + + 'frameborder="0" allowTransparency="true" allowFullScreen="true"' + }, /** * CSS that will be added to the to dropdown menu (eg. z-index) diff --git a/src/lib/templates.js b/src/lib/templates.js index 3cfa60e5a..4bac066c3 100644 --- a/src/lib/templates.js +++ b/src/lib/templates.js @@ -85,9 +85,9 @@ var _templates = { '', youtube: - '' + '' }; /** diff --git a/src/plugins/video.js b/src/plugins/video.js new file mode 100644 index 000000000..384f9d773 --- /dev/null +++ b/src/plugins/video.js @@ -0,0 +1,204 @@ +/** + * SCEditor Inline-Code Plugin for BBCode format + * http://www.sceditor.com/ + * + * Copyright (C) 2011-2013, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * @fileoverview SCEditor Video plugin + * This plugin replaces the youtube command with "add video" command, which + * will recognize youtube and facebook URLs (hopefully other URLs as well + * in the future) and generate the tags accordingly + * @author Alex Betis + */ + +(function (sceditor) { + 'use strict'; + + sceditor.plugins.video = function () { + var base = this; + + var utils = sceditor.utils; + var dom = sceditor.dom; + + /** + * Private functions + * @private + */ + var commandHandler; + var processYoutube; + var processFacebook; + var youtubeHtml; + var facebookHtml; + + base.init = function () { + var opts = this.opts; + + // Enable for BBCode only + if (opts.format && opts.format !== 'bbcode') { + return; + } + + if (opts.toolbar === sceditor.defaultOptions.toolbar) { + opts.toolbar = opts.toolbar.replace(',image,', + ',image,video,'); + + opts.toolbar = opts.toolbar.replace(',youtube', ''); + } + + // Remove youtube command + sceditor.command.remove('youtube'); + + // Add new movie command + sceditor.command.set('video', { + exec: commandHandler, + txtExec: commandHandler, + tooltip: 'Insert a video (YouTube, Facebook)' + }); + + sceditor.formats.bbcode.set('facebook', { + allowsEmpty: true, + tags: { + iframe: { + 'data-facebook-id': null + } + }, + format: function (element, content) { + var id = dom.attr(element, 'data-facebook-id'); + + return id ? '[facebook]' + id + '[/facebook]' : content; + }, + html: function (token, attrs, content) { + return facebookHtml(this.opts.facebookParameters, content); + } + }); + }; + + youtubeHtml = function (pOpts, id, time) { + return ''; + }; + + facebookHtml = function (pOpts, id) { + return ''; + }; + + processYoutube = function (editor, val) { + var pOpts = editor.opts.parserOptions; + var idMatch = val.match(/(?:v=|v\/|embed\/|youtu.be\/)(.{11})/); + var timeMatch = val.match(/[&|?](?:star)?t=((\d+[hms]?){1,3})/); + var time = 0; + + if (timeMatch) { + utils.each(timeMatch[1].split(/[hms]/), function (i, val) { + if (val !== '') { + time = (time * 60) + Number(val); + } + }); + } + + if (idMatch && /^[a-zA-Z0-9_\-]{11}$/.test(idMatch[1])) { + var id = idMatch[1]; + + if (editor.sourceMode()) { + if (time === 0) { + editor.insertText('[youtube]' + id + '[/youtube]'); + } else { + editor.insertText('[youtube=' + time + ']' + id + + '[/youtube]'); + } + } else { + editor.wysiwygEditorInsertHtml( + youtubeHtml(pOpts.youtubeParameters, id, time)); + } + + return true; + } else { + return false; + } + }; + + processFacebook = function (editor, val) { + var pOpts = editor.opts.parserOptions; + var idMatch = val.match(/videos\/(\d+)+|v=(\d+)|vb.\d+\/(\d+)/); + + if (idMatch && /^[a-zA-Z0-9]/.test(idMatch[1])) { + var id = idMatch[1]; + + if (editor.sourceMode()) { + editor.insertText('[facebook]' + id + '[/facebook]'); + } else { + editor.wysiwygEditorInsertHtml( + facebookHtml(pOpts.facebookParameters, id)); + } + + return true; + } else { + return false; + } + }; + + /** + * Function for the txtExec and exec properties + * + * @param {node} caller + * @private + */ + commandHandler = function (caller) { + var editor = this; + var content = document.createElement('div'); + + var div; + var label; + var input; + var button; + + div = document.createElement('div'); + label = document.createElement('label'); + label.setAttribute('for', 'link'); + label.textContent = editor._('Video URL:'); + input = document.createElement('input'); + input.type = 'text'; + input.id = 'link'; + input.dir = 'ltr'; + input.placeholder = 'https://'; + div.appendChild(label); + div.appendChild(input); + + content.appendChild(div); + + div = document.createElement('div'); + button = document.createElement('input'); + button.type = 'button'; + button.className = 'button'; + button.value = editor._('Insert'); + div.appendChild(button); + + content.appendChild(div); + + button.addEventListener('click', function (e) { + var val = input.value; + var done; + + done = processYoutube(editor, val); + if (!done) { + processFacebook(editor, val); + } + + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.createDropDown(caller, 'insertlink', content); + }; + }; +})(sceditor); diff --git a/src/themes/icons/famfamfam.less b/src/themes/icons/famfamfam.less index a0f6edc0c..53e9c4db6 100644 --- a/src/themes/icons/famfamfam.less +++ b/src/themes/icons/famfamfam.less @@ -7,49 +7,51 @@ div.sceditor-grip, .sceditor-button div { width: 16px; height: 16px; } -.sceditor-button-youtube div { background-position: 0px 0px; } -.sceditor-button-link div { background-position: 0px -16px; } -.sceditor-button-unlink div { background-position: 0px -32px; } -.sceditor-button-underline div { background-position: 0px -48px; } -.sceditor-button-time div { background-position: 0px -64px; } -.sceditor-button-table div { background-position: 0px -80px; } -.sceditor-button-superscript div { background-position: 0px -96px; } -.sceditor-button-subscript div { background-position: 0px -112px; } -.sceditor-button-strike div { background-position: 0px -128px; } -.sceditor-button-source div { background-position: 0px -144px; } -.sceditor-button-size div { background-position: 0px -160px; } -.sceditor-button-rtl div { background-position: 0px -176px; } -.sceditor-button-right div { background-position: 0px -192px; } -.sceditor-button-removeformat div { background-position: 0px -208px; } -.sceditor-button-quote div { background-position: 0px -224px; } -.sceditor-button-print div { background-position: 0px -240px; } -.sceditor-button-pastetext div { background-position: 0px -256px; } -.sceditor-button-paste div { background-position: 0px -272px; } -.sceditor-button-outdent div { background-position: 0px -288px; } -.sceditor-button-orderedlist div { background-position: 0px -304px; } -.sceditor-button-maximize div { background-position: 0px -320px; } -.sceditor-button-ltr div { background-position: 0px -336px; } -.sceditor-button-left div { background-position: 0px -352px; } -.sceditor-button-justify div { background-position: 0px -368px; } -.sceditor-button-italic div { background-position: 0px -384px; } -.sceditor-button-indent div { background-position: 0px -400px; } -.sceditor-button-image div { background-position: 0px -416px; } -.sceditor-button-horizontalrule div { background-position: 0px -432px; } -.sceditor-button-format div { background-position: 0px -448px; } -.sceditor-button-font div { background-position: 0px -464px; } -.sceditor-button-emoticon div { background-position: 0px -480px; } -.sceditor-button-email div { background-position: 0px -496px; } -.sceditor-button-date div { background-position: 0px -512px; } -.sceditor-button-cut div { background-position: 0px -528px; } -.sceditor-button-copy div { background-position: 0px -544px; } -.sceditor-button-color div { background-position: 0px -560px; } -.sceditor-button-code div { background-position: 0px -576px; } -.sceditor-button-center div { background-position: 0px -592px; } -.sceditor-button-bulletlist div { background-position: 0px -608px; } -.sceditor-button-bold div { background-position: 0px -624px; } +.sceditor-button-bold div { background-position: 0px 0px; } +.sceditor-button-bulletlist div { background-position: 0px -16px; } +.sceditor-button-center div { background-position: 0px -32px; } +.sceditor-button-code div { background-position: 0px -48px; } +.sceditor-button-color div { background-position: 0px -64px; } +.sceditor-button-copy div { background-position: 0px -80px; } +.sceditor-button-cut div { background-position: 0px -96px; } +.sceditor-button-date div { background-position: 0px -112px; } +.sceditor-button-email div { background-position: 0px -128px; } +.sceditor-button-emoticon div { background-position: 0px -144px; } +.sceditor-button-font div { background-position: 0px -160px; } +.sceditor-button-format div { background-position: 0px -176px; } +.sceditor-button-horizontalrule div { background-position: 0px -192px; } +.sceditor-button-image div { background-position: 0px -208px; } +.sceditor-button-indent div { background-position: 0px -224px; } +.sceditor-button-italic div { background-position: 0px -240px; } +.sceditor-button-justify div { background-position: 0px -256px; } +.sceditor-button-left div { background-position: 0px -272px; } +.sceditor-button-ltr div { background-position: 0px -288px; } +.sceditor-button-maximize div { background-position: 0px -304px; } +.sceditor-button-orderedlist div { background-position: 0px -320px; } +.sceditor-button-outdent div { background-position: 0px -336px; } +.sceditor-button-paste div { background-position: 0px -352px; } +.sceditor-button-pastetext div { background-position: 0px -368px; } +.sceditor-button-print div { background-position: 0px -384px; } +.sceditor-button-quote div { background-position: 0px -400px; } +.sceditor-button-removeformat div { background-position: 0px -416px; } +.sceditor-button-right div { background-position: 0px -432px; } +.sceditor-button-rtl div { background-position: 0px -448px; } +.sceditor-button-size div { background-position: 0px -464px; } +.sceditor-button-source div { background-position: 0px -480px; } +.sceditor-button-strike div { background-position: 0px -496px; } +.sceditor-button-subscript div { background-position: 0px -512px; } +.sceditor-button-superscript div { background-position: 0px -528px; } +.sceditor-button-table div { background-position: 0px -544px; } +.sceditor-button-time div { background-position: 0px -560px; } +.sceditor-button-underline div { background-position: 0px -576px; } +.sceditor-button-unlink div { background-position: 0px -592px; } +.sceditor-button-url div { background-position: 0px -608px; } +.sceditor-button-video div { background-position: 0px -624px; } +.sceditor-button-youtube div { background-position: 0px -640px; } + div.sceditor-grip { - background-position: 0px -640px; + background-position: 0px -656px; width: 10px; height: 10px; } -.rtl div.sceditor-grip { background-position: 0px -650px; } +.rtl div.sceditor-grip { background-position: 0px -666px; } diff --git a/src/themes/icons/famfamfam.png b/src/themes/icons/famfamfam.png index 488c72aca..fe99fce9a 100644 Binary files a/src/themes/icons/famfamfam.png and b/src/themes/icons/famfamfam.png differ diff --git a/src/themes/icons/src/famfamfam/video.png b/src/themes/icons/src/famfamfam/video.png new file mode 100644 index 000000000..40d681feb Binary files /dev/null and b/src/themes/icons/src/famfamfam/video.png differ diff --git a/tests/manual/debug/index.html b/tests/manual/debug/index.html index 23e309d92..77d1c5c2a 100644 --- a/tests/manual/debug/index.html +++ b/tests/manual/debug/index.html @@ -20,6 +20,7 @@ + diff --git a/tests/unit/formats/bbcode.js b/tests/unit/formats/bbcode.js index b58aae2d7..42ceb8660 100644 --- a/tests/unit/formats/bbcode.js +++ b/tests/unit/formats/bbcode.js @@ -853,4 +853,15 @@ QUnit.test('YouTube', function (assert) { this.htmlToBBCode(''), '[youtube]xyz[/youtube]' ); + + assert.equal( + this.htmlToBBCode(''), + '[youtube]xyz[/youtube]' + ); + + assert.equal( + this.htmlToBBCode(''), + '[youtube=123]xyz[/youtube]' + ); }); + diff --git a/tests/unit/formats/bbcode.parser.js b/tests/unit/formats/bbcode.parser.js index 21649dd9f..571c9a69c 100644 --- a/tests/unit/formats/bbcode.parser.js +++ b/tests/unit/formats/bbcode.parser.js @@ -628,7 +628,12 @@ QUnit.test('Attributes QuoteType custom', function (assert) { QUnit.module('plugins/bbcode#Parser - To HTML', { beforeEach: function () { - this.parser = new sceditor.BBCodeParser({}); + this.parser = new sceditor.BBCodeParser( + { + youtubeParameters: 'width="123" height="456" frameborder="0" ' + + 'allowfullscreen' + } + ); } }); @@ -918,9 +923,18 @@ QUnit.test('Justify', function (assert) { QUnit.test('YouTube', function (assert) { assert.htmlEqual( this.parser.toHTML('[youtube]xyz[/youtube]'), - '
\n', + 'Normal' + ); + + assert.htmlEqual( + this.parser.toHTML('[youtube=321]xyz[/youtube]'), + '
\n', 'Normal' );