Santhosh has uploaded a new change for review. https://gerrit.wikimedia.org/r/158369
Change subject: WIP: Format Tool card ...................................................................... WIP: Format Tool card Don't merge. A quick and minimal functionality is added, just to find out how it goes * Remove experimental medium editor Change-Id: I794d7e6bfcd3792acea6cae6ec00492251018ba7 --- M Resources.php M modules/editor/ext.cx.editor.js D modules/editor/medium/medium-editor.css D modules/editor/medium/medium-editor.js D modules/editor/medium/theme/agora.css A modules/tools/ext.cx.tools.formatter.js A modules/tools/images/bold-b.svg A modules/tools/images/bullet-list-ltr.svg A modules/tools/images/bullet-list-rtl.svg A modules/tools/images/italic-i.svg A modules/tools/images/number-list-ltr.svg A modules/tools/images/number-list-rtl.svg A modules/tools/styles/ext.cx.tools.formatter.less M specials/SpecialContentTranslation.php 14 files changed, 281 insertions(+), 1,716 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/ContentTranslation refs/changes/69/158369/1 diff --git a/Resources.php b/Resources.php index 9a0ac02..68d0f9f 100644 --- a/Resources.php +++ b/Resources.php @@ -122,6 +122,7 @@ ), 'dependencies' => array( 'ext.cx.translation.progress', + 'ext.cx.editor', 'jquery.uls.data', 'mediawiki.Uri', 'jquery.throttle-debounce', @@ -164,6 +165,7 @@ 'mediawiki.jqueryMsg', 'ext.cx.tools.manager', 'ext.cx.tools.dictionary', + 'ext.cx.tools.formatter', 'ext.cx.tools.instructions', 'ext.cx.tools.link', 'ext.cx.tools.images', @@ -217,6 +219,19 @@ 'cx-mt-abuse-warning-text', 'cx-tools-view-guidelines', 'cx-tools-view-guidelines-link', + ), + 'dependencies' => array( + 'ext.cx.tools.manager', + 'ext.cx.tools.card', + ), +) + $resourcePaths; + +$wgResourceModules['ext.cx.tools.formatter'] = array( + 'scripts' => array( + 'tools/ext.cx.tools.formatter.js', + ), + 'styles' => array( + 'tools/styles/ext.cx.tools.formatter.less', ), 'dependencies' => array( 'ext.cx.tools.manager', @@ -422,15 +437,3 @@ 'jquery.throttle-debounce', ), ) + $resourcePaths; - -$wgResourceModules['ext.cx.editor.medium'] = array( - 'scripts' => array( - 'editor/ext.cx.editor.js', - 'editor/medium/medium-editor.js', - ), - 'styles' => array( - 'editor/medium/medium-editor.css', - 'editor/medium/theme/agora.css', - ), -) + $resourcePaths; - diff --git a/modules/editor/ext.cx.editor.js b/modules/editor/ext.cx.editor.js index 455216a..091f521 100644 --- a/modules/editor/ext.cx.editor.js +++ b/modules/editor/ext.cx.editor.js @@ -34,7 +34,6 @@ } // Make the element editable this.$editableElement.attr( 'contenteditable', true ); - this.wysiwygEditor(); }; /** @@ -69,38 +68,6 @@ return e.which !== 13; // Enter key code } ); } - }; - - /** - * Enhance the basic content editable with a WYSIWYG editor - * MediumEditor is used here. But any such simple editor - * can work here. - */ - CXSectionEditor.prototype.wysiwygEditor = function () { - var editorOptions; - - if ( !window.MediumEditor ) { - return; - } - editorOptions = { - cleanPastedHTML: true, - buttons: [ 'bold', 'italic', 'header1', 'header2', - 'unorderedlist', 'orderedlist', 'indent', 'outdent' ], - firstHeader: 'h2', - secondHeader: 'h3', - disableDoubleReturn: true - }; - // Avoid previews on mouse over of links - MediumEditor.prototype.editorAnchorObserver = function () {}; - - if ( this.$editableElement.get( 0 ).tagName === 'FIGURECAPTION' ) { - // Prevent pressing return on caption to avoid - // creation of <p> nodes - editorOptions.disableReturn = true; - } - - /*jshint -W031 */ - new MediumEditor( this.$editableElement, editorOptions ); }; $.fn.cxEditor = function () { diff --git a/modules/editor/medium/medium-editor.css b/modules/editor/medium/medium-editor.css deleted file mode 100644 index 244f74c..0000000 --- a/modules/editor/medium/medium-editor.css +++ /dev/null @@ -1,168 +0,0 @@ -.clearfix:after { - display: block; - visibility: hidden; - clear: both; - height: 0; - content: " "; - font-size: 0; } - -@-webkit-keyframes pop-upwards { - 0% { - -webkit-transform: matrix(0.97, 0, 0, 1, 0, 12); - transform: matrix(0.97, 0, 0, 1, 0, 12); - opacity: 0; } - - 20% { - -webkit-transform: matrix(0.99, 0, 0, 1, 0, 2); - transform: matrix(0.99, 0, 0, 1, 0, 2); - opacity: 0.7; } - - 40% { - -webkit-transform: matrix(1, 0, 0, 1, 0, -1); - transform: matrix(1, 0, 0, 1, 0, -1); - opacity: 1; } - - 70% { - -webkit-transform: matrix(1, 0, 0, 1, 0, 0); - transform: matrix(1, 0, 0, 1, 0, 0); - opacity: 1; } - - 100% { - -webkit-transform: matrix(1, 0, 0, 1, 0, 0); - transform: matrix(1, 0, 0, 1, 0, 0); - opacity: 1; } } - -@keyframes pop-upwards { - 0% { - -webkit-transform: matrix(0.97, 0, 0, 1, 0, 12); - transform: matrix(0.97, 0, 0, 1, 0, 12); - opacity: 0; } - - 20% { - -webkit-transform: matrix(0.99, 0, 0, 1, 0, 2); - transform: matrix(0.99, 0, 0, 1, 0, 2); - opacity: 0.7; } - - 40% { - -webkit-transform: matrix(1, 0, 0, 1, 0, -1); - transform: matrix(1, 0, 0, 1, 0, -1); - opacity: 1; } - - 70% { - -webkit-transform: matrix(1, 0, 0, 1, 0, 0); - transform: matrix(1, 0, 0, 1, 0, 0); - opacity: 1; } - - 100% { - -webkit-transform: matrix(1, 0, 0, 1, 0, 0); - transform: matrix(1, 0, 0, 1, 0, 0); - opacity: 1; } } - -.medium-toolbar-arrow-under:after, .medium-toolbar-arrow-over:before { - position: absolute; - left: 50%; - display: block; - margin-left: -8px; - width: 0; - height: 0; - border-style: solid; - content: ""; } - -.medium-toolbar-arrow-under:after { - border-width: 8px 8px 0 8px; } - -.medium-toolbar-arrow-over:before { - top: -8px; - border-width: 0 8px 8px 8px; } - -.medium-editor-toolbar, .medium-editor-anchor-preview { - position: absolute; - top: 0; - left: 0; - z-index: 2000; - visibility: hidden; - font-size: 16px; - font-family: HelveticaNeue, Helvetica, Arial, sans-serif; } - .medium-editor-toolbar ul, .medium-editor-anchor-preview ul { - margin: 0; - padding: 0; } - .medium-editor-toolbar li, .medium-editor-anchor-preview li { - float: left; - margin: 0; - padding: 0; - list-style: none; } - .medium-editor-toolbar li button, .medium-editor-anchor-preview li button { - display: block; - margin: 0; - padding: 15px; - cursor: pointer; - font-size: 14px; - line-height: 1.33; - text-decoration: none; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; } - .medium-editor-toolbar li .medium-editor-action-underline, .medium-editor-anchor-preview li .medium-editor-action-underline { - text-decoration: underline; } - .medium-editor-toolbar li .medium-editor-action-pre, .medium-editor-anchor-preview li .medium-editor-action-pre { - padding: 15px 0; - font-weight: 100; - font-size: 12px; - font-family: 'Menlo', monospace; } - -.medium-editor-anchor-preview i { - display: inline-block; - margin: 5px 5px 5px 10px; - text-decoration: underline; - font-style: normal; - cursor: pointer; } - -.medium-editor-toolbar-active, .medium-editor-anchor-preview-active { - visibility: visible; - -webkit-animation: pop-upwards 160ms forwards linear; - -ms-animation: pop-upwards 160ms forwards linear; - animation: pop-upwards 160ms forwards linear; - -webkit-transition: top 0.075s ease-out, left 0.075s ease-out; - transition: top 0.075s ease-out, left 0.075s ease-out; } - -.medium-editor-action-bold { - font-weight: bolder; } - -.medium-editor-action-italic { - font-style: italic; } - -.medium-editor-toolbar-form-anchor { - display: none; } - .medium-editor-toolbar-form-anchor input, .medium-editor-toolbar-form-anchor a { - font-family: HelveticaNeue, Helvetica, Arial, sans-serif; } - .medium-editor-toolbar-form-anchor input { - margin: 0; - padding: 6px; - width: 316px; - border: none; - font-size: 14px; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; } - .medium-editor-toolbar-form-anchor input:focus { - outline: 0; - border: none; - -webkit-box-shadow: none; - box-shadow: none; - -webkit-appearance: none; - -moz-appearance: none; } - .medium-editor-toolbar-form-anchor a { - display: inline-block; - margin: 0 10px; - text-decoration: none; - font-weight: bolder; - font-size: 24px; } - -.medium-editor-placeholder { - position: relative; } - .medium-editor-placeholder:after { - position: absolute; - top: 0; - left: 0; - content: attr(data-placeholder); - font-style: italic; } diff --git a/modules/editor/medium/medium-editor.js b/modules/editor/medium/medium-editor.js deleted file mode 100644 index 8b3caab..0000000 --- a/modules/editor/medium/medium-editor.js +++ /dev/null @@ -1,1365 +0,0 @@ -function MediumEditor(elements, options) { - 'use strict'; - return this.init(elements, options); -} - -if (typeof module === 'object') { - module.exports = MediumEditor; -} - -(function (window, document) { - 'use strict'; - - function extend(b, a) { - var prop; - if (b === undefined) { - return a; - } - for (prop in a) { - if (a.hasOwnProperty(prop) && b.hasOwnProperty(prop) === false) { - b[prop] = a[prop]; - } - } - return b; - } - - // http://stackoverflow.com/questions/5605401/insert-link-in-contenteditable-element - // by Tim Down - function saveSelection() { - var i, - len, - ranges, - sel = window.getSelection(); - if (sel.getRangeAt && sel.rangeCount) { - ranges = []; - for (i = 0, len = sel.rangeCount; i < len; i += 1) { - ranges.push(sel.getRangeAt(i)); - } - return ranges; - } - return null; - } - - function restoreSelection(savedSel) { - var i, - len, - sel = window.getSelection(); - if (savedSel) { - sel.removeAllRanges(); - for (i = 0, len = savedSel.length; i < len; i += 1) { - sel.addRange(savedSel[i]); - } - } - } - - // http://stackoverflow.com/questions/1197401/how-can-i-get-the-element-the-caret-is-in-with-javascript-when-using-contentedi - // by You - function getSelectionStart() { - var node = document.getSelection().anchorNode, - startNode = (node && node.nodeType === 3 ? node.parentNode : node); - return startNode; - } - - // http://stackoverflow.com/questions/4176923/html-of-selected-text - // by Tim Down - function getSelectionHtml() { - var i, - html = '', - sel, - len, - container; - if (window.getSelection !== undefined) { - sel = window.getSelection(); - if (sel.rangeCount) { - container = document.createElement('div'); - for (i = 0, len = sel.rangeCount; i < len; i += 1) { - container.appendChild(sel.getRangeAt(i).cloneContents()); - } - html = container.innerHTML; - } - } else if (document.selection !== undefined) { - if (document.selection.type === 'Text') { - html = document.selection.createRange().htmlText; - } - } - return html; - } - - // https://github.com/jashkenas/underscore - function isElement(obj) { - return !!(obj && obj.nodeType === 1); - } - - MediumEditor.prototype = { - defaults: { - allowMultiParagraphSelection: true, - anchorInputPlaceholder: 'Paste or type a link', - anchorPreviewHideDelay: 500, - buttons: ['bold', 'italic', 'underline', 'anchor', 'header1', 'header2', 'quote'], - buttonLabels: false, - checkLinkFormat: false, - cleanPastedHTML: false, - delay: 0, - diffLeft: 0, - diffTop: -10, - disableReturn: false, - disableDoubleReturn: false, - disableToolbar: false, - disableEditing: false, - elementsContainer: false, - firstHeader: 'h3', - forcePlainText: true, - placeholder: 'Type your text', - secondHeader: 'h4', - targetBlank: false, - extensions: {}, - activeButtonClass: 'medium-editor-button-active', - firstButtonClass: 'medium-editor-button-first', - lastButtonClass: 'medium-editor-button-last' - }, - - // http://stackoverflow.com/questions/17907445/how-to-detect-ie11#comment30165888_17907562 - // by rg89 - isIE: ((navigator.appName === 'Microsoft Internet Explorer') || ((navigator.appName === 'Netscape') && (new RegExp('Trident/.*rv:([0-9]{1,}[.0-9]{0,})').exec(navigator.userAgent) !== null))), - - init: function (elements, options) { - this.setElementSelection(elements); - if (this.elements.length === 0) { - return; - } - this.parentElements = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre']; - this.id = document.querySelectorAll('.medium-editor-toolbar').length + 1; - this.options = extend(options, this.defaults); - return this.setup(); - }, - - setup: function () { - this.isActive = true; - this.initElements() - .bindSelect() - .bindPaste() - .setPlaceholders() - .bindWindowActions(); - }, - - initElements: function () { - this.updateElementList(); - var i, - addToolbar = false; - for (i = 0; i < this.elements.length; i += 1) { - if (!this.options.disableEditing && !this.elements[i].getAttribute('data-disable-editing')) { - this.elements[i].setAttribute('contentEditable', true); - } - if (!this.elements[i].getAttribute('data-placeholder')) { - this.elements[i].setAttribute('data-placeholder', this.options.placeholder); - } - this.elements[i].setAttribute('data-medium-element', true); - this.bindParagraphCreation(i).bindReturn(i).bindTab(i); - if (!this.options.disableToolbar && !this.elements[i].getAttribute('data-disable-toolbar')) { - addToolbar = true; - } - } - // Init toolbar - if (addToolbar) { - if (!this.options.elementsContainer) { - this.options.elementsContainer = document.body; - } - this.initToolbar() - .bindButtons() - .bindAnchorForm() - .bindAnchorPreview(); - } - return this; - }, - - setElementSelection: function (selector) { - this.elementSelection = selector; - this.updateElementList(); - }, - - updateElementList: function () { - this.elements = typeof this.elementSelection === 'string' ? document.querySelectorAll(this.elementSelection) : this.elementSelection; - if (this.elements.nodeType === 1) { - this.elements = [this.elements]; - } - }, - - serialize: function () { - var i, - elementid, - content = {}; - for (i = 0; i < this.elements.length; i += 1) { - elementid = (this.elements[i].id !== '') ? this.elements[i].id : 'element-' + i; - content[elementid] = { - value: this.elements[i].innerHTML.trim() - }; - } - return content; - }, - - /** - * Helper function to call a method with a number of parameters on all registered extensions. - * The function assures that the function exists before calling. - * - * @param {string} funcName name of the function to call - * @param [args] arguments passed into funcName - */ - callExtensions: function (funcName) { - if (arguments.length < 1) { - return; - } - - var args = Array.prototype.slice.call(arguments, 1), - ext, - name; - - for (name in this.options.extensions) { - if (this.options.extensions.hasOwnProperty(name)) { - ext = this.options.extensions[name]; - if (ext[funcName] !== undefined) { - ext[funcName].apply(ext, args); - } - } - } - }, - - bindParagraphCreation: function (index) { - var self = this; - this.elements[index].addEventListener('keypress', function (e) { - var node = getSelectionStart(), - tagName; - if (e.which === 32) { - tagName = node.tagName.toLowerCase(); - if (tagName === 'a') { - document.execCommand('unlink', false, null); - } - } - }); - - this.elements[index].addEventListener('keyup', function (e) { - var node = getSelectionStart(), - tagName; - if (node && node.getAttribute('data-medium-element') && node.children.length === 0 && !(self.options.disableReturn || node.getAttribute('data-disable-return'))) { - document.execCommand('formatBlock', false, 'p'); - } - if (e.which === 13) { - node = getSelectionStart(); - tagName = node.tagName.toLowerCase(); - if (!(self.options.disableReturn || this.getAttribute('data-disable-return')) && - tagName !== 'li' && !self.isListItemChild(node)) { - if (!e.shiftKey) { - document.execCommand('formatBlock', false, 'p'); - } - if (tagName === 'a') { - document.execCommand('unlink', false, null); - } - } - } - }); - return this; - }, - - isListItemChild: function (node) { - var parentNode = node.parentNode, - tagName = parentNode.tagName.toLowerCase(); - while (this.parentElements.indexOf(tagName) === -1 && tagName !== 'div') { - if (tagName === 'li') { - return true; - } - parentNode = parentNode.parentNode; - if (parentNode && parentNode.tagName) { - tagName = parentNode.tagName.toLowerCase(); - } else { - return false; - } - } - return false; - }, - - bindReturn: function (index) { - var self = this; - this.elements[index].addEventListener('keypress', function (e) { - if (e.which === 13) { - if (self.options.disableReturn || this.getAttribute('data-disable-return')) { - e.preventDefault(); - } else if (self.options.disableDoubleReturn || this.getAttribute('data-disable-double-return')) { - var node = getSelectionStart(); - if (node && node.innerText === '\n') { - e.preventDefault(); - } - } - } - }); - return this; - }, - - bindTab: function (index) { - this.elements[index].addEventListener('keydown', function (e) { - if (e.which === 9) { - // Override tab only for pre nodes - var tag = getSelectionStart().tagName.toLowerCase(); - if (tag === 'pre') { - e.preventDefault(); - document.execCommand('insertHtml', null, ' '); - } - } - }); - return this; - }, - - buttonTemplate: function (btnType) { - var buttonLabels = this.getButtonLabels(this.options.buttonLabels), - buttonTemplates = { - 'bold': '<button class="medium-editor-action medium-editor-action-bold" data-action="bold" data-element="b">' + buttonLabels.bold + '</button>', - 'italic': '<button class="medium-editor-action medium-editor-action-italic" data-action="italic" data-element="i">' + buttonLabels.italic + '</button>', - 'underline': '<button class="medium-editor-action medium-editor-action-underline" data-action="underline" data-element="u">' + buttonLabels.underline + '</button>', - 'strikethrough': '<button class="medium-editor-action medium-editor-action-strikethrough" data-action="strikethrough" data-element="strike"><strike>A</strike></button>', - 'superscript': '<button class="medium-editor-action medium-editor-action-superscript" data-action="superscript" data-element="sup">' + buttonLabels.superscript + '</button>', - 'subscript': '<button class="medium-editor-action medium-editor-action-subscript" data-action="subscript" data-element="sub">' + buttonLabels.subscript + '</button>', - 'anchor': '<button class="medium-editor-action medium-editor-action-anchor" data-action="anchor" data-element="a">' + buttonLabels.anchor + '</button>', - 'image': '<button class="medium-editor-action medium-editor-action-image" data-action="image" data-element="img">' + buttonLabels.image + '</button>', - 'header1': '<button class="medium-editor-action medium-editor-action-header1" data-action="append-' + this.options.firstHeader + '" data-element="' + this.options.firstHeader + '">' + buttonLabels.header1 + '</button>', - 'header2': '<button class="medium-editor-action medium-editor-action-header2" data-action="append-' + this.options.secondHeader + '" data-element="' + this.options.secondHeader + '">' + buttonLabels.header2 + '</button>', - 'quote': '<button class="medium-editor-action medium-editor-action-quote" data-action="append-blockquote" data-element="blockquote">' + buttonLabels.quote + '</button>', - 'orderedlist': '<button class="medium-editor-action medium-editor-action-orderedlist" data-action="insertorderedlist" data-element="ol">' + buttonLabels.orderedlist + '</button>', - 'unorderedlist': '<button class="medium-editor-action medium-editor-action-unorderedlist" data-action="insertunorderedlist" data-element="ul">' + buttonLabels.unorderedlist + '</button>', - 'pre': '<button class="medium-editor-action medium-editor-action-pre" data-action="append-pre" data-element="pre">' + buttonLabels.pre + '</button>', - 'indent': '<button class="medium-editor-action medium-editor-action-indent" data-action="indent" data-element="ul">' + buttonLabels.indent + '</button>', - 'outdent': '<button class="medium-editor-action medium-editor-action-outdent" data-action="outdent" data-element="ul">' + buttonLabels.outdent + '</button>' - }; - return buttonTemplates[btnType] || false; - }, - - // TODO: break method - getButtonLabels: function (buttonLabelType) { - var customButtonLabels, - attrname, - buttonLabels = { - 'bold': '<b>B</b>', - 'italic': '<b><i>I</i></b>', - 'underline': '<b><u>U</u></b>', - 'superscript': '<b>x<sup>1</sup></b>', - 'subscript': '<b>x<sub>1</sub></b>', - 'anchor': '<b>#</b>', - 'image': '<b>image</b>', - 'header1': '<b>H1</b>', - 'header2': '<b>H2</b>', - 'quote': '<b>“</b>', - 'orderedlist': '<b>1.</b>', - 'unorderedlist': '<b>•</b>', - 'pre': '<b>0101</b>', - 'indent': '<b>→</b>', - 'outdent': '<b>←</b>' - }; - if (buttonLabelType === 'fontawesome') { - customButtonLabels = { - 'bold': '<i class="fa fa-bold"></i>', - 'italic': '<i class="fa fa-italic"></i>', - 'underline': '<i class="fa fa-underline"></i>', - 'superscript': '<i class="fa fa-superscript"></i>', - 'subscript': '<i class="fa fa-subscript"></i>', - 'anchor': '<i class="fa fa-link"></i>', - 'image': '<i class="fa fa-picture-o"></i>', - 'quote': '<i class="fa fa-quote-right"></i>', - 'orderedlist': '<i class="fa fa-list-ol"></i>', - 'unorderedlist': '<i class="fa fa-list-ul"></i>', - 'pre': '<i class="fa fa-code fa-lg"></i>', - 'indent': '<i class="fa fa-indent"></i>', - 'outdent': '<i class="fa fa-outdent"></i>' - }; - } else if (typeof buttonLabelType === 'object') { - customButtonLabels = buttonLabelType; - } - if (typeof customButtonLabels === 'object') { - for (attrname in customButtonLabels) { - if (customButtonLabels.hasOwnProperty(attrname)) { - buttonLabels[attrname] = customButtonLabels[attrname]; - } - } - } - return buttonLabels; - }, - - initToolbar: function () { - if (this.toolbar) { - return this; - } - this.toolbar = this.createToolbar(); - this.keepToolbarAlive = false; - this.anchorForm = this.toolbar.querySelector('.medium-editor-toolbar-form-anchor'); - this.anchorInput = this.anchorForm.querySelector('input'); - this.toolbarActions = this.toolbar.querySelector('.medium-editor-toolbar-actions'); - this.anchorPreview = this.createAnchorPreview(); - - return this; - }, - - createToolbar: function () { - var toolbar = document.createElement('div'); - toolbar.id = 'medium-editor-toolbar-' + this.id; - toolbar.className = 'medium-editor-toolbar'; - toolbar.appendChild(this.toolbarButtons()); - toolbar.appendChild(this.toolbarFormAnchor()); - this.options.elementsContainer.appendChild(toolbar); - return toolbar; - }, - - //TODO: actionTemplate - toolbarButtons: function () { - var btns = this.options.buttons, - ul = document.createElement('ul'), - li, - i, - btn, - ext; - - ul.id = 'medium-editor-toolbar-actions'; - ul.className = 'medium-editor-toolbar-actions clearfix'; - - for (i = 0; i < btns.length; i += 1) { - if (this.options.extensions.hasOwnProperty(btns[i])) { - ext = this.options.extensions[btns[i]]; - btn = ext.getButton !== undefined ? ext.getButton() : null; - } else { - btn = this.buttonTemplate(btns[i]); - } - - if (btn) { - li = document.createElement('li'); - if (isElement(btn)) { - li.appendChild(btn); - } else { - li.innerHTML = btn; - } - ul.appendChild(li); - } - } - - return ul; - }, - - toolbarFormAnchor: function () { - var anchor = document.createElement('div'), - input = document.createElement('input'), - a = document.createElement('a'); - - a.setAttribute('href', '#'); - a.innerHTML = '×'; - - input.setAttribute('type', 'text'); - input.setAttribute('placeholder', this.options.anchorInputPlaceholder); - - anchor.className = 'medium-editor-toolbar-form-anchor'; - anchor.id = 'medium-editor-toolbar-form-anchor'; - anchor.appendChild(input); - anchor.appendChild(a); - - return anchor; - }, - - bindSelect: function () { - var self = this, - timer = '', - i; - - this.checkSelectionWrapper = function (e) { - - // Do not close the toolbar when bluring the editable area and clicking into the anchor form - if (e && self.clickingIntoArchorForm(e)) { - return false; - } - - clearTimeout(timer); - timer = setTimeout(function () { - self.checkSelection(); - }, self.options.delay); - }; - - document.documentElement.addEventListener('mouseup', this.checkSelectionWrapper); - - for (i = 0; i < this.elements.length; i += 1) { - this.elements[i].addEventListener('keyup', this.checkSelectionWrapper); - this.elements[i].addEventListener('blur', this.checkSelectionWrapper); - } - return this; - }, - - checkSelection: function () { - var newSelection, - selectionElement; - - if (this.keepToolbarAlive !== true && !this.options.disableToolbar) { - newSelection = window.getSelection(); - if (newSelection.toString().trim() === '' || - (this.options.allowMultiParagraphSelection === false && this.hasMultiParagraphs())) { - this.hideToolbarActions(); - } else { - selectionElement = this.getSelectionElement(); - if (!selectionElement || selectionElement.getAttribute('data-disable-toolbar')) { - this.hideToolbarActions(); - } else { - this.checkSelectionElement(newSelection, selectionElement); - } - } - } - return this; - }, - - clickingIntoArchorForm: function (e) { - var self = this; - if (e.type && e.type.toLowerCase() === 'blur' && e.relatedTarget && e.relatedTarget === self.anchorInput) { - return true; - } - return false; - }, - - hasMultiParagraphs: function () { - var selectionHtml = getSelectionHtml().replace(/<[\S]+><\/[\S]+>/gim, ''), - hasMultiParagraphs = selectionHtml.match(/<(p|h[0-6]|blockquote)>([\s\S]*?)<\/(p|h[0-6]|blockquote)>/g); - - return (hasMultiParagraphs ? hasMultiParagraphs.length : 0); - }, - - checkSelectionElement: function (newSelection, selectionElement) { - var i; - this.selection = newSelection; - this.selectionRange = this.selection.getRangeAt(0); - for (i = 0; i < this.elements.length; i += 1) { - if (this.elements[i] === selectionElement) { - this.setToolbarButtonStates() - .setToolbarPosition() - .showToolbarActions(); - return; - } - } - this.hideToolbarActions(); - }, - - getSelectionElement: function () { - var selection = window.getSelection(), - range, current, parent, - result, - getMediumElement = function (e) { - var localParent = e; - try { - while (!localParent.getAttribute('data-medium-element')) { - localParent = localParent.parentNode; - } - } catch (errb) { - return false; - } - return localParent; - }; - // First try on current node - try { - range = selection.getRangeAt(0); - current = range.commonAncestorContainer; - parent = current.parentNode; - - if (current.getAttribute('data-medium-element')) { - result = current; - } else { - result = getMediumElement(parent); - } - // If not search in the parent nodes. - } catch (err) { - result = getMediumElement(parent); - } - return result; - }, - - setToolbarPosition: function () { - var buttonHeight = 50, - selection = window.getSelection(), - range = selection.getRangeAt(0), - boundary = range.getBoundingClientRect(), - defaultLeft = (this.options.diffLeft) - (this.toolbar.offsetWidth / 2), - middleBoundary = (boundary.left + boundary.right) / 2, - halfOffsetWidth = this.toolbar.offsetWidth / 2; - if (boundary.top < buttonHeight) { - this.toolbar.classList.add('medium-toolbar-arrow-over'); - this.toolbar.classList.remove('medium-toolbar-arrow-under'); - this.toolbar.style.top = buttonHeight + boundary.bottom - this.options.diffTop + window.pageYOffset - this.toolbar.offsetHeight + 'px'; - } else { - this.toolbar.classList.add('medium-toolbar-arrow-under'); - this.toolbar.classList.remove('medium-toolbar-arrow-over'); - this.toolbar.style.top = boundary.top + this.options.diffTop + window.pageYOffset - this.toolbar.offsetHeight + 'px'; - } - if (middleBoundary < halfOffsetWidth) { - this.toolbar.style.left = defaultLeft + halfOffsetWidth + 'px'; - } else if ((window.innerWidth - middleBoundary) < halfOffsetWidth) { - this.toolbar.style.left = window.innerWidth + defaultLeft - halfOffsetWidth + 'px'; - } else { - this.toolbar.style.left = defaultLeft + middleBoundary + 'px'; - } - - this.hideAnchorPreview(); - - return this; - }, - - setToolbarButtonStates: function () { - var buttons = this.toolbarActions.querySelectorAll('button'), - i; - for (i = 0; i < buttons.length; i += 1) { - buttons[i].classList.remove(this.options.activeButtonClass); - } - this.checkActiveButtons(); - return this; - }, - - checkActiveButtons: function () { - var elements = Array.prototype.slice.call(this.elements), - parentNode = this.getSelectedParentElement(); - while (parentNode.tagName !== undefined && this.parentElements.indexOf(parentNode.tagName.toLowerCase) === -1) { - this.activateButton(parentNode.tagName.toLowerCase()); - this.callExtensions('checkState', parentNode); - - // we can abort the search upwards if we leave the contentEditable element - if (elements.indexOf(parentNode) !== -1) { - break; - } - parentNode = parentNode.parentNode; - } - }, - - activateButton: function (tag) { - var el = this.toolbar.querySelector('[data-element="' + tag + '"]'); - if (el !== null && el.className.indexOf(this.options.activeButtonClass) === -1) { - el.className += ' ' + this.options.activeButtonClass; - } - }, - - bindButtons: function () { - var buttons = this.toolbar.querySelectorAll('button'), - i, - self = this, - triggerAction = function (e) { - e.preventDefault(); - e.stopPropagation(); - if (self.selection === undefined) { - self.checkSelection(); - } - if (this.className.indexOf(self.options.activeButtonClass) > -1) { - this.classList.remove(self.options.activeButtonClass); - } else { - this.className += ' ' + self.options.activeButtonClass; - } - if (this.hasAttribute('data-action')) { - self.execAction(this.getAttribute('data-action'), e); - } - }; - for (i = 0; i < buttons.length; i += 1) { - buttons[i].addEventListener('click', triggerAction); - } - this.setFirstAndLastItems(buttons); - return this; - }, - - setFirstAndLastItems: function (buttons) { - if (buttons.length > 0) { - buttons[0].className += ' ' + this.options.firstButtonClass; - buttons[buttons.length - 1].className += ' ' + this.options.lastButtonClass; - } - return this; - }, - - execAction: function (action, e) { - if (action.indexOf('append-') > -1) { - this.execFormatBlock(action.replace('append-', '')); - this.setToolbarPosition(); - this.setToolbarButtonStates(); - } else if (action === 'anchor') { - this.triggerAnchorAction(e); - } else if (action === 'image') { - document.execCommand('insertImage', false, window.getSelection()); - } else { - document.execCommand(action, false, null); - this.setToolbarPosition(); - } - }, - - // http://stackoverflow.com/questions/15867542/range-object-get-selection-parent-node-chrome-vs-firefox - rangeSelectsSingleNode: function (range) { - var startNode = range.startContainer; - return startNode === range.endContainer && - startNode.hasChildNodes() && - range.endOffset === range.startOffset + 1; - }, - - getSelectedParentElement: function () { - var selectedParentElement = null, - range = this.selectionRange; - if (this.rangeSelectsSingleNode(range)) { - selectedParentElement = range.startContainer.childNodes[range.startOffset]; - } else if (range.startContainer.nodeType === 3) { - selectedParentElement = range.startContainer.parentNode; - } else { - selectedParentElement = range.startContainer; - } - return selectedParentElement; - }, - - triggerAnchorAction: function () { - var selectedParentElement = this.getSelectedParentElement(); - if (selectedParentElement.tagName && - selectedParentElement.tagName.toLowerCase() === 'a') { - document.execCommand('unlink', false, null); - } else { - if (this.anchorForm.style.display === 'block') { - this.showToolbarActions(); - } else { - this.showAnchorForm(); - } - } - return this; - }, - - execFormatBlock: function (el) { - var selectionData = this.getSelectionData(this.selection.anchorNode); - // FF handles blockquote differently on formatBlock - // allowing nesting, we need to use outdent - // https://developer.mozilla.org/en-US/docs/Rich-Text_Editing_in_Mozilla - if (el === 'blockquote' && selectionData.el && - selectionData.el.parentNode.tagName.toLowerCase() === 'blockquote') { - return document.execCommand('outdent', false, null); - } - if (selectionData.tagName === el) { - el = 'p'; - } - // When IE we need to add <> to heading elements and - // blockquote needs to be called as indent - // http://stackoverflow.com/questions/10741831/execcommand-formatblock-headings-in-ie - // http://stackoverflow.com/questions/1816223/rich-text-editor-with-blockquote-function/1821777#1821777 - if (this.isIE) { - if (el === 'blockquote') { - return document.execCommand('indent', false, el); - } - el = '<' + el + '>'; - } - return document.execCommand('formatBlock', false, el); - }, - - getSelectionData: function (el) { - var tagName; - - if (el && el.tagName) { - tagName = el.tagName.toLowerCase(); - } - - while (el && this.parentElements.indexOf(tagName) === -1) { - el = el.parentNode; - if (el && el.tagName) { - tagName = el.tagName.toLowerCase(); - } - } - - return { - el: el, - tagName: tagName - }; - }, - - getFirstChild: function (el) { - var firstChild = el.firstChild; - while (firstChild !== null && firstChild.nodeType !== 1) { - firstChild = firstChild.nextSibling; - } - return firstChild; - }, - - hideToolbarActions: function () { - this.keepToolbarAlive = false; - if (this.toolbar !== undefined) { - this.toolbar.classList.remove('medium-editor-toolbar-active'); - } - }, - - showToolbarActions: function () { - var self = this, - timer; - this.anchorForm.style.display = 'none'; - this.toolbarActions.style.display = 'block'; - this.keepToolbarAlive = false; - clearTimeout(timer); - timer = setTimeout(function () { - if (self.toolbar && !self.toolbar.classList.contains('medium-editor-toolbar-active')) { - self.toolbar.classList.add('medium-editor-toolbar-active'); - } - }, 100); - }, - - saveSelection: function() { - this.savedSelection = saveSelection(); - }, - - restoreSelection: function() { - restoreSelection(this.savedSelection); - }, - - showAnchorForm: function (link_value) { - this.toolbarActions.style.display = 'none'; - this.saveSelection(); - this.anchorForm.style.display = 'block'; - this.keepToolbarAlive = true; - this.anchorInput.focus(); - this.anchorInput.value = link_value || ''; - }, - - bindAnchorForm: function () { - var linkCancel = this.anchorForm.querySelector('a'), - self = this; - this.anchorForm.addEventListener('click', function (e) { - e.stopPropagation(); - }); - this.anchorInput.addEventListener('keyup', function (e) { - if (e.keyCode === 13) { - e.preventDefault(); - self.createLink(this); - } - }); - this.anchorInput.addEventListener('click', function (e) { - // make sure not to hide form when cliking into the input - e.stopPropagation(); - self.keepToolbarAlive = true; - }); - this.anchorInput.addEventListener('blur', function () { - self.keepToolbarAlive = false; - self.checkSelection(); - }); - linkCancel.addEventListener('click', function (e) { - e.preventDefault(); - self.showToolbarActions(); - restoreSelection(self.savedSelection); - }); - return this; - }, - - - hideAnchorPreview: function () { - this.anchorPreview.classList.remove('medium-editor-anchor-preview-active'); - }, - - // TODO: break method - showAnchorPreview: function (anchorEl) { - if (this.anchorPreview.classList.contains('medium-editor-anchor-preview-active')) { - return true; - } - - var self = this, - buttonHeight = 40, - boundary = anchorEl.getBoundingClientRect(), - middleBoundary = (boundary.left + boundary.right) / 2, - halfOffsetWidth, - defaultLeft, - timer; - - self.anchorPreview.querySelector('i').textContent = anchorEl.href; - halfOffsetWidth = self.anchorPreview.offsetWidth / 2; - defaultLeft = self.options.diffLeft - halfOffsetWidth; - - clearTimeout(timer); - timer = setTimeout(function () { - if (self.anchorPreview && !self.anchorPreview.classList.contains('medium-editor-anchor-preview-active')) { - self.anchorPreview.classList.add('medium-editor-anchor-preview-active'); - } - }, 100); - - self.observeAnchorPreview(anchorEl); - - self.anchorPreview.classList.add('medium-toolbar-arrow-over'); - self.anchorPreview.classList.remove('medium-toolbar-arrow-under'); - self.anchorPreview.style.top = Math.round(buttonHeight + boundary.bottom - self.options.diffTop + window.pageYOffset - self.anchorPreview.offsetHeight) + 'px'; - if (middleBoundary < halfOffsetWidth) { - self.anchorPreview.style.left = defaultLeft + halfOffsetWidth + 'px'; - } else if ((window.innerWidth - middleBoundary) < halfOffsetWidth) { - self.anchorPreview.style.left = window.innerWidth + defaultLeft - halfOffsetWidth + 'px'; - } else { - self.anchorPreview.style.left = defaultLeft + middleBoundary + 'px'; - } - - return this; - }, - - // TODO: break method - observeAnchorPreview: function (anchorEl) { - var self = this, - lastOver = (new Date()).getTime(), - over = true, - stamp = function () { - lastOver = (new Date()).getTime(); - over = true; - }, - unstamp = function (e) { - if (!e.relatedTarget || !/anchor-preview/.test(e.relatedTarget.className)) { - over = false; - } - }, - interval_timer = setInterval(function () { - if (over) { - return true; - } - var durr = (new Date()).getTime() - lastOver; - if (durr > self.options.anchorPreviewHideDelay) { - // hide the preview 1/2 second after mouse leaves the link - self.hideAnchorPreview(); - - // cleanup - clearInterval(interval_timer); - self.anchorPreview.removeEventListener('mouseover', stamp); - self.anchorPreview.removeEventListener('mouseout', unstamp); - anchorEl.removeEventListener('mouseover', stamp); - anchorEl.removeEventListener('mouseout', unstamp); - - } - }, 200); - - self.anchorPreview.addEventListener('mouseover', stamp); - self.anchorPreview.addEventListener('mouseout', unstamp); - anchorEl.addEventListener('mouseover', stamp); - anchorEl.addEventListener('mouseout', unstamp); - }, - - createAnchorPreview: function () { - var self = this, - anchorPreview = document.createElement('div'); - - anchorPreview.id = 'medium-editor-anchor-preview-' + this.id; - anchorPreview.className = 'medium-editor-anchor-preview'; - anchorPreview.innerHTML = this.anchorPreviewTemplate(); - this.options.elementsContainer.appendChild(anchorPreview); - - anchorPreview.addEventListener('click', function () { - self.anchorPreviewClickHandler(); - }); - - return anchorPreview; - }, - - anchorPreviewTemplate: function () { - return '<div class="medium-editor-toolbar-anchor-preview" id="medium-editor-toolbar-anchor-preview">' + - ' <i class="medium-editor-toolbar-anchor-preview-inner"></i>' + - '</div>'; - }, - - anchorPreviewClickHandler: function (e) { - if (this.activeAnchor) { - - var self = this, - range = document.createRange(), - sel = window.getSelection(); - - range.selectNodeContents(self.activeAnchor); - sel.removeAllRanges(); - sel.addRange(range); - setTimeout(function () { - if (self.activeAnchor) { - self.showAnchorForm(self.activeAnchor.href); - } - self.keepToolbarAlive = false; - }, 100 + self.options.delay); - - } - - this.hideAnchorPreview(); - }, - - editorAnchorObserver: function (e) { - var self = this, - overAnchor = true, - leaveAnchor = function () { - // mark the anchor as no longer hovered, and stop listening - overAnchor = false; - self.activeAnchor.removeEventListener('mouseout', leaveAnchor); - }; - - if (e.target && e.target.tagName.toLowerCase() === 'a') { - - // Detect empty href attributes - // The browser will make href="" or href="#top" - // into absolute urls when accessed as e.targed.href, so check the html - if (!/href=["']\S+["']/.test(e.target.outerHTML) || /href=["']#\S+["']/.test(e.target.outerHTML)) { - return true; - } - - // only show when hovering on anchors - if (this.toolbar.classList.contains('medium-editor-toolbar-active')) { - // only show when toolbar is not present - return true; - } - this.activeAnchor = e.target; - this.activeAnchor.addEventListener('mouseout', leaveAnchor); - // show the anchor preview according to the configured delay - // if the mouse has not left the anchor tag in that time - setTimeout(function () { - if (overAnchor) { - self.showAnchorPreview(e.target); - } - }, self.options.delay); - - - } - }, - - bindAnchorPreview: function (index) { - var i, self = this; - this.editorAnchorObserverWrapper = function (e) { - self.editorAnchorObserver(e); - }; - for (i = 0; i < this.elements.length; i += 1) { - this.elements[i].addEventListener('mouseover', this.editorAnchorObserverWrapper); - } - return this; - }, - - checkLinkFormat: function (value) { - var re = /^https?:\/\//; - if (value.match(re)) { - return value; - } - return "http://" + value; - }, - - setTargetBlank: function () { - var el = getSelectionStart(), - i; - if (el.tagName.toLowerCase() === 'a') { - el.target = '_blank'; - } else { - el = el.getElementsByTagName('a'); - for (i = 0; i < el.length; i += 1) { - el[i].target = '_blank'; - } - } - }, - - createLink: function (input) { - if (input.value.trim().length === 0) { - this.hideToolbarActions(); - return; - } - restoreSelection(this.savedSelection); - if (this.options.checkLinkFormat) { - input.value = this.checkLinkFormat(input.value); - } - document.execCommand('createLink', false, input.value); - if (this.options.targetBlank) { - this.setTargetBlank(); - } - this.checkSelection(); - this.showToolbarActions(); - input.value = ''; - }, - - bindWindowActions: function () { - var timerResize, - self = this; - this.windowResizeHandler = function () { - clearTimeout(timerResize); - timerResize = setTimeout(function () { - if (self.toolbar && self.toolbar.classList.contains('medium-editor-toolbar-active')) { - self.setToolbarPosition(); - } - }, 100); - }; - window.addEventListener('resize', this.windowResizeHandler); - return this; - }, - - activate: function () { - if (this.isActive) { - return; - } - - this.setup(); - }, - - // TODO: break method - deactivate: function () { - var i; - if (!this.isActive) { - return; - } - this.isActive = false; - - if (this.toolbar !== undefined) { - this.options.elementsContainer.removeChild(this.anchorPreview); - this.options.elementsContainer.removeChild(this.toolbar); - delete this.toolbar; - delete this.anchorPreview; - } - - document.documentElement.removeEventListener('mouseup', this.checkSelectionWrapper); - window.removeEventListener('resize', this.windowResizeHandler); - - for (i = 0; i < this.elements.length; i += 1) { - this.elements[i].removeEventListener('mouseover', this.editorAnchorObserverWrapper); - this.elements[i].removeEventListener('keyup', this.checkSelectionWrapper); - this.elements[i].removeEventListener('blur', this.checkSelectionWrapper); - this.elements[i].removeEventListener('paste', this.pasteWrapper); - this.elements[i].removeAttribute('contentEditable'); - this.elements[i].removeAttribute('data-medium-element'); - } - - }, - - htmlEntities: function (str) { - // converts special characters (like <) into their escaped/encoded values (like <). - // This allows you to show to display the string without the browser reading it as HTML. - return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); - }, - - bindPaste: function () { - var i, self = this; - this.pasteWrapper = function (e) { - var paragraphs, - html = '', - p; - - this.classList.remove('medium-editor-placeholder'); - if (!self.options.forcePlainText && !self.options.cleanPastedHTML) { - return this; - } - - if (e.clipboardData && e.clipboardData.getData && !e.defaultPrevented) { - e.preventDefault(); - - if (self.options.cleanPastedHTML && e.clipboardData.getData('text/html')) { - return self.cleanPaste(e.clipboardData.getData('text/html')); - } - if (!(self.options.disableReturn || this.getAttribute('data-disable-return'))) { - paragraphs = e.clipboardData.getData('text/plain').split(/[\r\n]/g); - for (p = 0; p < paragraphs.length; p += 1) { - if (paragraphs[p] !== '') { - if (navigator.userAgent.match(/firefox/i) && p === 0) { - html += self.htmlEntities(paragraphs[p]); - } else { - html += '<p>' + self.htmlEntities(paragraphs[p]) + '</p>'; - } - } - } - document.execCommand('insertHTML', false, html); - } else { - document.execCommand('insertHTML', false, e.clipboardData.getData('text/plain')); - } - } - }; - for (i = 0; i < this.elements.length; i += 1) { - this.elements[i].addEventListener('paste', this.pasteWrapper); - } - return this; - }, - - setPlaceholders: function () { - var i, - activatePlaceholder = function (el) { - if (!(el.querySelector('img')) && - !(el.querySelector('blockquote')) && - el.textContent.replace(/^\s+|\s+$/g, '') === '') { - el.classList.add('medium-editor-placeholder'); - } - }, - placeholderWrapper = function (e) { - this.classList.remove('medium-editor-placeholder'); - if (e.type !== 'keypress') { - activatePlaceholder(this); - } - }; - for (i = 0; i < this.elements.length; i += 1) { - activatePlaceholder(this.elements[i]); - this.elements[i].addEventListener('blur', placeholderWrapper); - this.elements[i].addEventListener('keypress', placeholderWrapper); - } - return this; - }, - - cleanPaste: function (text) { - - /*jslint regexp: true*/ - /* - jslint does not allow character negation, because the negation - will not match any unicode characters. In the regexes in this - block, negation is used specifically to match the end of an html - tag, and in fact unicode characters *should* be allowed. - */ - var i, elList, workEl, - el = this.getSelectionElement(), - multiline = /<p|<br|<div/.test(text), - replacements = [ - - // replace two bogus tags that begin pastes from google docs - [new RegExp(/<[^>]*docs-internal-guid[^>]*>/gi), ""], - [new RegExp(/<\/b>(<br[^>]*>)?$/gi), ""], - - // un-html spaces and newlines inserted by OS X - [new RegExp(/<span class="Apple-converted-space">\s+<\/span>/g), ' '], - [new RegExp(/<br class="Apple-interchange-newline">/g), '<br>'], - - // replace google docs italics+bold with a span to be replaced once the html is inserted - [new RegExp(/<span[^>]*(font-style:italic;font-weight:bold|font-weight:bold;font-style:italic)[^>]*>/gi), '<span class="replace-with italic bold">'], - - // replace google docs italics with a span to be replaced once the html is inserted - [new RegExp(/<span[^>]*font-style:italic[^>]*>/gi), '<span class="replace-with italic">'], - - //[replace google docs bolds with a span to be replaced once the html is inserted - [new RegExp(/<span[^>]*font-weight:bold[^>]*>/gi), '<span class="replace-with bold">'], - - // replace manually entered b/i/a tags with real ones - [new RegExp(/<(\/?)(i|b|a)>/gi), '<$1$2>'], - - // replace manually a tags with real ones, converting smart-quotes from google docs - [new RegExp(/<a\s+href=("|”|“|“|”)([^&]+)("|”|“|“|”)>/gi), '<a href="$2">'] - - ]; - /*jslint regexp: false*/ - - for (i = 0; i < replacements.length; i += 1) { - text = text.replace(replacements[i][0], replacements[i][1]); - } - - if (multiline) { - - // double br's aren't converted to p tags, but we want paragraphs. - elList = text.split('<br><br>'); - - this.pasteHTML('<p>' + elList.join('</p><p>') + '</p>'); - document.execCommand('insertText', false, "\n"); - - // block element cleanup - elList = el.querySelectorAll('p,div,br'); - for (i = 0; i < elList.length; i += 1) { - - workEl = elList[i]; - - switch (workEl.tagName.toLowerCase()) { - case 'p': - case 'div': - this.filterCommonBlocks(workEl); - break; - case 'br': - this.filterLineBreak(workEl); - break; - } - - } - - - } else { - - this.pasteHTML(text); - - } - - }, - - pasteHTML: function (html) { - var elList, workEl, i, fragmentBody, pasteBlock = document.createDocumentFragment(); - - pasteBlock.appendChild(document.createElement('body')); - - fragmentBody = pasteBlock.querySelector('body'); - fragmentBody.innerHTML = html; - - this.cleanupSpans(fragmentBody); - - elList = fragmentBody.querySelectorAll('*'); - for (i = 0; i < elList.length; i += 1) { - - workEl = elList[i]; - - // delete ugly attributes - workEl.removeAttribute('class'); - workEl.removeAttribute('style'); - workEl.removeAttribute('dir'); - - if (workEl.tagName.toLowerCase() === 'meta') { - workEl.parentNode.removeChild(workEl); - } - - } - document.execCommand('insertHTML', false, fragmentBody.innerHTML.replace(/ /g, ' ')); - }, - isCommonBlock: function (el) { - return (el && (el.tagName.toLowerCase() === 'p' || el.tagName.toLowerCase() === 'div')); - }, - filterCommonBlocks: function (el) { - if (/^\s*$/.test(el.innerText)) { - el.parentNode.removeChild(el); - } - }, - filterLineBreak: function (el) { - if (this.isCommonBlock(el.previousElementSibling)) { - - // remove stray br's following common block elements - el.parentNode.removeChild(el); - - } else if (this.isCommonBlock(el.parentNode) && (el.parentNode.firstChild === el || el.parentNode.lastChild === el)) { - - // remove br's just inside open or close tags of a div/p - el.parentNode.removeChild(el); - - } else if (el.parentNode.childElementCount === 1) { - - // and br's that are the only child of a div/p - this.removeWithParent(el); - - } - - }, - - // remove an element, including its parent, if it is the only element within its parent - removeWithParent: function (el) { - if (el && el.parentNode) { - if (el.parentNode.parentNode && el.parentNode.childElementCount === 1) { - el.parentNode.parentNode.removeChild(el.parentNode); - } else { - el.parentNode.removeChild(el.parentNode); - } - } - }, - - cleanupSpans: function (container_el) { - - var i, - el, - new_el, - spans = container_el.querySelectorAll('.replace-with'); - - for (i = 0; i < spans.length; i += 1) { - - el = spans[i]; - new_el = document.createElement(el.classList.contains('bold') ? 'b' : 'i'); - - if (el.classList.contains('bold') && el.classList.contains('italic')) { - - // add an i tag as well if this has both italics and bold - new_el.innerHTML = '<i>' + el.innerHTML + '</i>'; - - } else { - - new_el.innerHTML = el.innerHTML; - - } - el.parentNode.replaceChild(new_el, el); - - } - - spans = container_el.querySelectorAll('span'); - for (i = 0; i < spans.length; i += 1) { - - el = spans[i]; - - // remove empty spans, replace others with their contents - if (/^\s*$/.test()) { - el.parentNode.removeChild(el); - } else { - el.parentNode.replaceChild(document.createTextNode(el.innerText), el); - } - - } - - } - - }; - -}(window, document)); diff --git a/modules/editor/medium/theme/agora.css b/modules/editor/medium/theme/agora.css deleted file mode 100644 index 705225b..0000000 --- a/modules/editor/medium/theme/agora.css +++ /dev/null @@ -1,128 +0,0 @@ -.medium-toolbar-arrow-under:after { - top: 45px; - border-color: #ffffff transparent transparent transparent; -} - -.medium-toolbar-arrow-under:before { - top: 48px; - border-color: #cccccc transparent transparent transparent; - position: absolute; - left: 50%; - display: block; - margin-left: -8px; - width: 0; - height: 0; - border-style: solid; - content: ""; - border-width: 8px 8px 0 8px; -} - -.medium-toolbar-arrow-over:before { - border-color: transparent transparent #cccccc transparent; -} - -.medium-toolbar-arrow-over:after { - top: -2px; - border-color: transparent transparent #ffffff transparent; - position: absolute; - left: 50%; - display: block; - margin-left: -8px; - width: 0; - height: 0; - border-style: solid; - content: ""; - top: -6px; - border-width: 0 8px 8px 8px; -} - -.medium-editor-toolbar { - border: 1px solid #cccccc; - background-color: #ffffff; - border-radius: 3px; - box-shadow: 0 2px 0 #cccccc; - transition: top .075s ease-out,left .075s ease-out; -} - -.medium-editor-toolbar li button { - min-width: 45px; - height: 45px; - border: none; - border-right: 1px solid #fbfbfb; - background-color: transparent; - color: #555555; - box-sizing: border-box; - transition: background-color .2s ease-in, color .2s ease-in; -} - -.medium-editor-toolbar li button:hover { - color: #333; - background: #fbfbfb; -} - -.medium-editor-toolbar li .medium-editor-button-first { - border-top-left-radius: 3px; - border-bottom-left-radius: 3px; -} - -.medium-editor-toolbar li .medium-editor-button-last { - border-right: none; - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; -} - -.medium-editor-toolbar li .medium-editor-button-active, .medium-editor-toolbar li .medium-editor-button-active:hover { - background-color: #f0f0f0; - color: #111111; - } - -.medium-editor-toolbar-form-anchor { - background: #ffffff; - color: #333; - border-radius: 3px; - -} - -.medium-editor-toolbar-form-anchor input { - height: 45px; - background: #ffffff; - color: #222222; -} - -.medium-editor-toolbar-form-anchor input::-webkit-input-placeholder { - color: #888888; -} - -.medium-editor-toolbar-form-anchor input:-moz-placeholder { - /* Firefox 18- */ - color: #888888; -} - -.medium-editor-toolbar-form-anchor input::-moz-placeholder { - /* Firefox 19+ */ - color: #888888; -} - -.medium-editor-toolbar-form-anchor input:-ms-input-placeholder { - color: #888888; -} - -.medium-editor-toolbar-form-anchor a { - color: #222222; -} - -.medium-editor-toolbar-anchor-preview { - background: #eeeeee; - color: #222222; - border-radius: 3px; - font-size: 0.8em; -} - -.medium-editor-anchor-preview.medium-toolbar-arrow-over:before { - border-color: transparent transparent #eeeeee transparent; -} - -.medium-editor-anchor-preview.medium-toolbar-arrow-over:after { - display:none; -} - diff --git a/modules/tools/ext.cx.tools.formatter.js b/modules/tools/ext.cx.tools.formatter.js new file mode 100644 index 0000000..8e1a1c7 --- /dev/null +++ b/modules/tools/ext.cx.tools.formatter.js @@ -0,0 +1,162 @@ +/** + * ContentTranslation Tools + * A tool that allows editors to translate pages from one language + * to another with the help of machine translation and other translation tools + * + * @file + * @ingroup Extensions + * @copyright See AUTHORS.txt + * @license GPL-2.0+ + */ +( function ( $, mw ) { + 'use strict'; + + var template = '<div class="card formatter">' + + '<span class="card__format-bold"></span>' + + '<span class="card__format-italics"></span>' + + '<span class="card__format-orderedlist"></span>' + + '<span class="card__format-bulletlist"></span>' + + '</div>'; + + /** + * Save the selection while other screen elements are clicked. + * See http://stackoverflow.com/a/3316483/337907 + */ + function saveSelection() { + var sel; + + if ( window.getSelection ) { + sel = window.getSelection(); + if ( sel.getRangeAt && sel.rangeCount ) { + return sel.getRangeAt( 0 ); + } + } else if ( document.selection && document.selection.createRange ) { + return document.selection.createRange(); + } + return null; + } + + /** + * Restore a saved selection + * See http://stackoverflow.com/a/3316483/337907 + */ + function restoreSelection( range ) { + var sel; + + if ( range ) { + if ( window.getSelection ) { + sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange( range ); + } else if ( document.selection && range.select ) { + range.select(); + } + } + } + + /** + * Returns the parent node of the current selection as jQuery object + * @return {jQuery} + */ + function getSelectionParent() { + var parent, selection; + + if ( window.getSelection ) { + selection = window.getSelection(); + if ( selection.rangeCount ) { + parent = selection.getRangeAt( 0 ).commonAncestorContainer; + if ( parent.nodeType !== 1 ) { + parent = parent.parentNode; + } + } + } else if ( document.selection ) { + selection = document.selection; + if ( selection.type !== 'Control' ) { + parent = selection.createRange().parentElement(); + } + } + + return $( parent ); + } + + function getParentSection() { + var $selectionParent = getSelectionParent(); + if ( $selectionParent.is( '[contenteditable="true"]' ) ) { + return $selectionParent; + } + return $selectionParent.parents( '[contenteditable="true"]' ); + } + + function FormatTool() { + this.$card = $( template ); + this.$section = null; + this.buttons = {}; + this.selection = null; + this.render(); + } + + FormatTool.prototype.render = function () { + this.buttons = { + bold: this.$card.find( '.card__format-bold' ), + italics: this.$card.find( '.card__format-italics' ), + orderedlist: this.$card.find( '.card__format-orderedlist' ), + bulletlist: this.$card.find( '.card__format-bulletlist' ), + }; + this.$card.hide(); + }; + + FormatTool.prototype.onShow = function () { + mw.hook( 'mw.cx.tools.shown' ).fire( true ); + }; + + FormatTool.prototype.getCard = function () { + return this.$card; + }; + + FormatTool.prototype.listen = function () { + var formatter = this; + this.buttons.bold.on( 'click', function () { + restoreSelection( formatter.selection ); + document.execCommand( 'bold', false, null ); + } ); + this.buttons.italics.on( 'click', function () { + restoreSelection( formatter.selection ); + document.execCommand( 'italic', false, null ); + } ); + this.buttons.orderedlist.on( 'click', function () { + restoreSelection( formatter.selection ); + document.execCommand( 'insertOrderedList', false, null ); + } ); + this.buttons.bulletlist.on( 'click', function () { + restoreSelection( formatter.selection ); + document.execCommand( 'insertUnorderedList', false, null ); + } ); + }; + + FormatTool.prototype.start = function ( section ) { + this.$section = section.jquery ? section : getParentSection(); + if ( !this.$section || !this.$section.length ) { + return; + } + // Capture the selection + this.selection = saveSelection(); + this.listen(); + this.$card.show(); + this.onShow(); + }; + + FormatTool.prototype.stop = function () { + this.$card.remove(); + mw.hook( 'mw.cx.tools.shown' ).fire( false ); + }; + + FormatTool.prototype.getTriggerEvents = function () { + return [ + 'mw.cx.translation.focus', + 'mw.cx.select.word', + 'mw.cx.progress' + ]; + }; + + mw.cx.tools.formatter = FormatTool; +}( jQuery, mediaWiki ) ); diff --git a/modules/tools/images/bold-b.svg b/modules/tools/images/bold-b.svg new file mode 100644 index 0000000..457b572 --- /dev/null +++ b/modules/tools/images/bold-b.svg @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> + <g id="bold-b" opacity=".75"> + <path id="b" d="M7 18h6c2 0 4-1 4-3 0-1.064.011-1.975-1.989-3 2-.975 1.989-1.935 1.989-3 0-2-2-3-4-3h-6v12zm7-8c0 1.001 0 1-2 1h-2v-3h2c2 0 2 0 2 1v1zm-2 6h-2v-3h2c2 0 2 0 2 1v1s0 1-2 1z"/> + </g> +</svg> diff --git a/modules/tools/images/bullet-list-ltr.svg b/modules/tools/images/bullet-list-ltr.svg new file mode 100644 index 0000000..316882a --- /dev/null +++ b/modules/tools/images/bullet-list-ltr.svg @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> + <g id="bullet-list-ltr" opacity=".75"> + <path id="bottom_dot" d="M5 10h-1c-.552 0-1 .447-1 1v1c0 .553.448 1 1 1h1c.552 0 1-.447 1-1v-1c0-.553-.448-1-1-1z"/> + <path id="middle_dot" d="M5 17h-1c-.552 0-1 .447-1 1v1c0 .553.448 1 1 1h1c.552 0 1-.447 1-1v-1c0-.553-.448-1-1-1z"/> + <path id="top_dot" d="M5 3h-1c-.552 0-1 .447-1 1v1c0 .553.448 1 1 1h1c.552 0 1-.447 1-1v-1c0-.553-.448-1-1-1z"/> + <path id="bottom_line" d="M20 17h-11c-.552 0-1 .447-1 1v1c0 .553.448 1 1 1h11c.552 0 1-.447 1-1v-1c0-.553-.448-1-1-1z"/> + <path id="middle_line" d="M20 10h-11c-.552 0-1 .447-1 1v1c0 .553.448 1 1 1h11c.552 0 1-.447 1-1v-1c0-.553-.448-1-1-1z"/> + <path id="top_line" d="M20 3h-11c-.552 0-1 .447-1 1v1c0 .553.448 1 1 1h11c.552 0 1-.447 1-1v-1c0-.553-.448-1-1-1z"/> + </g> +</svg> diff --git a/modules/tools/images/bullet-list-rtl.svg b/modules/tools/images/bullet-list-rtl.svg new file mode 100644 index 0000000..34057ac --- /dev/null +++ b/modules/tools/images/bullet-list-rtl.svg @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> + <g id="bullet-list-rtl" opacity=".75"> + <path id="bottom_dot_1_" d="M19 10h1c.552 0 1 .447 1 1v1c0 .553-.448 1-1 1h-1c-.552 0-1-.447-1-1v-1c0-.553.448-1 1-1z"/> + <path id="middle_dot_1_" d="M19 17h1c.552 0 1 .447 1 1v1c0 .553-.448 1-1 1h-1c-.552 0-1-.447-1-1v-1c0-.553.448-1 1-1z"/> + <path id="top_dot_1_" d="M19 3h1c.552 0 1 .447 1 1v1c0 .553-.448 1-1 1h-1c-.552 0-1-.447-1-1v-1c0-.553.448-1 1-1z"/> + <path id="bottom_line_7_" d="M4 17h11c.552 0 1 .447 1 1v1c0 .553-.448 1-1 1h-11c-.552 0-1-.447-1-1v-1c0-.553.448-1 1-1z"/> + <path id="middle_line_7_" d="M4 10h11c.552 0 1 .447 1 1v1c0 .553-.448 1-1 1h-11c-.552 0-1-.447-1-1v-1c0-.553.448-1 1-1z"/> + <path id="top_line_7_" d="M4 3h11c.552 0 1 .447 1 1v1c0 .553-.448 1-1 1h-11c-.552 0-1-.447-1-1v-1c0-.553.448-1 1-1z"/> + </g> +</svg> diff --git a/modules/tools/images/italic-i.svg b/modules/tools/images/italic-i.svg new file mode 100644 index 0000000..7af7b96 --- /dev/null +++ b/modules/tools/images/italic-i.svg @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> + <g id="italic-i" opacity=".75"> + <path id="i" d="M12.5 17.999l.249-.994h-1.5l2.509-10.037h1.5l.242-.967h-5l-.242.967h1.5l-2.509 10.037h-1.5l-.249.994z"/> + </g> +</svg> diff --git a/modules/tools/images/number-list-ltr.svg b/modules/tools/images/number-list-ltr.svg new file mode 100644 index 0000000..f4ce7d3 --- /dev/null +++ b/modules/tools/images/number-list-ltr.svg @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> + <g id="number-list-ltr" opacity=".75"> + <path id="bottom_dot" d="M3 16v1h1.993l.03 1h-1.023v1h1v1h-2v1h2.023l.977-1.002v-1l-.955-.531.955-.5v-.969l-1.007-.998z"/> + <path id="middle_dot" d="M3 9v1h2.117l-2.117 2.187v1.811l3-.062v-.936h-2.118l2.118-2.188v-1.032l-.668-.78z"/> + <path id="top_dot" d="M4.993 2h-.648l-1.327 1.391.031.609h1.025l-.068 2h-1.006v1h3v-1h-1.037z"/> + <path id="bottom_line" d="M20.002 17h-11.002c-.553 0-1 .447-1 1v1c0 .553.447 1 1 1h11.002c.551 0 .998-.447.998-1v-1c0-.553-.447-1-.998-1z"/> + <path id="middle_line" d="M20.002 10h-11.002c-.553 0-1 .447-1 1v1c0 .553.447 1 1 1h11.002c.551 0 .998-.447.998-1v-1c0-.553-.447-1-.998-1z"/> + <path id="top_line" d="M20.002 3h-11.002c-.553 0-1 .447-1 1v1c0 .553.447 1 1 1h11.002c.551 0 .998-.447.998-1v-1c0-.553-.447-1-.998-1z"/> + </g> +</svg> diff --git a/modules/tools/images/number-list-rtl.svg b/modules/tools/images/number-list-rtl.svg new file mode 100644 index 0000000..b0328a9 --- /dev/null +++ b/modules/tools/images/number-list-rtl.svg @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> + <g id="number-list-rtl" opacity=".75"> + <path id="bottom_dot" d="M18 16v1h1.993l.03 1h-1.023v1h1v1h-2v1h2.023l.977-1.002v-1l-.956-.531.956-.5v-.969l-1.007-.998z"/> + <path id="middle_dot" d="M18 9v1h2.116l-2.116 2.187v1.811l3-.062v-.936h-2.118l2.118-2.188v-1.032l-.669-.78z"/> + <path id="top_dot" d="M19.993 2h-.648l-1.328 1.391.031.609h1.026l-.069 2h-1.005v1h3v-1h-1.038z"/> + <path id="bottom_line" d="M3.999 17h11.002c.552 0 .999.447.999 1v1c0 .553-.447 1-.999 1h-11.002c-.552 0-.999-.447-.999-1v-1c0-.553.447-1 .999-1z"/> + <path id="middle_line" d="M3.999 10h11.002c.552 0 .999.447.999 1v1c0 .553-.447 1-.999 1h-11.002c-.552 0-.999-.447-.999-1v-1c0-.553.447-1 .999-1z"/> + <path id="top_line" d="M3.999 3h11.002c.552 0 .999.447.999 1v1c0 .553-.447 1-.999 1h-11.002c-.552 0-.999-.447-.999-1v-1c0-.553.447-1 .999-1z"/> + </g> +</svg> diff --git a/modules/tools/styles/ext.cx.tools.formatter.less b/modules/tools/styles/ext.cx.tools.formatter.less new file mode 100644 index 0000000..babfd94 --- /dev/null +++ b/modules/tools/styles/ext.cx.tools.formatter.less @@ -0,0 +1,48 @@ +@import "../../base/styles/grid/agora-grid"; +@import "mediawiki.mixins"; + +.card.formatter { + animation-name: card-show-animation; + animation-duration: 0.5s; + position: relative; + min-height: 45px; +} + +.formatter-button { + min-width: 45px; + height: 45px; + border: none; + border-right: 1px solid #fbfbfb; + background-color: transparent; + color: #555555; + box-sizing: border-box; + float: left; + transition: background-color .2s ease-in, color .2s ease-in; + &:hover { + border: 1px solid #eee; + } +} +.card__format-bold { + .formatter-button; + .background-image-svg('../images/bold-b.svg','../images/bold-b.png'); + background-repeat: no-repeat; + background-position: center; +} +.card__format-italics { + .formatter-button; + .background-image-svg('../images/italic-i.svg','../images/italic-i.png'); + background-repeat: no-repeat; + background-position: center; +} +.card__format-orderedlist { + .formatter-button; + .background-image-svg('../images/number-list-ltr.svg', '../images/number-list-ltr.png'); + background-repeat: no-repeat; + background-position: center; +} +.card__format-bulletlist { + .formatter-button; + .background-image-svg('../images/bullet-list-ltr.svg', '../images/bullet-list-ltr.png'); + background-repeat: no-repeat; + background-position: center; +} diff --git a/specials/SpecialContentTranslation.php b/specials/SpecialContentTranslation.php index 53c6030..961912f 100644 --- a/specials/SpecialContentTranslation.php +++ b/specials/SpecialContentTranslation.php @@ -22,21 +22,11 @@ } public function execute( $parameters ) { - global $wgContentTranslationExperimentalFeatures; - $out = $this->getOutput(); $skin = $this->getSkin(); $out->addModuleStyles( 'mediawiki.ui.button' ); $out->addModules( 'ext.cx.base' ); - if ( $wgContentTranslationExperimentalFeatures ) { - // WYSIWYGEditor - $out->addModules( 'ext.cx.editor.medium' ); - } else { - // Just ContentEditable - $out->addModules( 'ext.cx.editor' ); - } - $this->setHeaders(); $out->setArticleBodyOnly( true ); -- To view, visit https://gerrit.wikimedia.org/r/158369 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I794d7e6bfcd3792acea6cae6ec00492251018ba7 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/ContentTranslation Gerrit-Branch: master Gerrit-Owner: Santhosh <santhosh.thottin...@gmail.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits