From 3b633d7c5edffce356cb829fab1fdc8aca3cac29 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 10 Apr 2018 18:05:16 +0300 Subject: [PATCH 1/8] Fix plugins initialization problems --- src/lib/SCEditor.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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(); From 879cd1733c820e6fdcb4184bc2b43593b797dac4 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 11 Apr 2018 11:22:50 +0300 Subject: [PATCH 2/8] Parameters for youtube tag --- src/formats/bbcode.js | 11 ++++++++--- src/lib/defaultCommands.js | 4 +++- src/lib/defaultOptions.js | 10 +++++++++- src/lib/templates.js | 2 +- tests/unit/formats/bbcode.parser.js | 11 ++++++++--- 5 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/formats/bbcode.js b/src/formats/bbcode.js index fc89cd0b7..284f8490b 100644 --- a/src/formats/bbcode.js +++ b/src/formats/bbcode.js @@ -793,9 +793,14 @@ return element ? '[youtube]' + element + '[/youtube]' : content; }, - html: '' + html: function (token, attrs, content) { + var pOpts = this.opts; + var id = content; + + return ''; + } }, // END_COMMAND 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..7a4c7b436 100644 --- a/src/lib/defaultOptions.js +++ b/src/lib/defaultOptions.js @@ -350,7 +350,15 @@ 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' + }, /** * 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..de5686bde 100644 --- a/src/lib/templates.js +++ b/src/lib/templates.js @@ -85,7 +85,7 @@ var _templates = { '', youtube: - '' }; diff --git a/tests/unit/formats/bbcode.parser.js b/tests/unit/formats/bbcode.parser.js index 21649dd9f..b961da4be 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,9 @@ QUnit.test('Justify', function (assert) { QUnit.test('YouTube', function (assert) { assert.htmlEqual( this.parser.toHTML('[youtube]xyz[/youtube]'), - '
\n', 'Normal' ); From a90076f73bf03c69c41e8ac11d7e8ba4bbea46c4 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 11 Apr 2018 11:39:45 +0300 Subject: [PATCH 3/8] BBCode start time support --- src/formats/bbcode.js | 26 +++++++++++++++++++++----- src/lib/templates.js | 4 ++-- tests/unit/formats/bbcode.js | 11 +++++++++++ tests/unit/formats/bbcode.parser.js | 13 +++++++++++-- 4 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/formats/bbcode.js b/src/formats/bbcode.js index 284f8490b..341fc1a37 100644 --- a/src/formats/bbcode.js +++ b/src/formats/bbcode.js @@ -785,21 +785,37 @@ 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: function (token, attrs, content) { var pOpts = this.opts; var id = content; + var start = 0; + + if (attrs.defaultattr) { + start = escapeEntities(attrs.defaultattr); + } return ''; + 'src="https://www.youtube.com/embed/' + id + + '?start=' + start + + '&wmode=opaque" ' + + 'data-youtube-id="' + id + '" ' + + 'data-youtube-start="' + start + '">' + + ''; } }, // END_COMMAND diff --git a/src/lib/templates.js b/src/lib/templates.js index de5686bde..4bac066c3 100644 --- a/src/lib/templates.js +++ b/src/lib/templates.js @@ -86,8 +86,8 @@ var _templates = { youtube: '' + 'src="https://www.youtube.com/embed/{id}?start={time}&wmode=opaque" ' + + 'data-youtube-id="{id}" data-youtube-start="{time}">' }; /** 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 b961da4be..571c9a69c 100644 --- a/tests/unit/formats/bbcode.parser.js +++ b/tests/unit/formats/bbcode.parser.js @@ -924,8 +924,17 @@ 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' ); From a4c75862057782deb445b2a838a14f3f4fb7bb6b Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 11 Apr 2018 12:00:39 +0300 Subject: [PATCH 4/8] XHTML implementation --- src/formats/xhtml.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) 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 + '">' + + '' ); } ); From d39f1dbf2fa0d6be92690e35e8b1b848ed662061 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 16 Apr 2018 13:38:15 +0300 Subject: [PATCH 5/8] Adding video command --- src/icons/material.js | 3 +- src/icons/monocons.js | 3 +- src/lib/defaultOptions.js | 2 +- src/plugins/video.js | 153 +++++++++++++++++++++++ src/themes/icons/famfamfam.less | 86 ++++++------- src/themes/icons/famfamfam.png | Bin 4583 -> 13447 bytes src/themes/icons/src/famfamfam/video.png | Bin 0 -> 739 bytes tests/manual/debug/index.html | 1 + 8 files changed, 203 insertions(+), 45 deletions(-) create mode 100644 src/plugins/video.js create mode 100644 src/themes/icons/src/famfamfam/video.png 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/defaultOptions.js b/src/lib/defaultOptions.js index bff9a0237..722204f73 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. diff --git a/src/plugins/video.js b/src/plugins/video.js new file mode 100644 index 000000000..dc1803611 --- /dev/null +++ b/src/plugins/video.js @@ -0,0 +1,153 @@ +/** + * 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; + + /** + * Private functions + * @private + */ + var commandHandler; + + 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,'); + } + + // 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)' + }); + + /* + // Override current implementation + sceditor.formats.bbcode.set('list', { + breakStart: true, + isInline: false, + skipLastLineBreak: true, + html: function (token, attrs, content) { + var listType = 'disc'; + var toHtml = null; + + if (attrs.defaultattr) { + listType = attrs.defaultattr; + } + + if (listType === '1') { + // This listType belongs to orderedList (OL) + toHtml = sceditor.formats.bbcode.get('ol').html; + } else { + // unknown listType, use default bullet list behavior + toHtml = sceditor.formats.bbcode.get('ul').html; + } + + if (isFunction(toHtml)) { + return toHtml.call(this, token, attrs, content); + } else { + token.attrs['0'] = content; + return sceditor.formats.bbcode.formatBBCodeString( + toHtml, token.attrs); + } + } + }); + + sceditor.formats.bbcode.set('ul', { + tags: { + ul: null + }, + breakStart: true, + isInline: false, + skipLastLineBreak: true, + format: '[list]{0}[/list]', + html: '
    {0}
' + }); + + sceditor.formats.bbcode.set('ol', { + tags: { + ol: null + }, + breakStart: true, + isInline: false, + skipLastLineBreak: true, + format: '[list=1]{0}[/list]', + html: '
    {0}
' + }); + + sceditor.formats.bbcode.set('li', { + tags: { + li: null + }, + isInline: false, + closedBy: ['/ul', '/ol', '/list', '*', 'li'], + format: '[*]{0}', + html: '
  • {0}
  • ' + }); + + sceditor.formats.bbcode.set('*', { + isInline: false, + excludeClosing: true, + closedBy: ['/ul', '/ol', '/list', '*', 'li'], + html: '
  • {0}
  • ' + }); + }; + + insertListTag = function (editor, listType, selected) { + var content = ''; + + utils.each(selected.split(/\r?\n/), function (item) { + content += (content ? '\n' : '') + + '[*]' + item; + }); + + if (listType === '') { + editor.insertText('[list]\n' + content + '\n[/list]'); + } else { + editor.insertText('[list=' + listType + ']\n' + content + + '\n[/list]'); + } + */ + }; + + /** + * Function for the txtExec and exec properties + * + * @param {node} caller + * @private + */ + commandHandler = function (caller, selected) { + var editor = this; + + editor.insertText(selected); + }; + }; +})(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 488c72aca95abf57bd15d3544696eddaa4a60750..fe99fce9a90961f01593c5ec1d6a3c2b2e7ed0bb 100644 GIT binary patch literal 13447 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

    15z~-4K$zWlhSNas zhg~7W4_2)zNs~|s%08$TR|k@^vK+NEH15=cf*v{-%z``7J2io-swzJL0fC+d+sr?- zKelacYx@Hi7gxk&p%gtupAWolcZ7w7XWJK=_t|oE_4HDvOgt{=Sf|&E(z#>*7L06H zWJ!8gKj-6v@}=XUEAsi3tW0W@R`br@zD1gi&Gu`JxIZW~tARz;C9c5UsNR+!K$Gfy zGj##S8x~c+w;zkf7{@4qHpgkQI4rb-9YropomR905D_iVaWxd<+? z7T~Ns%rK>Kj7E_bsX_myl)u;|!w0Z+)Xa)f*Tw9{ zvuhodrRWGDjE@ZAm{O+L07o&I+MlP97D+`BkMODjthSynf~W=6{iy}0ad$we2IR8> z1UH+4xGG!5o?-xvnn(}%pIdmWWQ*`KO$|85Gzs80e_wp(+*VD#-}aDQy^kZ|W=^5h zU_p_B-v_tUz`E+~B(y4uVwiRcSPc+|ibcvT>`;d%iH9d;xB~s~cmesLT5G(dbn~be zX87rLVwpMQ1!cUr;V>l#Cv!N9H<%%OR~o@MlTvR^#$?Wbag>40aJXR(?wMI9a-?Kr z5v7q8V*FM2>(?(L`VG^kCHM-vZPL*&cXXBnjVw3gubjF%0p{CWZ>1FlJP4L2lt*I~ z@weg$3*9fW-bNy*9p1=P!Q<7`Uf3npvb=%8yt{mK7VP6okuL(QatAmu19F0hwAf4C z?egAd1G)ddW_|NKLOxoiut|CVQN7QWXT{m`_DM5RE+Gtt(m7o}I*;k%5yNGyA(qz| zCNnoMlVl<$?~87G%KKG#ZJ#8NCY~YOSRJA&BlAs06i)>9mcHeXz-Mla!?RNR9|$Ht zlww~6c+^lZY=iGd=n%xN zK-I_f@+MQVM|>x0qb*`XaAsV5Ct$BY^a{_+;BPK5SQK#aoxAmCBZUP1I(#dJklo9Z z63D?o#myf;Szvc7xJZ+GDjZ}YWxRjD zDZeMgAP%L(jHQPY;qxo&rLL?8U!|M7|&`9O^N z>ine`#^o>%}PE=til-JB=V-IsJH$eeX6%(yH> zSQLVr&0qbCq9fFGUH>bj!tTW1G+ubn|Jc_cK&PR4Pk!f8UiFO7BrEyz=g%KMe{TKK z4!8792329YPEcZaMtA&;2YMBb#Bod%j?yqTJ(Jbd)m`?W3>jg933qx0qobn)(u}$M z*AYI1h);GHTR5+Mn-~~rvI$h!q2r`1Eo|>)cenzy(cjtTeLk@8l;Qq(8EpXjzem6- X#5`$<(uU-J{{*QjYAIC9n}_}%uz^@` literal 4583 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 R^t1o~002ovPDHLkV1hp-8@>Pl diff --git a/src/themes/icons/src/famfamfam/video.png b/src/themes/icons/src/famfamfam/video.png new file mode 100644 index 0000000000000000000000000000000000000000..40d681feba594596c64e0947b8cae5ce9086f919 GIT binary patch literal 739 zcmV<90v!E`P)GL6il?1Z7P2*|7g?{OO9$@TbH1?j^i>BB&)xBSKKI=BdzS+s=YPijNo#8> z*V5AR)9?3h9JknJv&E~ct9MK$)0#*?;BA_wANhPfI2?{62Z2#lRW+c~=~hJo0)xOm zJRVPmX1?&(C%^N7u`S*k-QpKxJ^Z(|9^Pm)X0%%ELPbRdL;|nZyG-C)6h(2$>2CPu zHV}LVG)@CAV;R;g61NZ@w6lP;HQ#_4pj`q>&t7&BZ`>!@b{&p2>% z;uFft%U6_2<&0b|Pl^NtP7rvQyz7!PR)fwp2bz|(sCys8g|R5kjl@7;mcYXji6kKs z)YjH=1P+m;7l+bqc)r<%r<)y!q^=_J`7#F6mmrhL*y7^iP(eWfClc80_O#V%Wl0vt zwop`5#OLSdv$?ssX_0`yoW)`Zk)#&_^V?|u0JO~k^enKn{t&OHI?)`q?>5}4fxY|e zuBk)XbwFS?n-d0uVVESnbhw2(ueuR>LE$YI0sECfc%li8@ENF@PxkK%^m=`=va)i9 zBrR&;%I)(Q{dgav@drZx=%4f;JlQA+e(wti%o3O;Nv|Gm!E!^6DEAou9&c76g5d9W zURG8Hg+eh;lK%B18OT0MwP%M?*Rq2fby;=r6z($h=w)1caPPQFyo1ux(o9K72}LBL z^bM2>7yD0RfN}3VS(t_E0~tuA()Gf^!c1OX-ojCTB3az0XgskiY+KQ=H}?tm{{Txk VbfZtx1EK%`002ovPDHLkV1hC*Sxo={ literal 0 HcmV?d00001 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 @@ + From 73af14b752b0af787c16d6fb59b3cd850316f777 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 16 Apr 2018 18:16:48 +0300 Subject: [PATCH 6/8] BBCode implementation --- src/lib/defaultOptions.js | 11 ++- src/plugins/video.js | 199 ++++++++++++++++++++++++-------------- 2 files changed, 135 insertions(+), 75 deletions(-) diff --git a/src/lib/defaultOptions.js b/src/lib/defaultOptions.js index 73f3fbdec..079ea278e 100644 --- a/src/lib/defaultOptions.js +++ b/src/lib/defaultOptions.js @@ -357,7 +357,16 @@ export default { * @type {string} */ youtubeParameters: 'width="560" height="315" frameborder="0" ' + - 'allowfullscreen' + '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"' }, /** diff --git a/src/plugins/video.js b/src/plugins/video.js index dc1803611..6d456b47b 100644 --- a/src/plugins/video.js +++ b/src/plugins/video.js @@ -20,11 +20,18 @@ 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; @@ -37,6 +44,8 @@ if (opts.toolbar === sceditor.defaultOptions.toolbar) { opts.toolbar = opts.toolbar.replace(',image,', ',image,video,'); + + opts.toolbar = opts.toolbar.replace(',youtube', ''); } // Remove youtube command @@ -49,93 +58,93 @@ tooltip: 'Insert a video (YouTube, Facebook)' }); - /* - // Override current implementation - sceditor.formats.bbcode.set('list', { - breakStart: true, - isInline: false, - skipLastLineBreak: true, + 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) { - var listType = 'disc'; - var toHtml = null; + return facebookHtml(this.opts.facebookParameters, content); + } + }); + }; - if (attrs.defaultattr) { - listType = attrs.defaultattr; - } + youtubeHtml = function (pOpts, id, time) { + return ''; + }; - if (listType === '1') { - // This listType belongs to orderedList (OL) - toHtml = sceditor.formats.bbcode.get('ol').html; - } else { - // unknown listType, use default bullet list behavior - toHtml = sceditor.formats.bbcode.get('ul').html; + 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 (isFunction(toHtml)) { - return toHtml.call(this, token, attrs, content); + if (editor.sourceMode()) { + if (time === 0) { + editor.insertText('[youtube]' + id + '[/youtube]'); } else { - token.attrs['0'] = content; - return sceditor.formats.bbcode.formatBBCodeString( - toHtml, token.attrs); + editor.insertText('[youtube=' + time + ']' + id + + '[/youtube]'); } + } else { + editor.wysiwygEditorInsertHtml( + youtubeHtml(pOpts.youtubeParameters, id, time)); } - }); - - sceditor.formats.bbcode.set('ul', { - tags: { - ul: null - }, - breakStart: true, - isInline: false, - skipLastLineBreak: true, - format: '[list]{0}[/list]', - html: '

    ' - }); - - sceditor.formats.bbcode.set('ol', { - tags: { - ol: null - }, - breakStart: true, - isInline: false, - skipLastLineBreak: true, - format: '[list=1]{0}[/list]', - html: '
      {0}
    ' - }); - - sceditor.formats.bbcode.set('li', { - tags: { - li: null - }, - isInline: false, - closedBy: ['/ul', '/ol', '/list', '*', 'li'], - format: '[*]{0}', - html: '
  • {0}
  • ' - }); - sceditor.formats.bbcode.set('*', { - isInline: false, - excludeClosing: true, - closedBy: ['/ul', '/ol', '/list', '*', 'li'], - html: '
  • {0}
  • ' - }); + return true; + } else { + return false; + } }; - insertListTag = function (editor, listType, selected) { - var content = ''; + processFacebook = function (editor, val) { + var pOpts = editor.opts.parserOptions; + var idMatch = val.match(/videos\/(\d+)+|v=(\d+)|vb.\d+\/(\d+)/); - utils.each(selected.split(/\r?\n/), function (item) { - content += (content ? '\n' : '') + - '[*]' + item; - }); + if (idMatch && /^[a-zA-Z0-9]/.test(idMatch[1])) { + var id = idMatch[1]; - if (listType === '') { - editor.insertText('[list]\n' + content + '\n[/list]'); + if (editor.sourceMode()) { + editor.insertText('[facebook]' + id + '[/facebook]'); + } else { + editor.wysiwygEditorInsertHtml( + facebookHtml(pOpts.facebookParameters, id)); + } + + return true; } else { - editor.insertText('[list=' + listType + ']\n' + content + - '\n[/list]'); + return false; } - */ }; /** @@ -144,10 +153,52 @@ * @param {node} caller * @private */ - commandHandler = function (caller, selected) { + 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 = false; + + done = processYoutube(editor, val); + if (!done) { + done = processFacebook(editor, val); + } + + editor.closeDropDown(true); + e.preventDefault(); + }); - editor.insertText(selected); + editor.createDropDown(caller, 'insertlink', content); }; }; })(sceditor); From 6c921eadd13c549c5bfa84bdaa58686724d79336 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 16 Apr 2018 18:26:47 +0300 Subject: [PATCH 7/8] Make DeepScan happy --- src/plugins/video.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/video.js b/src/plugins/video.js index 6d456b47b..384f9d773 100644 --- a/src/plugins/video.js +++ b/src/plugins/video.js @@ -187,11 +187,11 @@ button.addEventListener('click', function (e) { var val = input.value; - var done = false; + var done; done = processYoutube(editor, val); if (!done) { - done = processFacebook(editor, val); + processFacebook(editor, val); } editor.closeDropDown(true); From 91d175a9ada536cc01bfd94b8550d737f6772aa5 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 16 Apr 2018 19:44:44 +0300 Subject: [PATCH 8/8] Fix missing space --- src/lib/defaultOptions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/defaultOptions.js b/src/lib/defaultOptions.js index 079ea278e..c6afd6750 100644 --- a/src/lib/defaultOptions.js +++ b/src/lib/defaultOptions.js @@ -364,7 +364,7 @@ export default { * * @type {string} */ - facebookParameters: 'width="560" height="315"' + + facebookParameters: 'width="560" height="315" ' + 'style="border:none;overflow:hidden" scrolling="no" ' + 'frameborder="0" allowTransparency="true" allowFullScreen="true"' },