'; } catch (ex) { // Can't set innerHTML if we're in XHTML mode so just assume we don't get custom styles. return false; } return Token.token(div.firstChild).getStyle('mso-list') === 'Ignore'; }; var supportsCustomStyles = checkSupportsCustomStyles(); var spanOrA = function(token) { return token.tag() === 'A' || token.tag() === 'SPAN'; }; var hasMsoListStyle = function(token) { var style = token.getStyle('mso-list'); return style && style !== 'skip'; }; var hasNoAttributes = function(token, allowStyle) { if (token.type() === Token.START_ELEMENT_TYPE) { return token.getAttributeCount() === 0 || (allowStyle && token.getAttributeCount() === 1 && (token.getAttribute('style') !== null && token.getAttribute('style') !== undefined)); } else { return token.type() === Token.END_ELEMENT_TYPE; } }; return { hasNoAttributes: hasNoAttributes, supportsCustomStyles: supportsCustomStyles, spanOrA: spanOrA, hasMsoListStyle: hasMsoListStyle }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.powerpaste.legacy.filters.list.ListTypes', [ 'ephox.powerpaste.legacy.data.tokens.Token', 'ephox.powerpaste.legacy.tinymce.Util' ], function (Token, Util) { var orderedListTypes = [ { regex: /^\(?[dc][\.\)]$/, type: { tag: 'OL', type: 'lower-alpha' } }, { regex: /^\(?[DC][\.\)]$/, type: { tag: 'OL', type: 'upper-alpha' } }, { regex: /^\(?M*(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})[\.\)]$/, type: { tag: 'OL', type: 'upper-roman' } }, { regex: /^\(?m*(cm|cd|d?c{0,3})(xc|xl|l?x{0,3})(ix|iv|v?i{0,3})[\.\)]$/, type: { tag: 'OL', type: 'lower-roman' } }, { regex: /^\(?[0-9]+[\.\)]$/, type: { tag: 'OL' } }, { regex: /^([0-9]+\.)*[0-9]+\.?$/, type: { tag: 'OL', variant: 'outline' } }, { regex: /^\(?[a-z]+[\.\)]$/, type: { tag: 'OL', type: 'lower-alpha' } }, { regex: /^\(?[A-Z]+[\.\)]$/, type: { tag: 'OL', type: 'upper-alpha' } } ]; var ulChars = { '\u2022': { tag: 'UL', type: 'disc' }, '\u00B7': { tag: 'UL', type: 'disc' }, '\u00A7': { tag: 'UL', type: 'square' } }; var ulNonSymbolChars = { 'o': { tag: 'UL', type: 'circle' }, '-': { tag: 'UL', type: 'disc' }, '\u25CF': { tag: 'UL', type: 'disc' } }; var createVariant = function(type, variant) { var newType = { tag: type.tag, type: type.type, variant: variant }; if (type.start){ newType.start = type.start; } if (!type.type) delete newType.type; return newType; }; var guessListType = function(bulletInfo, preferredType, originalToken) { var listType = null, text, symbolFont, variant; if (bulletInfo) { text = bulletInfo.text; symbolFont = bulletInfo.symbolFont; } text = Util.trim(text); listType = ulNonSymbolChars[text]; if (!listType) { if (symbolFont) { listType = ulChars[text]; if (!listType) { listType = { tag: 'UL', variant: text }; } else { listType = createVariant(listType, text); } } else { Util.each(orderedListTypes, function(def) { if (def.regex.test(text)) { if (preferredType && eqListType(def.type, preferredType, true)) { listType = def.type; listType.start=parseInt(text); return false; } if (!listType) listType = def.type; listType.start=parseInt(text); } }); if (listType && !listType.variant) { if (text.charAt(0) === '(') variant = '()'; else if (text.charAt(text.length - 1) === ')') variant = ')'; else variant = '.'; listType = createVariant(listType, variant); } } } else { listType = createVariant(listType, text); } if (listType && listType.tag === 'OL' && originalToken && (originalToken.tag() !== 'P' || /^MsoHeading/.test(originalToken.getAttribute('class')))) { // Don't convert numbered headings but do convert bulleted headings. listType = null; } return listType; }; var eqListType = function(t1, t2, ignoreVariant) { return t1 === t2 || (t1 && t2 && t1.tag === t2.tag && t1.type === t2.type && (ignoreVariant || t1.variant === t2.variant)); }; var checkFont = function(token, symbolFont) { if (token.type() == Token.START_ELEMENT_TYPE) { font = token.getStyle('font-family'); if (font) { symbolFont = (font === 'Wingdings' || font === 'Symbol'); } else if (/^(P|H[1-6]|DIV)$/.test(token.tag())) { symbolFont = false; } } return symbolFont; }; return { guessListType: guessListType, eqListType: eqListType, checkFont: checkFont }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.powerpaste.legacy.filters.list.CommentHeuristics', [ 'ephox.powerpaste.legacy.data.tokens.Token', 'ephox.powerpaste.legacy.filters.list.ListTypes', 'ephox.powerpaste.legacy.tinymce.Util' ], function (Token, ListTypes, Util) { var isListWithoutCommentsOrStyles = function(token, state) { var indent, cls, node, symbolFont = false, value, listType; var checkFont = function(n) { var font = n.style.fontFamily; if (font) { symbolFont = (font === 'Wingdings' || font === 'Symbol'); } }; if (token.type() === Token.START_ELEMENT_TYPE && state.openedTag && token.tag() === 'SPAN') { node = state.openedTag.getNode(); checkFont(node); if (node.childNodes.length > 1 && node.firstChild.tagName === 'A' && node.firstChild.textContent === '') { node = node.childNodes[1]; } while (node.firstChild && (node.firstChild.tagName === 'SPAN' || node.firstChild.tagName === 'A')) { node = node.firstChild; checkFont(node); } node = node.firstChild; if (node && node.nodeType === 3) { value = node.value; if (!Util.trim(value)) { // This handles the case where there's a SPAN with nbsps before the bullet such as with roman numerals. node = node.parentNode.nextSibling; value = node ? node.value : ''; } // Real lists have the bullet with NBSPs either side surrounded in a SPAN. If there's anything else, it's not a list. if (!node || Util.trim(node.parentNode.textContent) != value) { return false; } listType = ListTypes.guessListType({ text: value, symbolFont: symbolFont }, null, state.originalToken); if (listType) { // Don't convert numbered headings to lists. return node.nextSibling && node.nextSibling.tagName === 'SPAN' && /^[\u00A0\s]/.test(node.nextSibling.firstChild.value) && (state.openedTag.tag() === 'P' || listType.tag === 'UL'); } } else { return node && node.tagName === 'IMG'; } } return false; }; var getLeftOffset = function(node, paragraph) { var parent, child, offset = 0; parent = node.parentNode; while (parent !== null && parent !== undefined && parent !== paragraph.parentNode) { offset += parent.offsetLeft; parent = parent.offsetParent; } return offset; }; /** A simplified memoize function which only supports one or two function parameters. * * @param fn * @param param the funtion p * @returns */ var memoize2 = function(fn) { var cache = {}; return function(param1, param2) { var result, key = param1 + "," + param2; if (cache.hasOwnProperty(key)) { return cache[key]; } result = fn.call(null, param1, param2); cache[key] = result; return result; }; }; var findStylesInner = function(selector) { var dotIndex = selector.indexOf('.'); if (dotIndex >= 0 && Util.trim(selector.substring(dotIndex + 1)) === className) { match = results[2]; return false; } }; var findStyles = memoize2(function(css, className) { var results, matcher = /([^{]+){([^}]+)}/g, match, el, computedStyle; matcher.lastIndex = 0; // Firefox Mac reuses the same regex so we need to reset it. while ((results = matcher.exec(css)) !== null && !match) { Util.each(results[1].split(','), findStylesInner(selector) ); } if (match) { el = document.createElement('p'); el.setAttribute("style", match); computedStyle = Util.ephoxGetComputedStyle(el); return computedStyle ? "" + computedStyle.marginLeft : false; } return false; }); var indentGuesser = function() { var listIndentAdjust; var listIndentAmount; var guessIndentLevel = function(currentToken, token, styles, bulletInfo) { var indentAmount, itemIndent, el, level = 1; if (bulletInfo && /^([0-9]+\.)+[0-9]+\.?$/.test(bulletInfo.text)) { // Outline list type so we can just count the number of sections. return bulletInfo.text.replace(/([0-9]+|\.$)/g, '').length + 1; } indentAmount = listIndentAmount || parseInt(findStyles(styles, token.getAttribute('class'))); itemIndent = getLeftOffset(currentToken.getNode(), token.getNode()); if (!indentAmount) { indentAmount = 48; } else { // We might get a 0 item indent if the list CSS code wasn't pasted as happens on Windows. if (listIndentAdjust) { itemIndent += listIndentAdjust; } else if (itemIndent === 0) { listIndentAdjust = indentAmount; itemIndent += indentAmount; } } listIndentAmount = indentAmount = Math.min(itemIndent, indentAmount); level = Math.max(1, Math.floor(itemIndent / indentAmount)) || 1; return level; }; return { guessIndentLevel: guessIndentLevel }; }; var styles = function() { var inStyle = false; var styles = ""; var check = function(token) { if (inStyle && token.type() === Token.TEXT_TYPE) { styles += token.text(); return true; } else if (token.type() === Token.START_ELEMENT_TYPE && token.tag() === 'STYLE') { inStyle = true; return true; } else if (token.type() === Token.END_ELEMENT_TYPE && token.tag() === 'STYLE') { inStyle = false; return true; } return false; }; return { check: check }; }; return { isListWithoutCommentsOrStyles: isListWithoutCommentsOrStyles, indentGuesser: indentGuesser, styles: styles }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.powerpaste.legacy.filters.list.Emitter', [ 'ephox.powerpaste.legacy.data.tokens.Token', 'ephox.powerpaste.legacy.filters.list.ListTypes' ], function (Token, ListTypes) { var impliedULatLevel = [ 'disc', 'circle', 'square' ]; var removeImpliedListType = function(type, level) { if (type.tag === 'UL') { if (impliedULatLevel[level - 1] === type.type) { type = { tag: 'UL' }; } } return type; }; return function(api, document) { var listTypes = []; var itemTags = []; var currentLevel = 0; var currentListType; var openList = function(type, useType) { var style = {}, attributes={}; currentLevel++; if (useType) { if (type.type) { style = { 'list-style-type': type.type }; } } if (type.start && type.start>1) { attributes={start:type.start}; } listTypes.push(type); api.emit(Token.createStartElement(type.tag, attributes, style, document)); currentListType = type; }; var closeList = function() { api.emit(Token.createEndElement(listTypes.pop().tag, document)); currentLevel--; currentListType = listTypes[listTypes.length - 1]; }; var closeAllLists = function() { while (currentLevel > 0) { closeItem(); closeList(); } api.commit(); }; var closeItem = function() { var tag = itemTags ? itemTags.pop() : 'P'; if (tag != 'P') { api.emit(Token.createEndElement(tag, document)); } api.emit(Token.createEndElement('LI', document)); }; var openLI = function(paragraphToken, type, skippedPara) { var style = {}; if (!paragraphToken) { style['list-style-type'] = 'none'; } else { var leftMargin = paragraphToken.getStyle('margin-left'); if (leftMargin !== undefined) { style['margin-left'] = leftMargin; } } if (currentListType && !ListTypes.eqListType(currentListType, type)) { closeList(); if (skippedPara) { api.emit(Token.createStartElement('P', {}, {}, document)); api.emit(Token.createText('\u00A0', document)); api.emit(Token.createEndElement('P', document)); } openList(type, true); } api.emit(Token.createStartElement('LI', {}, style, document)); if (paragraphToken && paragraphToken.tag() != 'P') { itemTags.push(paragraphToken.tag()); paragraphToken.filterStyles(function() { return null; }); api.emit(paragraphToken); } else { itemTags.push('P'); } }; var openItem = function(level, paragraphToken, type, skippedPara) { var style = {}, token; if (!type) return; if (!currentLevel) currentLevel = 0; while (currentLevel > level) { closeItem(); closeList(); } type = removeImpliedListType(type, level); if (currentLevel == level) { closeItem(); openLI(paragraphToken, type, skippedPara); } else { // If there's a heading item we opened in the list we need to close it before creating the indented list if (level > 1 && itemTags.length > 0 && itemTags[itemTags.length - 1] !== 'P') { api.emit(Token.createEndElement(itemTags[itemTags.length - 1], document)); itemTags[itemTags.length - 1] = 'P'; } while (currentLevel < level) { openList(type, currentLevel == level - 1); openLI(currentLevel == level ? paragraphToken : undefined, type); } } }; var getCurrentLevel = function() { return currentLevel; }; var getCurrentListType = function() { return currentListType; }; return { openList: openList, closelist: closeList, closeAllLists: closeAllLists, closeItem: closeItem, openLI: openLI, openItem: openItem, getCurrentListType: getCurrentListType, getCurrentLevel: getCurrentLevel }; }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.powerpaste.legacy.filters.list.ListStates', [ 'ephox.powerpaste.legacy.data.tokens.Helper', 'ephox.powerpaste.legacy.data.tokens.Token', 'ephox.powerpaste.legacy.filters.list.CommentHeuristics', 'ephox.powerpaste.legacy.filters.list.ListTypes', 'ephox.powerpaste.legacy.tinymce.Util' ], function (Helper, Token, CommentHeuristics, ListTypes, Util) { var unexpectedToken = function(api, token) { Util.log("Unexpected token in list conversion: " + token.toString()); api.rollback(); }; var preferredListType = function(currentType, currentLevel, newLevel) { if (currentLevel == newLevel) { return currentType; } return null; }; var afterListState = function(api, state, token) { if (token.type() === Token.TEXT_TYPE && Util.trim(token.text()) === '') { // Drop whitespace that's potentially between list items. api.defer(token); } else if (!state.skippedPara && token.type() === Token.START_ELEMENT_TYPE && token.tag() === 'P' && !Helper.hasMsoListStyle(token)) { state.openedTag = token; api.defer(token); state.nextFilter = skipEmptyParaState; } else { noListState(api, state, token); } }; var skipEmptyParaState = function(api, state, token) { if (token.type() === Token.START_ELEMENT_TYPE && token.tag() === 'SPAN' && state.spanCount.length === 0 && (Helper.supportsCustomStyles || !CommentHeuristics.isListWithoutCommentsOrStyles(token, state)) && !Helper.hasMsoListStyle(token)) { api.defer(token); state.spanCount.push(token); } else if (token.type() === Token.END_ELEMENT_TYPE) { if (token.tag() === 'SPAN') { api.defer(token); state.spanCount.pop(); } else if (token.tag() === 'P') { api.defer(token); state.skippedPara = true; state.openedTag = null; state.nextFilter = afterListState; } else { // Not an empty paragraph. state.nextFilter = noListState; state.nextFilter(api, state, token); } } else if (token.isWhitespace()) { api.defer(token); } else { state.nextFilter = noListState; state.nextFilter(api, state, token); } }; var msoListSkipState = function(api, state, token) { if (token.type() === Token.END_ELEMENT_TYPE && token.tag() === state.originalToken.tag()) { state.nextFilter = afterListState; } else if (token === Token.FINISHED) { state.emitter.closeAllLists(); api.emit(token); } // Else drop. }; var noListState = function(api, state, token) { var closeOutLists = function() { state.emitter.closeAllLists(); api.emitDeferred(); state.openedTag = null; api.emit(token); state.nextFilter = noListState; }; if (token.type() === Token.START_ELEMENT_TYPE && Helper.hasMsoListStyle(token) && token.tag() !== 'LI') { var msoList = token.getStyle('mso-list'); if (false && msoList === 'skip') { state.nextFilter = msoListSkipState; state.originalToken = token; } else { var lvl = / level([0-9]+)/.exec(token.getStyle('mso-list')); if (lvl && lvl[1]) { state.itemLevel = parseInt(lvl[1], 10) + state.styleLevelAdjust; // Tokens between lists should be dropped (they're just whitespace anyway) // however, tokens before a list should be emitted if we find an mso-list style // since this is the very first token of the list. if (state.nextFilter === noListState) { api.emitDeferred(); } else { api.dropDeferred(); } state.nextFilter = listStartState; api.startTransaction(); state.originalToken = token; state.commentMode = false; } else { closeOutLists(); } } } else if (!Helper.supportsCustomStyles && ((token.type() === Token.COMMENT_TYPE && token.text() === '[if !supportLists]') || (CommentHeuristics.isListWithoutCommentsOrStyles(token, api)))) { if (token.type() === Token.START_ELEMENT_TYPE && token.tag() === 'SPAN') { state.spanCount.push(token); } state.nextFilter = listStartState; api.startTransaction(); state.originalToken = state.openedTag; state.commentMode = true; state.openedTag = null; api.dropDeferred(); } else if (token.type() === Token.END_ELEMENT_TYPE && Helper.spanOrA(token)) { api.defer(token); state.spanCount.pop(); } else if (token.type() === Token.START_ELEMENT_TYPE) { // Might be the start of an item, store it and see if we get a comment next. if (Helper.spanOrA(token)) { api.defer(token); state.spanCount.push(token); } else { if (state.openedTag) { state.emitter.closeAllLists(); api.emitDeferred(); } state.openedTag = token; api.defer(token); } } else { closeOutLists(); } }; var afterNoBulletListState = function(api, state, token) { if (token.type() === Token.END_ELEMENT_TYPE && state.originalToken.tag() === token.tag()) { state.nextFilter = afterListState; state.styleLevelAdjust = -1; } api.emit(token); }; var listStartState = function(api, state, token) { if (token.type() == Token.START_ELEMENT_TYPE && token.getStyle('mso-list') === 'Ignore') { state.nextFilter = findListTypeState; } if (token.type() === Token.START_ELEMENT_TYPE && token.tag() === 'SPAN') { state.spanCount.push(token); if (state.commentMode && token.getAttribute("style") === "" || token.getAttribute("style") === null) { state.nextFilter = findListTypeState; } // Otherwise drop. } else if (token.tag() === 'A') { if (token.type() === Token.START_ELEMENT_TYPE) { state.spanCount.push(token); } else { state.spanCount.pop(); } } else if (token.type() === Token.TEXT_TYPE) { if (state.commentMode) { state.nextFilter = findListTypeState; state.nextFilter(api, state, token); } else { // List type without a bullet, we should treat it as a paragraph. var start = state.originalToken; var spans = state.spanCount; state.emitter.closeAllLists(); api.emit(start); Util.each(spans, Util.bind(api.emit, api)); api.emit(token); api.commit(); state.originalToken = start; state.nextFilter = afterNoBulletListState; } } else if (!state.commentMode && token.type() === Token.COMMENT_TYPE) { // Drop. We seem to be getting custom styles and comments. } else { unexpectedToken(api, token); } }; var findListTypeState = function(api, state, token) { if (token.type() === Token.TEXT_TYPE) { if (token.isWhitespace()) { // Ignore whitespace node, it's padding before the actual list type. } else { state.nextFilter = beforeSpacerState; state.bulletInfo = { text: token.text(), symbolFont: state.symbolFont }; } } else if (Helper.spanOrA(token)) { // Drop open and close span tags. if (token.type() === Token.START_ELEMENT_TYPE) { state.spanCount.push(token); } else { state.spanCount.pop(); } } else if (token.type() === Token.START_ELEMENT_TYPE && token.tag() === 'IMG') { // Custom list image type. We can't access the image so use a normal bullet instead. // EditLive! may want this to come through as a CSS reference. state.nextFilter = beforeSpacerState; state.bulletInfo = { text: '\u2202', symbolFont: true }; } else { unexpectedToken(api, token); } }; var beforeSpacerState = function(api, state, token) { if (token.type() === Token.START_ELEMENT_TYPE && Helper.spanOrA(token)) { state.spanCount.push(token); state.nextFilter = spacerState; } else if (token.type() === Token.END_ELEMENT_TYPE && Helper.spanOrA(token)) { state.spanCount.pop(); state.nextFilter = closeSpansState; } else if (token.type() === Token.END_ELEMENT_TYPE && token.tag() === 'IMG') { // Drop } else { unexpectedToken(api, token); } }; var spacerState = function(api, state, token) { if (token.type() === Token.END_ELEMENT_TYPE) { if (Helper.spanOrA(token)) { state.spanCount.pop(); } state.nextFilter = closeSpansState; } // Drop all other tokens. }; var closeSpansState = function(api, state, token) { var moveToItemContentState = function(includeToken) { state.nextFilter = itemContentState; if (state.commentMode) state.itemLevel = state.indentGuesser.guessIndentLevel(token, state.originalToken, state.styles.styles, state.bulletInfo); state.listType = ListTypes.guessListType(state.bulletInfo, preferredListType(state.emitter.getCurrentListType(), state.emitter.getCurrentLevel(), state.itemLevel), state.originalToken); if (state.listType) { state.emitter.openItem(state.itemLevel, state.originalToken, state.listType, state.skippedPara); api.emitDeferred(); while (state.spanCount.length > 0) { api.emit(state.spanCount.shift()); } if (includeToken) { api.emit(token); } } else { Util.log("Unknown list type: " + state.bulletInfo.text + " Symbol font? " + state.bulletInfo.symbolFont); api.rollback(); } }; if (token.type() === Token.TEXT_TYPE || token.type() === Token.START_ELEMENT_TYPE) { moveToItemContentState(true); } else if (token.type() === Token.COMMENT_TYPE) { moveToItemContentState(token.text() !== '[endif]'); } else if (token.type() === Token.END_ELEMENT_TYPE) { if (Helper.spanOrA(token)) { state.spanCount.pop(); } } else { unexpectedToken(api, token); } }; var itemContentState = function(api, state, token) { if (token.type() === Token.END_ELEMENT_TYPE && token.tag() === state.originalToken.tag()) { state.nextFilter = afterListState; state.skippedPara = false; } else { api.emit(token); } }; var initial = noListState; return { initial: initial }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.powerpaste.legacy.filters.list.Lists', [ 'ephox.powerpaste.legacy.data.tokens.Filter', 'ephox.powerpaste.legacy.data.tokens.Helper', 'ephox.powerpaste.legacy.data.tokens.Token', 'ephox.powerpaste.legacy.filters.list.CommentHeuristics', 'ephox.powerpaste.legacy.filters.list.Emitter', 'ephox.powerpaste.legacy.filters.list.ListStates', 'ephox.powerpaste.legacy.filters.list.ListTypes', 'ephox.powerpaste.legacy.tinymce.Util' ], function (Filter, Helper, Token, CommentHeuristics, Emitter, ListStates, ListTypes, Util) { var activeState = {}; var resetActiveState = function(api) { //It would be nice if this was creating a fresh object, but listStartState() expects state mutation when api.commit() is called activeState.nextFilter = ListStates.initial; activeState.itemLevel = 0; activeState.originalToken = null; activeState.commentMode = false; activeState.openedTag = null; activeState.symbolFont = false; activeState.listType = null; activeState.indentGuesser = CommentHeuristics.indentGuesser(); activeState.emitter = Emitter(api, api.document); activeState.styles = CommentHeuristics.styles(); activeState.spanCount = []; activeState.skippedPara = false; activeState.styleLevelAdjust = 0; activeState.bulletInfo = undefined; }; resetActiveState({}); var resetState = function(api) { resetActiveState(api); }; var receive = function(api, token) { if (activeState.styles.check(token)) { return; } activeState.symbolFont = ListTypes.checkFont(token, activeState.symbolFont); activeState.nextFilter(api, activeState, token); }; return Filter.createFilter(receive, resetState); } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { /** * Source code in this file has been taken under a commercial license from tinymce/jscripts/tiny_mce/plugins/paste/editor_plugin_src.js * Copyright 2009, Moxiecode Systems AB */ define( 'ephox.powerpaste.legacy.tinymce.BrowserFilters', [ 'ephox.powerpaste.legacy.tinymce.Util' ], function (Util) { var trailingSpaceCharacter = function(content) { var h = content; // Strip a trailing non-breaking, zero-width space which Firefox tends to insert. var hasCrazySpace = h.charCodeAt(h.length - 1) === 65279; return hasCrazySpace ? h.substring(0, h.length - 1) : content; }; // IE9 adds BRs before/after block elements when contents is pasted from word or for example another browser var removeBrNextToBlock = function(content) { return (/<(h[1-6r]|p|div|address|pre|form|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|caption|blockquote|center|dl|dt|dd|dir|fieldset)/).test(content) ? content.replace(/(?:
' + translations('cement.dialog.flash.press-escape') + '
' ); }; var paste = function (translations) { var container = Element.fromTag('div'); Class.add(container, Styles.resolve('flashbin-helpcopy')); var key = modifierKey(); var instructions = Element.fromHtml( '' + translations('cement.dialog.flash.trigger-paste') + '
'); var kbd = Element.fromHtml( '' + translations('cement.dialog.flash.missing') + '
' ); InsertAll.append(container, [ instructions, pressEscape(translations) ]); return container; }; var indicator = function (translations) { var loading = Element.fromTag('div'); Class.add(loading, Styles.resolve('flashbin-loading')); var spinner = Element.fromTag('div'); Class.add(spinner, Styles.resolve('flashbin-loading-spinner')); var loadNote = Element.fromTag('p'); loadNote.dom().innerHTML = translations('loading.wait'); InsertAll.append(loading, [spinner, loadNote]); return loading; }; return { paste: paste, noflash: noflash, indicator: indicator }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); ephox.bolt.module.api.define("global!window", [], function () { return window; }); (function (define, require, demand) { define( 'ephox.sugar.api.Css', [ 'ephox.classify.Type', 'ephox.compass.Obj', 'ephox.perhaps.Option', 'ephox.sugar.api.Attr', 'ephox.sugar.api.Body', 'ephox.sugar.api.Element', 'ephox.violin.Strings', 'global!Error', 'global!console', 'global!window' ], function (Type, Obj, Option, Attr, Body, Element, Strings, Error, console, window) { var internalSet = function (dom, property, value) { // This is going to hurt. Apologies. // JQuery coerces numbers to pixels for certain property names, and other times lets numbers through. // we're going to be explicit; strings only. if (!Type.isString(value)) { console.error('Invalid call to CSS.set. Property ', property, ':: Value ', value, ':: Element ', dom); throw new Error('CSS value must be a string: ' + value); } // removed: support for dom().style[property] where prop is camel case instead of normal property name dom.style.setProperty(property, value); }; var set = function (element, property, value) { var dom = element.dom(); internalSet(dom, property, value); }; var setAll = function (element, css) { var dom = element.dom(); Obj.each(css, function (v, k) { internalSet(dom, k, v); }); }; /* * NOTE: For certain properties, this returns the "used value" which is subtly different to the "computed value" (despite calling getComputedStyle). * Blame CSS 2.0. * * https://developer.mozilla.org/en-US/docs/Web/CSS/used_value */ var get = function (element, property) { var dom = element.dom(); /* * IE9 and above per * https://developer.mozilla.org/en/docs/Web/API/window.getComputedStyle * * Not in numerosity, because it doesn't memoize and looking this up dynamically in performance critical code would be horrendous. * * JQuery has some magic here for IE popups, but we don't really need that. * It also uses element.ownerDocument.defaultView to handle iframes but that hasn't been required since FF 3.6. */ var styles = window.getComputedStyle(dom); var r = styles.getPropertyValue(property); // jquery-ism: If r is an empty string, check that the element is not in a document. If it isn't, return the raw value. // Turns out we do this a lot. var v = (r === '' && !Body.inBody(element)) ? getUnsafeProperty(dom, property) : r; // undefined is the more appropriate value for JS. JQuery coerces to an empty string, but screw that! return v === null ? undefined : v; }; var getUnsafeProperty = function (dom, property) { // removed: support for dom().style[property] where prop is camel case instead of normal property name return dom.style.getPropertyValue(property); }; /* * Gets the raw value from the style attribute. Useful for retrieving "used values" from the DOM: * https://developer.mozilla.org/en-US/docs/Web/CSS/used_value * * Returns NONE if the property isn't set, or the value is an empty string. */ var getRaw = function (element, property) { var dom = element.dom(); var raw = getUnsafeProperty(dom, property); return Option.from(raw).filter(function (r) { return r.length > 0; }); }; var isValidValue = function (tag, property, value) { var element = Element.fromTag(tag); set(element, property, value); var style = getRaw(element, property); return style.isSome(); }; var remove = function (element, property) { var dom = element.dom(); /* * IE9 and above - MDN doesn't have details, but here's a couple of random internet claims * * http://help.dottoro.com/ljopsjck.php * http://stackoverflow.com/a/7901886/7546 */ dom.style.removeProperty(property); if (Attr.has(element, 'style') && Strings.trim(Attr.get(element, 'style')) === '') { // No more styles left, remove the style attribute as well Attr.remove(element, 'style'); } }; var preserve = function (element, f) { var oldStyles = Attr.get(element, 'style'); var result = f(element); var restore = oldStyles === undefined ? Attr.remove : Attr.set; restore(element, 'style', oldStyles); return result; }; var copy = function (source, target) { target.dom().style.cssText = source.dom().style.cssText; }; var reflow = function (e) { /* NOTE: * do not rely on this return value. * It's here so the closure compiler doesn't optimise the property access away. */ return e.dom().offsetWidth; }; return { copy: copy, set: set, preserve: preserve, setAll: setAll, remove: remove, get: get, getRaw: getRaw, isValidValue: isValidValue, reflow: reflow }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); ephox.bolt.module.api.define("global!navigator", [], function () { return navigator; }); (function (define, require, demand) { define( 'ephox.cement.flash.FlashInfo', [ 'ephox.cement.flash.HelpCopy', 'ephox.cement.style.Styles', 'ephox.fred.PlatformDetection', 'ephox.peanut.Fun', 'ephox.sugar.api.Class', 'ephox.sugar.api.Css', 'ephox.sugar.api.Element', 'ephox.sugar.api.Insert', 'ephox.sugar.api.InsertAll', 'global!navigator' ], function (HelpCopy, Styles, PlatformDetection, Fun, Class, Css, Element, Insert, InsertAll, navigator) { var platform = PlatformDetection.detect(); // separated out to constrain the scope of the JSHint override var ieFlash = function () { /* global ActiveXObject */ return new ActiveXObject('ShockwaveFlash.ShockwaveFlash'); }; var hasFlash = function () { try { var flashObj = platform.browser.isIE() ? ieFlash() : navigator.plugins['Shockwave Flash']; return flashObj !== undefined; } catch (err) { return false; } }; var noflash = function (container, _bin, reflow, translations) { var help = HelpCopy.noflash(translations); Insert.append(container, help); return { reset: Fun.noop }; }; var flash = function (container, bin, reflow, translations) { var help = HelpCopy.paste(translations); var indicator = HelpCopy.indicator(translations); InsertAll.append(container, [ indicator, help, bin.element() ]); var indicatorHide = function () { /* X-browser magic that makes the flash blocker/s info display nicely with the cement spinner */ Css.setAll(indicator, { 'height': '0', 'padding': '0' }); }; var reset = function () { Css.set(help, 'display', 'block'); Css.set(indicator, 'display', 'none'); reflow(); }; var busy = function () { Css.set(help, 'display', 'none'); Css.set(indicator, 'display', 'block'); Css.remove(indicator, 'height'); Css.remove(indicator, 'padding'); reflow(); }; bin.events.spin.bind(busy); bin.events.reset.bind(reset); bin.events.hide.bind(indicatorHide); return { reset: reset }; }; return function (bin, reflow, cementConfig) { console.log(bin, reflow, cementConfig) var container = Element.fromTag('div'); var style = 'flashbin-wrapper-' + (platform.os.isOSX() ? 'cmd' : 'ctrl'); Class.add(container, Styles.resolve(style)); var loader = hasFlash() ? flash : noflash; var info = loader(container, bin, reflow, cementConfig.translations); return { element: Fun.constant(container), reset: info.reset }; }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); ephox.bolt.module.api.define("global!clearInterval", [], function () { return clearInterval; }); ephox.bolt.module.api.define("global!setInterval", [], function () { return setInterval; }); (function (define, require, demand) { define( 'ephox.cement.alien.WaitForFlash', [ 'ephox.classify.Type', 'ephox.compass.Arr', 'global!clearInterval', 'global!setInterval' ], function (Type, Arr, clearInterval, setInterval) { return function (obj, flashFunctions, callback) { var functionsReady = function (dom) { // Plugin objects register functions on their DOM node *after* they load, until then they are undefined. return Arr.forall(flashFunctions, function (name) { return Type.isFunction(dom[name]); }); }; var search = function () { // Sometimes we never get the onload callback, but PercentLoaded reaches 100 indicating it is actually running. // If that happens, once the functions are available we are good to go. var dom = obj.dom(); if (Type.isFunction(dom.PercentLoaded) && dom.PercentLoaded() === 100 && functionsReady(dom)) { stop(); callback(); } }; var waiting = true; var reference = setInterval(search, 500); var stop = function () { if (waiting) { clearInterval(reference); waiting = false; } }; return { stop: stop }; }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.epithet.Namespace', [ 'ephox.epithet.Global' ], function (Global) { var step = function (o, part) { if (o[part] === undefined || o[part] === null) o[part] = {}; return o[part]; }; var namespace = function (name, target) { var o = target || Global; var parts = name.split('.'); for (var i = 0; i < parts.length; ++i) o = step(o, parts[i]); return o; }; return { namespace: namespace }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.oilspill.callback.Globaliser', [ 'ephox.epithet.Namespace' ], function (Namespace) { var install = function (namespace) { var manager = Namespace.namespace(namespace); manager.callbacks = {}; var count = 0; var next = function () { var ref = 'callback_' + count; count++; return ref; }; var global = function (ref) { return namespace + '.callbacks.' + ref; }; var register = function (callback, permanent) { var ref = next(); manager.callbacks[ref] = function () { if (!permanent) unregister(ref); callback.apply(null, arguments); }; return global(ref); }; var ephemeral = function (callback) { return register(callback, false); }; var permanent = function (callback) { return register(callback, true); }; var unregister = function (spec) { var ref = spec.substring(spec.lastIndexOf('.') + 1); if (manager.callbacks[ref] !== undefined) delete manager.callbacks[ref]; }; manager.ephemeral = ephemeral; manager.permanent = permanent; manager.unregister = unregister; return manager; }; return { install: install }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.sugar.api.PredicateFind', [ 'ephox.classify.Type', 'ephox.compass.Arr', 'ephox.peanut.Fun', 'ephox.perhaps.Option', 'ephox.sugar.api.Body', 'ephox.sugar.api.Compare', 'ephox.sugar.api.Element', 'ephox.sugar.impl.ClosestOrAncestor' ], function (Type, Arr, Fun, Option, Body, Compare, Element, ClosestOrAncestor) { var first = function (predicate) { return descendant(Body.body(), predicate); }; var ancestor = function (scope, predicate, isRoot) { var element = scope.dom(); var stop = Type.isFunction(isRoot) ? isRoot : Fun.constant(false); while (element.parentNode) { element = element.parentNode; var el = Element.fromDom(element); if (predicate(el)) return Option.some(el); else if (stop(el)) break; } return Option.none(); }; var closest = function (scope, predicate, isRoot) { // This is required to avoid ClosestOrAncestor passing the predicate to itself var is = function (scope) { return predicate(scope); }; return ClosestOrAncestor(is, ancestor, scope, predicate, isRoot); }; var sibling = function (scope, predicate) { var element = scope.dom(); if (!element.parentNode) return Option.none(); return child(Element.fromDom(element.parentNode), function (x) { return !Compare.eq(scope, x) && predicate(x); }); }; var child = function (scope, predicate) { var result = Arr.find(scope.dom().childNodes, Fun.compose(predicate, Element.fromDom)); return Option.from(result).map(Element.fromDom); }; var descendant = function (scope, predicate) { var descend = function (element) { for (var i = 0; i < element.childNodes.length; i++) { if (predicate(Element.fromDom(element.childNodes[i]))) return Option.some(Element.fromDom(element.childNodes[i])); var res = descend(element.childNodes[i]); if (res.isSome()) return res; } return Option.none(); }; return descend(scope.dom()); }; return { first: first, ancestor: ancestor, closest: closest, sibling: sibling, child: child, descendant: descendant }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.sugar.api.PredicateExists', [ 'ephox.sugar.api.PredicateFind' ], function (PredicateFind) { var any = function (predicate) { return PredicateFind.first(predicate).isSome(); }; var ancestor = function (scope, predicate, isRoot) { return PredicateFind.ancestor(scope, predicate, isRoot).isSome(); }; var closest = function (scope, predicate, isRoot) { return PredicateFind.closest(scope, predicate, isRoot).isSome(); }; var sibling = function (scope, predicate) { return PredicateFind.sibling(scope, predicate).isSome(); }; var child = function (scope, predicate) { return PredicateFind.child(scope, predicate).isSome(); }; var descendant = function (scope, predicate) { return PredicateFind.descendant(scope, predicate).isSome(); }; return { any: any, ancestor: ancestor, closest: closest, sibling: sibling, child: child, descendant: descendant }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.sugar.api.Focus', [ 'ephox.peanut.Fun', 'ephox.perhaps.Option', 'ephox.sugar.api.Compare', 'ephox.sugar.api.Element', 'ephox.sugar.api.PredicateExists', 'ephox.sugar.api.Traverse', 'global!document' ], function (Fun, Option, Compare, Element, PredicateExists, Traverse, document) { var focus = function (element) { element.dom().focus(); }; var blur = function (element) { element.dom().blur(); }; var hasFocus = function (element) { var doc = Traverse.owner(element).dom(); return element.dom() === doc.activeElement; }; var active = function (_doc) { var doc = _doc !== undefined ? _doc.dom() : document; return Option.from(doc.activeElement).map(Element.fromDom); }; var focusInside = function (element) { // Only call focus if the focus is not already inside it. var doc = Traverse.owner(element); var inside = active(doc).filter(function (a) { return PredicateExists.closest(a, Fun.curry(Compare.eq, element)); }); inside.fold(function () { focus(element); }, Fun.noop); }; /** * Return the descendant element that has focus. * Use instead of SelectorFind.descendant(container, ':focus') * because the :focus selector relies on keyboard focus. */ var search = function (element) { return active(Traverse.owner(element)).filter(function (e) { return element.dom().contains(e.dom()); }); }; return { hasFocus: hasFocus, focus: focus, blur: blur, active: active, search: search, focusInside: focusInside }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); ephox.bolt.module.api.define("global!clearTimeout", [], function () { return clearTimeout; }); ephox.bolt.module.api.define("global!unescape", [], function () { return unescape; }); (function (define, require, demand) { define( 'ephox.cement.flash.Flashbin', [ 'ephox.cement.alien.WaitForFlash', 'ephox.cement.style.Styles', 'ephox.compass.Arr', 'ephox.compass.Obj', 'ephox.epithet.Id', 'ephox.fred.PlatformDetection', 'ephox.oilspill.callback.Globaliser', 'ephox.perhaps.Option', 'ephox.porkbun.Event', 'ephox.porkbun.Events', 'ephox.sugar.api.Class', 'ephox.sugar.api.Css', 'ephox.sugar.api.Element', 'ephox.sugar.api.Focus', 'ephox.sugar.api.Insert', 'global!clearTimeout', 'global!console', 'global!setTimeout', 'global!unescape', 'global!window' ], function (WaitForFlash, Styles, Arr, Obj, Id, PlatformDetection, Globaliser, Option, Event, Events, Class, Css, Element, Focus, Insert, clearTimeout, console, setTimeout, unescape, window) { var globaliser = Globaliser.install('ephox.flash'); var platform = PlatformDetection.detect(); var unbinders = Option.none(); return function (swfLoc) { var events = Events.create({ response: Event(['rtf']), spin: Event([]), cancel: Event([]), error: Event(['message']), reset: Event([]), hide: Event([]) }); var hasLoaded = false; var target = Element.fromTag('div'); Class.add(target, Styles.resolve('flashbin-target')); var process = function (rtfData) { // The setTimeout here is to detach it from the event stack/queue, so that the UI is updated // during this block. setTimeout(function () { events.trigger.response(unescape(rtfData)); }, 0); }; var loadComplete = function () { waiting.stop(); if (hasLoaded) return; hasLoaded = true; try { var dom = bin.dom(); Obj.each(flashFunctionMapping, function (v, k) { var f = dom[k]; f.call(dom, v); // plugin requires 'this' to be set correctly }); events.trigger.reset(); clearOverTime(); focus(); } catch (e) { console.log('Flash dialog - Error during onLoad ', e); } }; var onloadCallback = globaliser.permanent(loadComplete); var flashFunctionMapping = { 'setSpinCallback': globaliser.permanent(events.trigger.spin), 'setPasteCallback': globaliser.permanent(process), 'setEscapeCallback': globaliser.permanent(events.trigger.cancel), 'setErrorCallback': globaliser.permanent(events.trigger.error) }; var createObject = function () { var swf = swfLoc.replace(/^https?:\/\//, '//'); var flashVars = 'onLoad=' + onloadCallback; // // NOTE: the wmode (window mode) of "opaque" here has been suggested as on various forums as // required to get the swf to focus on Firefox. e.g. // http://stackoverflow.com/questions/7921690/how-do-i-make-my-flash-object-get-focus-on-load // This setting did not seem to cause problems on our other supported browsers. // Please do not return this setting to "transparent" without serious testing. // var commonParams = ' ' + ' ' + ' '; if (platform.browser.isIE() && platform.browser.version.major === 10) { var id = Id.generate('flash-bin'); return Element.fromHtml( ''); } else { return Element.fromHtml( ''); } }; var bin = createObject(); var binStealth = function () { Css.setAll(bin, { 'width' : '2px', 'height': '2px' }); }; binStealth(); var waiting = WaitForFlash(bin, Obj.keys(flashFunctionMapping), loadComplete); Insert.append(target, bin); var element = function () { return target; }; var focus = function () { if (platform.browser.isFirefox()) { // On Firefox, pasting into flash also fires a paste event wherever the window selection is window.getSelection().removeAllRanges(); } Focus.focus(bin); }; var timerHandle = null; var overTime = function () { Class.add(target, Styles.resolve('flash-activate')); Css.remove(bin, 'height'); Css.remove(bin, 'width'); events.trigger.hide(); }; var clearOverTime = function () { clearTimeout(timerHandle); Class.remove(target, Styles.resolve('flash-activate')); binStealth(); }; var activate = function () { // TODO: improvement create a custom poll event for .swf has loaded, then fire overtime. timerHandle = setTimeout(overTime, 3000); events.trigger.spin(); Css.set(target, 'display', 'block'); focus(); }; var deactivate = function () { Css.set(target, 'display', 'none'); unbinders.each(function (unbinders) { Arr.each(unbinders, function (unbinder) { unbinder.unbind(); }); }); }; var destroy = function () { deactivate(); Arr.each(Obj.values(flashFunctionMapping), function (s) { globaliser.unregister(s); }); globaliser.unregister(onloadCallback); waiting.stop(); // in case the user cancels }; return { focus: focus, element: element, activate: activate, deactivate: deactivate, destroy: destroy, events: events.registry }; }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.sugar.impl.FilteredEvent', [ 'ephox.peanut.Fun', 'ephox.sugar.api.Element' ], function (Fun, Element) { var mkEvent = function (target, x, y, stop, prevent, kill, raw) { // switched from a struct to manual Fun.constant() because we are passing functions now, not just values return { 'target': Fun.constant(target), 'x': Fun.constant(x), 'y': Fun.constant(y), 'stop': stop, 'prevent': prevent, 'kill': kill, 'raw': Fun.constant(raw) }; }; var handle = function (filter, handler) { return function (rawEvent) { if (!filter(rawEvent)) return; // IE9 minimum var target = Element.fromDom(rawEvent.target); var stop = function () { rawEvent.stopPropagation(); }; var prevent = function () { rawEvent.preventDefault(); }; var kill = Fun.compose(prevent, stop); // more of a sequence than a compose, but same effect // FIX: Don't just expose the raw event. Need to identify what needs standardisation. var evt = mkEvent(target, rawEvent.clientX, rawEvent.clientY, stop, prevent, kill, rawEvent); handler(evt); }; }; var binder = function (element, event, filter, handler, useCapture) { var wrapped = handle(filter, handler); // IE9 minimum element.dom().addEventListener(event, wrapped, useCapture); return { unbind: Fun.curry(unbind, element, event, wrapped, useCapture) }; }; var bind = function (element, event, filter, handler) { return binder(element, event, filter, handler, false); }; var capture = function (element, event, filter, handler) { return binder(element, event, filter, handler, true); }; var unbind = function (element, event, handler, useCapture) { // IE9 minimum element.dom().removeEventListener(event, handler, useCapture); }; return { bind: bind, capture: capture }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.sugar.api.DomEvent', [ 'ephox.peanut.Fun', 'ephox.sugar.impl.FilteredEvent' ], function (Fun, FilteredEvent) { var filter = Fun.constant(true); // no filter on plain DomEvents var bind = function (element, event, handler) { return FilteredEvent.bind(element, event, filter, handler); }; var capture = function (element, event, handler) { return FilteredEvent.capture(element, event, filter, handler); }; return { bind: bind, capture: capture }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.cement.flash.FlashDialog', [ 'ephox.cement.flash.FlashInfo', 'ephox.cement.flash.Flashbin', 'ephox.porkbun.Event', 'ephox.porkbun.Events', 'ephox.sugar.api.DomEvent', 'ephox.sugar.api.Element', 'global!window' ], function (FlashInfo, Flashbin, Event, Events, DomEvent, Element, window) { return function (createDialog, settings) { var translations = settings.translations; var events = Events.create({ response: Event(['rtf', 'hide']), cancel: Event([]), error: Event(['message']) }); var open = function () { var bin = Flashbin(settings.swf); bin.deactivate(); var win = Element.fromDom(window); var clickEvent = DomEvent.bind(win, 'mouseup', bin.focus); var hide = function () { destroy(); }; var cancel = function () { hide(); events.trigger.cancel(); }; bin.events.cancel.bind(cancel); bin.events.response.bind(function (event) { // Don't hide immediately - keep the please wait going until the images are converted events.trigger.response(event.rtf(), hide); }); bin.events.error.bind(function (event) { hide(); events.trigger.error(event.message()); }); var dialog = createDialog(); dialog.setTitle( translations('cement.dialog.flash.title')); var information = FlashInfo(bin, dialog.reflow, settings); information.reset(); dialog.setContent(information.element()); dialog.events.close.bind(cancel); dialog.show(); bin.activate(); var destroy = function () { clickEvent.unbind(); dialog.destroy(); bin.destroy(); }; }; return { open: open, events: events.registry }; }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.sugar.api.Replication', [ 'ephox.sugar.api.Attr', 'ephox.sugar.api.Element', 'ephox.sugar.api.Insert', 'ephox.sugar.api.InsertAll', 'ephox.sugar.api.Remove', 'ephox.sugar.api.Traverse' ], function (Attr, Element, Insert, InsertAll, Remove, Traverse) { var clone = function (original, deep) { return Element.fromDom(original.dom().cloneNode(deep)); }; /** Shallow clone - just the tag, no children */ var shallow = function (original) { return clone(original, false); }; /** Deep clone - everything copied including children */ var deep = function (original) { return clone(original, true); }; /** Shallow clone, with a new tag */ var shallowAs = function (original, tag) { var nu = Element.fromTag(tag); var attributes = Attr.clone(original); Attr.setAll(nu, attributes); return nu; }; /** Deep clone, with a new tag */ var copy = function (original, tag) { var nu = shallowAs(original, tag); // NOTE // previously this used serialisation: // nu.dom().innerHTML = original.dom().innerHTML; // // Clone should be equivalent (and faster), but if TD <-> TH toggle breaks, put it back. var cloneChildren = Traverse.children(deep(original)); InsertAll.append(nu, cloneChildren); return nu; }; /** Change the tag name, but keep all children */ var mutate = function (original, tag) { var nu = shallowAs(original, tag); Insert.before(original, nu); var children = Traverse.children(original); InsertAll.append(nu, children); Remove.remove(original); return nu; }; return { shallow: shallow, shallowAs: shallowAs, deep: deep, copy: copy, mutate: mutate }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.limbo.api.RtfImage', [ 'ephox.perhaps.Option', 'ephox.sugar.api.Attr', 'ephox.sugar.api.Class', 'ephox.sugar.api.Css', 'ephox.sugar.api.Element', 'ephox.sugar.api.Node', 'ephox.sugar.api.PredicateFind', 'ephox.sugar.api.Replication', 'ephox.sugar.api.SelectorFilter', 'ephox.violin.Strings' ], function (Option, Attr, Class, Css, Element, Node, PredicateFind, Replication, SelectorFilter, Strings) { var searchIn = function (comment, selector) { var innards = Node.value(comment); var container = Element.fromTag('div'); var substr = innards.indexOf(']>'); container.dom().innerHTML = innards.substr(substr + ']>'.length); // Note: It doesn't look like sizzle can pick up namespaced tags with selectors. return PredicateFind.descendant(container, function (elem) { return Node.name(elem) === selector; }); }; var scour = function (target) { return Node.isComment(target) ? searchIn(target, 'v:shape') : Option.none(); }; var vshape = function (original) { return scour(original).map(function (image) { // NOTE: If we discover more than 2 possible attributes, de-dupe with HtmlPaste somehow var spid = Attr.get(image, 'o:spid'); var vShapeId = spid === undefined ? Attr.get(image, 'id') : spid; var result = Element.fromTag('img'); Class.add(result, 'rtf-data-image'); Attr.set(result, 'data-image-id', vShapeId.substr('_x0000_'.length)); Attr.set(result, 'data-image-type', 'code'); Css.setAll(result, { width: Css.get(image, 'width'), height: Css.get(image, 'height') }); return result; }); }; var local = function (original) { if (Node.name(original) === 'img') { var src = Attr.get(original, 'src'); if (src !== undefined && src !== null && Strings.startsWith(src, 'file://')) { var result = Replication.shallow(original); var parts = src.split(/[\/\\]/); var last = parts[parts.length - 1]; Attr.set(result, 'data-image-id', last); Attr.remove(result, 'src'); Attr.set(result, 'data-image-type', 'local'); Class.add(result, 'rtf-data-image'); return Option.some(result); } else { return Option.none(); } } else { return Option.none(); } }; var exists = function (container) { return find(container).length > 0; }; var find = function (container) { return SelectorFilter.descendants(container, '.rtf-data-image'); }; return { local: local, vshape: vshape, find: find, exists: exists, scour: scour }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.cement.flash.Flash', [ 'ephox.bowerbird.api.Rtf', 'ephox.cement.flash.Correlation', 'ephox.cement.flash.FlashDialog', 'ephox.compass.Arr', 'ephox.limbo.api.RtfImage', 'ephox.porkbun.Event', 'ephox.porkbun.Events', 'ephox.sugar.api.Remove', 'ephox.sugar.api.Traverse' ], function (Rtf, Correlation, FlashDialog, Arr, RtfImage, Event, Events, Remove, Traverse) { return function (createDialog, cementConfig) { var events = Events.create({ error: Event(['message']), insert: Event(['elements', 'assets']) }); var gordon = function (content, baseAssets) { var response = function (event) { var hideDialog = event.hide(); var imageData = Rtf.images(event.rtf()); var images = RtfImage.find(content); Correlation.convert(images, imageData, function (assets) { events.trigger.insert(Traverse.children(content), assets.concat(baseAssets)); // when images are pasted, the flash dialog doesn't automatically hide hideDialog(); }); }; var cancelImages = function () { var images = RtfImage.find(content); Arr.each(images, Remove.remove); events.trigger.insert(Traverse.children(content), baseAssets); }; if (cementConfig.allowLocalImages) { var flash = FlashDialog(createDialog, cementConfig); flash.events.response.bind(response); flash.events.cancel.bind(cancelImages); flash.events.error.bind(function (event) { events.trigger.error(event.message()); }); flash.open(); } else { cancelImages(); events.trigger.error('errors.local.images.disallowed'); } }; return { events: events.registry, gordon: gordon }; }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.cement.smartpaste.MergeSettings', [ 'ephox.cement.style.Styles', 'ephox.highway.Merger', 'ephox.peanut.Fun', 'ephox.porkbun.Event', 'ephox.porkbun.Events', 'ephox.sugar.api.Class', 'ephox.sugar.api.Element', 'ephox.sugar.api.Insert' ], function (Styles, Merger, Fun, Event, Events, Class, Element, Insert) { return function (createDialog, settings) { var translations = settings.translations; var result = function (value, settings, callback) { callback(Merger.merge(settings, { mergeOfficeStyles: value, mergeHtmlStyles: value })); }; var events = Events.create({ open: Event([]), cancel: Event([]), close: Event([]) }); var showDialog = function (settings, callback) { var clean = function () { hide(); result(false, settings, callback); }; var merge = function () { hide(); result(true, settings, callback); }; var content = Element.fromTag('div'); Class.add(content, Styles.resolve('styles-dialog-content')); var instructions = Element.fromTag('p'); var text = Element.fromText(translations('cement.dialog.paste.instructions')); Insert.append(instructions, text); Insert.append(content, instructions); var cleanButton = { text: translations('cement.dialog.paste.clean'), tabindex: 0, className: Styles.resolve('clean-styles'), click: clean }; var mergeButton = { text: translations('cement.dialog.paste.merge'), tabindex: 1, className: Styles.resolve('merge-styles'), click: merge }; var dialog = createDialog(true); dialog.setTitle(translations('cement.dialog.paste.title')); dialog.setContent(content); dialog.setButtons([cleanButton, mergeButton]); dialog.show(); var hide = function () { events.trigger.close(); dialog.destroy(); }; var cancel = function () { events.trigger.cancel(); hide(); }; dialog.events.close.bind(cancel); events.trigger.open(); }; var get = function (officePaste, callback) { var input = officePaste ? 'officeStyles' : 'htmlStyles'; var settingToUse = settings[input]; if (settingToUse === 'clean') { result(false, settings, callback); } else if (settingToUse === 'merge') { result(true, settings, callback); } else { // everything else is prompt showDialog(settings, callback); } }; return { events: events.registry, get: get, destroy: Fun.noop }; }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.cement.smartpaste.Inspection', [ ], function () { var isValidData = function (data) { // IE doesn't even have data. Spartan has data, but no types. return data !== undefined && data.types !== undefined && data.types !== null; }; return { isValidData: isValidData }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.cement.html.Transform', [ 'ephox.classify.Type', 'ephox.sugar.api.Attr' ], function (Type, Attr) { var rotateImage = function (img, vshapeAttrs) { // MS Word tends to store rotated images as raw with a rotation applied var style = vshapeAttrs.style; if (Attr.has(img, 'width') && Attr.has(img, 'height') && Type.isString(style)) { var rotation = style.match(/rotation:([^;]*)/); if (rotation !== null && (rotation[1] === '90' || rotation[1] === '-90')) { // We can't actually rotate the binary data, so just swap the width and height. // When we decide to rotate the data, we can't do it here. Attr.setAll(img, { width: Attr.get(img, 'height'), height: Attr.get(img, 'width') }); } } }; return { rotateImage: rotateImage }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.sugar.api.Comments', [ 'ephox.fred.PlatformDetection', 'ephox.peanut.Fun', 'ephox.sugar.api.Element', 'global!document' ], function (PlatformDetection, Fun, Element, document) { var regularGetNodes = function (texas) { var ret = []; while (texas.nextNode() !== null) ret.push(Element.fromDom(texas.currentNode)); return ret; }; var ieGetNodes = function (texas) { // IE throws an error on nextNode() when there are zero nodes available, and any attempts I made to detect this // just resulted in throwing away valid cases try { return regularGetNodes(texas); } catch (e) { return []; } }; // I hate needing platform detection in Sugar, but the alternative is to always try/catch which will swallow coding errors as well var browser = PlatformDetection.detect().browser; var getNodes = browser.isIE() || browser.isSpartan() ? ieGetNodes : regularGetNodes; // Weird, but oh well var noFilter = Fun.constant(Fun.constant(true)); var find = function (node, filterOpt) { var vmlFilter = filterOpt.fold(noFilter, function (filter) { return function (comment) { return filter(comment.nodeValue); }; }); // the standard wants an object, IE wants a function. But everything is an object, so let's be sneaky // http://www.bennadel.com/blog/2607-finding-html-comment-nodes-in-the-dom-using-treewalker.htm vmlFilter.acceptNode = vmlFilter; // TODO: Add NodeFilter to numerosity (requires IE9 so can't be a global import) var texas = document.createTreeWalker(node.dom(), NodeFilter.SHOW_COMMENT, vmlFilter, false); return getNodes(texas); }; return { find: find }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.cement.alien.Comments', [ 'ephox.perhaps.Option', 'ephox.sugar.api.Comments', 'ephox.violin.Strings', 'global!document' ], function (Option, Comments, Strings, document) { var find = function (node) { // TODO: Should this be in lingo? return Comments.find(node, Option.some(function (value) { return Strings.startsWith(value, '[if gte vml 1]'); // faster than contains on huge MS office comments })); }; return { find: find }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.perhaps.Options', [ 'ephox.perhaps.Option' ], function (Option) { var cat = function (arr) { var r = []; var push = function (x) { r.push(x); }; for (var i = 0; i < arr.length; i++) { arr[i].each(push); } return r; }; var findMap = function (arr, f) { for (var i = 0; i < arr.length; i++) { var r = f(arr[i], i); if (r.isSome()) { return r; } } return Option.none(); }; /** if all elements in arr are 'some', their inner values are passed as arguments to f f must have arity arr.length */ var liftN = function(arr, f) { var r = []; for (var i = 0; i < arr.length; i++) { var x = arr[i]; if (x.isSome()) { r.push(x.getOrDie()); } else { return Option.none(); } }; return Option.some(f.apply(null, r)); }; return { cat: cat, findMap: findMap, liftN: liftN }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.cement.images.ImageReference', [ 'ephox.cement.alien.Comments', 'ephox.compass.Arr', 'ephox.limbo.api.RtfImage', 'ephox.perhaps.Option', 'ephox.perhaps.Options', 'ephox.scullion.Struct', 'ephox.sugar.api.Attr', 'ephox.sugar.api.Elements', 'ephox.sugar.api.SelectorFilter', 'global!console' ], function (Comments, Arr, RtfImage, Option, Options, Struct, Attr, Elements, SelectorFilter, console) { var imgData = Struct.immutable('img', 'vshape'); var getImageAttrs = function (image) { var imgAttrs = getAttrs(image); imgAttrs._rawElement = image.dom(); return imgAttrs; }; var getVshapeAttrs = function (vshape) { var vsAttr = getAttrs(vshape); vsAttr._rawElement = vshape.dom(); return vsAttr; }; var getAttrs = function (element) { return Attr.clone(element); }; var extract = function (raw) { var nodes = Elements.fromHtml(raw); var images = Arr.bind(nodes, function (n) { return SelectorFilter.descendants(n, 'img'); }); var comments = Arr.bind(nodes, Comments.find); var vshapes = Options.cat(Arr.map(comments, RtfImage.scour)); var list = Arr.map(images, function (image) { return traverse(image, vshapes); }); return Options.cat(list); }; var traverse = function (element, vshapes) { var vshapeTarget = Attr.get(element, 'v:shapes'); var vshapeOpt = Option.from(Arr.find(vshapes, function (vshape) { return Attr.get(vshape, 'id') === vshapeTarget; })); if (vshapeOpt.isNone()) console.log('WARNING: unable to find data for image', element.dom()); return vshapeOpt.map(function (vshape) { return pack(element, vshape); }); }; var pack = function (img, vshape) { return imgData( getImageAttrs(img), getVshapeAttrs(vshape) ); }; return { extract: extract }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.photon.Reader', [ 'ephox.perhaps.Option', 'ephox.sugar.api.Element' ], function (Option, Element) { var iframeDoc = function (element) { var dom = element.dom(); try { var idoc = dom.contentWindow ? dom.contentWindow.document : dom.contentDocument; return idoc !== undefined && idoc !== null ? Option.some(Element.fromDom(idoc)) : Option.none(); } catch (err) { // ASSUMPTION: Permission errors result in an unusable iframe. console.log('Error reading iframe: ', dom); console.log('Error was: ' + err); return Option.none(); } }; var doc = function (element) { var optDoc = iframeDoc(element); return optDoc.fold(function () { return element; }, function (v) { return v; }); }; return { doc: doc }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.photon.Writer', [ 'ephox.photon.Reader', 'ephox.sugar.api.Body' ], function (Reader, Body) { var write = function (element, content) { if (!Body.inBody(element)) throw 'Internal error: attempted to write to an iframe that is not in the DOM'; var doc = Reader.doc(element); var dom = doc.dom(); dom.open(); dom.writeln(content); dom.close(); }; return { write: write }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.sugar.api.Ready', [ 'ephox.sugar.api.DomEvent', 'ephox.sugar.api.Element', 'global!document' ], function (DomEvent, Element, document) { var execute = function (f) { /* * We only use this in one place, so creating one listener per ready request is more optimal than managing * a single event with a queue of functions. */ /* The general spec describes three states: loading, complete, and interactive. * https://html.spec.whatwg.org/multipage/dom.html#current-document-readiness * * loading: the document is not ready (still loading) * interactive: the document is ready, but sub-resources are still loading * complete: the document is completely ready. * * Note, IE and w3 schools talk about: uninitialized and loaded. We may have to handle them in the future. */ if (document.readyState === 'complete' || document.readyState === 'interactive') f(); else { // Note that this fires when DOM manipulation is allowed, but before all resources are // available. This is the best practice but might be a bit weird. var listener = DomEvent.bind(Element.fromDom(document), 'DOMContentLoaded', function () { // IE9 minimum f(); listener.unbind(); }); } }; return { execute: execute }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.keurig.loader.GWTLoader', [ 'ephox.fred.PlatformDetection', 'ephox.oilspill.callback.Globaliser', 'ephox.peanut.Fun', 'ephox.peanut.Thunk', 'ephox.perhaps.Option', 'ephox.photon.Writer', 'ephox.sugar.api.Body', 'ephox.sugar.api.Css', 'ephox.sugar.api.DomEvent', 'ephox.sugar.api.Element', 'ephox.sugar.api.Insert', 'ephox.sugar.api.Ready', 'ephox.sugar.api.Remove' ], function (PlatformDetection, Globaliser, Fun, Thunk, Option, Writer, Body, Css, DomEvent, Element, Insert, Ready, Remove) { var globaliser = Globaliser.install('ephox.keurig.init'); var cleanFunction = Option.none(); // IE11 can't handle loading GWT in an iframe, not with the collapse into a single JS file instead of 5 HTML files. // It seems to load ok in WordTest.html, though, which does it directly - worth thinking about if we ever need GWT in IE. var load = PlatformDetection.detect().browser.isIE() ? Fun.noop : Thunk.cached(function (baseUrl) { var container = Element.fromTag('div'); if (baseUrl === undefined) throw 'baseUrl was undefined'; var iframe = Element.fromTag('iframe'); Css.setAll(container, { display: 'none' }); var frameLoad = DomEvent.bind(iframe, 'load', function () { // called when gwt moudle has finished loading. var gwtInitCallback = function (cleanDocument) { cleanFunction = Option.some(cleanDocument); if (!PlatformDetection.detect().browser.isSafari()) { // Safari can't handle executing JS in an iframe that has been removed from the page Remove.remove(container); } }; var gwtInitRef = globaliser.ephemeral(gwtInitCallback); var gwtjs = baseUrl + '/wordimport.js'; Writer.write(iframe, '' + ''); frameLoad.unbind(); }); Ready.execute(function () { Insert.append(Body.body(), container); Insert.append(container, iframe); }); }); var cleanDocument = function (wordHTML, merge) { return cleanFunction.map(function (f) { // TODO: This should probably do something with the log instead of throwing it away in the Java side return f(wordHTML, merge); }); }; var ready = function () { return cleanFunction.isSome(); }; return { load: load, cleanDocument: cleanDocument, ready: ready }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.keurig.api.WordCleaner', [ 'ephox.keurig.loader.GWTLoader' ], function (GWTLoader) { return function (baseUrl) { if (!GWTLoader.ready()) GWTLoader.load(baseUrl); return { cleanDocument: GWTLoader.cleanDocument }; }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.photon.Sandbox', [ 'ephox.peanut.Fun', 'ephox.photon.Writer', 'ephox.sugar.api.Css', 'ephox.sugar.api.DomEvent', 'ephox.sugar.api.Element', 'ephox.sugar.api.Insert', 'ephox.sugar.api.Remove', 'global!setTimeout' ], function (Fun, Writer, Css, DomEvent, Element, Insert, Remove, setTimeout) { return function (uiContainer) { /** * Creates a sandbox to play in. * * Asynchronously creates an iframe, runs the synchronous function `f` on the DOM, and then passes the result to the callback. * * This is done so that the sandbox can guarantee the iframe has been removed from the page, and available for garbage collection, before the callback is executed. * * html: * source to load into the iframe * f: (document -> body -> A) * function that operates on the iframe DOM, passed both document reference and body element * callback: (A -> Unit) * function that receives the output of `f` when the iframe has been cleaned up */ var play = function (html, f, callback) { var outputContainer = Element.fromTag('div'); var iframe = Element.fromTag('iframe'); Css.setAll(outputContainer, { display: 'none' }); var load = DomEvent.bind(iframe, 'load', function () { Writer.write(iframe, html); var rawDoc = iframe.dom().contentWindow.document; if (rawDoc === undefined) throw "sandbox iframe load event did not fire correctly"; var doc = Element.fromDom(rawDoc); var rawBody = rawDoc.body; if (rawBody === undefined) throw "sandbox iframe does not have a body"; var body = Element.fromDom(rawBody); // cache var result = f(doc, body); // unbind and remove everything load.unbind(); Remove.remove(outputContainer); // setTimeout should allow the garbage collector to cleanup if necessary setTimeout(Fun.curry(callback, result), 0); }); Insert.append(outputContainer, iframe); Insert.append(uiContainer, outputContainer); }; return { play: play }; }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.sugar.impl.NodeValue', [ 'ephox.perhaps.Option', 'global!Error' ], function (Option, Error) { return function (is, name) { var get = function (element) { if (!is(element)) throw new Error('Can only get ' + name + ' value of a ' + name + ' node'); return getOption(element).getOr(''); }; var getOption = function (element) { try { return is(element) ? Option.some(element.dom().nodeValue) : Option.none(); } catch (e) { return Option.none(); // Prevent IE10 from throwing exception when setting parent innerHTML clobbers (TBIO-451). } }; var set = function (element, value) { if (!is(element)) throw new Error('Can only set raw ' + name + ' value of a ' + name + ' node'); element.dom().nodeValue = value; }; return { get: get, getOption: getOption, set: set }; }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.sugar.api.Comment', [ 'ephox.sugar.api.Node', 'ephox.sugar.impl.NodeValue' ], function (Node, NodeValue) { var api = NodeValue(Node.isComment, 'comment'); var get = function (element) { return api.get(element); }; var getOption = function (element) { return api.getOption(element); }; var set = function (element, value) { api.set(element, value); }; return { get: get, getOption: getOption, set: set }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.sugar.api.Html', [ 'ephox.sugar.api.Element', 'ephox.sugar.api.Insert' ], function ( Element, Insert) { var get = function (element) { return element.dom().innerHTML; }; var set = function (element, content) { element.dom().innerHTML = content; }; var getOuter = function (element) { var container = Element.fromTag('div'); var clone = Element.fromDom(element.dom().cloneNode(true)); Insert.append(container, clone); return get(container); }; return { get: get, set: set, getOuter: getOuter }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.vogue.css.Set', [ 'ephox.sugar.api.Insert' ], function (Insert) { var setCss = function (style, css, element) { if (style.dom().styleSheet) style.dom().styleSheet.cssText = css; // IE else Insert.append(style, element); }; return { setCss: setCss }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.vogue.util.Regex', [ ], function () { var escape = function (text) { return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); }; return { escape: escape }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); ephox.bolt.module.api.define("global!RegExp", [], function () { return RegExp; }); (function (define, require, demand) { define( 'ephox.vogue.css.Url', [ 'ephox.compass.Obj', 'ephox.vogue.util.Regex', 'global!RegExp' ], function (Obj, Regex, RegExp) { var replace = function (css, urlPrefix, replacement) { var r = new RegExp('url\\(\\s*[\'"]?' + Regex.escape(urlPrefix) + '(.*?)[\'"]?\\s*\\)', 'g'); return css.replace(r, 'url("' + replacement + '$1")'); }; var replaceMany = function (css, replacements) { var current = css; Obj.each(replacements, function (value, key) { current = replace(current, key, value); }); return current; }; return { replace: replace, replaceMany: replaceMany }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.vogue.api.DocStyle', [ 'ephox.sugar.api.Attr', 'ephox.sugar.api.Element', 'ephox.sugar.api.Insert', 'ephox.sugar.api.SelectorFind', 'ephox.vogue.css.Set', 'ephox.vogue.css.Url', 'global!Array' ], function (Attr, Element, Insert, SelectorFind, Set, Url, Array) { var styletag = function (doc) { var style = Element.fromTag('style', doc.dom()); Attr.set(style, 'type', 'text/css'); return style; }; var setCss = function (style, css, doc) { Set.setCss(style, css, Element.fromText(css, doc.dom())); }; var inject = function (css, replacements, doc) { var style = styletag(doc); var replacedCss = replacements === undefined ? css : Url.replaceMany(css, replacements); setCss(style, replacedCss, doc); var head = SelectorFind.descendant(doc, 'head').getOrDie(); Insert.append(head, style); }; var stylesheets = function (doc) { var domStyleSheets = doc.dom().styleSheets; return Array.prototype.slice.call(domStyleSheets); }; return { stylesheets: stylesheets, inject: inject }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.vogue.css.Rules', [ 'ephox.compass.Arr', 'ephox.scullion.Struct' ], function (Arr, Struct) { var ruleStruct = Struct.immutable('selector', 'style'); var extract = function (stylesheet) { var domRules = stylesheet.cssRules; return Arr.map(domRules, function (rule) { var selector = rule.selectorText; var style = rule.style.cssText; if (style === undefined) { // This should be picked up in testing, and perhaps delete the check eventually throw "WARNING: Browser does not support cssText property"; } return ruleStruct(selector, style); }); }; var extractAll = function (stylesheets) { return Arr.bind(stylesheets, extract); }; return { extract: extract, extractAll: extractAll }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.vogue.api.Rules', [ 'ephox.vogue.css.Rules' ], function (Rules) { var extract = function (stylesheet) { return Rules.extract(stylesheet); }; var extractAll = function (stylesheets) { return Rules.extractAll(stylesheets); }; return { extract: extract, extractAll: extractAll }; } ); })(ephox.bolt.module.api.define, ephox.bolt.module.api.require, ephox.bolt.module.api.demand); (function (define, require, demand) { define( 'ephox.cement.html.HtmlPaste', [ 'ephox.cement.html.Transform', 'ephox.cement.images.ImageReference', 'ephox.classify.Type', 'ephox.compass.Arr', 'ephox.keurig.api.WordCleaner', 'ephox.peanut.Fun', 'ephox.photon.Sandbox', 'ephox.porkbun.Event', 'ephox.porkbun.Events', 'ephox.sugar.api.Attr', 'ephox.sugar.api.Class', 'ephox.sugar.api.Comment', 'ephox.sugar.api.Element', 'ephox.sugar.api.Elements', 'ephox.sugar.api.Html', 'ephox.sugar.api.Remove', 'ephox.sugar.api.SelectorFilter', 'ephox.sugar.api.Traverse', 'ephox.vogue.api.DocStyle', 'ephox.vogue.api.Rules', 'global!document' ], function (Transform, ImageReference, Type, Arr, WordCleaner, Fun, Sandbox, Event, Events, Attr, Class, Comment, Element, Elements, Html, Remove, SelectorFilter, Traverse, DocStyle, Rules, document) { var flagAttribute = 'data-textbox-image'; var emptyString = function (s) { return s === undefined || s === null || s.length === 0; }; var stripImageSources = function (html) { var count = 1; return html.replace(/(]*)src=".*?"/g, function (match, p1, offset) { // the actual contents are irrelevant, it just needs to be unique return p1 + flagAttribute + '="' + count++ + '"'; }); }; var removeFragmentComments = function (body) { var bodyChildren = Traverse.children(body); Arr.each(bodyChildren, function (c) { Comment.getOption(c).each(function (commentText) { if (commentText === 'StartFragment' || commentText === 'EndFragment') { Remove.remove(c); } }); }); }; var insertRtfCorrelation = function (sourceImageList, tordImages) { Arr.each(tordImages, function (img) { var imageCounter = Attr.get(img, flagAttribute); Arr.each(sourceImageList, function (imgData) { var imgAttrs = imgData.img(); var vshapeAttrs = imgData.vshape(); if (imgAttrs[flagAttribute] == imageCounter) { // NOTE: If we discover more than 2 possible attributes, de-dupe with RtfImage somehow var spid = vshapeAttrs['o:spid']; var vshapeId = spid === undefined ? vshapeAttrs.id : spid; Transform.rotateImage(img, vshapeAttrs); Class.add(img, 'rtf-data-image'); Attr.set(img, 'data-image-id', vshapeId.substr('_x0000_'.length)); Attr.set(img, 'data-image-type', 'code'); Attr.remove(img, flagAttribute); } }); }); }; var mergeInlineStyles = function (body, stylesheets) { var rules = Rules.extractAll(stylesheets); Arr.each(rules, function (rule) { var matchingElements = SelectorFilter.descendants(body, rule.selector()); Arr.each(matchingElements, function (element) { Attr.remove(element, 'class'); Attr.set(element, 'style', rule.style()); }); }); }; var tordPostProcessor = function (sourceImageList, mergeStyles) { var sandbox = Sandbox(Element.fromDom(document.body)); return function (dump, callback) { // loading dump into the sandbox *will* perform some built-in browser cleanup operations, // we are hoping this is a suitable replacement for the use of HTML Tidy in ELJ. sandbox.play(dump, function (iframeDoc, body) { var images = SelectorFilter.descendants(body, 'img'); // post-tord DOM filters removeFragmentComments(body); insertRtfCorrelation(sourceImageList, images); if (mergeStyles) { mergeInlineStyles(body, DocStyle.stylesheets(iframeDoc)); } return Html.get(body); }, callback); }; }; var cleanEnd = function (raw) { // Trim any weirdness that exists after the closing HTML tag. var i = raw.indexOf(''); return (i > -1) ? raw.substr(0, i + '