!function(e){var val=e(); if("object"==typeof exports&&"undefined"!=typeof module)module.exports=val;if("function"==typeof define&&define.amd)define("emmet",[],val);{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.emmet=val}}(function(){var define,module,exports;return (function outer (modules, cache, entry) { // Save the require from previous bundle to this closure if any var previousRequire = typeof require == "function" && require; function newRequire(name, jumped){ if(!cache[name]) { if(!modules[name]) { // if we cannot find the the module within our internal map or // cache jump to the current global require ie. the last bundle // that was added to the page. var currentRequire = typeof require == "function" && require; if (!jumped && currentRequire) return currentRequire(name, true); // If there are other bundles on this page the require from the // previous one is saved to 'previousRequire'. Repeat this as // many times as there are bundles until the module is found or // we exhaust the require chain. if (previousRequire) return previousRequire(name, true); var err = new Error('Cannot find module \'' + name + '\''); err.code = 'MODULE_NOT_FOUND'; throw err; } var m = cache[name] = {exports:{}}; modules[name][0].call(m.exports, function(x){ var id = modules[name][1][x]; return newRequire(id ? id : x); },m,m.exports,outer,modules,cache,entry); } return cache[name].exports; } for(var i=0;ifile module of IEmmetFile * interface to be implemented. * @memberOf bootstrap */ loadExtensions: function(fileList) { var payload = {}; var userSnippets = null; var that = this; // make sure file list contians only valid extension files fileList = fileList.filter(function(f) { var ext = file.getExt(f); return ext === 'json' || ext === 'js'; }); var reader = (file.readText || file.read).bind(file); var next = function() { if (fileList.length) { var f = fileList.shift(); reader(f, function(err, content) { if (err) { logger.log('Unable to read "' + f + '" file: '+ err); return next(); } switch (file.getExt(f)) { case 'js': try { eval(content); } catch (e) { logger.log('Unable to eval "' + f + '" file: '+ e); } break; case 'json': var fileName = getFileName(f).toLowerCase().replace(/\.json$/, ''); content = utils.parseJSON(content); if (/^snippets/.test(fileName)) { if (fileName === 'snippets') { // data in snippets.json is more important to user userSnippets = content; } else { payload.snippets = utils.deepMerge(payload.snippets || {}, content); } } else { payload[fileName] = content; } break; } next(); }); } else { // complete if (userSnippets) { payload.snippets = utils.deepMerge(payload.snippets || {}, userSnippets); } that.loadUserData(payload); } }; next(); }, /** * Loads preferences from JSON object (or string representation of JSON) * @param {Object} data * @returns */ loadPreferences: function(data) { preferences.load(utils.parseJSON(data)); }, /** * Loads user snippets and abbreviations. It doesn’t replace current * user resource vocabulary but merges it with passed one. If you need * to replaces user snippets you should call * resetSnippets() method first */ loadSnippets: function(data) { data = utils.parseJSON(data); var userData = resources.getVocabulary('user') || {}; resources.setVocabulary(utils.deepMerge(userData, data), 'user'); }, /** * Helper function that loads default snippets, defined in project’s * snippets.json * @param {Object} data */ loadSystemSnippets: function(data) { resources.setVocabulary(utils.parseJSON(data), 'system'); }, /** * Helper function that loads Can I Use database * @param {Object} data */ loadCIU: function(data) { ciu.load(utils.parseJSON(data)); }, /** * Removes all user-defined snippets */ resetSnippets: function() { resources.setVocabulary({}, 'user'); }, /** * Helper function that loads all user data (snippets and preferences) * defined as a single JSON object. This is useful for loading data * stored in a common storage, for example NSUserDefaults * @param {Object} data */ loadUserData: function(data) { data = utils.parseJSON(data); if (data.snippets) { this.loadSnippets(data.snippets); } if (data.preferences) { this.loadPreferences(data.preferences); } if (data.profiles) { this.loadProfiles(data.profiles); } if (data.caniuse) { this.loadCIU(data.caniuse); } var profiles = data.syntaxProfiles || data.syntaxprofiles; if (profiles) { this.loadSyntaxProfiles(profiles); } }, /** * Resets all user-defined data: preferences, snippets etc. * @returns */ resetUserData: function() { this.resetSnippets(); preferences.reset(); profile.reset(); }, /** * Load syntax-specific output profiles. These are essentially * an extension to syntax snippets * @param {Object} profiles Dictionary of profiles */ loadSyntaxProfiles: function(profiles) { profiles = utils.parseJSON(profiles); var snippets = {}; Object.keys(profiles).forEach(function(syntax) { var options = profiles[syntax]; if (!(syntax in snippets)) { snippets[syntax] = {}; } snippets[syntax].profile = normalizeProfile(options); }); this.loadSnippets(snippets); }, /** * Load named profiles * @param {Object} profiles */ loadProfiles: function(profiles) { profiles = utils.parseJSON(profiles); Object.keys(profiles).forEach(function(name) { profile.create(name, normalizeProfile(profiles[name])); }); }, // expose some useful data for plugin authors actions: actions, parser: parser, file: file, preferences: preferences, resources: resources, profile: profile, tabStops: require('./assets/tabStops'), htmlMatcher: require('./assets/htmlMatcher'), utils: { common: utils, action: require('./utils/action'), editor: require('./utils/editor') } }; }); },{"./action/main":"action\\main.js","./assets/caniuse":"assets\\caniuse.js","./assets/htmlMatcher":"assets\\htmlMatcher.js","./assets/logger":"assets\\logger.js","./assets/preferences":"assets\\preferences.js","./assets/profile":"assets\\profile.js","./assets/resources":"assets\\resources.js","./assets/tabStops":"assets\\tabStops.js","./parser/abbreviation":"parser\\abbreviation.js","./plugin/file":"plugin\\file.js","./utils/action":"utils\\action.js","./utils/common":"utils\\common.js","./utils/editor":"utils\\editor.js"}],"action\\balance.js":[function(require,module,exports){ /** * HTML pair matching (balancing) actions * @constructor */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var htmlMatcher = require('../assets/htmlMatcher'); var utils = require('../utils/common'); var editorUtils = require('../utils/editor'); var actionUtils = require('../utils/action'); var range = require('../assets/range'); var cssEditTree = require('../editTree/css'); var cssSections = require('../utils/cssSections'); var lastMatch = null; function last(arr) { return arr[arr.length - 1]; } function balanceHTML(editor, direction) { var info = editorUtils.outputInfo(editor); var content = info.content; var sel = range(editor.getSelectionRange()); // validate previous match if (lastMatch && !lastMatch.range.equal(sel)) { lastMatch = null; } if (lastMatch && sel.length()) { if (direction == 'in') { // user has previously selected tag and wants to move inward if (lastMatch.type == 'tag' && !lastMatch.close) { // unary tag was selected, can't move inward return false; } else { if (lastMatch.range.equal(lastMatch.outerRange)) { lastMatch.range = lastMatch.innerRange; } else { var narrowed = utils.narrowToNonSpace(content, lastMatch.innerRange); lastMatch = htmlMatcher.find(content, narrowed.start + 1); if (lastMatch && lastMatch.range.equal(sel) && lastMatch.outerRange.equal(sel)) { lastMatch.range = lastMatch.innerRange; } } } } else { if ( !lastMatch.innerRange.equal(lastMatch.outerRange) && lastMatch.range.equal(lastMatch.innerRange) && sel.equal(lastMatch.range)) { lastMatch.range = lastMatch.outerRange; } else { lastMatch = htmlMatcher.find(content, sel.start); if (lastMatch && lastMatch.range.equal(sel) && lastMatch.innerRange.equal(sel)) { lastMatch.range = lastMatch.outerRange; } } } } else { lastMatch = htmlMatcher.find(content, sel.start); } if (lastMatch) { if (lastMatch.innerRange.equal(sel)) { lastMatch.range = lastMatch.outerRange; } if (!lastMatch.range.equal(sel)) { editor.createSelection(lastMatch.range.start, lastMatch.range.end); return true; } } lastMatch = null; return false; } function rangesForCSSRule(rule, pos) { // find all possible ranges var ranges = [rule.range(true)]; // braces content ranges.push(rule.valueRange(true)); // find nested sections var nestedSections = cssSections.nestedSectionsInRule(rule); // real content, e.g. from first property name to // last property value var items = rule.list(); if (items.length || nestedSections.length) { var start = Number.POSITIVE_INFINITY, end = -1; if (items.length) { start = items[0].namePosition(true); end = last(items).range(true).end; } if (nestedSections.length) { if (nestedSections[0].start < start) { start = nestedSections[0].start; } if (last(nestedSections).end > end) { end = last(nestedSections).end; } } ranges.push(range.create2(start, end)); } ranges = ranges.concat(nestedSections); var prop = cssEditTree.propertyFromPosition(rule, pos) || items[0]; if (prop) { ranges.push(prop.range(true)); var valueRange = prop.valueRange(true); if (!prop.end()) { valueRange._unterminated = true; } ranges.push(valueRange); } return ranges; } /** * Returns all possible selection ranges for given caret position * @param {String} content CSS content * @param {Number} pos Caret position(where to start searching) * @return {Array} */ function getCSSRanges(content, pos) { var rule; if (typeof content === 'string') { var ruleRange = cssSections.matchEnclosingRule(content, pos); if (ruleRange) { rule = cssEditTree.parse(ruleRange.substring(content), { offset: ruleRange.start }); } } else { // passed parsed CSS rule rule = content; } if (!rule) { return null; } // find all possible ranges var ranges = rangesForCSSRule(rule, pos); // remove empty ranges ranges = ranges.filter(function(item) { return !!item.length; }); return utils.unique(ranges, function(item) { return item.valueOf(); }); } function balanceCSS(editor, direction) { var info = editorUtils.outputInfo(editor); var content = info.content; var sel = range(editor.getSelectionRange()); var ranges = getCSSRanges(info.content, sel.start); if (!ranges && sel.length()) { // possible reason: user has already selected // CSS rule from last match try { var rule = cssEditTree.parse(sel.substring(info.content), { offset: sel.start }); ranges = getCSSRanges(rule, sel.start); } catch(e) {} } if (!ranges) { return false; } ranges = range.sort(ranges, true); // edge case: find match that equals current selection, // in case if user moves inward after selecting full CSS rule var bestMatch = utils.find(ranges, function(r) { return r.equal(sel); }); if (!bestMatch) { bestMatch = utils.find(ranges, function(r) { // Check for edge case: caret right after CSS value // but it doesn‘t contains terminating semicolon. // In this case we have to check full value range return r._unterminated ? r.include(sel.start) : r.inside(sel.start); }); } if (!bestMatch) { return false; } // if best match equals to current selection, move index // one position up or down, depending on direction var bestMatchIx = ranges.indexOf(bestMatch); if (bestMatch.equal(sel)) { bestMatchIx += direction == 'out' ? 1 : -1; } if (bestMatchIx < 0 || bestMatchIx >= ranges.length) { if (bestMatchIx >= ranges.length && direction == 'out') { pos = bestMatch.start - 1; var outerRanges = getCSSRanges(content, pos); if (outerRanges) { bestMatch = last(outerRanges.filter(function(r) { return r.inside(pos); })); } } else if (bestMatchIx < 0 && direction == 'in') { bestMatch = null; } else { bestMatch = null; } } else { bestMatch = ranges[bestMatchIx]; } if (bestMatch) { editor.createSelection(bestMatch.start, bestMatch.end); return true; } return false; } return { /** * Find and select HTML tag pair * @param {IEmmetEditor} editor Editor instance * @param {String} direction Direction of pair matching: 'in' or 'out'. * Default is 'out' */ balance: function(editor, direction) { direction = String((direction || 'out').toLowerCase()); var info = editorUtils.outputInfo(editor); if (actionUtils.isSupportedCSS(info.syntax)) { return balanceCSS(editor, direction); } return balanceHTML(editor, direction); }, balanceInwardAction: function(editor) { return this.balance(editor, 'in'); }, balanceOutwardAction: function(editor) { return this.balance(editor, 'out'); }, /** * Moves caret to matching opening or closing tag * @param {IEmmetEditor} editor */ goToMatchingPairAction: function(editor) { var content = String(editor.getContent()); var caretPos = editor.getCaretPos(); if (content.charAt(caretPos) == '<') // looks like caret is outside of tag pair caretPos++; var tag = htmlMatcher.tag(content, caretPos); if (tag && tag.close) { // exclude unary tags if (tag.open.range.inside(caretPos)) { editor.setCaretPos(tag.close.range.start); } else { editor.setCaretPos(tag.open.range.start); } return true; } return false; } }; }); },{"../assets/htmlMatcher":"assets\\htmlMatcher.js","../assets/range":"assets\\range.js","../editTree/css":"editTree\\css.js","../utils/action":"utils\\action.js","../utils/common":"utils\\common.js","../utils/cssSections":"utils\\cssSections.js","../utils/editor":"utils\\editor.js"}],"action\\base64.js":[function(require,module,exports){ /** * Encodes/decodes image under cursor to/from base64 * @param {IEmmetEditor} editor */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var file = require('../plugin/file'); var base64 = require('../utils/base64'); var actionUtils = require('../utils/action'); var editorUtils = require('../utils/editor'); /** * Test if text starts with token at pos * position. If pos is omitted, search from beginning of text * @param {String} token Token to test * @param {String} text Where to search * @param {Number} pos Position where to start search * @return {Boolean} * @since 0.65 */ function startsWith(token, text, pos) { pos = pos || 0; return text.charAt(pos) == token.charAt(0) && text.substr(pos, token.length) == token; } /** * Encodes image to base64 * * @param {IEmmetEditor} editor * @param {String} imgPath Path to image * @param {Number} pos Caret position where image is located in the editor * @return {Boolean} */ function encodeToBase64(editor, imgPath, pos) { var editorFile = editor.getFilePath(); var defaultMimeType = 'application/octet-stream'; if (editorFile === null) { throw "You should save your file before using this action"; } // locate real image path file.locateFile(editorFile, imgPath, function(realImgPath) { if (realImgPath === null) { throw "Can't find " + imgPath + ' file'; } file.read(realImgPath, function(err, content) { if (err) { throw 'Unable to read ' + realImgPath + ': ' + err; } var b64 = base64.encode(String(content)); if (!b64) { throw "Can't encode file content to base64"; } b64 = 'data:' + (actionUtils.mimeTypes[String(file.getExt(realImgPath))] || defaultMimeType) + ';base64,' + b64; editor.replaceContent('$0' + b64, pos, pos + imgPath.length); }); }); return true; } /** * Decodes base64 string back to file. * @param {IEmmetEditor} editor * @param {String} filePath to new image * @param {String} data Base64-encoded file content * @param {Number} pos Caret position where image is located in the editor */ function decodeFromBase64(editor, filePath, data, pos) { // ask user to enter path to file filePath = filePath || String(editor.prompt('Enter path to file (absolute or relative)')); if (!filePath) { return false; } var editorFile = editor.getFilePath(); file.createPath(editorFile, filePath, function(err, absPath) { if (err || !absPath) { throw "Can't save file"; } var content = data.replace(/^data\:.+?;.+?,/, ''); file.save(absPath, base64.decode(content), function(err) { if (err) { throw 'Unable to save ' + absPath + ': ' + err; } editor.replaceContent('$0' + filePath, pos, pos + data.length); }); }); return true; } return { /** * Action to encode or decode file to data:url * @param {IEmmetEditor} editor Editor instance * @param {String} syntax Current document syntax * @param {String} profile Output profile name * @return {Boolean} */ encodeDecodeDataUrlAction: function(editor, filepath) { var data = String(editor.getSelection()); var caretPos = editor.getCaretPos(); var info = editorUtils.outputInfo(editor); if (!data) { // no selection, try to find image bounds from current caret position var text = info.content, m; while (caretPos-- >= 0) { if (startsWith('src=', text, caretPos)) { // found if ((m = text.substr(caretPos).match(/^(src=(["'])?)([^'"<>\s]+)\1?/))) { data = m[3]; caretPos += m[1].length; } break; } else if (startsWith('url(', text, caretPos)) { // found CSS url() pattern if ((m = text.substr(caretPos).match(/^(url\((['"])?)([^'"\)\s]+)\1?/))) { data = m[3]; caretPos += m[1].length; } break; } } } if (data) { if (startsWith('data:', data)) { return decodeFromBase64(editor, filepath, data, caretPos); } else { return encodeToBase64(editor, data, caretPos); } } return false; } }; }); },{"../plugin/file":"plugin\\file.js","../utils/action":"utils\\action.js","../utils/base64":"utils\\base64.js","../utils/editor":"utils\\editor.js"}],"action\\editPoints.js":[function(require,module,exports){ /** * Move between next/prev edit points. 'Edit points' are places between tags * and quotes of empty attributes in html */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { /** * Search for new caret insertion point * @param {IEmmetEditor} editor Editor instance * @param {Number} inc Search increment: -1 — search left, 1 — search right * @param {Number} offset Initial offset relative to current caret position * @return {Number} Returns -1 if insertion point wasn't found */ function findNewEditPoint(editor, inc, offset) { inc = inc || 1; offset = offset || 0; var curPoint = editor.getCaretPos() + offset; var content = String(editor.getContent()); var maxLen = content.length; var nextPoint = -1; var reEmptyLine = /^\s+$/; function getLine(ix) { var start = ix; while (start >= 0) { var c = content.charAt(start); if (c == '\n' || c == '\r') break; start--; } return content.substring(start, ix); } while (curPoint <= maxLen && curPoint >= 0) { curPoint += inc; var curChar = content.charAt(curPoint); var nextChar = content.charAt(curPoint + 1); var prevChar = content.charAt(curPoint - 1); switch (curChar) { case '"': case '\'': if (nextChar == curChar && prevChar == '=') { // empty attribute nextPoint = curPoint + 1; } break; case '>': if (nextChar == '<') { // between tags nextPoint = curPoint + 1; } break; case '\n': case '\r': // empty line if (reEmptyLine.test(getLine(curPoint - 1))) { nextPoint = curPoint; } break; } if (nextPoint != -1) break; } return nextPoint; } return { /** * Move to previous edit point * @param {IEmmetEditor} editor Editor instance * @param {String} syntax Current document syntax * @param {String} profile Output profile name * @return {Boolean} */ previousEditPointAction: function(editor, syntax, profile) { var curPos = editor.getCaretPos(); var newPoint = findNewEditPoint(editor, -1); if (newPoint == curPos) // we're still in the same point, try searching from the other place newPoint = findNewEditPoint(editor, -1, -2); if (newPoint != -1) { editor.setCaretPos(newPoint); return true; } return false; }, /** * Move to next edit point * @param {IEmmetEditor} editor Editor instance * @param {String} syntax Current document syntax * @param {String} profile Output profile name * @return {Boolean} */ nextEditPointAction: function(editor, syntax, profile) { var newPoint = findNewEditPoint(editor, 1); if (newPoint != -1) { editor.setCaretPos(newPoint); return true; } return false; } }; }); },{}],"action\\evaluateMath.js":[function(require,module,exports){ /** * Evaluates simple math expression under caret */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var actionUtils = require('../utils/action'); var utils = require('../utils/common'); var math = require('../utils/math'); var range = require('../assets/range'); return { /** * Evaluates math expression under the caret * @param {IEmmetEditor} editor * @return {Boolean} */ evaluateMathAction: function(editor) { var content = editor.getContent(); var chars = '.+-*/\\'; /** @type Range */ var sel = range(editor.getSelectionRange()); if (!sel.length()) { sel = actionUtils.findExpressionBounds(editor, function(ch) { return utils.isNumeric(ch) || chars.indexOf(ch) != -1; }); } if (sel && sel.length()) { var expr = sel.substring(content); // replace integral division: 11\2 => Math.round(11/2) expr = expr.replace(/([\d\.\-]+)\\([\d\.\-]+)/g, 'round($1/$2)'); try { var result = utils.prettifyNumber(math.evaluate(expr)); editor.replaceContent(result, sel.start, sel.end); editor.setCaretPos(sel.start + result.length); return true; } catch (e) {} } return false; } }; }); },{"../assets/range":"assets\\range.js","../utils/action":"utils\\action.js","../utils/common":"utils\\common.js","../utils/math":"utils\\math.js"}],"action\\expandAbbreviation.js":[function(require,module,exports){ /** * 'Expand abbreviation' editor action: extracts abbreviation from current caret * position and replaces it with formatted output. *

* This behavior can be overridden with custom handlers which can perform * different actions when 'Expand Abbreviation' action is called. * For example, a CSS gradient handler that produces vendor-prefixed gradient * definitions registers its own expand abbreviation handler. */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var handlerList = require('../assets/handlerList'); var range = require('../assets/range'); var prefs = require('../assets/preferences'); var utils = require('../utils/common'); var editorUtils = require('../utils/editor'); var actionUtils = require('../utils/action'); var cssGradient = require('../resolver/cssGradient'); var parser = require('../parser/abbreviation'); /** * Search for abbreviation in editor from current caret position * @param {IEmmetEditor} editor Editor instance * @return {String} */ function findAbbreviation(editor) { var r = range(editor.getSelectionRange()); var content = String(editor.getContent()); if (r.length()) { // abbreviation is selected by user return r.substring(content); } // search for new abbreviation from current caret position var curLine = editor.getCurrentLineRange(); return actionUtils.extractAbbreviation(content.substring(curLine.start, r.start)); } /** * @type HandlerList List of registered handlers */ var handlers = handlerList.create(); // XXX setup default expand handlers /** * Extracts abbreviation from current caret * position and replaces it with formatted output * @param {IEmmetEditor} editor Editor instance * @param {String} syntax Syntax type (html, css, etc.) * @param {String} profile Output profile name (html, xml, xhtml) * @return {Boolean} Returns true if abbreviation was expanded * successfully */ handlers.add(function(editor, syntax, profile) { var caretPos = editor.getSelectionRange().end; var abbr = findAbbreviation(editor); if (abbr) { var content = parser.expand(abbr, { syntax: syntax, profile: profile, contextNode: actionUtils.captureContext(editor) }); if (content) { var replaceFrom = caretPos - abbr.length; var replaceTo = caretPos; // a special case for CSS: if editor already contains // semicolon right after current caret position — replace it too var cssSyntaxes = prefs.getArray('css.syntaxes'); if (cssSyntaxes && ~cssSyntaxes.indexOf(syntax)) { var curContent = editor.getContent(); if (curContent.charAt(caretPos) == ';' && content.charAt(content.length - 1) == ';') { replaceTo++; } } editor.replaceContent(content, replaceFrom, replaceTo); return true; } } return false; }, {order: -1}); handlers.add(cssGradient.expandAbbreviationHandler.bind(cssGradient)); return { /** * The actual “Expand Abbreviation“ action routine * @param {IEmmetEditor} editor Editor instance * @param {String} syntax Current document syntax * @param {String} profile Output profile name * @return {Boolean} */ expandAbbreviationAction: function(editor, syntax, profile) { var args = utils.toArray(arguments); // normalize incoming arguments var info = editorUtils.outputInfo(editor, syntax, profile); args[1] = info.syntax; args[2] = info.profile; return handlers.exec(false, args); }, /** * A special case of “Expand Abbreviation“ action, invoked by Tab key. * In this case if abbreviation wasn’t expanded successfully or there’s a selecetion, * the current line/selection will be indented. * @param {IEmmetEditor} editor Editor instance * @param {String} syntax Current document syntax * @param {String} profile Output profile name * @return {Boolean} */ expandAbbreviationWithTabAction: function(editor, syntax, profile) { var sel = editor.getSelection(); var indent = '\t'; // if something is selected in editor, // we should indent the selected content if (sel) { var selRange = range(editor.getSelectionRange()); var content = utils.padString(sel, indent); editor.replaceContent(indent + '${0}', editor.getCaretPos()); var replaceRange = range(editor.getCaretPos(), selRange.length()); editor.replaceContent(content, replaceRange.start, replaceRange.end, true); editor.createSelection(replaceRange.start, replaceRange.start + content.length); return true; } // nothing selected, try to expand if (!this.expandAbbreviationAction(editor, syntax, profile)) { editor.replaceContent(indent, editor.getCaretPos()); } return true; }, _defaultHandler: function(editor, syntax, profile) { var caretPos = editor.getSelectionRange().end; var abbr = this.findAbbreviation(editor); if (abbr) { var ctx = actionUtils.captureContext(editor); var content = parser.expand(abbr, syntax, profile, ctx); if (content) { editor.replaceContent(content, caretPos - abbr.length, caretPos); return true; } } return false; }, /** * Adds custom expand abbreviation handler. The passed function should * return true if it was performed successfully, * false otherwise. * * Added handlers will be called when 'Expand Abbreviation' is called * in order they were added * @memberOf expandAbbreviation * @param {Function} fn * @param {Object} options */ addHandler: function(fn, options) { handlers.add(fn, options); }, /** * Removes registered handler * @returns */ removeHandler: function(fn) { handlers.remove(fn); }, findAbbreviation: findAbbreviation }; }); },{"../assets/handlerList":"assets\\handlerList.js","../assets/preferences":"assets\\preferences.js","../assets/range":"assets\\range.js","../parser/abbreviation":"parser\\abbreviation.js","../resolver/cssGradient":"resolver\\cssGradient.js","../utils/action":"utils\\action.js","../utils/common":"utils\\common.js","../utils/editor":"utils\\editor.js"}],"action\\incrementDecrement.js":[function(require,module,exports){ /** * Increment/decrement number under cursor */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var utils = require('../utils/common'); var actionUtils = require('../utils/action'); /** * Returns length of integer part of number * @param {String} num */ function intLength(num) { num = num.replace(/^\-/, ''); if (~num.indexOf('.')) { return num.split('.')[0].length; } return num.length; } return { increment01Action: function(editor) { return this.incrementNumber(editor, .1); }, increment1Action: function(editor) { return this.incrementNumber(editor, 1); }, increment10Action: function(editor) { return this.incrementNumber(editor, 10); }, decrement01Action: function(editor) { return this.incrementNumber(editor, -.1); }, decrement1Action: function(editor) { return this.incrementNumber(editor, -1); }, decrement10Action: function(editor) { return this.incrementNumber(editor, -10); }, /** * Default method to increment/decrement number under * caret with given step * @param {IEmmetEditor} editor * @param {Number} step * @return {Boolean} */ incrementNumber: function(editor, step) { var hasSign = false; var hasDecimal = false; var r = actionUtils.findExpressionBounds(editor, function(ch, pos, content) { if (utils.isNumeric(ch)) return true; if (ch == '.') { // make sure that next character is numeric too if (!utils.isNumeric(content.charAt(pos + 1))) return false; return hasDecimal ? false : hasDecimal = true; } if (ch == '-') return hasSign ? false : hasSign = true; return false; }); if (r && r.length()) { var strNum = r.substring(String(editor.getContent())); var num = parseFloat(strNum); if (!isNaN(num)) { num = utils.prettifyNumber(num + step); // do we have zero-padded number? if (/^(\-?)0+[1-9]/.test(strNum)) { var minus = ''; if (RegExp.$1) { minus = '-'; num = num.substring(1); } var parts = num.split('.'); parts[0] = utils.zeroPadString(parts[0], intLength(strNum)); num = minus + parts.join('.'); } editor.replaceContent(num, r.start, r.end); editor.createSelection(r.start, r.start + num.length); return true; } } return false; } }; }); },{"../utils/action":"utils\\action.js","../utils/common":"utils\\common.js"}],"action\\lineBreaks.js":[function(require,module,exports){ /** * Actions to insert line breaks. Some simple editors (like browser's * <textarea>, for example) do not provide such simple things */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var prefs = require('../assets/preferences'); var utils = require('../utils/common'); var resources = require('../assets/resources'); var htmlMatcher = require('../assets/htmlMatcher'); var editorUtils = require('../utils/editor'); var xmlSyntaxes = ['html', 'xml', 'xsl']; // setup default preferences prefs.define('css.closeBraceIndentation', '\n', 'Indentation before closing brace of CSS rule. Some users prefere ' + 'indented closing brace of CSS rule for better readability. ' + 'This preference’s value will be automatically inserted before ' + 'closing brace when user adds newline in newly created CSS rule ' + '(e.g. when “Insert formatted linebreak” action will be performed ' + 'in CSS file). If you’re such user, you may want to write put a value ' + 'like \\n\\t in this preference.'); return { /** * Inserts newline character with proper indentation. This action is used in * editors that doesn't have indentation control (like textarea element) to * provide proper indentation for inserted newlines * @param {IEmmetEditor} editor Editor instance */ insertLineBreakAction: function(editor) { if (!this.insertLineBreakOnlyAction(editor)) { var curPadding = editorUtils.getCurrentLinePadding(editor); var content = String(editor.getContent()); var caretPos = editor.getCaretPos(); var len = content.length; var nl = '\n'; // check out next line padding var lineRange = editor.getCurrentLineRange(); var nextPadding = ''; for (var i = lineRange.end, ch; i < len; i++) { ch = content.charAt(i); if (ch == ' ' || ch == '\t') nextPadding += ch; else break; } if (nextPadding.length > curPadding.length) { editor.replaceContent(nl + nextPadding, caretPos, caretPos, true); } else { editor.replaceContent(nl, caretPos); } } return true; }, /** * Inserts newline character with proper indentation in specific positions only. * @param {IEmmetEditor} editor * @return {Boolean} Returns true if line break was inserted */ insertLineBreakOnlyAction: function(editor) { var info = editorUtils.outputInfo(editor); var caretPos = editor.getCaretPos(); var nl = '\n'; var pad = '\t'; if (~xmlSyntaxes.indexOf(info.syntax)) { // let's see if we're breaking newly created tag var tag = htmlMatcher.tag(info.content, caretPos); if (tag && !tag.innerRange.length()) { editor.replaceContent(nl + pad + utils.getCaretPlaceholder() + nl, caretPos); return true; } } else if (info.syntax == 'css') { /** @type String */ var content = info.content; if (caretPos && content.charAt(caretPos - 1) == '{') { var append = prefs.get('css.closeBraceIndentation'); var hasCloseBrace = content.charAt(caretPos) == '}'; if (!hasCloseBrace) { // do we really need special formatting here? // check if this is really a newly created rule, // look ahead for a closing brace for (var i = caretPos, il = content.length, ch; i < il; i++) { ch = content.charAt(i); if (ch == '{') { // ok, this is a new rule without closing brace break; } if (ch == '}') { // not a new rule, just add indentation append = ''; hasCloseBrace = true; break; } } } if (!hasCloseBrace) { append += '}'; } // defining rule set var insValue = nl + pad + utils.getCaretPlaceholder() + append; editor.replaceContent(insValue, caretPos); return true; } } return false; } }; }); },{"../assets/htmlMatcher":"assets\\htmlMatcher.js","../assets/preferences":"assets\\preferences.js","../assets/resources":"assets\\resources.js","../utils/common":"utils\\common.js","../utils/editor":"utils\\editor.js"}],"action\\main.js":[function(require,module,exports){ /** * Module describes and performs Emmet actions. The actions themselves are * defined in actions folder */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var utils = require('../utils/common'); // all registered actions var actions = {}; // load all default actions var actionModules = { base64: require('./base64'), editPoints: require('./editPoints'), evaluateMath: require('./evaluateMath'), expandAbbreviation: require('./expandAbbreviation'), incrementDecrement: require('./incrementDecrement'), lineBreaks: require('./lineBreaks'), balance: require('./balance'), mergeLines: require('./mergeLines'), reflectCSSValue: require('./reflectCSSValue'), removeTag: require('./removeTag'), selectItem: require('./selectItem'), selectLine: require('./selectLine'), splitJoinTag: require('./splitJoinTag'), toggleComment: require('./toggleComment'), updateImageSize: require('./updateImageSize'), wrapWithAbbreviation: require('./wrapWithAbbreviation'), updateTag: require('./updateTag') }; function addAction(name, fn, options) { name = name.toLowerCase(); options = options || {}; if (typeof options === 'string') { options = {label: options}; } if (!options.label) { options.label = humanizeActionName(name); } actions[name] = { name: name, fn: fn, options: options }; } /** * “Humanizes” action name, makes it more readable for people * @param {String} name Action name (like 'expand_abbreviation') * @return Humanized name (like 'Expand Abbreviation') */ function humanizeActionName(name) { return utils.trim(name.charAt(0).toUpperCase() + name.substring(1).replace(/_[a-z]/g, function(str) { return ' ' + str.charAt(1).toUpperCase(); })); } var bind = function(name, method) { var m = actionModules[name]; return m[method].bind(m); }; // XXX register default actions addAction('encode_decode_data_url', bind('base64', 'encodeDecodeDataUrlAction'), 'Encode\\Decode data:URL image'); addAction('prev_edit_point', bind('editPoints', 'previousEditPointAction'), 'Previous Edit Point'); addAction('next_edit_point', bind('editPoints', 'nextEditPointAction'), 'Next Edit Point'); addAction('evaluate_math_expression', bind('evaluateMath', 'evaluateMathAction'), 'Numbers/Evaluate Math Expression'); addAction('expand_abbreviation_with_tab', bind('expandAbbreviation', 'expandAbbreviationWithTabAction'), {hidden: true}); addAction('expand_abbreviation', bind('expandAbbreviation', 'expandAbbreviationAction'), 'Expand Abbreviation'); addAction('insert_formatted_line_break_only', bind('lineBreaks', 'insertLineBreakOnlyAction'), {hidden: true}); addAction('insert_formatted_line_break', bind('lineBreaks', 'insertLineBreakAction'), {hidden: true}); addAction('balance_inward', bind('balance', 'balanceInwardAction'), 'Balance (inward)'); addAction('balance_outward', bind('balance', 'balanceOutwardAction'), 'Balance (outward)'); addAction('matching_pair', bind('balance', 'goToMatchingPairAction'), 'HTML/Go To Matching Tag Pair'); addAction('merge_lines', bind('mergeLines', 'mergeLinesAction'), 'Merge Lines'); addAction('reflect_css_value', bind('reflectCSSValue', 'reflectCSSValueAction'), 'CSS/Reflect Value'); addAction('remove_tag', bind('removeTag', 'removeTagAction'), 'HTML/Remove Tag'); addAction('select_next_item', bind('selectItem', 'selectNextItemAction'), 'Select Next Item'); addAction('select_previous_item', bind('selectItem', 'selectPreviousItemAction'), 'Select Previous Item'); addAction('split_join_tag', bind('splitJoinTag', 'splitJoinTagAction'), 'HTML/Split\\Join Tag Declaration'); addAction('toggle_comment', bind('toggleComment', 'toggleCommentAction'), 'Toggle Comment'); addAction('update_image_size', bind('updateImageSize', 'updateImageSizeAction'), 'Update Image Size'); addAction('wrap_with_abbreviation', bind('wrapWithAbbreviation', 'wrapWithAbbreviationAction'), 'Wrap With Abbreviation'); addAction('update_tag', bind('updateTag', 'updateTagAction'), 'HTML/Update Tag'); [1, -1, 10, -10, 0.1, -0.1].forEach(function(num) { var prefix = num > 0 ? 'increment' : 'decrement'; var suffix = String(Math.abs(num)).replace('.', '').substring(0, 2); var actionId = prefix + '_number_by_' + suffix; var actionMethod = prefix + suffix + 'Action'; var actionLabel = 'Numbers/' + prefix.charAt(0).toUpperCase() + prefix.substring(1) + ' number by ' + Math.abs(num); addAction(actionId, bind('incrementDecrement', actionMethod), actionLabel); }); return { /** * Registers new action * @param {String} name Action name * @param {Function} fn Action function * @param {Object} options Custom action options:
* label : (String) – Human-readable action name. * May contain '/' symbols as submenu separators
* hidden : (Boolean) – Indicates whether action * should be displayed in menu (getMenu() method) */ add: addAction, /** * Returns action object * @param {String} name Action name * @returns {Object} */ get: function(name) { return actions[name.toLowerCase()]; }, /** * Runs Emmet action. For list of available actions and their * arguments see actions folder. * @param {String} name Action name * @param {Array} args Additional arguments. It may be array of arguments * or inline arguments. The first argument should be IEmmetEditor instance * @returns {Boolean} Status of performed operation, true * means action was performed successfully. * @example * require('action/main').run('expand_abbreviation', editor); * require('action/main').run('wrap_with_abbreviation', [editor, 'div']); */ run: function(name, args) { if (!Array.isArray(args)) { args = utils.toArray(arguments, 1); } var action = this.get(name); if (!action) { throw new Error('Action "' + name + '" is not defined'); } return action.fn.apply(action, args); }, /** * Returns all registered actions as object * @returns {Object} */ getAll: function() { return actions; }, /** * Returns all registered actions as array * @returns {Array} */ getList: function() { var all = this.getAll(); return Object.keys(all).map(function(key) { return all[key]; }); }, /** * Returns actions list as structured menu. If action has label, * it will be splitted by '/' symbol into submenus (for example: * CSS/Reflect Value) and grouped with other items * @param {Array} skipActions List of action identifiers that should be * skipped from menu * @returns {Array} */ getMenu: function(skipActions) { var result = []; skipActions = skipActions || []; this.getList().forEach(function(action) { if (action.options.hidden || ~skipActions.indexOf(action.name)) return; var actionName = humanizeActionName(action.name); var ctx = result; if (action.options.label) { var parts = action.options.label.split('/'); actionName = parts.pop(); // create submenus, if needed var menuName, submenu; while ((menuName = parts.shift())) { submenu = utils.find(ctx, function(item) { return item.type == 'submenu' && item.name == menuName; }); if (!submenu) { submenu = { name: menuName, type: 'submenu', items: [] }; ctx.push(submenu); } ctx = submenu.items; } } ctx.push({ type: 'action', name: action.name, label: actionName }); }); return result; }, /** * Returns action name associated with menu item title * @param {String} title * @returns {String} */ getActionNameForMenuTitle: function(title, menu) { return utils.find(menu || this.getMenu(), function(val) { if (val.type == 'action') { if (val.label == title || val.name == title) { return val.name; } } else { return this.getActionNameForMenuTitle(title, val.items); } }, this); } }; }); },{"../utils/common":"utils\\common.js","./balance":"action\\balance.js","./base64":"action\\base64.js","./editPoints":"action\\editPoints.js","./evaluateMath":"action\\evaluateMath.js","./expandAbbreviation":"action\\expandAbbreviation.js","./incrementDecrement":"action\\incrementDecrement.js","./lineBreaks":"action\\lineBreaks.js","./mergeLines":"action\\mergeLines.js","./reflectCSSValue":"action\\reflectCSSValue.js","./removeTag":"action\\removeTag.js","./selectItem":"action\\selectItem.js","./selectLine":"action\\selectLine.js","./splitJoinTag":"action\\splitJoinTag.js","./toggleComment":"action\\toggleComment.js","./updateImageSize":"action\\updateImageSize.js","./updateTag":"action\\updateTag.js","./wrapWithAbbreviation":"action\\wrapWithAbbreviation.js"}],"action\\mergeLines.js":[function(require,module,exports){ /** * Merges selected lines or lines between XHTML tag pairs * @param {Function} require * @param {Underscore} _ */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var htmlMatcher = require('../assets/htmlMatcher'); var utils = require('../utils/common'); var editorUtils = require('../utils/editor'); var range = require('../assets/range'); return { mergeLinesAction: function(editor) { var info = editorUtils.outputInfo(editor); var selection = range(editor.getSelectionRange()); if (!selection.length()) { // find matching tag var pair = htmlMatcher.find(info.content, editor.getCaretPos()); if (pair) { selection = pair.outerRange; } } if (selection.length()) { // got range, merge lines var text = selection.substring(info.content); var lines = utils.splitByLines(text); for (var i = 1; i < lines.length; i++) { lines[i] = lines[i].replace(/^\s+/, ''); } text = lines.join('').replace(/\s{2,}/, ' '); var textLen = text.length; text = utils.escapeText(text); editor.replaceContent(text, selection.start, selection.end); editor.createSelection(selection.start, selection.start + textLen); return true; } return false; } }; }); },{"../assets/htmlMatcher":"assets\\htmlMatcher.js","../assets/range":"assets\\range.js","../utils/common":"utils\\common.js","../utils/editor":"utils\\editor.js"}],"action\\reflectCSSValue.js":[function(require,module,exports){ /** * Reflect CSS value: takes rule's value under caret and pastes it for the same * rules with vendor prefixes */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var handlerList = require('../assets/handlerList'); var prefs = require('../assets/preferences'); var cssResolver = require('../resolver/css'); var cssEditTree = require('../editTree/css'); var utils = require('../utils/common'); var actionUtils = require('../utils/action'); var editorUtils = require('../utils/editor'); var cssGradient = require('../resolver/cssGradient'); prefs.define('css.reflect.oldIEOpacity', false, 'Support IE6/7/8 opacity notation, e.g. filter:alpha(opacity=...).\ Note that CSS3 and SVG also provides filter property so this option is disabled by default.') /** * @type HandlerList List of registered handlers */ var handlers = handlerList.create(); function doCSSReflection(editor) { var outputInfo = editorUtils.outputInfo(editor); var caretPos = editor.getCaretPos(); var cssRule = cssEditTree.parseFromPosition(outputInfo.content, caretPos); if (!cssRule) return; var property = cssRule.itemFromPosition(caretPos, true); // no property under cursor, nothing to reflect if (!property) return; var oldRule = cssRule.source; var offset = cssRule.options.offset; var caretDelta = caretPos - offset - property.range().start; handlers.exec(false, [property]); if (oldRule !== cssRule.source) { return { data: cssRule.source, start: offset, end: offset + oldRule.length, caret: offset + property.range().start + caretDelta }; } } /** * Returns regexp that should match reflected CSS property names * @param {String} name Current CSS property name * @return {RegExp} */ function getReflectedCSSName(name) { name = cssEditTree.baseName(name); var vendorPrefix = '^(?:\\-\\w+\\-)?', m; if ((name == 'opacity' || name == 'filter') && prefs.get('css.reflect.oldIEOpacity')) { return new RegExp(vendorPrefix + '(?:opacity|filter)$'); } else if ((m = name.match(/^border-radius-(top|bottom)(left|right)/))) { // Mozilla-style border radius return new RegExp(vendorPrefix + '(?:' + name + '|border-' + m[1] + '-' + m[2] + '-radius)$'); } else if ((m = name.match(/^border-(top|bottom)-(left|right)-radius/))) { return new RegExp(vendorPrefix + '(?:' + name + '|border-radius-' + m[1] + m[2] + ')$'); } return new RegExp(vendorPrefix + name + '$'); } /** * Reflects inner CSS properites in given value * agains name‘s vendor prefix. In other words, it tries * to modify `transform 0.2s linear` value for `-webkit-transition` * property * @param {String} name Reciever CSS property name * @param {String} value New property value * @return {String} */ function reflectValueParts(name, value) { // detects and updates vendor-specific properties in value, // e.g. -webkit-transition: -webkit-transform var reVendor = /^\-(\w+)\-/; var propPrefix = reVendor.test(name) ? RegExp.$1.toLowerCase() : ''; var parts = cssEditTree.findParts(value); parts.reverse(); parts.forEach(function(part) { var partValue = part.substring(value).replace(reVendor, ''); var prefixes = cssResolver.vendorPrefixes(partValue); if (prefixes) { // if prefixes are not null then given value can // be resolved against Can I Use database and may or // may not contain prefixed variant if (propPrefix && ~prefixes.indexOf(propPrefix)) { partValue = '-' + propPrefix + '-' + partValue; } value = utils.replaceSubstring(value, partValue, part); } }); return value; } /** * Reflects value from donor into receiver * @param {CSSProperty} donor Donor CSS property from which value should * be reflected * @param {CSSProperty} receiver Property that should receive reflected * value from donor */ function reflectValue(donor, receiver) { var value = getReflectedValue(donor.name(), donor.value(), receiver.name(), receiver.value()); value = reflectValueParts(receiver.name(), value); receiver.value(value); } /** * Returns value that should be reflected for refName CSS property * from curName property. This function is used for special cases, * when the same result must be achieved with different properties for different * browsers. For example: opаcity:0.5; → filter:alpha(opacity=50);

* * This function does value conversion between different CSS properties * * @param {String} curName Current CSS property name * @param {String} curValue Current CSS property value * @param {String} refName Receiver CSS property's name * @param {String} refValue Receiver CSS property's value * @return {String} New value for receiver property */ function getReflectedValue(curName, curValue, refName, refValue) { curName = cssEditTree.baseName(curName); refName = cssEditTree.baseName(refName); if (curName == 'opacity' && refName == 'filter') { return refValue.replace(/opacity=[^)]*/i, 'opacity=' + Math.floor(parseFloat(curValue) * 100)); } else if (curName == 'filter' && refName == 'opacity') { var m = curValue.match(/opacity=([^)]*)/i); return m ? utils.prettifyNumber(parseInt(m[1], 10) / 100) : refValue; } return curValue; } module = module || {}; module.exports = { reflectCSSValueAction: function(editor) { if (editor.getSyntax() != 'css') { return false; } return actionUtils.compoundUpdate(editor, doCSSReflection(editor)); }, _defaultHandler: function(property) { var reName = getReflectedCSSName(property.name()); property.parent.list().forEach(function(p) { if (reName.test(p.name())) { reflectValue(property, p); } }); }, /** * Adds custom reflect handler. The passed function will receive matched * CSS property (as CSSEditElement object) and should * return true if it was performed successfully (handled * reflection), false otherwise. * @param {Function} fn * @param {Object} options */ addHandler: function(fn, options) { handlers.add(fn, options); }, /** * Removes registered handler * @returns */ removeHandler: function(fn) { handlers.remove(fn); } }; // XXX add default handlers handlers.add(module.exports._defaultHandler.bind(module.exports), {order: -1}); handlers.add(cssGradient.reflectValueHandler.bind(cssGradient)); return module.exports; }); },{"../assets/handlerList":"assets\\handlerList.js","../assets/preferences":"assets\\preferences.js","../editTree/css":"editTree\\css.js","../resolver/css":"resolver\\css.js","../resolver/cssGradient":"resolver\\cssGradient.js","../utils/action":"utils\\action.js","../utils/common":"utils\\common.js","../utils/editor":"utils\\editor.js"}],"action\\removeTag.js":[function(require,module,exports){ /** * Gracefully removes tag under cursor */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var utils = require('../utils/common'); var editorUtils = require('../utils/editor'); var htmlMatcher = require('../assets/htmlMatcher'); return { removeTagAction: function(editor) { var info = editorUtils.outputInfo(editor); // search for tag var tag = htmlMatcher.tag(info.content, editor.getCaretPos()); if (tag) { if (!tag.close) { // simply remove unary tag editor.replaceContent(utils.getCaretPlaceholder(), tag.range.start, tag.range.end); } else { // remove tag and its newlines /** @type Range */ var tagContentRange = utils.narrowToNonSpace(info.content, tag.innerRange); /** @type Range */ var startLineBounds = utils.findNewlineBounds(info.content, tagContentRange.start); var startLinePad = utils.getLinePadding(startLineBounds.substring(info.content)); var tagContent = tagContentRange.substring(info.content); tagContent = utils.unindentString(tagContent, startLinePad); editor.replaceContent(utils.getCaretPlaceholder() + utils.escapeText(tagContent), tag.outerRange.start, tag.outerRange.end); } return true; } return false; } }; }); },{"../assets/htmlMatcher":"assets\\htmlMatcher.js","../utils/common":"utils\\common.js","../utils/editor":"utils\\editor.js"}],"action\\selectItem.js":[function(require,module,exports){ /** * Actions that use stream parsers and tokenizers for traversing: * -- Search for next/previous items in HTML * -- Search for next/previous items in CSS */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var range = require('../assets/range'); var utils = require('../utils/common'); var editorUtils = require('../utils/editor'); var actionUtils = require('../utils/action'); var stringStream = require('../assets/stringStream'); var xmlParser = require('../parser/xml'); var cssEditTree = require('../editTree/css'); var cssSections = require('../utils/cssSections'); var startTag = /^<([\w\:\-]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/; /** * Generic function for searching for items to select * @param {IEmmetEditor} editor * @param {Boolean} isBackward Search backward (search forward otherwise) * @param {Function} extractFn Function that extracts item content * @param {Function} rangeFn Function that search for next token range */ function findItem(editor, isBackward, extractFn, rangeFn) { var content = editorUtils.outputInfo(editor).content; var contentLength = content.length; var itemRange, rng; /** @type Range */ var prevRange = range(-1, 0); /** @type Range */ var sel = range(editor.getSelectionRange()); var searchPos = sel.start, loop = 100000; // endless loop protection while (searchPos >= 0 && searchPos < contentLength && --loop > 0) { if ( (itemRange = extractFn(content, searchPos, isBackward)) ) { if (prevRange.equal(itemRange)) { break; } prevRange = itemRange.clone(); rng = rangeFn(itemRange.substring(content), itemRange.start, sel.clone()); if (rng) { editor.createSelection(rng.start, rng.end); return true; } else { searchPos = isBackward ? itemRange.start : itemRange.end - 1; } } searchPos += isBackward ? -1 : 1; } return false; } // XXX HTML section /** * Find next HTML item * @param {IEmmetEditor} editor */ function findNextHTMLItem(editor) { var isFirst = true; return findItem(editor, false, function(content, searchPos){ if (isFirst) { isFirst = false; return findOpeningTagFromPosition(content, searchPos); } else { return getOpeningTagFromPosition(content, searchPos); } }, function(tag, offset, selRange) { return getRangeForHTMLItem(tag, offset, selRange, false); }); } /** * Find previous HTML item * @param {IEmmetEditor} editor */ function findPrevHTMLItem(editor) { return findItem(editor, true, getOpeningTagFromPosition, function (tag, offset, selRange) { return getRangeForHTMLItem(tag, offset, selRange, true); }); } /** * Creates possible selection ranges for HTML tag * @param {String} source Original HTML source for tokens * @param {Array} tokens List of HTML tokens * @returns {Array} */ function makePossibleRangesHTML(source, tokens, offset) { offset = offset || 0; var result = []; var attrStart = -1, attrName = '', attrValue = '', attrValueRange, tagName; tokens.forEach(function(tok) { switch (tok.type) { case 'tag': tagName = source.substring(tok.start, tok.end); if (/^<[\w\:\-]/.test(tagName)) { // add tag name result.push(range({ start: tok.start + 1, end: tok.end })); } break; case 'attribute': attrStart = tok.start; attrName = source.substring(tok.start, tok.end); break; case 'string': // attribute value // push full attribute first result.push(range(attrStart, tok.end - attrStart)); attrValueRange = range(tok); attrValue = attrValueRange.substring(source); // is this a quoted attribute? if (isQuote(attrValue.charAt(0))) attrValueRange.start++; if (isQuote(attrValue.charAt(attrValue.length - 1))) attrValueRange.end--; result.push(attrValueRange); if (attrName == 'class') { result = result.concat(classNameRanges(attrValueRange.substring(source), attrValueRange.start)); } break; } }); // offset ranges result = result.filter(function(item) { if (item.length()) { item.shift(offset); return true; } }); // remove duplicates return utils.unique(result, function(item) { return item.toString(); }); } /** * Returns ranges of class names in "class" attribute value * @param {String} className * @returns {Array} */ function classNameRanges(className, offset) { offset = offset || 0; var result = []; /** @type StringStream */ var stream = stringStream.create(className); // skip whitespace stream.eatSpace(); stream.start = stream.pos; var ch; while ((ch = stream.next())) { if (/[\s\u00a0]/.test(ch)) { result.push(range(stream.start + offset, stream.pos - stream.start - 1)); stream.eatSpace(); stream.start = stream.pos; } } result.push(range(stream.start + offset, stream.pos - stream.start)); return result; } /** * Returns best HTML tag range match for current selection * @param {String} tag Tag declaration * @param {Number} offset Tag's position index inside content * @param {Range} selRange Selection range * @return {Range} Returns range if next item was found, null otherwise */ function getRangeForHTMLItem(tag, offset, selRange, isBackward) { var ranges = makePossibleRangesHTML(tag, xmlParser.parse(tag), offset); if (isBackward) ranges.reverse(); // try to find selected range var curRange = utils.find(ranges, function(r) { return r.equal(selRange); }); if (curRange) { var ix = ranges.indexOf(curRange); if (ix < ranges.length - 1) return ranges[ix + 1]; return null; } // no selected range, find nearest one if (isBackward) // search backward return utils.find(ranges, function(r) { return r.start < selRange.start; }); // search forward // to deal with overlapping ranges (like full attribute definition // and attribute value) let's find range under caret first if (!curRange) { var matchedRanges = ranges.filter(function(r) { return r.inside(selRange.end); }); if (matchedRanges.length > 1) return matchedRanges[1]; } return utils.find(ranges, function(r) { return r.end > selRange.end; }); } /** * Search for opening tag in content, starting at specified position * @param {String} html Where to search tag * @param {Number} pos Character index where to start searching * @return {Range} Returns range if valid opening tag was found, * null otherwise */ function findOpeningTagFromPosition(html, pos) { var tag; while (pos >= 0) { if ((tag = getOpeningTagFromPosition(html, pos))) return tag; pos--; } return null; } /** * @param {String} html Where to search tag * @param {Number} pos Character index where to start searching * @return {Range} Returns range if valid opening tag was found, * null otherwise */ function getOpeningTagFromPosition(html, pos) { var m; if (html.charAt(pos) == '<' && (m = html.substring(pos, html.length).match(startTag))) { return range(pos, m[0]); } } function isQuote(ch) { return ch == '"' || ch == "'"; } /** * Returns all ranges inside given rule, available for selection * @param {CSSEditContainer} rule * @return {Array} */ function findInnerRanges(rule) { // rule selector var ranges = [rule.nameRange(true)]; // find nested sections, keep selectors only var nestedSections = cssSections.nestedSectionsInRule(rule); nestedSections.forEach(function(section) { ranges.push(range.create2(section.start, section._selectorEnd)); }); // add full property ranges and values rule.list().forEach(function(property) { ranges = ranges.concat(makePossibleRangesCSS(property)); }); ranges = range.sort(ranges); // optimize result: remove empty ranges and duplicates ranges = ranges.filter(function(item) { return !!item.length(); }); return utils.unique(ranges, function(item) { return item.toString(); }); } /** * Makes all possible selection ranges for specified CSS property * @param {CSSProperty} property * @returns {Array} */ function makePossibleRangesCSS(property) { // find all possible ranges, sorted by position and size var valueRange = property.valueRange(true); var result = [property.range(true), valueRange]; // locate parts of complex values. // some examples: // – 1px solid red: 3 parts // – arial, sans-serif: enumeration, 2 parts // – url(image.png): function value part var value = property.value(); property.valueParts().forEach(function(r) { // add absolute range var clone = r.clone(); result.push(clone.shift(valueRange.start)); /** @type StringStream */ var stream = stringStream.create(r.substring(value)); if (stream.match(/^[\w\-]+\(/, true)) { // we have a function, find values in it. // but first add function contents stream.start = stream.pos; stream.backUp(1); stream.skipToPair('(', ')'); stream.backUp(1); var fnBody = stream.current(); result.push(range(clone.start + stream.start, fnBody)); // find parts cssEditTree.findParts(fnBody).forEach(function(part) { result.push(range(clone.start + stream.start + part.start, part.substring(fnBody))); }); } }); return result; } /** * Tries to find matched CSS property and nearest range for selection * @param {CSSRule} rule * @param {Range} selRange * @param {Boolean} isBackward * @returns {Range} */ function matchedRangeForCSSProperty(rule, selRange, isBackward) { var ranges = findInnerRanges(rule); if (isBackward) { ranges.reverse(); } // return next to selected range, if possible var r = utils.find(ranges, function(item) { return item.equal(selRange); }); if (r) { return ranges[ranges.indexOf(r) + 1]; } // find matched and (possibly) overlapping ranges var nested = ranges.filter(function(item) { return item.inside(selRange.end); }); if (nested.length) { return nested.sort(function(a, b) { return a.length() - b.length(); })[0]; } // return range next to caret var test = r = utils.find(ranges, isBackward ? function(item) {return item.end < selRange.start;} : function(item) {return item.end > selRange.start;} ); if (!r) { // can’t find anything, just pick first one r = ranges[0]; } return r; } function findNextCSSItem(editor) { return findItem(editor, false, cssSections.locateRule.bind(cssSections), getRangeForNextItemInCSS); } function findPrevCSSItem(editor) { return findItem(editor, true, cssSections.locateRule.bind(cssSections), getRangeForPrevItemInCSS); } /** * Returns range for item to be selected in CSS after current caret * (selection) position * @param {String} rule CSS rule declaration * @param {Number} offset Rule's position index inside content * @param {Range} selRange Selection range * @return {Range} Returns range if next item was found, null otherwise */ function getRangeForNextItemInCSS(rule, offset, selRange) { var tree = cssEditTree.parse(rule, { offset: offset }); return matchedRangeForCSSProperty(tree, selRange, false); } /** * Returns range for item to be selected in CSS before current caret * (selection) position * @param {String} rule CSS rule declaration * @param {Number} offset Rule's position index inside content * @param {Range} selRange Selection range * @return {Range} Returns range if previous item was found, null otherwise */ function getRangeForPrevItemInCSS(rule, offset, selRange) { var tree = cssEditTree.parse(rule, { offset: offset }); return matchedRangeForCSSProperty(tree, selRange, true); } return { selectNextItemAction: function(editor) { if (actionUtils.isSupportedCSS(editor.getSyntax())) { return findNextCSSItem(editor); } else { return findNextHTMLItem(editor); } }, selectPreviousItemAction: function(editor) { if (actionUtils.isSupportedCSS(editor.getSyntax())) { return findPrevCSSItem(editor); } else { return findPrevHTMLItem(editor); } } }; }); },{"../assets/range":"assets\\range.js","../assets/stringStream":"assets\\stringStream.js","../editTree/css":"editTree\\css.js","../parser/xml":"parser\\xml.js","../utils/action":"utils\\action.js","../utils/common":"utils\\common.js","../utils/cssSections":"utils\\cssSections.js","../utils/editor":"utils\\editor.js"}],"action\\selectLine.js":[function(require,module,exports){ /** * Select current line (for simple editors like browser's <textarea>) */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { return { selectLineAction: function(editor) { var range = editor.getCurrentLineRange(); editor.createSelection(range.start, range.end); return true; } }; }); },{}],"action\\splitJoinTag.js":[function(require,module,exports){ /** * Splits or joins tag, e.g. transforms it into a short notation and vice versa:
* <div></div> → <div /> : join
* <div /> → <div></div> : split */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var utils = require('../utils/common'); var resources = require('../assets/resources'); var matcher = require('../assets/htmlMatcher'); var editorUtils = require('../utils/editor'); var profile = require('../assets/profile'); /** * @param {IEmmetEditor} editor * @param {Object} profile * @param {Object} tag */ function joinTag(editor, profile, tag) { // empty closing slash is a nonsense for this action var slash = profile.selfClosing() || ' /'; var content = tag.open.range.substring(tag.source).replace(/\s*>$/, slash + '>'); var caretPos = editor.getCaretPos(); // update caret position if (content.length + tag.outerRange.start < caretPos) { caretPos = content.length + tag.outerRange.start; } content = utils.escapeText(content); editor.replaceContent(content, tag.outerRange.start, tag.outerRange.end); editor.setCaretPos(caretPos); return true; } function splitTag(editor, profile, tag) { var caretPos = editor.getCaretPos(); // define tag content depending on profile var tagContent = (profile.tag_nl === true) ? '\n\t\n' : ''; var content = tag.outerContent().replace(/\s*\/>$/, '>'); caretPos = tag.outerRange.start + content.length; content += tagContent + ''; content = utils.escapeText(content); editor.replaceContent(content, tag.outerRange.start, tag.outerRange.end); editor.setCaretPos(caretPos); return true; } return { splitJoinTagAction: function(editor, profileName) { var info = editorUtils.outputInfo(editor, null, profileName); var curProfile = profile.get(info.profile); // find tag at current position var tag = matcher.tag(info.content, editor.getCaretPos()); if (tag) { return tag.close ? joinTag(editor, curProfile, tag) : splitTag(editor, curProfile, tag); } return false; } }; }); },{"../assets/htmlMatcher":"assets\\htmlMatcher.js","../assets/profile":"assets\\profile.js","../assets/resources":"assets\\resources.js","../utils/common":"utils\\common.js","../utils/editor":"utils\\editor.js"}],"action\\toggleComment.js":[function(require,module,exports){ /** * Toggles HTML and CSS comments depending on current caret context. Unlike * the same action in most editors, this action toggles comment on currently * matched item—HTML tag or CSS selector—when nothing is selected. */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var prefs = require('../assets/preferences'); var range = require('../assets/range'); var utils = require('../utils/common'); var actionUtils = require('../utils/action'); var editorUtils = require('../utils/editor'); var htmlMatcher = require('../assets/htmlMatcher'); var cssEditTree = require('../editTree/css'); /** * Toggle HTML comment on current selection or tag * @param {IEmmetEditor} editor * @return {Boolean} Returns true if comment was toggled */ function toggleHTMLComment(editor) { /** @type Range */ var r = range(editor.getSelectionRange()); var info = editorUtils.outputInfo(editor); if (!r.length()) { // no selection, find matching tag var tag = htmlMatcher.tag(info.content, editor.getCaretPos()); if (tag) { // found pair r = tag.outerRange; } } return genericCommentToggle(editor, '', r); } /** * Simple CSS commenting * @param {IEmmetEditor} editor * @return {Boolean} Returns true if comment was toggled */ function toggleCSSComment(editor) { /** @type Range */ var rng = range(editor.getSelectionRange()); var info = editorUtils.outputInfo(editor); if (!rng.length()) { // no selection, try to get current rule /** @type CSSRule */ var rule = cssEditTree.parseFromPosition(info.content, editor.getCaretPos()); if (rule) { var property = cssItemFromPosition(rule, editor.getCaretPos()); rng = property ? property.range(true) : range(rule.nameRange(true).start, rule.source); } } if (!rng.length()) { // still no selection, get current line rng = range(editor.getCurrentLineRange()); utils.narrowToNonSpace(info.content, rng); } return genericCommentToggle(editor, '/*', '*/', rng); } /** * Returns CSS property from rule that matches passed position * @param {EditContainer} rule * @param {Number} absPos * @returns {EditElement} */ function cssItemFromPosition(rule, absPos) { // do not use default EditContainer.itemFromPosition() here, because // we need to make a few assumptions to make CSS commenting more reliable var relPos = absPos - (rule.options.offset || 0); var reSafeChar = /^[\s\n\r]/; return utils.find(rule.list(), function(item) { if (item.range().end === relPos) { // at the end of property, but outside of it // if there’s a space character at current position, // use current property return reSafeChar.test(rule.source.charAt(relPos)); } return item.range().inside(relPos); }); } /** * Search for nearest comment in str, starting from index from * @param {String} text Where to search * @param {Number} from Search start index * @param {String} start_token Comment start string * @param {String} end_token Comment end string * @return {Range} Returns null if comment wasn't found */ function searchComment(text, from, startToken, endToken) { var commentStart = -1; var commentEnd = -1; var hasMatch = function(str, start) { return text.substr(start, str.length) == str; }; // search for comment start while (from--) { if (hasMatch(startToken, from)) { commentStart = from; break; } } if (commentStart != -1) { // search for comment end from = commentStart; var contentLen = text.length; while (contentLen >= from++) { if (hasMatch(endToken, from)) { commentEnd = from + endToken.length; break; } } } return (commentStart != -1 && commentEnd != -1) ? range(commentStart, commentEnd - commentStart) : null; } /** * Generic comment toggling routine * @param {IEmmetEditor} editor * @param {String} commentStart Comment start token * @param {String} commentEnd Comment end token * @param {Range} range Selection range * @return {Boolean} */ function genericCommentToggle(editor, commentStart, commentEnd, range) { var content = editorUtils.outputInfo(editor).content; var caretPos = editor.getCaretPos(); var newContent = null; /** * Remove comment markers from string * @param {Sting} str * @return {String} */ function removeComment(str) { return str .replace(new RegExp('^' + utils.escapeForRegexp(commentStart) + '\\s*'), function(str){ caretPos -= str.length; return ''; }).replace(new RegExp('\\s*' + utils.escapeForRegexp(commentEnd) + '$'), ''); } // first, we need to make sure that this substring is not inside // comment var commentRange = searchComment(content, caretPos, commentStart, commentEnd); if (commentRange && commentRange.overlap(range)) { // we're inside comment, remove it range = commentRange; newContent = removeComment(range.substring(content)); } else { // should add comment // make sure that there's no comment inside selection newContent = commentStart + ' ' + range.substring(content) .replace(new RegExp(utils.escapeForRegexp(commentStart) + '\\s*|\\s*' + utils.escapeForRegexp(commentEnd), 'g'), '') + ' ' + commentEnd; // adjust caret position caretPos += commentStart.length + 1; } // replace editor content if (newContent !== null) { newContent = utils.escapeText(newContent); editor.setCaretPos(range.start); editor.replaceContent(editorUtils.unindent(editor, newContent), range.start, range.end); editor.setCaretPos(caretPos); return true; } return false; } return { /** * Toggle comment on current editor's selection or HTML tag/CSS rule * @param {IEmmetEditor} editor */ toggleCommentAction: function(editor) { var info = editorUtils.outputInfo(editor); if (actionUtils.isSupportedCSS(info.syntax)) { // in case our editor is good enough and can recognize syntax from // current token, we have to make sure that cursor is not inside // 'style' attribute of html element var caretPos = editor.getCaretPos(); var tag = htmlMatcher.tag(info.content, caretPos); if (tag && tag.open.range.inside(caretPos)) { info.syntax = 'html'; } } var cssSyntaxes = prefs.getArray('css.syntaxes'); if (~cssSyntaxes.indexOf(info.syntax)) { return toggleCSSComment(editor); } return toggleHTMLComment(editor); } }; }); },{"../assets/htmlMatcher":"assets\\htmlMatcher.js","../assets/preferences":"assets\\preferences.js","../assets/range":"assets\\range.js","../editTree/css":"editTree\\css.js","../utils/action":"utils\\action.js","../utils/common":"utils\\common.js","../utils/editor":"utils\\editor.js"}],"action\\updateImageSize.js":[function(require,module,exports){ /** * Automatically updates image size attributes in HTML's <img> element or * CSS rule */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var utils = require('../utils/common'); var editorUtils = require('../utils/editor'); var actionUtils = require('../utils/action'); var xmlEditTree = require('../editTree/xml'); var cssEditTree = require('../editTree/css'); var base64 = require('../utils/base64'); var file = require('../plugin/file'); /** * Updates image size of <img src=""> tag * @param {IEmmetEditor} editor */ function updateImageSizeHTML(editor) { var offset = editor.getCaretPos(); // find tag from current caret position var info = editorUtils.outputInfo(editor); var xmlElem = xmlEditTree.parseFromPosition(info.content, offset, true); if (xmlElem && (xmlElem.name() || '').toLowerCase() == 'img') { getImageSizeForSource(editor, xmlElem.value('src'), function(size) { if (size) { var compoundData = xmlElem.range(true); xmlElem.value('width', size.width); xmlElem.value('height', size.height, xmlElem.indexOf('width') + 1); actionUtils.compoundUpdate(editor, utils.extend(compoundData, { data: xmlElem.toString(), caret: offset })); } }); } } /** * Updates image size of CSS property * @param {IEmmetEditor} editor */ function updateImageSizeCSS(editor) { var offset = editor.getCaretPos(); // find tag from current caret position var info = editorUtils.outputInfo(editor); var cssRule = cssEditTree.parseFromPosition(info.content, offset, true); if (cssRule) { // check if there is property with image under caret var prop = cssRule.itemFromPosition(offset, true), m; if (prop && (m = /url\((["']?)(.+?)\1\)/i.exec(prop.value() || ''))) { getImageSizeForSource(editor, m[2], function(size) { if (size) { var compoundData = cssRule.range(true); cssRule.value('width', size.width + 'px'); cssRule.value('height', size.height + 'px', cssRule.indexOf('width') + 1); actionUtils.compoundUpdate(editor, utils.extend(compoundData, { data: cssRule.toString(), caret: offset })); } }); } } } /** * Returns image dimensions for source * @param {IEmmetEditor} editor * @param {String} src Image source (path or data:url) */ function getImageSizeForSource(editor, src, callback) { var fileContent; if (src) { // check if it is data:url if (/^data:/.test(src)) { fileContent = base64.decode( src.replace(/^data\:.+?;.+?,/, '') ); return callback(actionUtils.getImageSize(fileContent)); } var filePath = editor.getFilePath(); file.locateFile(filePath, src, function(absPath) { if (absPath === null) { throw "Can't find " + src + ' file'; } file.read(absPath, function(err, content) { if (err) { throw 'Unable to read ' + absPath + ': ' + err; } content = String(content); callback(actionUtils.getImageSize(content)); }); }); } } return { updateImageSizeAction: function(editor) { // this action will definitely won’t work in SASS dialect, // but may work in SCSS or LESS if (actionUtils.isSupportedCSS(editor.getSyntax())) { updateImageSizeCSS(editor); } else { updateImageSizeHTML(editor); } return true; } }; }); },{"../editTree/css":"editTree\\css.js","../editTree/xml":"editTree\\xml.js","../plugin/file":"plugin\\file.js","../utils/action":"utils\\action.js","../utils/base64":"utils\\base64.js","../utils/common":"utils\\common.js","../utils/editor":"utils\\editor.js"}],"action\\updateTag.js":[function(require,module,exports){ /** * Update Tag action: allows users to update existing HTML tags and add/remove * attributes or even tag name */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var xmlEditTree = require('../editTree/xml'); var editorUtils = require('../utils/editor'); var actionUtils = require('../utils/action'); var utils = require('../utils/common'); var parser = require('../parser/abbreviation'); function updateAttributes(tag, abbrNode, ix) { var classNames = (abbrNode.attribute('class') || '').split(/\s+/g); if (ix) { classNames.push('+' + abbrNode.name()); } var r = function(str) { return utils.replaceCounter(str, abbrNode.counter); }; // update class classNames.forEach(function(className) { if (!className) { return; } className = r(className); var ch = className.charAt(0); if (ch == '+') { tag.addClass(className.substr(1)); } else if (ch == '-') { tag.removeClass(className.substr(1)); } else { tag.value('class', className); } }); // update attributes abbrNode.attributeList().forEach(function(attr) { if (attr.name.toLowerCase() == 'class') { return; } var ch = attr.name.charAt(0); if (ch == '+') { var attrName = attr.name.substr(1); var tagAttr = tag.get(attrName); if (tagAttr) { tagAttr.value(tagAttr.value() + r(attr.value)); } else { tag.value(attrName, r(attr.value)); } } else if (ch == '-') { tag.remove(attr.name.substr(1)); } else { tag.value(attr.name, r(attr.value)); } }); } return { /** * Matches HTML tag under caret and updates its definition * according to given abbreviation * @param {IEmmetEditor} Editor instance * @param {String} abbr Abbreviation to update with */ updateTagAction: function(editor, abbr) { abbr = abbr || editor.prompt("Enter abbreviation"); if (!abbr) { return false; } var content = editor.getContent(); var ctx = actionUtils.captureContext(editor); var tag = this.getUpdatedTag(abbr, ctx, content); if (!tag) { // nothing to update return false; } // check if tag name was updated if (tag.name() != ctx.name && ctx.match.close) { editor.replaceContent('', ctx.match.close.range.start, ctx.match.close.range.end, true); } editor.replaceContent(tag.source, ctx.match.open.range.start, ctx.match.open.range.end, true); return true; }, /** * Returns XMLEditContainer node with updated tag structure * of existing tag context. * This data can be used to modify existing tag * @param {String} abbr Abbreviation * @param {Object} ctx Tag to be updated (captured with `htmlMatcher`) * @param {String} content Original editor content * @return {XMLEditContainer} */ getUpdatedTag: function(abbr, ctx, content, options) { if (!ctx) { // nothing to update return null; } var tree = parser.parse(abbr, options || {}); // for this action some characters in abbreviation has special // meaning. For example, `.-c2` means “remove `c2` class from // element” and `.+c3` means “append class `c3` to exising one. // // But `.+c3` abbreviation will actually produce two elements: //
and . Thus, we have to walk on each element // of parsed tree and use their definitions to update current element var tag = xmlEditTree.parse(ctx.match.open.range.substring(content), { offset: ctx.match.outerRange.start }); tree.children.forEach(function(node, i) { updateAttributes(tag, node, i); }); // if tag name was resolved by implicit tag name resolver, // then user omitted it in abbreviation and wants to keep // original tag name var el = tree.children[0]; if (!el.data('nameResolved')) { tag.name(el.name()); } return tag; } }; }); },{"../editTree/xml":"editTree\\xml.js","../parser/abbreviation":"parser\\abbreviation.js","../utils/action":"utils\\action.js","../utils/common":"utils\\common.js","../utils/editor":"utils\\editor.js"}],"action\\wrapWithAbbreviation.js":[function(require,module,exports){ /** * Action that wraps content with abbreviation. For convenience, action is * defined as reusable module */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var range = require('../assets/range'); var htmlMatcher = require('../assets/htmlMatcher'); var utils = require('../utils/common'); var editorUtils = require('../utils/editor'); var actionUtils = require('../utils/action'); var parser = require('../parser/abbreviation'); return { /** * Wraps content with abbreviation * @param {IEmmetEditor} Editor instance * @param {String} abbr Abbreviation to wrap with * @param {String} syntax Syntax type (html, css, etc.) * @param {String} profile Output profile name (html, xml, xhtml) */ wrapWithAbbreviationAction: function(editor, abbr, syntax, profile) { var info = editorUtils.outputInfo(editor, syntax, profile); abbr = abbr || editor.prompt("Enter abbreviation"); if (!abbr) { return null; } abbr = String(abbr); var r = range(editor.getSelectionRange()); if (!r.length()) { // no selection, find tag pair var match = htmlMatcher.tag(info.content, r.start); if (!match) { // nothing to wrap return false; } r = utils.narrowToNonSpace(info.content, match.range); } var newContent = utils.escapeText(r.substring(info.content)); var result = parser.expand(abbr, { pastedContent: editorUtils.unindent(editor, newContent), syntax: info.syntax, profile: info.profile, contextNode: actionUtils.captureContext(editor) }); if (result) { editor.replaceContent(result, r.start, r.end); return true; } return false; } }; }); },{"../assets/htmlMatcher":"assets\\htmlMatcher.js","../assets/range":"assets\\range.js","../parser/abbreviation":"parser\\abbreviation.js","../utils/action":"utils\\action.js","../utils/common":"utils\\common.js","../utils/editor":"utils\\editor.js"}],"assets\\caniuse.js":[function(require,module,exports){ /** * Parsed resources (snippets, abbreviations, variables, etc.) for Emmet. * Contains convenient method to get access for snippets with respect of * inheritance. Also provides ability to store data in different vocabularies * ('system' and 'user') for fast and safe resource update * @author Sergey Chikuyonok (serge.che@gmail.com) * @link http://chikuyonok.ru */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var prefs = require('./preferences'); var utils = require('../utils/common'); prefs.define('caniuse.enabled', true, 'Enable support of Can I Use database. When enabled,\ CSS abbreviation resolver will look at Can I Use database first before detecting\ CSS properties that should be resolved'); prefs.define('caniuse.vendors', 'all', 'A comma-separated list vendor identifiers\ (as described in Can I Use database) that should be supported\ when resolving vendor-prefixed properties. Set value to all\ to support all available properties'); prefs.define('caniuse.era', 'e-2', 'Browser era, as defined in Can I Use database.\ Examples: e0 (current version), e1 (near future)\ e-2 (2 versions back) and so on.'); var cssSections = { 'border-image': ['border-image'], 'css-boxshadow': ['box-shadow'], 'css3-boxsizing': ['box-sizing'], 'multicolumn': ['column-width', 'column-count', 'columns', 'column-gap', 'column-rule-color', 'column-rule-style', 'column-rule-width', 'column-rule', 'column-span', 'column-fill'], 'border-radius': ['border-radius', 'border-top-left-radius', 'border-top-right-radius', 'border-bottom-right-radius', 'border-bottom-left-radius'], 'transforms2d': ['transform'], 'css-hyphens': ['hyphens'], 'css-transitions': ['transition', 'transition-property', 'transition-duration', 'transition-timing-function', 'transition-delay'], 'font-feature': ['font-feature-settings'], 'css-animation': ['animation', 'animation-name', 'animation-duration', 'animation-timing-function', 'animation-iteration-count', 'animation-direction', 'animation-play-state', 'animation-delay', 'animation-fill-mode', '@keyframes'], 'css-gradients': ['linear-gradient'], 'css-masks': ['mask-image', 'mask-source-type', 'mask-repeat', 'mask-position', 'mask-clip', 'mask-origin', 'mask-size', 'mask', 'mask-type', 'mask-box-image-source', 'mask-box-image-slice', 'mask-box-image-width', 'mask-box-image-outset', 'mask-box-image-repeat', 'mask-box-image', 'clip-path', 'clip-rule'], 'css-featurequeries': ['@supports'], 'flexbox': ['flex', 'inline-flex', 'flex-direction', 'flex-wrap', 'flex-flow', 'order', 'flex'], 'calc': ['calc'], 'object-fit': ['object-fit', 'object-position'], 'css-grid': ['grid', 'inline-grid', 'grid-template-rows', 'grid-template-columns', 'grid-template-areas', 'grid-template', 'grid-auto-rows', 'grid-auto-columns', ' grid-auto-flow', 'grid-auto-position', 'grid', ' grid-row-start', 'grid-column-start', 'grid-row-end', 'grid-column-end', 'grid-column', 'grid-row', 'grid-area', 'justify-self', 'justify-items', 'align-self', 'align-items'], 'css-repeating-gradients': ['repeating-linear-gradient'], 'css-filters': ['filter'], 'user-select-none': ['user-select'], 'intrinsic-width': ['min-content', 'max-content', 'fit-content', 'fill-available'], 'css3-tabsize': ['tab-size'] }; /** @type {Object} The Can I Use database for CSS */ var cssDB = null; /** @type {Object} A list of available vendors (browsers) and their prefixes */ var vendorsDB = null; var erasDB = null; function intersection(arr1, arr2) { var result = []; var smaller = arr1, larger = arr2; if (smaller.length > larger.length) { smaller = arr2; larger = arr1; } larger.forEach(function(item) { if (~smaller.indexOf(item)) { result.push(item); } }); return result; } /** * Parses raw Can I Use database for better lookups * @param {String} data Raw database * @param {Boolean} optimized Pass `true` if given `data` is already optimized * @return {Object} */ function parseDB(data, optimized) { if (typeof data == 'string') { data = JSON.parse(data); } if (!optimized) { data = optimize(data); } vendorsDB = data.vendors; cssDB = data.css; erasDB = data.era; } /** * Extract required data only from CIU database * @param {Object} data Raw Can I Use database * @return {Object} Optimized database */ function optimize(data) { if (typeof data == 'string') { data = JSON.parse(data); } return { vendors: parseVendors(data), css: parseCSS(data), era: parseEra(data) }; } /** * Parses vendor data * @param {Object} data * @return {Object} */ function parseVendors(data) { var out = {}; Object.keys(data.agents).forEach(function(name) { var agent = data.agents[name]; out[name] = { prefix: agent.prefix, versions: agent.versions }; }); return out; } /** * Parses CSS data from Can I Use raw database * @param {Object} data * @return {Object} */ function parseCSS(data) { var out = {}; var cssCategories = data.cats.CSS; Object.keys(data.data).forEach(function(name) { var section = data.data[name]; if (name in cssSections) { cssSections[name].forEach(function(kw) { out[kw] = section.stats; }); } }); return out; } /** * Parses era data from Can I Use raw database * @param {Object} data * @return {Array} */ function parseEra(data) { // some runtimes (like Mozilla Rhino) does not preserves // key order so we have to sort values manually return Object.keys(data.eras).sort(function(a, b) { return parseInt(a.substr(1)) - parseInt(b.substr(1)); }); } /** * Returs list of supported vendors, depending on user preferences * @return {Array} */ function getVendorsList() { var allVendors = Object.keys(vendorsDB); var vendors = prefs.getArray('caniuse.vendors'); if (!vendors || vendors[0] == 'all') { return allVendors; } return intersection(allVendors, vendors); } /** * Returns size of version slice as defined by era identifier * @return {Number} */ function getVersionSlice() { var era = prefs.get('caniuse.era'); var ix = erasDB.indexOf(era); if (!~ix) { ix = erasDB.indexOf('e-2'); } return ix; } // try to load caniuse database // hide it from Require.JS parser var db = null; (function(r) { if (typeof define === 'undefined' || !define.amd) { try { db = r('caniuse-db/data.json'); } catch(e) {} } })(require); if (db) { parseDB(db); } return { load: parseDB, optimize: optimize, /** * Resolves prefixes for given property * @param {String} property A property to resolve. It can start with `@` symbol * (CSS section, like `@keyframes`) or `:` (CSS value, like `flex`) * @return {Array} Array of resolved prefixes or null * if prefixes can't be resolved. Empty array means property has no vendor * prefixes */ resolvePrefixes: function(property) { if (!prefs.get('caniuse.enabled') || !cssDB || !(property in cssDB)) { return null; } var prefixes = []; var propStats = cssDB[property]; var versions = getVersionSlice(); getVendorsList().forEach(function(vendor) { var vendorVesions = vendorsDB[vendor].versions.slice(versions); for (var i = 0, v; i < vendorVesions.length; i++) { v = vendorVesions[i]; if (!v) { continue; } if (~propStats[vendor][v].indexOf('x')) { prefixes.push(vendorsDB[vendor].prefix); break; } } }); return utils.unique(prefixes).sort(function(a, b) { return b.length - a.length; }); } }; }); },{"../utils/common":"utils\\common.js","./preferences":"assets\\preferences.js"}],"assets\\elements.js":[function(require,module,exports){ /** * Module that contains factories for element types used by Emmet */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var factories = {}; var reAttrs = /([@\!]?)([\w\-:]+)\s*=\s*(['"])(.*?)\3/g; // register resource references function commonFactory(value) { return {data: value}; } module = module || {}; module.exports = { /** * Create new element factory * @param {String} name Element identifier * @param {Function} factory Function that produces element of specified * type. The object generated by this factory is automatically * augmented with type property pointing to element * name * @memberOf elements */ add: function(name, factory) { var that = this; factories[name] = function() { var elem = factory.apply(that, arguments); if (elem) elem.type = name; return elem; }; }, /** * Returns factory for specified name * @param {String} name * @returns {Function} */ get: function(name) { return factories[name]; }, /** * Creates new element with specified type * @param {String} name * @returns {Object} */ create: function(name) { var args = [].slice.call(arguments, 1); var factory = this.get(name); return factory ? factory.apply(this, args) : null; }, /** * Check if passed element is of specified type * @param {Object} elem * @param {String} type * @returns {Boolean} */ is: function(elem, type) { return this.type(elem) === type; }, /** * Returns type of element * @param {Object} elem * @return {String} */ type: function(elem) { return elem && elem.type; } }; /** * Element factory * @param {String} elementName Name of output element * @param {String} attrs Attributes definition. You may also pass * Array where each contains object with name * and value properties, or Object * @param {Boolean} isEmpty Is expanded element should be empty */ module.exports.add('element', function(elementName, attrs, isEmpty) { var ret = { name: elementName, is_empty: !!isEmpty }; if (attrs) { ret.attributes = []; if (Array.isArray(attrs)) { ret.attributes = attrs; } else if (typeof attrs === 'string') { var m; while ((m = reAttrs.exec(attrs))) { ret.attributes.push({ name: m[2], value: m[4], isDefault: m[1] == '@', isImplied: m[1] == '!' }); } } else { ret.attributes = Object.keys(attrs).map(function(name) { return { name: name, value: attrs[name] }; }); } } return ret; }); module.exports.add('snippet', commonFactory); module.exports.add('reference', commonFactory); module.exports.add('empty', function() { return {}; }); return module.exports; }); },{}],"assets\\handlerList.js":[function(require,module,exports){ /** * Utility module that provides ordered storage of function handlers. * Many Emmet modules' functionality can be extended/overridden by custom * function. This modules provides unified storage of handler functions, their * management and execution */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var utils = require('../utils/common'); /** * @type HandlerList * @constructor */ function HandlerList() { this._list = []; } HandlerList.prototype = { /** * Adds function handler * @param {Function} fn Handler * @param {Object} options Handler options. Possible values are:

* order : (Number) – order in handler list. Handlers * with higher order value will be executed earlier. */ add: function(fn, options) { // TODO hack for stable sort, remove after fixing `list()` var order = this._list.length; if (options && 'order' in options) { order = options.order * 10000; } this._list.push(utils.extend({}, options, {order: order, fn: fn})); }, /** * Removes handler from list * @param {Function} fn */ remove: function(fn) { var item = utils.find(this._list, function(item) { return item.fn === fn; }); if (item) { this._list.splice(this._list.indexOf(item), 1); } }, /** * Returns ordered list of handlers. By default, handlers * with the same order option returned in reverse order, * i.e. the latter function was added into the handlers list, the higher * it will be in the returned array * @returns {Array} */ list: function() { // TODO make stable sort return this._list.sort(function(a, b) { return b.order - a.order; }); }, /** * Returns ordered list of handler functions * @returns {Array} */ listFn: function() { return this.list().map(function(item) { return item.fn; }); }, /** * Executes handler functions in their designated order. If function * returns skipVal, meaning that function was unable to * handle passed args, the next function will be executed * and so on. * @param {Object} skipValue If function returns this value, execute * next handler. * @param {Array} args Arguments to pass to handler function * @returns {Boolean} Whether any of registered handlers performed * successfully */ exec: function(skipValue, args) { args = args || []; var result = null; utils.find(this.list(), function(h) { result = h.fn.apply(h, args); if (result !== skipValue) { return true; } }); return result; } }; return { /** * Factory method that produces HandlerList instance * @returns {HandlerList} * @memberOf handlerList */ create: function() { return new HandlerList(); } }; }); },{"../utils/common":"utils\\common.js"}],"assets\\htmlMatcher.js":[function(require,module,exports){ /** * HTML matcher: takes string and searches for HTML tag pairs for given position * * Unlike “classic” matchers, it parses content from the specified * position, not from the start, so it may work even outside HTML documents * (for example, inside strings of programming languages like JavaScript, Python * etc.) */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var range = require('./range'); // Regular Expressions for parsing tags and attributes var reOpenTag = /^<([\w\:\-]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/; var reCloseTag = /^<\/([\w\:\-]+)[^>]*>/; function openTag(i, match) { return { name: match[1], selfClose: !!match[3], /** @type Range */ range: range(i, match[0]), type: 'open' }; } function closeTag(i, match) { return { name: match[1], /** @type Range */ range: range(i, match[0]), type: 'close' }; } function comment(i, match) { return { /** @type Range */ range: range(i, typeof match == 'number' ? match - i : match[0]), type: 'comment' }; } /** * Creates new tag matcher session * @param {String} text */ function createMatcher(text) { var memo = {}, m; return { /** * Test if given position matches opening tag * @param {Number} i * @returns {Object} Matched tag object */ open: function(i) { var m = this.matches(i); return m && m.type == 'open' ? m : null; }, /** * Test if given position matches closing tag * @param {Number} i * @returns {Object} Matched tag object */ close: function(i) { var m = this.matches(i); return m && m.type == 'close' ? m : null; }, /** * Matches either opening or closing tag for given position * @param i * @returns */ matches: function(i) { var key = 'p' + i; if (!(key in memo)) { memo[key] = false; if (text.charAt(i) == '<') { var substr = text.slice(i); if ((m = substr.match(reOpenTag))) { memo[key] = openTag(i, m); } else if ((m = substr.match(reCloseTag))) { memo[key] = closeTag(i, m); } } } return memo[key]; }, /** * Returns original text * @returns {String} */ text: function() { return text; }, clean: function() { memo = text = m = null; } }; } function matches(text, pos, pattern) { return text.substring(pos, pos + pattern.length) == pattern; } /** * Search for closing pair of opening tag * @param {Object} open Open tag instance * @param {Object} matcher Matcher instance */ function findClosingPair(open, matcher) { var stack = [], tag = null; var text = matcher.text(); for (var pos = open.range.end, len = text.length; pos < len; pos++) { if (matches(text, pos, '')) { pos = j + 3; break; } } } if ((tag = matcher.matches(pos))) { if (tag.type == 'open' && !tag.selfClose) { stack.push(tag.name); } else if (tag.type == 'close') { if (!stack.length) { // found valid pair? return tag.name == open.name ? tag : null; } // check if current closing tag matches previously opened one if (stack[stack.length - 1] == tag.name) { stack.pop(); } else { var found = false; while (stack.length && !found) { var last = stack.pop(); if (last == tag.name) { found = true; } } if (!stack.length && !found) { return tag.name == open.name ? tag : null; } } } pos = tag.range.end - 1; } } } return { /** * Main function: search for tag pair in text for given * position * @memberOf htmlMatcher * @param {String} text * @param {Number} pos * @returns {Object} */ find: function(text, pos) { var matcher = createMatcher(text); var open = null, close = null; var j, jl; for (var i = pos; i >= 0; i--) { if ((open = matcher.open(i))) { // found opening tag if (open.selfClose) { if (open.range.cmp(pos, 'lt', 'gt')) { // inside self-closing tag, found match break; } // outside self-closing tag, continue continue; } close = findClosingPair(open, matcher); if (close) { // found closing tag. var r = range.create2(open.range.start, close.range.end); if (r.contains(pos)) { break; } } else if (open.range.contains(pos)) { // we inside empty HTML tag like
break; } open = null; } else if (matches(text, i, '-->')) { // skip back to comment start for (j = i - 1; j >= 0; j--) { if (matches(text, j, '-->')) { // found another comment end, do nothing break; } else if (matches(text, j, '')) { j += 3; break; } } open = comment(i, j); break; } } matcher.clean(); if (open) { var outerRange = null; var innerRange = null; if (close) { outerRange = range.create2(open.range.start, close.range.end); innerRange = range.create2(open.range.end, close.range.start); } else { outerRange = innerRange = range.create2(open.range.start, open.range.end); } if (open.type == 'comment') { // adjust positions of inner range for comment var _c = outerRange.substring(text); innerRange.start += _c.length - _c.replace(/^<\!--\s*/, '').length; innerRange.end -= _c.length - _c.replace(/\s*-->$/, '').length; } return { open: open, close: close, type: open.type == 'comment' ? 'comment' : 'tag', innerRange: innerRange, innerContent: function() { return this.innerRange.substring(text); }, outerRange: outerRange, outerContent: function() { return this.outerRange.substring(text); }, range: !innerRange.length() || !innerRange.cmp(pos, 'lte', 'gte') ? outerRange : innerRange, content: function() { return this.range.substring(text); }, source: text }; } }, /** * The same as find() method, but restricts matched result * to tag type * @param {String} text * @param {Number} pos * @returns {Object} */ tag: function(text, pos) { var result = this.find(text, pos); if (result && result.type == 'tag') { return result; } } }; }); },{"./range":"assets\\range.js"}],"assets\\logger.js":[function(require,module,exports){ /** * Simple logger for Emmet */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { return { log: function() { if (typeof console != 'undefined' && console.log) { console.log.apply(console, arguments); } } } }) },{}],"assets\\preferences.js":[function(require,module,exports){ /** * Common module's preferences storage. This module * provides general storage for all module preferences, their description and * default values.

* * This module can also be used to list all available properties to create * UI for updating properties */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var utils = require('../utils/common'); var preferences = {}; var defaults = {}; var _dbgDefaults = null; var _dbgPreferences = null; function toBoolean(val) { if (typeof val === 'string') { val = val.toLowerCase(); return val == 'yes' || val == 'true' || val == '1'; } return !!val; } function isValueObj(obj) { return typeof obj === 'object' && !Array.isArray(obj) && 'value' in obj && Object.keys(obj).length < 3; } return { /** * Creates new preference item with default value * @param {String} name Preference name. You can also pass object * with many options * @param {Object} value Preference default value * @param {String} description Item textual description * @memberOf preferences */ define: function(name, value, description) { var prefs = name; if (typeof name === 'string') { prefs = {}; prefs[name] = { value: value, description: description }; } Object.keys(prefs).forEach(function(k) { var v = prefs[k]; defaults[k] = isValueObj(v) ? v : {value: v}; }); }, /** * Updates preference item value. Preference value should be defined * first with define method. * @param {String} name Preference name. You can also pass object * with many options * @param {Object} value Preference default value * @memberOf preferences */ set: function(name, value) { var prefs = name; if (typeof name === 'string') { prefs = {}; prefs[name] = value; } Object.keys(prefs).forEach(function(k) { var v = prefs[k]; if (!(k in defaults)) { throw new Error('Property "' + k + '" is not defined. You should define it first with `define` method of current module'); } // do not set value if it equals to default value if (v !== defaults[k].value) { // make sure we have value of correct type switch (typeof defaults[k].value) { case 'boolean': v = toBoolean(v); break; case 'number': v = parseInt(v + '', 10) || 0; break; default: // convert to string if (v !== null) { v += ''; } } preferences[k] = v; } else if (k in preferences) { delete preferences[k]; } }); }, /** * Returns preference value * @param {String} name * @returns {String} Returns undefined if preference is * not defined */ get: function(name) { if (name in preferences) { return preferences[name]; } if (name in defaults) { return defaults[name].value; } return void 0; }, /** * Returns comma-separated preference value as array of values * @param {String} name * @returns {Array} Returns undefined if preference is * not defined, null if string cannot be converted to array */ getArray: function(name) { var val = this.get(name); if (typeof val === 'undefined' || val === null || val === '') { return null; } val = val.split(',').map(utils.trim); if (!val.length) { return null; } return val; }, /** * Returns comma and colon-separated preference value as dictionary * @param {String} name * @returns {Object} */ getDict: function(name) { var result = {}; this.getArray(name).forEach(function(val) { var parts = val.split(':'); result[parts[0]] = parts[1]; }); return result; }, /** * Returns description of preference item * @param {String} name Preference name * @returns {Object} */ description: function(name) { return name in defaults ? defaults[name].description : void 0; }, /** * Completely removes specified preference(s) * @param {String} name Preference name (or array of names) */ remove: function(name) { if (!Array.isArray(name)) { name = [name]; } name.forEach(function(key) { if (key in preferences) { delete preferences[key]; } if (key in defaults) { delete defaults[key]; } }); }, /** * Returns sorted list of all available properties * @returns {Array} */ list: function() { return Object.keys(defaults).sort().map(function(key) { return { name: key, value: this.get(key), type: typeof defaults[key].value, description: defaults[key].description }; }, this); }, /** * Loads user-defined preferences from JSON * @param {Object} json * @returns */ load: function(json) { Object.keys(json).forEach(function(key) { this.set(key, json[key]); }, this); }, /** * Returns hash of user-modified preferences * @returns {Object} */ exportModified: function() { return utils.extend({}, preferences); }, /** * Reset to defaults * @returns */ reset: function() { preferences = {}; }, /** * For unit testing: use empty storage */ _startTest: function() { _dbgDefaults = defaults; _dbgPreferences = preferences; defaults = {}; preferences = {}; }, /** * For unit testing: restore original storage */ _stopTest: function() { defaults = _dbgDefaults; preferences = _dbgPreferences; } }; }); },{"../utils/common":"utils\\common.js"}],"assets\\profile.js":[function(require,module,exports){ /** * Output profile module. * Profile defines how XHTML output data should look like */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var utils = require('../utils/common'); var resources = require('./resources'); var prefs = require('./preferences'); prefs.define('profile.allowCompactBoolean', true, 'This option can be used to globally disable compact form of boolean ' + 'attribues (attributes where name and value are equal). With compact' + 'form enabled, HTML tags can be outputted as <div contenteditable> ' + 'instead of <div contenteditable="contenteditable">'); prefs.define('profile.booleanAttributes', '^contenteditable|seamless|async|autofocus|autoplay|checked|controls|defer|disabled|formnovalidate|hidden|ismap|loop|multiple|muted|novalidate|readonly|required|reversed|selected|typemustmatch$', 'A regular expression for attributes that should be boolean by default.' + 'If attribute name matches this expression, you don’t have to write dot ' + 'after attribute name in Emmet abbreviation to mark it as boolean.'); var profiles = {}; var defaultProfile = { tag_case: 'asis', attr_case: 'asis', attr_quotes: 'double', // Each tag on new line tag_nl: 'decide', // With tag_nl === true, defines if leaf node (e.g. node with no children) // should have formatted line breaks tag_nl_leaf: false, place_cursor: true, // Indent tags indent: true, // How many inline elements should be to force line break // (set to 0 to disable) inline_break: 3, // Produce compact notation of boolean attribues: // attributes where name and value are equal. // With this option enabled, HTML filter will // produce
instead of
compact_bool: false, // Use self-closing style for writing empty elements, e.g.
or
self_closing_tag: 'xhtml', // Profile-level output filters, re-defines syntax filters filters: '', // Additional filters applied to abbreviation. // Unlike "filters", this preference doesn't override default filters // but add the instead every time given profile is chosen extraFilters: '' }; /** * @constructor * @type OutputProfile * @param {Object} options */ function OutputProfile(options) { utils.extend(this, defaultProfile, options); } OutputProfile.prototype = { /** * Transforms tag name case depending on current profile settings * @param {String} name String to transform * @returns {String} */ tagName: function(name) { return stringCase(name, this.tag_case); }, /** * Transforms attribute name case depending on current profile settings * @param {String} name String to transform * @returns {String} */ attributeName: function(name) { return stringCase(name, this.attr_case); }, /** * Returns quote character for current profile * @returns {String} */ attributeQuote: function() { return this.attr_quotes == 'single' ? "'" : '"'; }, /** * Returns self-closing tag symbol for current profile * @returns {String} */ selfClosing: function() { if (this.self_closing_tag == 'xhtml') return ' /'; if (this.self_closing_tag === true) return '/'; return ''; }, /** * Returns cursor token based on current profile settings * @returns {String} */ cursor: function() { return this.place_cursor ? utils.getCaretPlaceholder() : ''; }, /** * Check if attribute with given name is boolean, * e.g. written as `contenteditable` instead of * `contenteditable="contenteditable"` * @param {String} name Attribute name * @return {Boolean} */ isBoolean: function(name, value) { if (name == value) { return true; } var boolAttrs = prefs.get('profile.booleanAttributes'); if (!value && boolAttrs) { boolAttrs = new RegExp(boolAttrs, 'i'); return boolAttrs.test(name); } return false; }, /** * Check if compact boolean attribute record is * allowed for current profile * @return {Boolean} */ allowCompactBoolean: function() { return this.compact_bool && prefs.get('profile.allowCompactBoolean'); } }; /** * Helper function that converts string case depending on * caseValue * @param {String} str String to transform * @param {String} caseValue Case value: can be lower, * upper and leave * @returns {String} */ function stringCase(str, caseValue) { switch (String(caseValue || '').toLowerCase()) { case 'lower': return str.toLowerCase(); case 'upper': return str.toUpperCase(); } return str; } /** * Creates new output profile * @param {String} name Profile name * @param {Object} options Profile options */ function createProfile(name, options) { return profiles[name.toLowerCase()] = new OutputProfile(options); } function createDefaultProfiles() { createProfile('xhtml'); createProfile('html', {self_closing_tag: false, compact_bool: true}); createProfile('xml', {self_closing_tag: true, tag_nl: true}); createProfile('plain', {tag_nl: false, indent: false, place_cursor: false}); createProfile('line', {tag_nl: false, indent: false, extraFilters: 's'}); createProfile('css', {tag_nl: true}); createProfile('css_line', {tag_nl: false}); } createDefaultProfiles(); return { /** * Creates new output profile and adds it into internal dictionary * @param {String} name Profile name * @param {Object} options Profile options * @memberOf emmet.profile * @returns {Object} New profile */ create: function(name, options) { if (arguments.length == 2) return createProfile(name, options); else // create profile object only return new OutputProfile(utils.defaults(name || {}, defaultProfile)); }, /** * Returns profile by its name. If profile wasn't found, returns * 'plain' profile * @param {String} name Profile name. Might be profile itself * @param {String} syntax. Optional. Current editor syntax. If defined, * profile is searched in resources first, then in predefined profiles * @returns {Object} */ get: function(name, syntax) { if (!name && syntax) { // search in user resources first var profile = resources.findItem(syntax, 'profile'); if (profile) { name = profile; } } if (!name) { return profiles.plain; } if (name instanceof OutputProfile) { return name; } if (typeof name === 'string' && name.toLowerCase() in profiles) { return profiles[name.toLowerCase()]; } return this.create(name); }, /** * Deletes profile with specified name * @param {String} name Profile name */ remove: function(name) { name = (name || '').toLowerCase(); if (name in profiles) delete profiles[name]; }, /** * Resets all user-defined profiles */ reset: function() { profiles = {}; createDefaultProfiles(); }, /** * Helper function that converts string case depending on * caseValue * @param {String} str String to transform * @param {String} caseValue Case value: can be lower, * upper and leave * @returns {String} */ stringCase: stringCase }; }); },{"../utils/common":"utils\\common.js","./preferences":"assets\\preferences.js","./resources":"assets\\resources.js"}],"assets\\range.js":[function(require,module,exports){ /** * Helper module to work with ranges */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { function cmp(a, b, op) { switch (op) { case 'eq': case '==': return a === b; case 'lt': case '<': return a < b; case 'lte': case '<=': return a <= b; case 'gt': case '>': return a > b; case 'gte': case '>=': return a >= b; } } /** * @type Range * @constructor * @param {Object} start * @param {Number} len */ function Range(start, len) { if (typeof start === 'object' && 'start' in start) { // create range from object stub this.start = Math.min(start.start, start.end); this.end = Math.max(start.start, start.end); } else if (Array.isArray(start)) { this.start = start[0]; this.end = start[1]; } else { len = typeof len === 'string' ? len.length : +len; this.start = start; this.end = start + len; } } Range.prototype = { length: function() { return Math.abs(this.end - this.start); }, /** * Returns true if passed range is equals to current one * @param {Range} range * @returns {Boolean} */ equal: function(range) { return this.cmp(range, 'eq', 'eq'); // return this.start === range.start && this.end === range.end; }, /** * Shifts indexes position with passed delta * @param {Number} delta * @returns {Range} range itself */ shift: function(delta) { this.start += delta; this.end += delta; return this; }, /** * Check if two ranges are overlapped * @param {Range} range * @returns {Boolean} */ overlap: function(range) { return range.start <= this.end && range.end >= this.start; }, /** * Finds intersection of two ranges * @param {Range} range * @returns {Range} null if ranges does not overlap */ intersection: function(range) { if (this.overlap(range)) { var start = Math.max(range.start, this.start); var end = Math.min(range.end, this.end); return new Range(start, end - start); } return null; }, /** * Returns the union of the thow ranges. * @param {Range} range * @returns {Range} null if ranges are not overlapped */ union: function(range) { if (this.overlap(range)) { var start = Math.min(range.start, this.start); var end = Math.max(range.end, this.end); return new Range(start, end - start); } return null; }, /** * Returns a Boolean value that indicates whether a specified position * is in a given range. * @param {Number} loc */ inside: function(loc) { return this.cmp(loc, 'lte', 'gt'); // return this.start <= loc && this.end > loc; }, /** * Returns a Boolean value that indicates whether a specified position * is in a given range, but not equals bounds. * @param {Number} loc */ contains: function(loc) { return this.cmp(loc, 'lt', 'gt'); }, /** * Check if current range completely includes specified one * @param {Range} r * @returns {Boolean} */ include: function(r) { return this.cmp(r, 'lte', 'gte'); // return this.start <= r.start && this.end >= r.end; }, /** * Low-level comparision method * @param {Number} loc * @param {String} left Left comparison operator * @param {String} right Right comaprison operator */ cmp: function(loc, left, right) { var a, b; if (loc instanceof Range) { a = loc.start; b = loc.end; } else { a = b = loc; } return cmp(this.start, a, left || '<=') && cmp(this.end, b, right || '>'); }, /** * Returns substring of specified str for current range * @param {String} str * @returns {String} */ substring: function(str) { return this.length() > 0 ? str.substring(this.start, this.end) : ''; }, /** * Creates copy of current range * @returns {Range} */ clone: function() { return new Range(this.start, this.length()); }, /** * @returns {Array} */ toArray: function() { return [this.start, this.end]; }, toString: function() { return this.valueOf(); }, valueOf: function() { return '{' + this.start + ', ' + this.length() + '}'; } }; /** * Creates new range object instance * @param {Object} start Range start or array with 'start' and 'end' * as two first indexes or object with 'start' and 'end' properties * @param {Number} len Range length or string to produce range from * @returns {Range} */ module.exports = function(start, len) { if (typeof start == 'undefined' || start === null) return null; if (start instanceof Range) return start; if (typeof start == 'object' && 'start' in start && 'end' in start) { len = start.end - start.start; start = start.start; } return new Range(start, len); }; module.exports.create = module.exports; module.exports.isRange = function(val) { return val instanceof Range; }; /** * Range object factory, the same as this.create() * but last argument represents end of range, not length * @returns {Range} */ module.exports.create2 = function(start, end) { if (typeof start === 'number' && typeof end === 'number') { end -= start; } return this.create(start, end); }; /** * Helper function that sorts ranges in order as they * appear in text * @param {Array} ranges * @return {Array} */ module.exports.sort = function(ranges, reverse) { ranges = ranges.sort(function(a, b) { if (a.start === b.start) { return b.end - a.end; } return a.start - b.start; }); reverse && ranges.reverse(); return ranges; }; return module.exports; }); },{}],"assets\\resources.js":[function(require,module,exports){ /** * Parsed resources (snippets, abbreviations, variables, etc.) for Emmet. * Contains convenient method to get access for snippets with respect of * inheritance. Also provides ability to store data in different vocabularies * ('system' and 'user') for fast and safe resource update * @author Sergey Chikuyonok (serge.che@gmail.com) * @link http://chikuyonok.ru */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var handlerList = require('./handlerList'); var utils = require('../utils/common'); var elements = require('./elements'); var logger = require('../assets/logger'); var stringScore = require('../vendor/stringScore'); var cssResolver = require('../resolver/css'); var VOC_SYSTEM = 'system'; var VOC_USER = 'user'; var cache = {}; /** Regular expression for XML tag matching */ var reTag = /^<(\w+\:?[\w\-]*)((?:\s+[@\!]?[\w\:\-]+\s*=\s*(['"]).*?\3)*)\s*(\/?)>/; var systemSettings = {}; var userSettings = {}; /** @type HandlerList List of registered abbreviation resolvers */ var resolvers = handlerList.create(); function each(obj, fn) { if (!obj) { return; } Object.keys(obj).forEach(function(key) { fn(obj[key], key); }); } /** * Normalizes caret plceholder in passed text: replaces | character with * default caret placeholder * @param {String} text * @returns {String} */ function normalizeCaretPlaceholder(text) { return utils.replaceUnescapedSymbol(text, '|', utils.getCaretPlaceholder()); } function parseItem(name, value, type) { value = normalizeCaretPlaceholder(value); if (type == 'snippets') { return elements.create('snippet', value); } if (type == 'abbreviations') { return parseAbbreviation(name, value); } } /** * Parses single abbreviation * @param {String} key Abbreviation name * @param {String} value Abbreviation value * @return {Object} */ function parseAbbreviation(key, value) { key = utils.trim(key); var m; if ((m = reTag.exec(value))) { return elements.create('element', m[1], m[2], m[4] == '/'); } else { // assume it's reference to another abbreviation return elements.create('reference', value); } } /** * Normalizes snippet key name for better fuzzy search * @param {String} str * @returns {String} */ function normalizeName(str) { return str.replace(/:$/, '').replace(/:/g, '-'); } function expandSnippetsDefinition(snippets) { var out = {}; each(snippets, function(val, key) { var items = key.split('|'); // do not use iterators for better performance for (var i = items.length - 1; i >= 0; i--) { out[items[i]] = val; } }); return out; } utils.extend(exports, { /** * Sets new unparsed data for specified settings vocabulary * @param {Object} data * @param {String} type Vocabulary type ('system' or 'user') * @memberOf resources */ setVocabulary: function(data, type) { cache = {}; // sections like "snippets" and "abbreviations" could have // definitions like `"f|fs": "fieldset"` which is the same as distinct // "f" and "fs" keys both equals to "fieldset". // We should parse these definitions first var voc = {}; each(data, function(section, syntax) { var _section = {}; each(section, function(subsection, name) { if (name == 'abbreviations' || name == 'snippets') { subsection = expandSnippetsDefinition(subsection); } _section[name] = subsection; }); voc[syntax] = _section; }); if (type == VOC_SYSTEM) { systemSettings = voc; } else { userSettings = voc; } }, /** * Returns resource vocabulary by its name * @param {String} name Vocabulary name ('system' or 'user') * @return {Object} */ getVocabulary: function(name) { return name == VOC_SYSTEM ? systemSettings : userSettings; }, /** * Returns resource (abbreviation, snippet, etc.) matched for passed * abbreviation * @param {AbbreviationNode} node * @param {String} syntax * @returns {Object} */ getMatchedResource: function(node, syntax) { return resolvers.exec(null, utils.toArray(arguments)) || this.findSnippet(syntax, node.name()); }, /** * Returns variable value * @return {String} */ getVariable: function(name) { return (this.getSection('variables') || {})[name]; }, /** * Store runtime variable in user storage * @param {String} name Variable name * @param {String} value Variable value */ setVariable: function(name, value){ var voc = this.getVocabulary('user') || {}; if (!('variables' in voc)) voc.variables = {}; voc.variables[name] = value; this.setVocabulary(voc, 'user'); }, /** * Check if there are resources for specified syntax * @param {String} syntax * @return {Boolean} */ hasSyntax: function(syntax) { return syntax in this.getVocabulary(VOC_USER) || syntax in this.getVocabulary(VOC_SYSTEM); }, /** * Registers new abbreviation resolver. * @param {Function} fn Abbreviation resolver which will receive * abbreviation as first argument and should return parsed abbreviation * object if abbreviation has handled successfully, null * otherwise * @param {Object} options Options list as described in * {@link HandlerList#add()} method */ addResolver: function(fn, options) { resolvers.add(fn, options); }, removeResolver: function(fn) { resolvers.remove(fn); }, /** * Returns actual section data, merged from both * system and user data * @param {String} name Section name (syntax) * @param {String} ...args Subsections * @returns */ getSection: function(name) { if (!name) return null; if (!(name in cache)) { cache[name] = utils.deepMerge({}, systemSettings[name], userSettings[name]); } var data = cache[name], subsections = utils.toArray(arguments, 1), key; while (data && (key = subsections.shift())) { if (key in data) { data = data[key]; } else { return null; } } return data; }, /** * Recursively searches for a item inside top level sections (syntaxes) * with respect of `extends` attribute * @param {String} topSection Top section name (syntax) * @param {String} subsection Inner section name * @returns {Object} */ findItem: function(topSection, subsection) { var data = this.getSection(topSection); while (data) { if (subsection in data) return data[subsection]; data = this.getSection(data['extends']); } }, /** * Recursively searches for a snippet definition inside syntax section. * Definition is searched inside `snippets` and `abbreviations` * subsections * @param {String} syntax Top-level section name (syntax) * @param {String} name Snippet name * @returns {Object} */ findSnippet: function(syntax, name, memo) { if (!syntax || !name) return null; memo = memo || []; var names = [name]; // create automatic aliases to properties with colons, // e.g. pos-a == pos:a if (~name.indexOf('-')) { names.push(name.replace(/\-/g, ':')); } var data = this.getSection(syntax), matchedItem = null; ['snippets', 'abbreviations'].some(function(sectionName) { var data = this.getSection(syntax, sectionName); if (data) { return names.some(function(n) { if (data[n]) { return matchedItem = parseItem(n, data[n], sectionName); } }); } }, this); memo.push(syntax); if (!matchedItem && data['extends'] && !~memo.indexOf(data['extends'])) { // try to find item in parent syntax section return this.findSnippet(data['extends'], name, memo); } return matchedItem; }, /** * Performs fuzzy search of snippet definition * @param {String} syntax Top-level section name (syntax) * @param {String} name Snippet name * @returns */ fuzzyFindSnippet: function(syntax, name, minScore) { var result = this.fuzzyFindMatches(syntax, name, minScore)[0]; if (result) { return result.value.parsedValue; } }, fuzzyFindMatches: function(syntax, name, minScore) { minScore = minScore || 0.3; name = normalizeName(name); var snippets = this.getAllSnippets(syntax); return Object.keys(snippets) .map(function(key) { var value = snippets[key]; return { key: key, score: stringScore.score(value.nk, name, 0.1), value: value }; }) .filter(function(item) { return item.score >= minScore; }) .sort(function(a, b) { return a.score - b.score; }) .reverse(); }, /** * Returns plain dictionary of all available abbreviations and snippets * for specified syntax with respect of inheritance * @param {String} syntax * @returns {Object} */ getAllSnippets: function(syntax) { var cacheKey = 'all-' + syntax; if (!cache[cacheKey]) { var stack = [], sectionKey = syntax; var memo = []; do { var section = this.getSection(sectionKey); if (!section) break; ['snippets', 'abbreviations'].forEach(function(sectionName) { var stackItem = {}; each(section[sectionName] || null, function(v, k) { stackItem[k] = { nk: normalizeName(k), value: v, parsedValue: parseItem(k, v, sectionName), type: sectionName }; }); stack.push(stackItem); }); memo.push(sectionKey); sectionKey = section['extends']; } while (sectionKey && !~memo.indexOf(sectionKey)); cache[cacheKey] = utils.extend.apply(utils, stack.reverse()); } return cache[cacheKey]; }, /** * Returns newline character * @returns {String} */ getNewline: function() { var nl = this.getVariable('newline'); return typeof nl === 'string' ? nl : '\n'; }, /** * Sets new newline character that will be used in output * @param {String} str */ setNewline: function(str) { this.setVariable('newline', str); this.setVariable('nl', str); } }); // XXX add default resolvers exports.addResolver(cssResolver.resolve.bind(cssResolver)); // try to load snippets // hide it from Require.JS parser (function(r) { if (typeof define === 'undefined' || !define.amd) { try { exports.setVocabulary(r('../snippets.json'), VOC_SYSTEM); } catch (e) {} } })(require); return exports; }); },{"../assets/logger":"assets\\logger.js","../resolver/css":"resolver\\css.js","../utils/common":"utils\\common.js","../vendor/stringScore":"vendor\\stringScore.js","./elements":"assets\\elements.js","./handlerList":"assets\\handlerList.js"}],"assets\\stringStream.js":[function(require,module,exports){ /** * A trimmed version of CodeMirror's StringStream module for string parsing */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { /** * @type StringStream * @constructor * @param {String} string Assuming that bound string should be * immutable */ function StringStream(string) { this.pos = this.start = 0; this.string = string; this._length = string.length; } StringStream.prototype = { /** * Returns true only if the stream is at the end of the line. * @returns {Boolean} */ eol: function() { return this.pos >= this._length; }, /** * Returns true only if the stream is at the start of the line * @returns {Boolean} */ sol: function() { return this.pos === 0; }, /** * Returns the next character in the stream without advancing it. * Will return undefined at the end of the line. * @returns {String} */ peek: function() { return this.string.charAt(this.pos); }, /** * Returns the next character in the stream and advances it. * Also returns undefined when no more characters are available. * @returns {String} */ next: function() { if (this.pos < this._length) return this.string.charAt(this.pos++); }, /** * match can be a character, a regular expression, or a function that * takes a character and returns a boolean. If the next character in the * stream 'matches' the given argument, it is consumed and returned. * Otherwise, undefined is returned. * @param {Object} match * @returns {String} */ eat: function(match) { var ch = this.string.charAt(this.pos), ok; if (typeof match == "string") ok = ch == match; else ok = ch && (match.test ? match.test(ch) : match(ch)); if (ok) { ++this.pos; return ch; } }, /** * Repeatedly calls eat with the given argument, until it * fails. Returns true if any characters were eaten. * @param {Object} match * @returns {Boolean} */ eatWhile: function(match) { var start = this.pos; while (this.eat(match)) {} return this.pos > start; }, /** * Shortcut for eatWhile when matching white-space. * @returns {Boolean} */ eatSpace: function() { var start = this.pos; while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; return this.pos > start; }, /** * Moves the position to the end of the line. */ skipToEnd: function() { this.pos = this._length; }, /** * Skips to the next occurrence of the given character, if found on the * current line (doesn't advance the stream if the character does not * occur on the line). Returns true if the character was found. * @param {String} ch * @returns {Boolean} */ skipTo: function(ch) { var found = this.string.indexOf(ch, this.pos); if (found > -1) { this.pos = found; return true; } }, /** * Skips to close character which is pair to open * character, considering possible pair nesting. This function is used * to consume pair of characters, like opening and closing braces * @param {String} open * @param {String} close * @returns {Boolean} Returns true if pair was successfully * consumed */ skipToPair: function(open, close, skipString) { var braceCount = 0, ch; var pos = this.pos, len = this._length; while (pos < len) { ch = this.string.charAt(pos++); if (ch == open) { braceCount++; } else if (ch == close) { braceCount--; if (braceCount < 1) { this.pos = pos; return true; } } else if (skipString && (ch == '"' || ch == "'")) { this.skipString(ch); } } return false; }, /** * A helper function which, in case of either single or * double quote was found in current position, skips entire * string (quoted value) * @return {Boolean} Wether quoted string was skipped */ skipQuoted: function(noBackup) { var ch = this.string.charAt(noBackup ? this.pos : this.pos - 1); if (ch === '"' || ch === "'") { if (noBackup) { this.pos++; } return this.skipString(ch); } }, /** * A custom function to skip string literal, e.g. a "double-quoted" * or 'single-quoted' value * @param {String} quote An opening quote * @return {Boolean} */ skipString: function(quote) { var pos = this.pos, len = this._length, ch; while (pos < len) { ch = this.string.charAt(pos++); if (ch == '\\') { continue; } else if (ch == quote) { this.pos = pos; return true; } } return false; }, /** * Backs up the stream n characters. Backing it up further than the * start of the current token will cause things to break, so be careful. * @param {Number} n */ backUp : function(n) { this.pos -= n; }, /** * Act like a multi-character eat—if consume is true or * not given—or a look-ahead that doesn't update the stream position—if * it is false. pattern can be either a string or a * regular expression starting with ^. When it is a string, * caseInsensitive can be set to true to make the match * case-insensitive. When successfully matching a regular expression, * the returned value will be the array returned by match, * in case you need to extract matched groups. * * @param {RegExp} pattern * @param {Boolean} consume * @param {Boolean} caseInsensitive * @returns */ match: function(pattern, consume, caseInsensitive) { if (typeof pattern == "string") { var cased = caseInsensitive ? function(str) {return str.toLowerCase();} : function(str) {return str;}; if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { if (consume !== false) this.pos += pattern.length; return true; } } else { var match = this.string.slice(this.pos).match(pattern); if (match && consume !== false) this.pos += match[0].length; return match; } }, /** * Get the string between the start of the current token and the * current stream position. * @returns {String} */ current: function(backUp) { return this.string.slice(this.start, this.pos - (backUp ? 1 : 0)); } }; module.exports = function(string) { return new StringStream(string); }; /** @deprecated */ module.exports.create = module.exports; return module.exports; }); },{}],"assets\\tabStops.js":[function(require,module,exports){ /** * Utility module for handling tabstops tokens generated by Emmet's * "Expand Abbreviation" action. The main extract method will take * raw text (for example: ${0} some ${1:text}), find all tabstops * occurrences, replace them with tokens suitable for your editor of choice and * return object with processed text and list of found tabstops and their ranges. * For sake of portability (Objective-C/Java) the tabstops list is a plain * sorted array with plain objects. * * Placeholders with the same are meant to be linked in your editor. */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var utils = require('../utils/common'); var stringStream = require('./stringStream'); var resources = require('./resources'); /** * Global placeholder value, automatically incremented by * variablesResolver() function */ var startPlaceholderNum = 100; var tabstopIndex = 0; var defaultOptions = { replaceCarets: false, escape: function(ch) { return '\\' + ch; }, tabstop: function(data) { return data.token; }, variable: function(data) { return data.token; } }; return { /** * Main function that looks for a tabstops in provided text * and returns a processed version of text with expanded * placeholders and list of tabstops found. * @param {String} text Text to process * @param {Object} options List of processor options:
* * replaceCarets : Boolean — replace all default * caret placeholders (like {%::emmet-caret::%}) with ${0:caret}
* * escape : Function — function that handle escaped * characters (mostly '$'). By default, it returns the character itself * to be displayed as is in output, but sometimes you will use * extract method as intermediate solution for further * processing and want to keep character escaped. Thus, you should override * escape method to return escaped symbol (e.g. '\\$')
* * tabstop : Function – a tabstop handler. Receives * a single argument – an object describing token: its position, number * group, placeholder and token itself. Should return a replacement * string that will appear in final output * * variable : Function – variable handler. Receives * a single argument – an object describing token: its position, name * and original token itself. Should return a replacement * string that will appear in final output * * @returns {Object} Object with processed text property * and array of tabstops found * @memberOf tabStops */ extract: function(text, options) { // prepare defaults var placeholders = {carets: ''}; var marks = []; options = utils.extend({}, defaultOptions, options, { tabstop: function(data) { var token = data.token; var ret = ''; if (data.placeholder == 'cursor') { marks.push({ start: data.start, end: data.start + token.length, group: 'carets', value: '' }); } else { // unify placeholder value for single group if ('placeholder' in data) placeholders[data.group] = data.placeholder; if (data.group in placeholders) ret = placeholders[data.group]; marks.push({ start: data.start, end: data.start + token.length, group: data.group, value: ret }); } return token; } }); if (options.replaceCarets) { text = text.replace(new RegExp( utils.escapeForRegexp( utils.getCaretPlaceholder() ), 'g'), '${0:cursor}'); } // locate tabstops and unify group's placeholders text = this.processText(text, options); // now, replace all tabstops with placeholders var buf = '', lastIx = 0; var tabStops = marks.map(function(mark) { buf += text.substring(lastIx, mark.start); var pos = buf.length; var ph = placeholders[mark.group] || ''; buf += ph; lastIx = mark.end; return { group: mark.group, start: pos, end: pos + ph.length }; }); buf += text.substring(lastIx); return { text: buf, tabstops: tabStops.sort(function(a, b) { return a.start - b.start; }) }; }, /** * Text processing routine. Locates escaped characters and tabstops and * replaces them with values returned by handlers defined in * options * @param {String} text * @param {Object} options See extract method options * description * @returns {String} */ processText: function(text, options) { options = utils.extend({}, defaultOptions, options); var buf = ''; /** @type StringStream */ var stream = stringStream.create(text); var ch, m, a; while ((ch = stream.next())) { if (ch == '\\' && !stream.eol()) { // handle escaped character buf += options.escape(stream.next()); continue; } a = ch; if (ch == '$') { // looks like a tabstop stream.start = stream.pos - 1; if ((m = stream.match(/^[0-9]+/))) { // it's $N a = options.tabstop({ start: buf.length, group: stream.current().substr(1), token: stream.current() }); } else if ((m = stream.match(/^\{([a-z_\-][\w\-]*)\}/))) { // ${variable} a = options.variable({ start: buf.length, name: m[1], token: stream.current() }); } else if ((m = stream.match(/^\{([0-9]+)(:.+?)?\}/, false))) { // ${N:value} or ${N} placeholder // parse placeholder, including nested ones stream.skipToPair('{', '}'); var obj = { start: buf.length, group: m[1], token: stream.current() }; var placeholder = obj.token.substring(obj.group.length + 2, obj.token.length - 1); if (placeholder) { obj.placeholder = placeholder.substr(1); } a = options.tabstop(obj); } } buf += a; } return buf; }, /** * Upgrades tabstops in output node in order to prevent naming conflicts * @param {AbbreviationNode} node * @param {Number} offset Tab index offset * @returns {Number} Maximum tabstop index in element */ upgrade: function(node, offset) { var maxNum = 0; var options = { tabstop: function(data) { var group = parseInt(data.group, 10); if (group > maxNum) maxNum = group; if (data.placeholder) return '${' + (group + offset) + ':' + data.placeholder + '}'; else return '${' + (group + offset) + '}'; } }; ['start', 'end', 'content'].forEach(function(p) { node[p] = this.processText(node[p], options); }, this); return maxNum; }, /** * Helper function that produces a callback function for * replaceVariables() method from {@link utils} * module. This callback will replace variable definitions (like * ${var_name}) with their value defined in resource module, * or outputs tabstop with variable name otherwise. * @param {AbbreviationNode} node Context node * @returns {Function} */ variablesResolver: function(node) { var placeholderMemo = {}; return function(str, varName) { // do not mark `child` variable as placeholder – it‘s a reserved // variable name if (varName == 'child') { return str; } if (varName == 'cursor') { return utils.getCaretPlaceholder(); } var attr = node.attribute(varName); if (typeof attr !== 'undefined' && attr !== str) { return attr; } var varValue = resources.getVariable(varName); if (varValue) { return varValue; } // output as placeholder if (!placeholderMemo[varName]) { placeholderMemo[varName] = startPlaceholderNum++; } return '${' + placeholderMemo[varName] + ':' + varName + '}'; }; }, /** * Replace variables like ${var} in string * @param {String} str * @param {Object} vars Variable set (defaults to variables defined in * snippets.json) or variable resolver (Function) * @return {String} */ replaceVariables: function(str, vars) { vars = vars || {}; var resolver = typeof vars === 'function' ? vars : function(str, p1) { return p1 in vars ? vars[p1] : null; }; return this.processText(str, { variable: function(data) { var newValue = resolver(data.token, data.name, data); if (newValue === null) { // try to find variable in resources newValue = resources.getVariable(data.name); } if (newValue === null || typeof newValue === 'undefined') // nothing found, return token itself newValue = data.token; return newValue; } }); }, /** * Resets global tabstop index. When parsed tree is converted to output * string (AbbreviationNode.toString()), all tabstops * defined in snippets and elements are upgraded in order to prevent * naming conflicts of nested. For example, ${1} of a node * should not be linked with the same placehilder of the child node. * By default, AbbreviationNode.toString() automatically * upgrades tabstops of the same index for each node and writes maximum * tabstop index into the tabstopIndex variable. To keep * this variable at reasonable value, it is recommended to call * resetTabstopIndex() method each time you expand variable * @returns */ resetTabstopIndex: function() { tabstopIndex = 0; startPlaceholderNum = 100; }, /** * Output processor for abbreviation parser that will upgrade tabstops * of parsed node in order to prevent tabstop index conflicts */ abbrOutputProcessor: function(text, node, type) { var maxNum = 0; var that = this; var tsOptions = { tabstop: function(data) { var group = parseInt(data.group, 10); if (group === 0) return '${0}'; if (group > maxNum) maxNum = group; if (data.placeholder) { // respect nested placeholders var ix = group + tabstopIndex; var placeholder = that.processText(data.placeholder, tsOptions); return '${' + ix + ':' + placeholder + '}'; } else { return '${' + (group + tabstopIndex) + '}'; } } }; // upgrade tabstops text = this.processText(text, tsOptions); // resolve variables text = this.replaceVariables(text, this.variablesResolver(node)); tabstopIndex += maxNum + 1; return text; } }; }); },{"../utils/common":"utils\\common.js","./resources":"assets\\resources.js","./stringStream":"assets\\stringStream.js"}],"assets\\tokenIterator.js":[function(require,module,exports){ /** * Helper class for convenient token iteration */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { /** * @type TokenIterator * @param {Array} tokens * @type TokenIterator * @constructor */ function TokenIterator(tokens) { /** @type Array */ this.tokens = tokens; this._position = 0; this.reset(); } TokenIterator.prototype = { next: function() { if (this.hasNext()) { var token = this.tokens[++this._i]; this._position = token.start; return token; } else { this._i = this._il; } return null; }, current: function() { return this.tokens[this._i]; }, peek: function() { return this.tokens[this._i + i]; }, position: function() { return this._position; }, hasNext: function() { return this._i < this._il - 1; }, reset: function() { this._i = 0; this._il = this.tokens.length; }, item: function() { return this.tokens[this._i]; }, itemNext: function() { return this.tokens[this._i + 1]; }, itemPrev: function() { return this.tokens[this._i - 1]; }, nextUntil: function(type, callback) { var token; var test = typeof type == 'string' ? function(t){return t.type == type;} : type; while ((token = this.next())) { if (callback) callback.call(this, token); if (test.call(this, token)) break; } } }; return { create: function(tokens) { return new TokenIterator(tokens); } }; }); },{}],"editTree\\base.js":[function(require,module,exports){ /** * Abstract implementation of edit tree interface. * Edit tree is a named container of editable “name-value” child elements, * parsed from source. This container provides convenient methods * for editing/adding/removing child elements. All these update actions are * instantly reflected in the source code with respect of formatting. *

* For example, developer can create an edit tree from CSS rule and add or * remove properties from it–all changes will be immediately reflected in the * original source. *

* All classes defined in this module should be extended the same way as in * Backbone framework: using extend method to create new class and * initialize method to define custom class constructor. * * @example *

 * var MyClass = require('editTree/base').EditElement.extend({
 *     initialize: function() {
 *     // constructor code here
 *   }
 * });
 * 
 * var elem = new MyClass(); 
 * 
*/ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var range = require('../assets/range'); var utils = require('../utils/common'); var klass = require('../vendor/klass'); /** * Named container of edited source * @type EditContainer * @param {String} source * @param {Object} options */ function EditContainer(source, options) { this.options = utils.extend({offset: 0}, options); /** * Source code of edited structure. All changes in the structure are * immediately reflected into this property */ this.source = source; /** * List of all editable children * @private */ this._children = []; /** * Hash of all positions of container * @private */ this._positions = { name: 0 }; this.initialize.apply(this, arguments); } /** * The self-propagating extend function for classes. * @type Function */ EditContainer.extend = klass.extend; EditContainer.prototype = { type: 'container', /** * Child class constructor */ initialize: function() {}, /** * Make position absolute * @private * @param {Number} num * @param {Boolean} isAbsolute * @returns {Boolean} */ _pos: function(num, isAbsolute) { return num + (isAbsolute ? this.options.offset : 0); }, /** * Replace substring of tag's source * @param {String} value * @param {Number} start * @param {Number} end * @private */ _updateSource: function(value, start, end) { // create modification range var r = range.create(start, typeof end === 'undefined' ? 0 : end - start); var delta = value.length - r.length(); var update = function(obj) { Object.keys(obj).forEach(function(k) { if (obj[k] >= r.end) { obj[k] += delta; } }); }; // update affected positions of current container update(this._positions); // update affected positions of children var recursiveUpdate = function(items) { items.forEach(function(item) { update(item._positions); if (item.type == 'container') { recursiveUpdate(item.list()); } }); }; recursiveUpdate(this.list()); this.source = utils.replaceSubstring(this.source, value, r); }, /** * Adds new attribute * @param {String} name Property name * @param {String} value Property value * @param {Number} pos Position at which to insert new property. By * default the property is inserted at the end of rule * @returns {EditElement} Newly created element */ add: function(name, value, pos) { // this is abstract implementation var item = new EditElement(name, value); this._children.push(item); return item; }, /** * Returns attribute object * @param {String} name Attribute name or its index * @returns {EditElement} */ get: function(name) { if (typeof name === 'number') { return this.list()[name]; } if (typeof name === 'string') { return utils.find(this.list(), function(prop) { return prop.name() === name; }); } return name; }, /** * Returns all children by name or indexes * @param {Object} name Element name(s) or indexes (String, * Array, Number) * @returns {Array} */ getAll: function(name) { if (!Array.isArray(name)) name = [name]; // split names and indexes var names = [], indexes = []; name.forEach(function(item) { if (typeof item === 'string') { names.push(item); } else if (typeof item === 'number') { indexes.push(item); } }); return this.list().filter(function(attribute, i) { return ~indexes.indexOf(i) || ~names.indexOf(attribute.name()); }); }, /** * Returns list of all editable child elements * @returns {Array} */ list: function() { return this._children; }, /** * Remove child element * @param {String} name Property name or its index */ remove: function(name) { var element = this.get(name); if (element) { this._updateSource('', element.fullRange()); var ix = this._children.indexOf(element); if (~ix) { this._children.splice(ix, 1); } } }, /** * Returns index of editble child in list * @param {Object} item * @returns {Number} */ indexOf: function(item) { return this.list().indexOf(this.get(item)); }, /** * Returns or updates element value. If such element doesn't exists, * it will be created automatically and added at the end of child list. * @param {String} name Element name or its index * @param {String} value New element value * @returns {String} */ value: function(name, value, pos) { var element = this.get(name); if (element) return element.value(value); if (typeof value !== 'undefined') { // no such element — create it return this.add(name, value, pos); } }, /** * Returns all values of child elements found by getAll() * method * @param {Object} name Element name(s) or indexes (String, * Array, Number) * @returns {Array} */ values: function(name) { return this.getAll(name).map(function(element) { return element.value(); }); }, /** * Sets or gets container name * @param {String} val New name. If not passed, current * name is returned * @return {String} */ name: function(val) { if (typeof val !== 'undefined' && this._name !== (val = String(val))) { this._updateSource(val, this._positions.name, this._positions.name + this._name.length); this._name = val; } return this._name; }, /** * Returns name range object * @param {Boolean} isAbsolute Return absolute range (with respect of * rule offset) * @returns {Range} */ nameRange: function(isAbsolute) { return range.create(this._positions.name + (isAbsolute ? this.options.offset : 0), this.name()); }, /** * Returns range of current source * @param {Boolean} isAbsolute */ range: function(isAbsolute) { return range.create(isAbsolute ? this.options.offset : 0, this.valueOf()); }, /** * Returns element that belongs to specified position * @param {Number} pos * @param {Boolean} isAbsolute * @returns {EditElement} */ itemFromPosition: function(pos, isAbsolute) { return utils.find(this.list(), function(elem) { return elem.range(isAbsolute).inside(pos); }); }, /** * Returns source code of current container * @returns {String} */ toString: function() { return this.valueOf(); }, valueOf: function() { return this.source; } }; /** * @param {EditContainer} parent * @param {Object} nameToken * @param {Object} valueToken */ function EditElement(parent, nameToken, valueToken) { /** @type EditContainer */ this.parent = parent; this._name = nameToken.value; this._value = valueToken ? valueToken.value : ''; this._positions = { name: nameToken.start, value: valueToken ? valueToken.start : -1 }; this.initialize.apply(this, arguments); } /** * The self-propagating extend function for classes. * @type Function */ EditElement.extend = klass.extend; EditElement.prototype = { type: 'element', /** * Child class constructor */ initialize: function() {}, /** * Make position absolute * @private * @param {Number} num * @param {Boolean} isAbsolute * @returns {Boolean} */ _pos: function(num, isAbsolute) { return num + (isAbsolute ? this.parent.options.offset : 0); }, /** * Sets of gets element value * @param {String} val New element value. If not passed, current * value is returned * @returns {String} */ value: function(val) { if (typeof val !== 'undefined' && this._value !== (val = String(val))) { this.parent._updateSource(val, this.valueRange()); this._value = val; } return this._value; }, /** * Sets of gets element name * @param {String} val New element name. If not passed, current * name is returned * @returns {String} */ name: function(val) { if (typeof val !== 'undefined' && this._name !== (val = String(val))) { this.parent._updateSource(val, this.nameRange()); this._name = val; } return this._name; }, /** * Returns position of element name token * @param {Boolean} isAbsolute Return absolute position * @returns {Number} */ namePosition: function(isAbsolute) { return this._pos(this._positions.name, isAbsolute); }, /** * Returns position of element value token * @param {Boolean} isAbsolute Return absolute position * @returns {Number} */ valuePosition: function(isAbsolute) { return this._pos(this._positions.value, isAbsolute); }, /** * Returns element name * @param {Boolean} isAbsolute Return absolute range * @returns {Range} */ range: function(isAbsolute) { return range.create(this.namePosition(isAbsolute), this.valueOf()); }, /** * Returns full element range, including possible indentation * @param {Boolean} isAbsolute Return absolute range * @returns {Range} */ fullRange: function(isAbsolute) { return this.range(isAbsolute); }, /** * Returns element name range * @param {Boolean} isAbsolute Return absolute range * @returns {Range} */ nameRange: function(isAbsolute) { return range.create(this.namePosition(isAbsolute), this.name()); }, /** * Returns element value range * @param {Boolean} isAbsolute Return absolute range * @returns {Range} */ valueRange: function(isAbsolute) { return range.create(this.valuePosition(isAbsolute), this.value()); }, /** * Returns current element string representation * @returns {String} */ toString: function() { return this.valueOf(); }, valueOf: function() { return this.name() + this.value(); } }; return { EditContainer: EditContainer, EditElement: EditElement, /** * Creates token that can be fed to EditElement * @param {Number} start * @param {String} value * @param {String} type * @returns */ createToken: function(start, value, type) { var obj = { start: start || 0, value: value || '', type: type }; obj.end = obj.start + obj.value.length; return obj; } }; }); },{"../assets/range":"assets\\range.js","../utils/common":"utils\\common.js","../vendor/klass":"vendor\\klass.js"}],"editTree\\css.js":[function(require,module,exports){ /** * CSS EditTree is a module that can parse a CSS rule into a tree with * convenient methods for adding, modifying and removing CSS properties. These * changes can be written back to string with respect of code formatting. */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var utils = require('../utils/common'); var editTree = require('./base'); var cssParser = require('../parser/css'); var cssSections = require('../utils/cssSections'); var range = require('../assets/range'); var stringStream = require('../assets/stringStream'); var tokenIterator = require('../assets/tokenIterator'); var defaultOptions = { styleBefore: '\n\t', styleSeparator: ': ', offset: 0 }; var reSpaceStart = /^\s+/; var reSpaceEnd = /\s+$/; var WHITESPACE_REMOVE_FROM_START = 1; var WHITESPACE_REMOVE_FROM_END = 2; /** * Modifies given range to remove whitespace from beginning * and/or from the end * @param {Range} rng Range to modify * @param {String} text Text that range belongs to * @param {Number} mask Mask indicating from which end * whitespace should be removed * @return {Range} */ function trimWhitespaceInRange(rng, text, mask) { mask = mask || (WHITESPACE_REMOVE_FROM_START | WHITESPACE_REMOVE_FROM_END); text = rng.substring(text); var m; if ((mask & WHITESPACE_REMOVE_FROM_START) && (m = text.match(reSpaceStart))) { rng.start += m[0].length; } if ((mask & WHITESPACE_REMOVE_FROM_END) && (m = text.match(reSpaceEnd))) { rng.end -= m[0].length; } // in case given range is just a whatespace if (rng.end < rng.start) { rng.end = rng.start; } return rng; } /** * Consumes CSS property and value from current token * iterator state. Offsets iterator pointer into token * that can be used for next value consmption * @param {TokenIterator} it * @param {String} text * @return {Object} Object with `name` and `value` properties * ar ranges. Value range can be zero-length. */ function consumeSingleProperty(it, text) { var name, value, end; var token = it.current(); if (!token) { return null; } // skip whitespace var ws = {'white': 1, 'line': 1, 'comment': 1}; while ((token = it.current())) { if (!(token.type in ws)) { break; } it.next(); } if (!it.hasNext()) { return null; } // consume property name token = it.current(); name = range(token.start, token.value); var isAtProperty = token.value.charAt(0) == '@'; while (token = it.next()) { name.end = token.end; if (token.type == ':' || token.type == 'white') { name.end = token.start; it.next(); if (token.type == ':' || isAtProperty) { // XXX I really ashame of this hardcode, but I need // to stop parsing if this is an SCSS mixin call, // for example: @include border-radius(10px) break; } } else if (token.type == ';' || token.type == 'line') { // there’s no value, looks like a mixin // or a special use case: // user is writing a new property or abbreviation name.end = token.start; value = range(token.start, 0); it.next(); break; } } token = it.current(); if (!value && token) { if (token.type == 'line') { lastNewline = token; } // consume value value = range(token.start, token.value); var lastNewline; while ((token = it.next())) { value.end = token.end; if (token.type == 'line') { lastNewline = token; } else if (token.type == '}' || token.type == ';') { value.end = token.start; if (token.type == ';') { end = range(token.start, token.value); } it.next(); break; } else if (token.type == ':' && lastNewline) { // A special case: // user is writing a value before existing // property, but didn’t inserted closing semi-colon. // In this case, limit value range to previous // newline value.end = lastNewline.start; it._i = it.tokens.indexOf(lastNewline); break; } } } if (!value) { value = range(name.end, 0); } return { name: trimWhitespaceInRange(name, text), value: trimWhitespaceInRange(value, text, WHITESPACE_REMOVE_FROM_START | (end ? WHITESPACE_REMOVE_FROM_END : 0)), end: end || range(value.end, 0) }; } /** * Finds parts of complex CSS value * @param {String} str * @returns {Array} Returns list of Range's */ function findParts(str) { /** @type StringStream */ var stream = stringStream.create(str); var ch; var result = []; var sep = /[\s\u00a0,;]/; var add = function() { stream.next(); result.push(range(stream.start, stream.current())); stream.start = stream.pos; }; // skip whitespace stream.eatSpace(); stream.start = stream.pos; while ((ch = stream.next())) { if (ch == '"' || ch == "'") { stream.next(); if (!stream.skipTo(ch)) break; add(); } else if (ch == '(') { // function found, may have nested function stream.backUp(1); if (!stream.skipToPair('(', ')')) break; stream.backUp(1); add(); } else { if (sep.test(ch)) { result.push(range(stream.start, stream.current().length - 1)); stream.eatWhile(sep); stream.start = stream.pos; } } } add(); return utils.unique(result.filter(function(item) { return !!item.length(); })); } /** * Parses CSS properties from given CSS source * and adds them to CSSEditContainer node * @param {CSSEditContainer} node * @param {String} source CSS source * @param {Number} offset Offset of properties subset from original source */ function consumeProperties(node, source, offset) { var list = extractPropertiesFromSource(source, offset); list.forEach(function(property) { node._children.push(new CSSEditElement(node, editTree.createToken(property.name.start, property.nameText), editTree.createToken(property.value.start, property.valueText), editTree.createToken(property.end.start, property.endText) )); }); } /** * Parses given CSS source and returns list of ranges of located CSS properties. * Normally, CSS source must contain properties only, it must be, * for example, a content of CSS selector or text between nested * CSS sections * @param {String} source CSS source * @param {Number} offset Offset of properties subset from original source. * Used to provide proper ranges of locates items */ function extractPropertiesFromSource(source, offset) { offset = offset || 0; source = source.replace(reSpaceEnd, ''); var out = []; if (!source) { return out; } var tokens = cssParser.parse(source); var it = tokenIterator.create(tokens); var property; while ((property = consumeSingleProperty(it, source))) { out.push({ nameText: property.name.substring(source), name: property.name.shift(offset), valueText: property.value.substring(source), value: property.value.shift(offset), endText: property.end.substring(source), end: property.end.shift(offset) }); } return out; } /** * @class * @extends EditContainer */ var CSSEditContainer = editTree.EditContainer.extend({ initialize: function(source, options) { utils.extend(this.options, defaultOptions, options); if (Array.isArray(source)) { source = cssParser.toSource(source); } var allRules = cssSections.findAllRules(source); var currentRule = allRules.shift(); // keep top-level rules only since they will // be parsed by nested CSSEditContainer call var topLevelRules = []; allRules.forEach(function(r) { var isTopLevel = !utils.find(topLevelRules, function(tr) { return tr.contains(r); }); if (isTopLevel) { topLevelRules.push(r); } }); var selectorRange = range.create2(currentRule.start, currentRule._selectorEnd); this._name = selectorRange.substring(source); this._positions.name = selectorRange.start; this._positions.contentStart = currentRule._contentStart + 1; var sectionOffset = currentRule._contentStart + 1; var sectionEnd = currentRule.end - 1; // parse properties between nested rules // and add nested rules as children var that = this; topLevelRules.forEach(function(r) { consumeProperties(that, source.substring(sectionOffset, r.start), sectionOffset); var opt = utils.extend({}, that.options, {offset: r.start + that.options.offset}); // XXX I think I don’t need nested containers here // They should be handled separately // that._children.push(new CSSEditContainer(r.substring(source), opt)); sectionOffset = r.end; }); // consume the rest of data consumeProperties(this, source.substring(sectionOffset, currentRule.end - 1), sectionOffset); this._saveStyle(); }, /** * Remembers all styles of properties * @private */ _saveStyle: function() { var start = this._positions.contentStart; var source = this.source; this.list().forEach(function(p) { if (p.type === 'container') { return; } p.styleBefore = source.substring(start, p.namePosition()); // a small hack here: // Sometimes users add empty lines before properties to logically // separate groups of properties. In this case, a blind copy of // characters between rules may lead to undesired behavior, // especially when current rule is duplicated or used as a donor // to create new rule. // To solve this issue, we‘ll take only last newline indentation var lines = utils.splitByLines(p.styleBefore); if (lines.length > 1) { p.styleBefore = '\n' + lines[lines.length - 1]; } p.styleSeparator = source.substring(p.nameRange().end, p.valuePosition()); // graceful and naive comments removal var parts = p.styleBefore.split('*/'); p.styleBefore = parts[parts.length - 1]; p.styleSeparator = p.styleSeparator.replace(/\/\*.*?\*\//g, ''); start = p.range().end; }); }, /** * Returns position of element name token * @param {Boolean} isAbsolute Return absolute position * @returns {Number} */ namePosition: function(isAbsolute) { return this._pos(this._positions.name, isAbsolute); }, /** * Returns position of element value token * @param {Boolean} isAbsolute Return absolute position * @returns {Number} */ valuePosition: function(isAbsolute) { return this._pos(this._positions.contentStart, isAbsolute); }, /** * Returns element value range * @param {Boolean} isAbsolute Return absolute range * @returns {Range} */ valueRange: function(isAbsolute) { return range.create2(this.valuePosition(isAbsolute), this._pos(this.valueOf().length, isAbsolute) - 1); }, /** * Adds new CSS property * @param {String} name Property name * @param {String} value Property value * @param {Number} pos Position at which to insert new property. By * default the property is inserted at the end of rule * @returns {CSSEditProperty} */ add: function(name, value, pos) { var list = this.list(); var start = this._positions.contentStart; var styles = utils.pick(this.options, 'styleBefore', 'styleSeparator'); if (typeof pos === 'undefined') { pos = list.length; } /** @type CSSEditProperty */ var donor = list[pos]; if (donor) { start = donor.fullRange().start; } else if ((donor = list[pos - 1])) { // make sure that donor has terminating semicolon donor.end(';'); start = donor.range().end; } if (donor) { styles = utils.pick(donor, 'styleBefore', 'styleSeparator'); } var nameToken = editTree.createToken(start + styles.styleBefore.length, name); var valueToken = editTree.createToken(nameToken.end + styles.styleSeparator.length, value); var property = new CSSEditElement(this, nameToken, valueToken, editTree.createToken(valueToken.end, ';')); utils.extend(property, styles); // write new property into the source this._updateSource(property.styleBefore + property.toString(), start); // insert new property this._children.splice(pos, 0, property); return property; } }); /** * @class * @type CSSEditElement * @constructor */ var CSSEditElement = editTree.EditElement.extend({ initialize: function(rule, name, value, end) { this.styleBefore = rule.options.styleBefore; this.styleSeparator = rule.options.styleSeparator; this._end = end.value; this._positions.end = end.start; }, /** * Returns ranges of complex value parts * @returns {Array} Returns null if value is not complex */ valueParts: function(isAbsolute) { var parts = findParts(this.value()); if (isAbsolute) { var offset = this.valuePosition(true); parts.forEach(function(p) { p.shift(offset); }); } return parts; }, /** * Sets of gets element value. * When setting value, this implementation will ensure that your have * proper name-value separator * @param {String} val New element value. If not passed, current * value is returned * @returns {String} */ value: function(val) { var isUpdating = typeof val !== 'undefined'; var allItems = this.parent.list(); if (isUpdating && this.isIncomplete()) { var self = this; var donor = utils.find(allItems, function(item) { return item !== self && !item.isIncomplete(); }); this.styleSeparator = donor ? donor.styleSeparator : this.parent.options.styleSeparator; this.parent._updateSource(this.styleSeparator, range(this.valueRange().start, 0)); } var value = this.constructor.__super__.value.apply(this, arguments); if (isUpdating) { // make sure current property has terminating semi-colon // if it’s not the last one var ix = allItems.indexOf(this); if (ix !== allItems.length - 1 && !this.end()) { this.end(';'); } } return value; }, /** * Test if current element is incomplete, e.g. has no explicit * name-value separator * @return {Boolean} [description] */ isIncomplete: function() { return this.nameRange().end === this.valueRange().start; }, /** * Sets of gets property end value (basically, it's a semicolon) * @param {String} val New end value. If not passed, current * value is returned */ end: function(val) { if (typeof val !== 'undefined' && this._end !== val) { this.parent._updateSource(val, this._positions.end, this._positions.end + this._end.length); this._end = val; } return this._end; }, /** * Returns full rule range, with indentation * @param {Boolean} isAbsolute Return absolute range (with respect of * rule offset) * @returns {Range} */ fullRange: function(isAbsolute) { var r = this.range(isAbsolute); r.start -= this.styleBefore.length; return r; }, /** * Returns item string representation * @returns {String} */ valueOf: function() { return this.name() + this.styleSeparator + this.value() + this.end(); } }); return { /** * Parses CSS rule into editable tree * @param {String} source * @param {Object} options * @memberOf emmet.cssEditTree * @returns {EditContainer} */ parse: function(source, options) { return new CSSEditContainer(source, options); }, /** * Extract and parse CSS rule from specified position in content * @param {String} content CSS source code * @param {Number} pos Character position where to start source code extraction * @returns {EditContainer} */ parseFromPosition: function(content, pos, isBackward) { var bounds = cssSections.locateRule(content, pos, isBackward); if (!bounds || !bounds.inside(pos)) { // no matching CSS rule or caret outside rule bounds return null; } return this.parse(bounds.substring(content), { offset: bounds.start }); }, /** * Locates CSS property in given CSS code fragment under specified character position * @param {String} css CSS code or parsed CSSEditContainer * @param {Number} pos Character position where to search CSS property * @return {CSSEditElement} */ propertyFromPosition: function(css, pos) { var cssProp = null; /** @type EditContainer */ var cssRule = typeof css === 'string' ? this.parseFromPosition(css, pos, true) : css; if (cssRule) { cssProp = cssRule.itemFromPosition(pos, true); if (!cssProp) { // in case user just started writing CSS property // and didn't include semicolon–try another approach cssProp = utils.find(cssRule.list(), function(elem) { return elem.range(true).end == pos; }); } } return cssProp; }, /** * Removes vendor prefix from CSS property * @param {String} name CSS property * @return {String} */ baseName: function(name) { return name.replace(/^\s*\-\w+\-/, ''); }, /** * Finds parts of complex CSS value * @param {String} str * @returns {Array} */ findParts: findParts, extractPropertiesFromSource: extractPropertiesFromSource }; }); },{"../assets/range":"assets\\range.js","../assets/stringStream":"assets\\stringStream.js","../assets/tokenIterator":"assets\\tokenIterator.js","../parser/css":"parser\\css.js","../utils/common":"utils\\common.js","../utils/cssSections":"utils\\cssSections.js","./base":"editTree\\base.js"}],"editTree\\xml.js":[function(require,module,exports){ /** * XML EditTree is a module that can parse an XML/HTML element into a tree with * convenient methods for adding, modifying and removing attributes. These * changes can be written back to string with respect of code formatting. */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var editTree = require('./base'); var xmlParser = require('../parser/xml'); var range = require('../assets/range'); var utils = require('../utils/common'); var defaultOptions = { styleBefore: ' ', styleSeparator: '=', styleQuote: '"', offset: 0 }; var startTag = /^<([\w\:\-]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/m; var XMLEditContainer = editTree.EditContainer.extend({ initialize: function(source, options) { utils.defaults(this.options, defaultOptions); this._positions.name = 1; var attrToken = null; var tokens = xmlParser.parse(source); tokens.forEach(function(token) { token.value = range.create(token).substring(source); switch (token.type) { case 'tag': if (/^<[^\/]+/.test(token.value)) { this._name = token.value.substring(1); } break; case 'attribute': // add empty attribute if (attrToken) { this._children.push(new XMLEditElement(this, attrToken)); } attrToken = token; break; case 'string': this._children.push(new XMLEditElement(this, attrToken, token)); attrToken = null; break; } }, this); if (attrToken) { this._children.push(new XMLEditElement(this, attrToken)); } this._saveStyle(); }, /** * Remembers all styles of properties * @private */ _saveStyle: function() { var start = this.nameRange().end; var source = this.source; this.list().forEach(function(p) { p.styleBefore = source.substring(start, p.namePosition()); if (p.valuePosition() !== -1) { p.styleSeparator = source.substring(p.namePosition() + p.name().length, p.valuePosition() - p.styleQuote.length); } start = p.range().end; }); }, /** * Adds new attribute * @param {String} name Property name * @param {String} value Property value * @param {Number} pos Position at which to insert new property. By * default the property is inserted at the end of rule */ add: function(name, value, pos) { var list = this.list(); var start = this.nameRange().end; var styles = utils.pick(this.options, 'styleBefore', 'styleSeparator', 'styleQuote'); if (typeof pos === 'undefined') { pos = list.length; } /** @type XMLEditAttribute */ var donor = list[pos]; if (donor) { start = donor.fullRange().start; } else if ((donor = list[pos - 1])) { start = donor.range().end; } if (donor) { styles = utils.pick(donor, 'styleBefore', 'styleSeparator', 'styleQuote'); } value = styles.styleQuote + value + styles.styleQuote; var attribute = new XMLEditElement(this, editTree.createToken(start + styles.styleBefore.length, name), editTree.createToken(start + styles.styleBefore.length + name.length + styles.styleSeparator.length, value) ); utils.extend(attribute, styles); // write new attribute into the source this._updateSource(attribute.styleBefore + attribute.toString(), start); // insert new attribute this._children.splice(pos, 0, attribute); return attribute; }, /** * A special case of attribute editing: adds class value to existing * `class` attribute * @param {String} value */ addClass: function(value) { var attr = this.get('class'); value = utils.trim(value); if (!attr) { return this.add('class', value); } var classVal = attr.value(); var classList = ' ' + classVal.replace(/\n/g, ' ') + ' '; if (!~classList.indexOf(' ' + value + ' ')) { attr.value(classVal + ' ' + value); } }, /** * A special case of attribute editing: removes class value from existing * `class` attribute * @param {String} value */ removeClass: function(value) { var attr = this.get('class'); value = utils.trim(value); if (!attr) { return; } var reClass = new RegExp('(^|\\s+)' + utils.escapeForRegexp(value)); var classVal = attr.value().replace(reClass, ''); if (!utils.trim(classVal)) { this.remove('class'); } else { attr.value(classVal); } } }); var XMLEditElement = editTree.EditElement.extend({ initialize: function(parent, nameToken, valueToken) { this.styleBefore = parent.options.styleBefore; this.styleSeparator = parent.options.styleSeparator; var value = '', quote = parent.options.styleQuote; if (valueToken) { value = valueToken.value; quote = value.charAt(0); if (quote == '"' || quote == "'") { value = value.substring(1); } else { quote = ''; } if (quote && value.charAt(value.length - 1) == quote) { value = value.substring(0, value.length - 1); } } this.styleQuote = quote; this._value = value; this._positions.value = valueToken ? valueToken.start + quote.length : -1; }, /** * Returns full rule range, with indentation * @param {Boolean} isAbsolute Return absolute range (with respect of * rule offset) * @returns {Range} */ fullRange: function(isAbsolute) { var r = this.range(isAbsolute); r.start -= this.styleBefore.length; return r; }, valueOf: function() { return this.name() + this.styleSeparator + this.styleQuote + this.value() + this.styleQuote; } }); return { /** * Parses HTML element into editable tree * @param {String} source * @param {Object} options * @memberOf emmet.htmlEditTree * @returns {EditContainer} */ parse: function(source, options) { return new XMLEditContainer(source, options); }, /** * Extract and parse HTML from specified position in content * @param {String} content CSS source code * @param {Number} pos Character position where to start source code extraction * @returns {XMLEditElement} */ parseFromPosition: function(content, pos, isBackward) { var bounds = this.extractTag(content, pos, isBackward); if (!bounds || !bounds.inside(pos)) // no matching HTML tag or caret outside tag bounds return null; return this.parse(bounds.substring(content), { offset: bounds.start }); }, /** * Extracts nearest HTML tag range from content, starting at * pos position * @param {String} content * @param {Number} pos * @param {Boolean} isBackward * @returns {Range} */ extractTag: function(content, pos, isBackward) { var len = content.length, i; // max extraction length. I don't think there may be tags larger // than 2000 characters length var maxLen = Math.min(2000, len); /** @type Range */ var r = null; var match = function(pos) { var m; if (content.charAt(pos) == '<' && (m = content.substr(pos, maxLen).match(startTag))) return range.create(pos, m[0]); }; // lookup backward, in case we are inside tag already for (i = pos; i >= 0; i--) { if ((r = match(i))) break; } if (r && (r.inside(pos) || isBackward)) return r; if (!r && isBackward) return null; // search forward for (i = pos; i < len; i++) { if ((r = match(i))) return r; } } }; }); },{"../assets/range":"assets\\range.js","../parser/xml":"parser\\xml.js","../utils/common":"utils\\common.js","./base":"editTree\\base.js"}],"filter\\bem.js":[function(require,module,exports){ /** * Filter for aiding of writing elements with complex class names as described * in Yandex's BEM (Block, Element, Modifier) methodology. This filter will * automatically inherit block and element names from parent elements and insert * them into child element classes */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var htmlFilter = require('./html'); var prefs = require('../assets/preferences'); var abbreviationUtils = require('../utils/abbreviation'); var utils = require('../utils/common'); prefs.define('bem.elementSeparator', '__', 'Class name’s element separator.'); prefs.define('bem.modifierSeparator', '_', 'Class name’s modifier separator.'); prefs.define('bem.shortElementPrefix', '-', 'Symbol for describing short “block-element” notation. Class names ' + 'prefixed with this symbol will be treated as element name for parent‘s ' + 'block name. Each symbol instance traverses one level up in parsed ' + 'tree for block name lookup. Empty value will disable short notation.'); var shouldRunHtmlFilter = false; function getSeparators() { return { element: prefs.get('bem.elementSeparator'), modifier: prefs.get('bem.modifierSeparator') }; } /** * @param {AbbreviationNode} item */ function bemParse(item) { if (abbreviationUtils.isSnippet(item)) return item; // save BEM stuff in cache for faster lookups item.__bem = { block: '', element: '', modifier: '' }; var classNames = normalizeClassName(item.attribute('class')).split(' '); // guess best match for block name var reBlockName = /^[a-z]\-/i; item.__bem.block = utils.find(classNames, function(name) { return reBlockName.test(name); }); // guessing doesn't worked, pick first class name as block name if (!item.__bem.block) { reBlockName = /^[a-z]/i; item.__bem.block = utils.find(classNames, function(name) { return reBlockName.test(name); }) || ''; } classNames = classNames.map(function(name) { return processClassName(name, item); }); classNames = utils.unique(utils.flatten(classNames)).join(' '); if (classNames) { item.attribute('class', classNames); } return item; } /** * @param {String} className * @returns {String} */ function normalizeClassName(className) { className = (' ' + (className || '') + ' ').replace(/\s+/g, ' '); var shortSymbol = prefs.get('bem.shortElementPrefix'); if (shortSymbol) { var re = new RegExp('\\s(' + utils.escapeForRegexp(shortSymbol) + '+)', 'g'); className = className.replace(re, function(str, p1) { return ' ' + utils.repeatString(getSeparators().element, p1.length); }); } return utils.trim(className); } /** * Processes class name * @param {String} name Class name item to process * @param {AbbreviationNode} item Host node for provided class name * @returns Processed class name. May return Array of * class names */ function processClassName(name, item) { name = transformClassName(name, item, 'element'); name = transformClassName(name, item, 'modifier'); // expand class name // possible values: // * block__element // * block__element_modifier // * block__element_modifier1_modifier2 // * block_modifier var block = '', element = '', modifier = ''; var separators = getSeparators(); if (~name.indexOf(separators.element)) { var elements = name.split(separators.element); block = elements.shift(); var modifiers = elements.pop().split(separators.modifier); elements.push(modifiers.shift()); element = elements.join(separators.element); modifier = modifiers.join(separators.modifier); } else if (~name.indexOf(separators.modifier)) { var blockModifiers = name.split(separators.modifier); block = blockModifiers.shift(); modifier = blockModifiers.join(separators.modifier); } if (block || element || modifier) { if (!block) { block = item.__bem.block; } // inherit parent bem element, if exists // if (item.parent && item.parent.__bem && item.parent.__bem.element) // element = item.parent.__bem.element + separators.element + element; // produce multiple classes var prefix = block; var result = []; if (element) { prefix += separators.element + element; result.push(prefix); } else { result.push(prefix); } if (modifier) { result.push(prefix + separators.modifier + modifier); } if (!item.__bem.block || modifier) { item.__bem.block = block; } item.__bem.element = element; item.__bem.modifier = modifier; return result; } // ...otherwise, return processed or original class name return name; } /** * Low-level function to transform user-typed class name into full BEM class * @param {String} name Class name item to process * @param {AbbreviationNode} item Host node for provided class name * @param {String} entityType Type of entity to be tried to transform * ('element' or 'modifier') * @returns {String} Processed class name or original one if it can't be * transformed */ function transformClassName(name, item, entityType) { var separators = getSeparators(); var reSep = new RegExp('^(' + separators[entityType] + ')+', 'g'); if (reSep.test(name)) { var depth = 0; // parent lookup depth var cleanName = name.replace(reSep, function(str) { depth = str.length / separators[entityType].length; return ''; }); // find donor element var donor = item; while (donor.parent && depth--) { donor = donor.parent; } if (!donor || !donor.__bem) donor = item; if (donor && donor.__bem) { var prefix = donor.__bem.block; // decide if we should inherit element name // if (entityType == 'element') { // var curElem = cleanName.split(separators.modifier, 1)[0]; // if (donor.__bem.element && donor.__bem.element != curElem) // prefix += separators.element + donor.__bem.element; // } if (entityType == 'modifier' && donor.__bem.element) prefix += separators.element + donor.__bem.element; return prefix + separators[entityType] + cleanName; } } return name; } /** * Recursive function for processing tags, which extends class names * according to BEM specs: http://bem.github.com/bem-method/pages/beginning/beginning.ru.html *

* It does several things:
*
    *
  • Expands complex class name (according to BEM symbol semantics): * .block__elem_modifier → .block.block__elem.block__elem_modifier *
  • *
  • Inherits block name on child elements: * .b-block > .__el > .__el → .b-block > .b-block__el > .b-block__el__el *
  • *
  • Treats first dash symbol as '__'
  • *
  • Double underscore (or typographic '–') is also treated as an element * level lookup, e.g. ____el will search for element definition in parent’s * parent element: * .b-block > .__el1 > .____el2 → .b-block > .b-block__el1 > .b-block__el2 *
  • *
* * @param {AbbreviationNode} tree * @param {Object} profile */ function process(tree, profile) { if (tree.name) { bemParse(tree, profile); } tree.children.forEach(function(item) { process(item, profile); if (!abbreviationUtils.isSnippet(item) && item.start) { shouldRunHtmlFilter = true; } }); return tree; } return function(tree, profile) { shouldRunHtmlFilter = false; tree = process(tree, profile); // in case 'bem' filter is applied after 'html' filter: run it again // to update output if (shouldRunHtmlFilter) { tree = htmlFilter(tree, profile); } return tree; }; }); },{"../assets/preferences":"assets\\preferences.js","../utils/abbreviation":"utils\\abbreviation.js","../utils/common":"utils\\common.js","./html":"filter\\html.js"}],"filter\\comment.js":[function(require,module,exports){ /** * Comment important tags (with 'id' and 'class' attributes) */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var prefs = require('../assets/preferences'); var utils = require('../utils/common'); var template = require('../utils/template'); var abbrUtils = require('../utils/abbreviation'); var filterCore = require('./main'); prefs.define('filter.commentAfter', '\n', 'A definition of comment that should be placed after matched ' + 'element when comment filter is applied. This definition ' + 'is an ERB-style template passed to _.template() ' + 'function (see Underscore.js docs for details). In template context, ' + 'the following properties and functions are availabe:\n' + '
    ' + '
  • attr(name, before, after) – a function that outputs' + 'specified attribute value concatenated with before ' + 'and after strings. If attribute doesn\'t exists, the ' + 'empty string will be returned.
  • ' + '
  • node – current node (instance of AbbreviationNode)
  • ' + '
  • name – name of current tag
  • ' + '
  • padding – current string padding, can be used ' + 'for formatting
  • ' +'
'); prefs.define('filter.commentBefore', '', 'A definition of comment that should be placed before matched ' + 'element when comment filter is applied. ' + 'For more info, read description of filter.commentAfter ' + 'property'); prefs.define('filter.commentTrigger', 'id, class', 'A comma-separated list of attribute names that should exist in abbreviatoin ' + 'where comment should be added. If you wish to add comment for ' + 'every element, set this option to *'); /** * Add comments to tag * @param {AbbreviationNode} node */ function addComments(node, templateBefore, templateAfter) { // check if comments should be added var trigger = prefs.get('filter.commentTrigger'); if (trigger != '*') { var shouldAdd = utils.find(trigger.split(','), function(name) { return !!node.attribute(utils.trim(name)); }); if (!shouldAdd) { return; } } var ctx = { node: node, name: node.name(), padding: node.parent ? node.parent.padding : '', attr: function(name, before, after) { var attr = node.attribute(name); if (attr) { return (before || '') + attr + (after || ''); } return ''; } }; var nodeBefore = templateBefore ? templateBefore(ctx) : ''; var nodeAfter = templateAfter ? templateAfter(ctx) : ''; node.start = node.start.replace(//, '>' + nodeAfter); } function process(tree, before, after) { tree.children.forEach(function(item) { if (abbrUtils.isBlock(item)) { addComments(item, before, after); } process(item, before, after); }); return tree; } return function(tree) { var templateBefore = template(prefs.get('filter.commentBefore')); var templateAfter = template(prefs.get('filter.commentAfter')); return process(tree, templateBefore, templateAfter); }; }); },{"../assets/preferences":"assets\\preferences.js","../utils/abbreviation":"utils\\abbreviation.js","../utils/common":"utils\\common.js","../utils/template":"utils\\template.js","./main":"filter\\main.js"}],"filter\\css.js":[function(require,module,exports){ /** * Filter for outputting CSS and alike */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { /** * Test if passed item is very first child in parsed tree * @param {AbbreviationNode} item */ function isVeryFirstChild(item) { return item.parent && !item.parent.parent && !item.index(); } return function process(tree, profile, level) { level = level || 0; tree.children.forEach(function(item) { if (!isVeryFirstChild(item) && profile.tag_nl !== false) { item.start = '\n' + item.start; } process(item, profile, level + 1); }); return tree; }; }); },{}],"filter\\escape.js":[function(require,module,exports){ /** * Filter for escaping unsafe XML characters: <, >, & */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var charMap = { '<': '<', '>': '>', '&': '&' }; function escapeChars(str) { return str.replace(/([<>&])/g, function(str, p1){ return charMap[p1]; }); } return function process(tree) { tree.children.forEach(function(item) { item.start = escapeChars(item.start); item.end = escapeChars(item.end); item.content = escapeChars(item.content); process(item); }); return tree; }; }); },{}],"filter\\format.js":[function(require,module,exports){ /** * Generic formatting filter: creates proper indentation for each tree node, * placing "%s" placeholder where the actual output should be. You can use * this filter to preformat tree and then replace %s placeholder to whatever you * need. This filter should't be called directly from editor as a part * of abbreviation. */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var utils = require('../utils/common'); var abbrUtils = require('../utils/abbreviation'); var prefs = require('../assets/preferences'); var resources = require('../assets/resources'); prefs.define('format.noIndentTags', 'html', 'A comma-separated list of tag names that should not get inner indentation.'); prefs.define('format.forceIndentationForTags', 'body', 'A comma-separated list of tag names that should always get inner indentation.'); var placeholder = '%s'; /** * Get indentation for given node * @param {AbbreviationNode} node * @returns {String} */ function getIndentation(node) { var items = prefs.getArray('format.noIndentTags') || []; if (~items.indexOf(node.name())) { return ''; } return '\t'; } /** * Test if passed node has block-level sibling element * @param {AbbreviationNode} item * @return {Boolean} */ function hasBlockSibling(item) { return item.parent && abbrUtils.hasBlockChildren(item.parent); } /** * Test if passed item is very first child in parsed tree * @param {AbbreviationNode} item */ function isVeryFirstChild(item) { return item.parent && !item.parent.parent && !item.index(); } /** * Check if a newline should be added before element * @param {AbbreviationNode} node * @param {OutputProfile} profile * @return {Boolean} */ function shouldAddLineBreak(node, profile) { if (profile.tag_nl === true || abbrUtils.isBlock(node)) return true; if (!node.parent || !profile.inline_break) return false; // check if there are required amount of adjacent inline element return shouldFormatInline(node.parent, profile); } /** * Need to add newline because item has too many inline children * @param {AbbreviationNode} node * @param {OutputProfile} profile */ function shouldBreakChild(node, profile) { // we need to test only one child element, because // hasBlockChildren() method will do the rest return node.children.length && shouldAddLineBreak(node.children[0], profile); } function shouldFormatInline(node, profile) { var nodeCount = 0; return !!utils.find(node.children, function(child) { if (child.isTextNode() || !abbrUtils.isInline(child)) nodeCount = 0; else if (abbrUtils.isInline(child)) nodeCount++; if (nodeCount >= profile.inline_break) return true; }); } function isRoot(item) { return !item.parent; } /** * Processes element with matched resource of type snippet * @param {AbbreviationNode} item * @param {OutputProfile} profile */ function processSnippet(item, profile) { item.start = item.end = ''; if (!isVeryFirstChild(item) && profile.tag_nl !== false && shouldAddLineBreak(item, profile)) { // check if we’re not inside inline element if (isRoot(item.parent) || !abbrUtils.isInline(item.parent)) { item.start = '\n' + item.start; } } return item; } /** * Check if we should add line breaks inside inline element * @param {AbbreviationNode} node * @param {OutputProfile} profile * @return {Boolean} */ function shouldBreakInsideInline(node, profile) { var hasBlockElems = node.children.some(function(child) { if (abbrUtils.isSnippet(child)) return false; return !abbrUtils.isInline(child); }); if (!hasBlockElems) { return shouldFormatInline(node, profile); } return true; } /** * Processes element with tag type * @param {AbbreviationNode} item * @param {OutputProfile} profile */ function processTag(item, profile) { item.start = item.end = placeholder; var isUnary = abbrUtils.isUnary(item); var nl = '\n'; var indent = getIndentation(item); // formatting output if (profile.tag_nl !== false) { var forceNl = profile.tag_nl === true && (profile.tag_nl_leaf || item.children.length); if (!forceNl) { var forceIndentTags = prefs.getArray('format.forceIndentationForTags') || []; forceNl = ~forceIndentTags.indexOf(item.name()); } // formatting block-level elements if (!item.isTextNode()) { if (shouldAddLineBreak(item, profile)) { // - do not indent the very first element // - do not indent first child of a snippet if (!isVeryFirstChild(item) && (!abbrUtils.isSnippet(item.parent) || item.index())) item.start = nl + item.start; if (abbrUtils.hasBlockChildren(item) || shouldBreakChild(item, profile) || (forceNl && !isUnary)) item.end = nl + item.end; if (abbrUtils.hasTagsInContent(item) || (forceNl && !item.children.length && !isUnary)) item.start += nl + indent; } else if (abbrUtils.isInline(item) && hasBlockSibling(item) && !isVeryFirstChild(item)) { item.start = nl + item.start; } else if (abbrUtils.isInline(item) && shouldBreakInsideInline(item, profile)) { item.end = nl + item.end; } item.padding = indent; } } return item; } /** * Processes simplified tree, making it suitable for output as HTML structure * @param {AbbreviationNode} tree * @param {OutputProfile} profile * @param {Number} level Depth level */ return function process(tree, profile, level) { level = level || 0; tree.children.forEach(function(item) { if (abbrUtils.isSnippet(item)) { processSnippet(item, profile, level); } else { processTag(item, profile, level); } process(item, profile, level + 1); }); return tree; }; }); },{"../assets/preferences":"assets\\preferences.js","../assets/resources":"assets\\resources.js","../utils/abbreviation":"utils\\abbreviation.js","../utils/common":"utils\\common.js"}],"filter\\haml.js":[function(require,module,exports){ /** * Filter for producing HAML code from abbreviation. */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var utils = require('../utils/common'); var abbrUtils = require('../utils/abbreviation'); var formatFilter = require('./format'); function transformClassName(className) { return utils.trim(className).replace(/\s+/g, '.'); } /** * Condenses all "data-" attributes into a single entry. * HAML allows data attributes to be ouputted as a sub-hash * of `:data` key * @param {Array} attrs * @return {Array} */ function condenseDataAttrs(attrs) { var out = [], data = null; var reData = /^data-/i; attrs.forEach(function(attr) { if (reData.test(attr.name)) { if (!data) { data = []; out.push({ name: 'data', value: data }); } data.push(utils.extend({}, attr, {name: attr.name.replace(reData, '')})); } else { out.push(attr); } }); return out; } function stringifyAttrs(attrs, profile) { var attrQuote = profile.attributeQuote(); return '{' + attrs.map(function(attr) { var value = attrQuote + attr.value + attrQuote; if (Array.isArray(attr.value)) { value = stringifyAttrs(attr.value, profile); } else if (attr.isBoolean) { value = 'true'; } return ':' + attr.name + ' => ' + value }).join(', ') + '}'; } /** * Creates HAML attributes string from tag according to profile settings * @param {AbbreviationNode} tag * @param {Object} profile */ function makeAttributesString(tag, profile) { var attrs = ''; var otherAttrs = []; var attrQuote = profile.attributeQuote(); var cursor = profile.cursor(); tag.attributeList().forEach(function(a) { var attrName = profile.attributeName(a.name); switch (attrName.toLowerCase()) { // use short notation for ID and CLASS attributes case 'id': attrs += '#' + (a.value || cursor); break; case 'class': attrs += '.' + transformClassName(a.value || cursor); break; // process other attributes default: otherAttrs.push({ name: attrName, value: a.value || cursor, isBoolean: profile.isBoolean(a.name, a.value) }); } }); if (otherAttrs.length) { attrs += stringifyAttrs(condenseDataAttrs(otherAttrs), profile); } return attrs; } /** * Processes element with tag type * @param {AbbreviationNode} item * @param {OutputProfile} profile */ function processTag(item, profile) { if (!item.parent) // looks like it's root element return item; var attrs = makeAttributesString(item, profile); var cursor = profile.cursor(); var isUnary = abbrUtils.isUnary(item); var selfClosing = profile.self_closing_tag && isUnary ? '/' : ''; var start= ''; // define tag name var tagName = '%' + profile.tagName(item.name()); if (tagName.toLowerCase() == '%div' && attrs && attrs.indexOf('{') == -1) // omit div tag tagName = ''; item.end = ''; start = tagName + attrs + selfClosing; if (item.content && !/^\s/.test(item.content)) { item.content = ' ' + item.content; } var placeholder = '%s'; // We can't just replace placeholder with new value because // JavaScript will treat double $ character as a single one, assuming // we're using RegExp literal. item.start = utils.replaceSubstring(item.start, start, item.start.indexOf(placeholder), placeholder); if (!item.children.length && !isUnary) item.start += cursor; return item; } return function process(tree, profile, level) { level = level || 0; if (!level) { tree = formatFilter(tree, '_format', profile); } tree.children.forEach(function(item) { if (!abbrUtils.isSnippet(item)) { processTag(item, profile, level); } process(item, profile, level + 1); }); return tree; }; }); },{"../utils/abbreviation":"utils\\abbreviation.js","../utils/common":"utils\\common.js","./format":"filter\\format.js"}],"filter\\html.js":[function(require,module,exports){ /** * Filter that produces HTML tree */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var abbrUtils = require('../utils/abbreviation'); var utils = require('../utils/common'); var tabStops = require('../assets/tabStops'); var formatFilter = require('./format'); /** * Creates HTML attributes string from tag according to profile settings * @param {AbbreviationNode} node * @param {OutputProfile} profile */ function makeAttributesString(node, profile) { var attrQuote = profile.attributeQuote(); var cursor = profile.cursor(); return node.attributeList().map(function(a) { var isBoolean = profile.isBoolean(a.name, a.value); var attrName = profile.attributeName(a.name); var attrValue = isBoolean ? attrName : a.value; if (isBoolean && profile.allowCompactBoolean()) { return ' ' + attrName; } return ' ' + attrName + '=' + attrQuote + (attrValue || cursor) + attrQuote; }).join(''); } /** * Processes element with tag type * @param {AbbreviationNode} item * @param {OutputProfile} profile */ function processTag(item, profile) { if (!item.parent) { // looks like it's root element return item; } var attrs = makeAttributesString(item, profile); var cursor = profile.cursor(); var isUnary = abbrUtils.isUnary(item); var start = ''; var end = ''; // define opening and closing tags if (!item.isTextNode()) { var tagName = profile.tagName(item.name()); if (isUnary) { start = '<' + tagName + attrs + profile.selfClosing() + '>'; item.end = ''; } else { start = '<' + tagName + attrs + '>'; end = ''; } } var placeholder = '%s'; // We can't just replace placeholder with new value because // JavaScript will treat double $ character as a single one, assuming // we're using RegExp literal. item.start = utils.replaceSubstring(item.start, start, item.start.indexOf(placeholder), placeholder); item.end = utils.replaceSubstring(item.end, end, item.end.indexOf(placeholder), placeholder); // should we put caret placeholder after opening tag? if ( !item.children.length && !isUnary && !~item.content.indexOf(cursor) && !tabStops.extract(item.content).tabstops.length ) { item.start += cursor; } return item; } return function process(tree, profile, level) { level = level || 0; if (!level) { tree = formatFilter(tree, profile, level) } tree.children.forEach(function(item) { if (!abbrUtils.isSnippet(item)) { processTag(item, profile, level); } process(item, profile, level + 1); }); return tree; }; }); },{"../assets/tabStops":"assets\\tabStops.js","../utils/abbreviation":"utils\\abbreviation.js","../utils/common":"utils\\common.js","./format":"filter\\format.js"}],"filter\\jade.js":[function(require,module,exports){ /** * Filter for producing Jade code from abbreviation. */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var utils = require('../utils/common'); var abbrUtils = require('../utils/abbreviation'); var formatFilter = require('./format'); var tabStops = require('../assets/tabStops'); var profile = require('../assets/profile'); var reNl = /[\n\r]/; var reIndentedText = /^\s*\|/; var reSpace = /^\s/; function transformClassName(className) { return utils.trim(className).replace(/\s+/g, '.'); } function stringifyAttrs(attrs, profile) { var attrQuote = profile.attributeQuote(); return '(' + attrs.map(function(attr) { if (attr.isBoolean) { return attr.name; } return attr.name + '=' + attrQuote + attr.value + attrQuote; }).join(', ') + ')'; } /** * Creates HAML attributes string from tag according to profile settings * @param {AbbreviationNode} tag * @param {Object} profile */ function makeAttributesString(tag, profile) { var attrs = ''; var otherAttrs = []; var attrQuote = profile.attributeQuote(); var cursor = profile.cursor(); tag.attributeList().forEach(function(a) { var attrName = profile.attributeName(a.name); switch (attrName.toLowerCase()) { // use short notation for ID and CLASS attributes case 'id': attrs += '#' + (a.value || cursor); break; case 'class': attrs += '.' + transformClassName(a.value || cursor); break; // process other attributes default: otherAttrs.push({ name: attrName, value: a.value || cursor, isBoolean: profile.isBoolean(a.name, a.value) }); } }); if (otherAttrs.length) { attrs += stringifyAttrs(otherAttrs, profile); } return attrs; } function processTagContent(item) { if (!item.content) { return; } var content = tabStops.replaceVariables(item.content, function(str, name) { if (name === 'nl' || name === 'newline') { return '\n'; } return str; }); if (reNl.test(content) && !reIndentedText.test(content)) { // multiline content: pad it with indentation and pipe var pad = '| '; item.content = '\n' + pad + utils.padString(content, pad); } else if (!reSpace.test(content)) { item.content = ' ' + content; } } /** * Processes element with tag type * @param {AbbreviationNode} item * @param {OutputProfile} profile */ function processTag(item, profile) { if (!item.parent) // looks like it's a root (empty) element return item; var attrs = makeAttributesString(item, profile); var cursor = profile.cursor(); var isUnary = abbrUtils.isUnary(item); // define tag name var tagName = profile.tagName(item.name()); if (tagName.toLowerCase() == 'div' && attrs && attrs.charAt(0) != '(') // omit div tag tagName = ''; item.end = ''; var start = tagName + attrs; processTagContent(item); var placeholder = '%s'; // We can't just replace placeholder with new value because // JavaScript will treat double $ character as a single one, assuming // we're using RegExp literal. item.start = utils.replaceSubstring(item.start, start, item.start.indexOf(placeholder), placeholder); if (!item.children.length && !isUnary) item.start += cursor; return item; } return function process(tree, curProfile, level) { level = level || 0; if (!level) { // always format with `xml` profile since // Jade requires all tags to be on separate lines tree = formatFilter(tree, profile.get('xml')); } tree.children.forEach(function(item) { if (!abbrUtils.isSnippet(item)) { processTag(item, curProfile, level); } process(item, curProfile, level + 1); }); return tree; }; }); },{"../assets/profile":"assets\\profile.js","../assets/tabStops":"assets\\tabStops.js","../utils/abbreviation":"utils\\abbreviation.js","../utils/common":"utils\\common.js","./format":"filter\\format.js"}],"filter\\jsx.js":[function(require,module,exports){ /** * A filter for React.js (JSX): * ranames attributes like `class` and `for` * for proper representation in JSX */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var attrMap = { 'class': 'className', 'for': 'htmlFor' }; return function process(tree) { tree.children.forEach(function(item) { item._attributes.forEach(function(attr) { if (attr.name in attrMap) { attr.name = attrMap[attr.name] } }); process(item); }); return tree; }; }); },{}],"filter\\main.js":[function(require,module,exports){ /** * Module for handling filters */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var utils = require('../utils/common'); var profile = require('../assets/profile'); var resources = require('../assets/resources'); /** List of registered filters */ var registeredFilters = { html: require('./html'), haml: require('./haml'), jade: require('./jade'), jsx: require('./jsx'), slim: require('./slim'), xsl: require('./xsl'), css: require('./css'), bem: require('./bem'), c: require('./comment'), e: require('./escape'), s: require('./singleLine'), t: require('./trim') }; /** Filters that will be applied for unknown syntax */ var basicFilters = 'html'; function list(filters) { if (!filters) return []; if (typeof filters === 'string') { return filters.split(/[\|,]/g); } return filters; } return { /** * Register new filter * @param {String} name Filter name * @param {Function} fn Filter function */ add: function(name, fn) { registeredFilters[name] = fn; }, /** * Apply filters for final output tree * @param {AbbreviationNode} tree Output tree * @param {Array} filters List of filters to apply. Might be a * String * @param {Object} profile Output profile, defined in profile * module. Filters defined it profile are not used, profile * is passed to filter function * @memberOf emmet.filters * @returns {AbbreviationNode} */ apply: function(tree, filters, profileName) { profileName = profile.get(profileName); list(filters).forEach(function(filter) { var name = utils.trim(filter.toLowerCase()); if (name && name in registeredFilters) { tree = registeredFilters[name](tree, profileName); } }); return tree; }, /** * Composes list of filters that should be applied to a tree, based on * passed data * @param {String} syntax Syntax name ('html', 'css', etc.) * @param {Object} profile Output profile * @param {String} additionalFilters List or pipe-separated * string of additional filters to apply * @returns {Array} */ composeList: function(syntax, profileName, additionalFilters) { profileName = profile.get(profileName); var filters = list(profileName.filters || resources.findItem(syntax, 'filters') || basicFilters); if (profileName.extraFilters) { filters = filters.concat(list(profileName.extraFilters)); } if (additionalFilters) { filters = filters.concat(list(additionalFilters)); } if (!filters || !filters.length) { // looks like unknown syntax, apply basic filters filters = list(basicFilters); } return filters; }, /** * Extracts filter list from abbreviation * @param {String} abbr * @returns {Array} Array with cleaned abbreviation and list of * extracted filters */ extract: function(abbr) { var filters = ''; abbr = abbr.replace(/\|([\w\|\-]+)$/, function(str, p1){ filters = p1; return ''; }); return [abbr, list(filters)]; } }; }); },{"../assets/profile":"assets\\profile.js","../assets/resources":"assets\\resources.js","../utils/common":"utils\\common.js","./bem":"filter\\bem.js","./comment":"filter\\comment.js","./css":"filter\\css.js","./escape":"filter\\escape.js","./haml":"filter\\haml.js","./html":"filter\\html.js","./jade":"filter\\jade.js","./jsx":"filter\\jsx.js","./singleLine":"filter\\singleLine.js","./slim":"filter\\slim.js","./trim":"filter\\trim.js","./xsl":"filter\\xsl.js"}],"filter\\singleLine.js":[function(require,module,exports){ /** * Output abbreviation on a single line (i.e. no line breaks) */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var abbrUtils = require('../utils/abbreviation'); var rePad = /^\s+/; var reNl = /[\n\r]/g; return function process(tree) { tree.children.forEach(function(item) { if (!abbrUtils.isSnippet(item)) { // remove padding from item item.start = item.start.replace(rePad, ''); item.end = item.end.replace(rePad, ''); } // remove newlines item.start = item.start.replace(reNl, ''); item.end = item.end.replace(reNl, ''); item.content = item.content.replace(reNl, ''); process(item); }); return tree; }; }); },{"../utils/abbreviation":"utils\\abbreviation.js"}],"filter\\slim.js":[function(require,module,exports){ /** * Filter for producing Jade code from abbreviation. */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var utils = require('../utils/common'); var abbrUtils = require('../utils/abbreviation'); var formatFilter = require('./format'); var tabStops = require('../assets/tabStops'); var prefs = require('../assets/preferences'); var profile = require('../assets/profile'); var reNl = /[\n\r]/; var reIndentedText = /^\s*\|/; var reSpace = /^\s/; prefs.define('slim.attributesWrapper', 'none', 'Defines how attributes will be wrapped:' + '
    ' + '
  • none – no wrapping;
  • ' + '
  • round — wrap attributes with round braces;
  • ' + '
  • square — wrap attributes with round braces;
  • ' + '
  • curly — wrap attributes with curly braces.
  • ' + '
'); function transformClassName(className) { return utils.trim(className).replace(/\s+/g, '.'); } function getAttrWrapper() { var start = ' ', end = ''; switch (prefs.get('slim.attributesWrapper')) { case 'round': start = '('; end = ')'; break; case 'square': start = '['; end = ']'; break; case 'curly': start = '{'; end = '}'; break; } return { start: start, end: end }; } function stringifyAttrs(attrs, profile) { var attrQuote = profile.attributeQuote(); var attrWrap = getAttrWrapper(); return attrWrap.start + attrs.map(function(attr) { var value = attrQuote + attr.value + attrQuote; if (attr.isBoolean) { if (!attrWrap.end) { value = 'true'; } else { return attr.name; } } return attr.name + '=' + value; }).join(' ') + attrWrap.end; } /** * Creates HAML attributes string from tag according to profile settings * @param {AbbreviationNode} tag * @param {Object} profile */ function makeAttributesString(tag, profile) { var attrs = ''; var otherAttrs = []; var attrQuote = profile.attributeQuote(); var cursor = profile.cursor(); tag.attributeList().forEach(function(a) { var attrName = profile.attributeName(a.name); switch (attrName.toLowerCase()) { // use short notation for ID and CLASS attributes case 'id': attrs += '#' + (a.value || cursor); break; case 'class': attrs += '.' + transformClassName(a.value || cursor); break; // process other attributes default: otherAttrs.push({ name: attrName, value: a.value || cursor, isBoolean: profile.isBoolean(a.name, a.value) }); } }); if (otherAttrs.length) { attrs += stringifyAttrs(otherAttrs, profile); } return attrs; } function processTagContent(item) { if (!item.content) { return; } var content = tabStops.replaceVariables(item.content, function(str, name) { if (name === 'nl' || name === 'newline') { return '\n'; } return str; }); if (reNl.test(content) && !reIndentedText.test(content)) { // multiline content: pad it with indentation and pipe var pad = ' '; item.content = '\n| ' + utils.padString(content, pad); } else if (!reSpace.test(content)) { item.content = ' ' + content; } } /** * Processes element with tag type * @param {AbbreviationNode} item * @param {OutputProfile} profile */ function processTag(item, profile) { if (!item.parent) // looks like it's a root (empty) element return item; var attrs = makeAttributesString(item, profile); var cursor = profile.cursor(); var isUnary = abbrUtils.isUnary(item); var selfClosing = profile.self_closing_tag && isUnary ? '/' : ''; // define tag name var tagName = profile.tagName(item.name()); if (tagName.toLowerCase() == 'div' && attrs && '([{'.indexOf(attrs.charAt(0)) == -1) // omit div tag tagName = ''; item.end = ''; var start = tagName + attrs + selfClosing; processTagContent(item); var placeholder = '%s'; // We can't just replace placeholder with new value because // JavaScript will treat double $ character as a single one, assuming // we're using RegExp literal. item.start = utils.replaceSubstring(item.start, start, item.start.indexOf(placeholder), placeholder); if (!item.children.length && !isUnary) item.start += cursor; return item; } return function process(tree, curProfile, level) { level = level || 0; if (!level) { // always format with `xml` profile since // Slim requires all tags to be on separate lines tree = formatFilter(tree, profile.get('xml')); } tree.children.forEach(function(item) { if (!abbrUtils.isSnippet(item)) { processTag(item, curProfile, level); } process(item, curProfile, level + 1); }); return tree; }; }); },{"../assets/preferences":"assets\\preferences.js","../assets/profile":"assets\\profile.js","../assets/tabStops":"assets\\tabStops.js","../utils/abbreviation":"utils\\abbreviation.js","../utils/common":"utils\\common.js","./format":"filter\\format.js"}],"filter\\trim.js":[function(require,module,exports){ /** * Trim filter: removes characters at the beginning of the text * content that indicates lists: numbers, #, *, -, etc. * * Useful for wrapping lists with abbreviation. */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var prefs = require('../assets/preferences'); prefs.define('filter.trimRegexp', '[\\s|\\u00a0]*[\\d|#|\\-|\*|\\u2022]+\\.?\\s*', 'Regular expression used to remove list markers (numbers, dashes, ' + 'bullets, etc.) in t (trim) filter. The trim filter ' + 'is useful for wrapping with abbreviation lists, pased from other ' + 'documents (for example, Word documents).'); function process(tree, re) { tree.children.forEach(function(item) { if (item.content) { item.content = item.content.replace(re, ''); } process(item, re); }); return tree; } return function(tree) { var re = new RegExp(prefs.get('filter.trimRegexp')); return process(tree, re); }; }); },{"../assets/preferences":"assets\\preferences.js"}],"filter\\xsl.js":[function(require,module,exports){ /** * Filter for trimming "select" attributes from some tags that contains * child elements */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var abbrUtils = require('../utils/abbreviation'); var tags = { 'xsl:variable': 1, 'xsl:with-param': 1 }; /** * Removes "select" attribute from node * @param {AbbreviationNode} node */ function trimAttribute(node) { node.start = node.start.replace(/\s+select\s*=\s*(['"]).*?\1/, ''); } return function process(tree) { tree.children.forEach(function(item) { if (!abbrUtils.isSnippet(item) && (item.name() || '').toLowerCase() in tags && item.children.length) trimAttribute(item); process(item); }); return tree; }; }); },{"../utils/abbreviation":"utils\\abbreviation.js"}],"generator\\lorem.js":[function(require,module,exports){ /** * "Lorem ipsum" text generator. Matches lipsum(num)? or * lorem(num)? abbreviation. * This code is based on Django's contribution: * https://code.djangoproject.com/browser/django/trunk/django/contrib/webdesign/lorem_ipsum.py *

* Examples to test:
* lipsum – generates 30 words text.
* lipsum*6 – generates 6 paragraphs (autowrapped with <p> element) of text.
* ol>lipsum10*5 — generates ordered list with 5 list items (autowrapped with <li> tag) * with text of 10 words on each line.
* span*3>lipsum20 – generates 3 paragraphs of 20-words text, each wrapped with <span> element. * Each paragraph phrase is unique. */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var prefs = require('../assets/preferences'); var langs = { en: { common: ['lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipisicing', 'elit'], words: ['exercitationem', 'perferendis', 'perspiciatis', 'laborum', 'eveniet', 'sunt', 'iure', 'nam', 'nobis', 'eum', 'cum', 'officiis', 'excepturi', 'odio', 'consectetur', 'quasi', 'aut', 'quisquam', 'vel', 'eligendi', 'itaque', 'non', 'odit', 'tempore', 'quaerat', 'dignissimos', 'facilis', 'neque', 'nihil', 'expedita', 'vitae', 'vero', 'ipsum', 'nisi', 'animi', 'cumque', 'pariatur', 'velit', 'modi', 'natus', 'iusto', 'eaque', 'sequi', 'illo', 'sed', 'ex', 'et', 'voluptatibus', 'tempora', 'veritatis', 'ratione', 'assumenda', 'incidunt', 'nostrum', 'placeat', 'aliquid', 'fuga', 'provident', 'praesentium', 'rem', 'necessitatibus', 'suscipit', 'adipisci', 'quidem', 'possimus', 'voluptas', 'debitis', 'sint', 'accusantium', 'unde', 'sapiente', 'voluptate', 'qui', 'aspernatur', 'laudantium', 'soluta', 'amet', 'quo', 'aliquam', 'saepe', 'culpa', 'libero', 'ipsa', 'dicta', 'reiciendis', 'nesciunt', 'doloribus', 'autem', 'impedit', 'minima', 'maiores', 'repudiandae', 'ipsam', 'obcaecati', 'ullam', 'enim', 'totam', 'delectus', 'ducimus', 'quis', 'voluptates', 'dolores', 'molestiae', 'harum', 'dolorem', 'quia', 'voluptatem', 'molestias', 'magni', 'distinctio', 'omnis', 'illum', 'dolorum', 'voluptatum', 'ea', 'quas', 'quam', 'corporis', 'quae', 'blanditiis', 'atque', 'deserunt', 'laboriosam', 'earum', 'consequuntur', 'hic', 'cupiditate', 'quibusdam', 'accusamus', 'ut', 'rerum', 'error', 'minus', 'eius', 'ab', 'ad', 'nemo', 'fugit', 'officia', 'at', 'in', 'id', 'quos', 'reprehenderit', 'numquam', 'iste', 'fugiat', 'sit', 'inventore', 'beatae', 'repellendus', 'magnam', 'recusandae', 'quod', 'explicabo', 'doloremque', 'aperiam', 'consequatur', 'asperiores', 'commodi', 'optio', 'dolor', 'labore', 'temporibus', 'repellat', 'veniam', 'architecto', 'est', 'esse', 'mollitia', 'nulla', 'a', 'similique', 'eos', 'alias', 'dolore', 'tenetur', 'deleniti', 'porro', 'facere', 'maxime', 'corrupti'] }, sp: { common: ['mujer', 'uno', 'dolor', 'más', 'de', 'poder', 'mismo', 'si'], words: ['ejercicio', 'preferencia', 'perspicacia', 'laboral', 'paño', 'suntuoso', 'molde', 'namibia', 'planeador', 'mirar', 'demás', 'oficinista', 'excepción', 'odio', 'consecuencia', 'casi', 'auto', 'chicharra', 'velo', 'elixir', 'ataque', 'no', 'odio', 'temporal', 'cuórum', 'dignísimo', 'facilismo', 'letra', 'nihilista', 'expedición', 'alma', 'alveolar', 'aparte', 'león', 'animal', 'como', 'paria', 'belleza', 'modo', 'natividad', 'justo', 'ataque', 'séquito', 'pillo', 'sed', 'ex', 'y', 'voluminoso', 'temporalidad', 'verdades', 'racional', 'asunción', 'incidente', 'marejada', 'placenta', 'amanecer', 'fuga', 'previsor', 'presentación', 'lejos', 'necesariamente', 'sospechoso', 'adiposidad', 'quindío', 'pócima', 'voluble', 'débito', 'sintió', 'accesorio', 'falda', 'sapiencia', 'volutas', 'queso', 'permacultura', 'laudo', 'soluciones', 'entero', 'pan', 'litro', 'tonelada', 'culpa', 'libertario', 'mosca', 'dictado', 'reincidente', 'nascimiento', 'dolor', 'escolar', 'impedimento', 'mínima', 'mayores', 'repugnante', 'dulce', 'obcecado', 'montaña', 'enigma', 'total', 'deletéreo', 'décima', 'cábala', 'fotografía', 'dolores', 'molesto', 'olvido', 'paciencia', 'resiliencia', 'voluntad', 'molestias', 'magnífico', 'distinción', 'ovni', 'marejada', 'cerro', 'torre', 'y', 'abogada', 'manantial', 'corporal', 'agua', 'crepúsculo', 'ataque', 'desierto', 'laboriosamente', 'angustia', 'afortunado', 'alma', 'encefalograma', 'materialidad', 'cosas', 'o', 'renuncia', 'error', 'menos', 'conejo', 'abadía', 'analfabeto', 'remo', 'fugacidad', 'oficio', 'en', 'almácigo', 'vos', 'pan', 'represión', 'números', 'triste', 'refugiado', 'trote', 'inventor', 'corchea', 'repelente', 'magma', 'recusado', 'patrón', 'explícito', 'paloma', 'síndrome', 'inmune', 'autoinmune', 'comodidad', 'ley', 'vietnamita', 'demonio', 'tasmania', 'repeler', 'apéndice', 'arquitecto', 'columna', 'yugo', 'computador', 'mula', 'a', 'propósito', 'fantasía', 'alias', 'rayo', 'tenedor', 'deleznable', 'ventana', 'cara', 'anemia', 'corrupto'] }, ru: { common: ['далеко-далеко', 'за', 'словесными', 'горами', 'в стране', 'гласных', 'и согласных', 'живут', 'рыбные', 'тексты'], words: ['вдали', 'от всех', 'они', 'буквенных', 'домах', 'на берегу', 'семантика', 'большого', 'языкового', 'океана', 'маленький', 'ручеек', 'даль', 'журчит', 'по всей', 'обеспечивает', 'ее','всеми', 'необходимыми', 'правилами', 'эта', 'парадигматическая', 'страна', 'которой', 'жаренные', 'предложения', 'залетают', 'прямо', 'рот', 'даже', 'всемогущая', 'пунктуация', 'не', 'имеет', 'власти', 'над', 'рыбными', 'текстами', 'ведущими', 'безорфографичный', 'образ', 'жизни', 'однажды', 'одна', 'маленькая', 'строчка','рыбного', 'текста', 'имени', 'lorem', 'ipsum', 'решила', 'выйти', 'большой', 'мир', 'грамматики', 'великий', 'оксмокс', 'предупреждал', 'о', 'злых', 'запятых', 'диких', 'знаках', 'вопроса', 'коварных', 'точках', 'запятой', 'но', 'текст', 'дал', 'сбить', 'себя', 'толку', 'он', 'собрал', 'семь', 'своих', 'заглавных', 'букв', 'подпоясал', 'инициал', 'за', 'пояс', 'пустился', 'дорогу', 'взобравшись', 'первую', 'вершину', 'курсивных', 'гор', 'бросил', 'последний', 'взгляд', 'назад', 'силуэт', 'своего', 'родного', 'города', 'буквоград', 'заголовок', 'деревни', 'алфавит', 'подзаголовок', 'своего', 'переулка', 'грустный', 'реторический', 'вопрос', 'скатился', 'его', 'щеке', 'продолжил', 'свой', 'путь', 'дороге', 'встретил', 'рукопись', 'она', 'предупредила', 'моей', 'все', 'переписывается', 'несколько', 'раз', 'единственное', 'что', 'меня', 'осталось', 'это', 'приставка', 'возвращайся', 'ты', 'лучше', 'свою', 'безопасную', 'страну', 'послушавшись', 'рукописи', 'наш', 'продолжил', 'свой', 'путь', 'вскоре', 'ему', 'повстречался', 'коварный', 'составитель', 'рекламных', 'текстов', 'напоивший', 'языком', 'речью', 'заманивший', 'свое', 'агентство', 'которое', 'использовало', 'снова', 'снова', 'своих', 'проектах', 'если', 'переписали', 'то', 'живет', 'там', 'до', 'сих', 'пор'] } }; prefs.define('lorem.defaultLang', 'en', 'Default language of generated dummy text. Currently, en\ and ru are supported, but users can add their own syntaxes\ see docs.'); prefs.define('lorem.omitCommonPart', false, 'Omit commonly used part (e.g. “Lorem ipsum dolor sit amet“) from generated text.'); /** * Returns random integer between from and to values * @param {Number} from * @param {Number} to * @returns {Number} */ function randint(from, to) { return Math.round(Math.random() * (to - from) + from); } /** * @param {Array} arr * @param {Number} count * @returns {Array} */ function sample(arr, count) { var len = arr.length; var iterations = Math.min(len, count); var result = []; while (result.length < iterations) { var randIx = randint(0, len - 1); if (!~result.indexOf(randIx)) { result.push(randIx); } } return result.map(function(ix) { return arr[ix]; }); } function choice(val) { if (typeof val === 'string') return val.charAt(randint(0, val.length - 1)); return val[randint(0, val.length - 1)]; } function sentence(words, end) { if (words.length) { words[0] = words[0].charAt(0).toUpperCase() + words[0].substring(1); } return words.join(' ') + (end || choice('?!...')); // more dots than question marks } /** * Insert commas at randomly selected words. This function modifies values * inside words array * @param {Array} words */ function insertCommas(words) { var len = words.length; if (len < 2) { return; } var totalCommas = 0; if (len > 3 && len <= 6) { totalCommas = randint(0, 1); } else if (len > 6 && len <= 12) { totalCommas = randint(0, 2); } else { totalCommas = randint(1, 4); } for (var i = 0, pos, word; i < totalCommas; i++) { pos = randint(0, words.length - 2); word = words[pos]; if (word.charAt(word.length - 1) !== ',') { words[pos] += ','; } } } /** * Generate a paragraph of "Lorem ipsum" text * @param {Number} wordCount Words count in paragraph * @param {Boolean} startWithCommon Should paragraph start with common * "lorem ipsum" sentence. * @returns {String} */ function paragraph(lang, wordCount, startWithCommon) { var data = langs[lang]; if (!data) { return ''; } var result = []; var totalWords = 0; var words; wordCount = parseInt(wordCount, 10); if (startWithCommon && data.common) { words = data.common.slice(0, wordCount); if (words.length > 5) { words[4] += ','; } totalWords += words.length; result.push(sentence(words, '.')); } while (totalWords < wordCount) { words = sample(data.words, Math.min(randint(2, 30), wordCount - totalWords)); totalWords += words.length; insertCommas(words); result.push(sentence(words)); } return result.join(' '); } return { /** * Adds new language words for Lorem Ipsum generator * @param {String} lang Two-letter lang definition * @param {Object} data Words for language. Maight be either a space-separated * list of words (String), Array of words or object with text and * common properties */ addLang: function(lang, data) { if (typeof data === 'string') { data = { words: data.split(' ').filter(function(item) { return !!item; }) }; } else if (Array.isArray(data)) { data = {words: data}; } langs[lang] = data; }, preprocessor: function(tree) { var re = /^(?:lorem|lipsum)([a-z]{2})?(\d*)$/i, match; var allowCommon = !prefs.get('lorem.omitCommonPart'); /** @param {AbbreviationNode} node */ tree.findAll(function(node) { if (node._name && (match = node._name.match(re))) { var wordCound = match[2] || 30; var lang = match[1] || prefs.get('lorem.defaultLang') || 'en'; // force node name resolving if node should be repeated // or contains attributes. In this case, node should be outputed // as tag, otherwise as text-only node node._name = ''; node.data('forceNameResolving', node.isRepeating() || node.attributeList().length); node.data('pasteOverwrites', true); node.data('paste', function(i) { return paragraph(lang, wordCound, !i && allowCommon); }); } }); } }; }); },{"../assets/preferences":"assets\\preferences.js"}],"parser\\abbreviation.js":[function(require,module,exports){ /** * Emmet abbreviation parser. * Takes string abbreviation and recursively parses it into a tree. The parsed * tree can be transformed into a string representation with * toString() method. Note that string representation is defined * by custom processors (called filters), not by abbreviation parser * itself. * * This module can be extended with custom pre-/post-processors to shape-up * final tree or its representation. Actually, many features of abbreviation * engine are defined in other modules as tree processors */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var tabStops = require('../assets/tabStops'); var profile = require('../assets/profile'); var filters = require('../filter/main'); var utils = require('../utils/common'); var abbreviationUtils = require('../utils/abbreviation'); var stringStream = require('../assets/stringStream'); // pre- and post-processorcs var lorem = require('../generator/lorem'); var procPastedContent = require('./processor/pastedContent'); var procTagName = require('./processor/tagName'); var procResourceMatcher = require('./processor/resourceMatcher'); var procAttributes = require('./processor/attributes'); var procHref = require('./processor/href'); var reValidName = /^[\w\-\$\:@\!%]+\+?$/i; var reWord = /[\w\-:\$@]/; var DEFAULT_ATTR_NAME = '%default'; var pairs = { '[': ']', '(': ')', '{': '}' }; var spliceFn = Array.prototype.splice; var preprocessors = []; var postprocessors = []; var outputProcessors = []; /** * @type AbbreviationNode */ function AbbreviationNode(parent) { /** @type AbbreviationNode */ this.parent = null; this.children = []; this._attributes = []; /** @type String Raw abbreviation for current node */ this.abbreviation = ''; this.counter = 1; this._name = null; this._text = ''; this.repeatCount = 1; this.hasImplicitRepeat = false; /** Custom data dictionary */ this._data = {}; // output properties this.start = ''; this.end = ''; this.content = ''; this.padding = ''; } AbbreviationNode.prototype = { /** * Adds passed node as child or creates new child * @param {AbbreviationNode} child * @param {Number} position Index in children array where child should * be inserted * @return {AbbreviationNode} */ addChild: function(child, position) { child = child || new AbbreviationNode(); child.parent = this; if (typeof position === 'undefined') { this.children.push(child); } else { this.children.splice(position, 0, child); } return child; }, /** * Creates a deep copy of current node * @returns {AbbreviationNode} */ clone: function() { var node = new AbbreviationNode(); var attrs = ['abbreviation', 'counter', '_name', '_text', 'repeatCount', 'hasImplicitRepeat', 'start', 'end', 'content', 'padding']; attrs.forEach(function(a) { node[a] = this[a]; }, this); // clone attributes node._attributes = this._attributes.map(function(attr) { return utils.extend({}, attr); }); node._data = utils.extend({}, this._data); // clone children node.children = this.children.map(function(child) { child = child.clone(); child.parent = node; return child; }); return node; }, /** * Removes current node from parent‘s child list * @returns {AbbreviationNode} Current node itself */ remove: function() { if (this.parent) { var ix = this.parent.children.indexOf(this); if (~ix) { this.parent.children.splice(ix, 1); } } return this; }, /** * Replaces current node in parent‘s children list with passed nodes * @param {AbbreviationNode} node Replacement node or array of nodes */ replace: function() { var parent = this.parent; var ix = parent.children.indexOf(this); var items = utils.flatten(arguments); spliceFn.apply(parent.children, [ix, 1].concat(items)); // update parent items.forEach(function(item) { item.parent = parent; }); }, /** * Recursively sets property to value of current * node and its children * @param {String} name Property to update * @param {Object} value New property value */ updateProperty: function(name, value) { this[name] = value; this.children.forEach(function(child) { child.updateProperty(name, value); }); return this; }, /** * Finds first child node that matches truth test for passed * fn function * @param {Function} fn * @returns {AbbreviationNode} */ find: function(fn) { return this.findAll(fn, {amount: 1})[0]; }, /** * Finds all child nodes that matches truth test for passed * fn function * @param {Function} fn * @returns {Array} */ findAll: function(fn, state) { state = utils.extend({amount: 0, found: 0}, state || {}); if (typeof fn !== 'function') { var elemName = fn.toLowerCase(); fn = function(item) {return item.name().toLowerCase() == elemName;}; } var result = []; this.children.forEach(function(child) { if (fn(child)) { result.push(child); state.found++; if (state.amount && state.found >= state.amount) { return; } } result = result.concat(child.findAll(fn)); }); return result.filter(function(item) { return !!item; }); }, /** * Sets/gets custom data * @param {String} name * @param {Object} value * @returns {Object} */ data: function(name, value) { if (arguments.length == 2) { this._data[name] = value; } return this._data[name]; }, /** * Returns name of current node * @returns {String} */ name: function() { return this._name; }, /** * Returns list of attributes for current node * @returns {Array} */ attributeList: function() { return optimizeAttributes(this._attributes.slice(0)); }, /** * Returns or sets attribute value * @param {String} name Attribute name * @param {String} value New attribute value. `Null` value * will remove attribute * @returns {String} */ attribute: function(name, value) { if (arguments.length == 2) { if (value === null) { // remove attribute var vals = this._attributes.filter(function(attr) { return attr.name === name; }); var that = this; vals.forEach(function(attr) { var ix = that._attributes.indexOf(attr); if (~ix) { that._attributes.splice(ix, 1); } }); return; } // modify attribute var attrNames = this._attributes.map(function(attr) { return attr.name; }); var ix = attrNames.indexOf(name.toLowerCase()); if (~ix) { this._attributes[ix].value = value; } else { this._attributes.push({ name: name, value: value }); } } return (utils.find(this.attributeList(), function(attr) { return attr.name == name; }) || {}).value; }, /** * Returns index of current node in parent‘s children list * @returns {Number} */ index: function() { return this.parent ? this.parent.children.indexOf(this) : -1; }, /** * Sets how many times current element should be repeated * @private */ _setRepeat: function(count) { if (count) { this.repeatCount = parseInt(count, 10) || 1; } else { this.hasImplicitRepeat = true; } }, /** * Sets abbreviation that belongs to current node * @param {String} abbr */ setAbbreviation: function(abbr) { abbr = abbr || ''; var that = this; // find multiplier abbr = abbr.replace(/\*(\d+)?$/, function(str, repeatCount) { that._setRepeat(repeatCount); return ''; }); this.abbreviation = abbr; var abbrText = extractText(abbr); if (abbrText) { abbr = abbrText.element; this.content = this._text = abbrText.text; } var abbrAttrs = parseAttributes(abbr); if (abbrAttrs) { abbr = abbrAttrs.element; this._attributes = abbrAttrs.attributes; } this._name = abbr; // validate name if (this._name && !reValidName.test(this._name)) { throw new Error('Invalid abbreviation'); } }, /** * Returns string representation of current node * @return {String} */ valueOf: function() { var start = this.start; var end = this.end; var content = this.content; // apply output processors var node = this; outputProcessors.forEach(function(fn) { start = fn(start, node, 'start'); content = fn(content, node, 'content'); end = fn(end, node, 'end'); }); var innerContent = this.children.map(function(child) { return child.valueOf(); }).join(''); content = abbreviationUtils.insertChildContent(content, innerContent, { keepVariable: false }); return start + utils.padString(content, this.padding) + end; }, toString: function() { return this.valueOf(); }, /** * Check if current node contains children with empty expr * property * @return {Boolean} */ hasEmptyChildren: function() { return !!utils.find(this.children, function(child) { return child.isEmpty(); }); }, /** * Check if current node has implied name that should be resolved * @returns {Boolean} */ hasImplicitName: function() { return !this._name && !this.isTextNode(); }, /** * Indicates that current element is a grouping one, e.g. has no * representation but serves as a container for other nodes * @returns {Boolean} */ isGroup: function() { return !this.abbreviation; }, /** * Indicates empty node (i.e. without abbreviation). It may be a * grouping node and should not be outputted * @return {Boolean} */ isEmpty: function() { return !this.abbreviation && !this.children.length; }, /** * Indicates that current node should be repeated * @returns {Boolean} */ isRepeating: function() { return this.repeatCount > 1 || this.hasImplicitRepeat; }, /** * Check if current node is a text-only node * @return {Boolean} */ isTextNode: function() { return !this.name() && !this.attributeList().length; }, /** * Indicates whether this node may be used to build elements or snippets * @returns {Boolean} */ isElement: function() { return !this.isEmpty() && !this.isTextNode(); }, /** * Returns latest and deepest child of current tree * @returns {AbbreviationNode} */ deepestChild: function() { if (!this.children.length) return null; var deepestChild = this; while (deepestChild.children.length) { deepestChild = deepestChild.children[deepestChild.children.length - 1]; } return deepestChild; } }; /** * Returns stripped string: a string without first and last character. * Used for “unquoting” strings * @param {String} str * @returns {String} */ function stripped(str) { return str.substring(1, str.length - 1); } function consumeQuotedValue(stream, quote) { var ch; while ((ch = stream.next())) { if (ch === quote) return true; if (ch == '\\') continue; } return false; } /** * Parses abbreviation into a tree * @param {String} abbr * @returns {AbbreviationNode} */ function parseAbbreviation(abbr) { abbr = utils.trim(abbr); var root = new AbbreviationNode(); var context = root.addChild(), ch; /** @type StringStream */ var stream = stringStream.create(abbr); var loopProtector = 1000, multiplier; var addChild = function(child) { context.addChild(child); }; var consumeAbbr = function() { stream.start = stream.pos; stream.eatWhile(function(c) { if (c == '[' || c == '{') { if (stream.skipToPair(c, pairs[c])) { stream.backUp(1); return true; } throw new Error('Invalid abbreviation: mo matching "' + pairs[c] + '" found for character at ' + stream.pos); } if (c == '+') { // let's see if this is an expando marker stream.next(); var isMarker = stream.eol() || ~'+>^*'.indexOf(stream.peek()); stream.backUp(1); return isMarker; } return c != '(' && isAllowedChar(c); }); }; while (!stream.eol() && --loopProtector > 0) { ch = stream.peek(); switch (ch) { case '(': // abbreviation group stream.start = stream.pos; if (stream.skipToPair('(', ')')) { var inner = parseAbbreviation(stripped(stream.current())); if ((multiplier = stream.match(/^\*(\d+)?/, true))) { context._setRepeat(multiplier[1]); } inner.children.forEach(addChild); } else { throw new Error('Invalid abbreviation: mo matching ")" found for character at ' + stream.pos); } break; case '>': // child operator context = context.addChild(); stream.next(); break; case '+': // sibling operator context = context.parent.addChild(); stream.next(); break; case '^': // climb up operator var parent = context.parent || context; context = (parent.parent || parent).addChild(); stream.next(); break; default: // consume abbreviation consumeAbbr(); context.setAbbreviation(stream.current()); stream.start = stream.pos; } } if (loopProtector < 1) { throw new Error('Endless loop detected'); } return root; } /** * Splits attribute set into a list of attributes string * @param {String} attrSet * @return {Array} */ function splitAttributes(attrSet) { attrSet = utils.trim(attrSet); var parts = []; // split attribute set by spaces var stream = stringStream(attrSet), ch; while ((ch = stream.next())) { if (ch == ' ') { parts.push(utils.trim(stream.current())); // skip spaces while (stream.peek() == ' ') { stream.next(); } stream.start = stream.pos; } else if (ch == '"' || ch == "'") { // skip values in strings if (!stream.skipString(ch)) { throw new Error('Invalid attribute set'); } } } parts.push(utils.trim(stream.current())); return parts; } /** * Removes opening and closing quotes from given string * @param {String} str * @return {String} */ function unquote(str) { var ch = str.charAt(0); if (ch == '"' || ch == "'") { str = str.substr(1); var last = str.charAt(str.length - 1); if (last === ch) { str = str.substr(0, str.length - 1); } } return str; } /** * Extract attributes and their values from attribute set: * [attr col=3 title="Quoted string"] (without square braces) * @param {String} attrSet * @returns {Array} */ function extractAttributes(attrSet) { var reAttrName = /^[\w\-:\$@]+\.?$/; return splitAttributes(attrSet).map(function(attr) { // attribute name: [attr] if (reAttrName.test(attr)) { var value = ''; if (attr.charAt(attr.length - 1) == '.') { // a boolean attribute attr = attr.substr(0, attr.length - 1); value = attr; } return { name: attr, value: value }; } // attribute with value: [name=val], [name="val"] if (~attr.indexOf('=')) { var parts = attr.split('='); return { name: parts.shift(), value: unquote(parts.join('=')) }; } // looks like it’s implied attribute return { name: DEFAULT_ATTR_NAME, value: unquote(attr) }; }); } /** * Parses tag attributes extracted from abbreviation. If attributes found, * returns object with element and attributes * properties * @param {String} abbr * @returns {Object} Returns null if no attributes found in * abbreviation */ function parseAttributes(abbr) { /* * Example of incoming data: * #header * .some.data * .some.data#header * [attr] * #item[attr=Hello other="World"].class */ var result = []; var attrMap = {'#': 'id', '.': 'class'}; var nameEnd = null; /** @type StringStream */ var stream = stringStream.create(abbr); while (!stream.eol()) { switch (stream.peek()) { case '#': // id case '.': // class if (nameEnd === null) nameEnd = stream.pos; var attrName = attrMap[stream.peek()]; stream.next(); stream.start = stream.pos; stream.eatWhile(reWord); result.push({ name: attrName, value: stream.current() }); break; case '[': //begin attribute set if (nameEnd === null) nameEnd = stream.pos; stream.start = stream.pos; if (!stream.skipToPair('[', ']')) { throw new Error('Invalid attribute set definition'); } result = result.concat( extractAttributes(stripped(stream.current())) ); break; default: stream.next(); } } if (!result.length) return null; return { element: abbr.substring(0, nameEnd), attributes: optimizeAttributes(result) }; } /** * Optimize attribute set: remove duplicates and merge class attributes * @param attrs */ function optimizeAttributes(attrs) { // clone all attributes to make sure that original objects are // not modified attrs = attrs.map(function(attr) { return utils.clone(attr); }); var lookup = {}; return attrs.filter(function(attr) { if (!(attr.name in lookup)) { return lookup[attr.name] = attr; } var la = lookup[attr.name]; if (attr.name.toLowerCase() == 'class') { la.value += (la.value.length ? ' ' : '') + attr.value; } else { la.value = attr.value; la.isImplied = !!attr.isImplied; } return false; }); } /** * Extract text data from abbreviation: if a{hello} abbreviation * is passed, returns object {element: 'a', text: 'hello'}. * If nothing found, returns null * @param {String} abbr * */ function extractText(abbr) { if (!~abbr.indexOf('{')) return null; /** @type StringStream */ var stream = stringStream.create(abbr); while (!stream.eol()) { switch (stream.peek()) { case '[': case '(': stream.skipToPair(stream.peek(), pairs[stream.peek()]); break; case '{': stream.start = stream.pos; stream.skipToPair('{', '}'); return { element: abbr.substring(0, stream.start), text: stripped(stream.current()) }; default: stream.next(); } } } /** * “Un-rolls“ contents of current node: recursively replaces all repeating * children with their repeated clones * @param {AbbreviationNode} node * @returns {AbbreviationNode} */ function unroll(node) { for (var i = node.children.length - 1, j, child, maxCount; i >= 0; i--) { child = node.children[i]; if (child.isRepeating()) { maxCount = j = child.repeatCount; child.repeatCount = 1; child.updateProperty('counter', 1); child.updateProperty('maxCount', maxCount); while (--j > 0) { child.parent.addChild(child.clone(), i + 1) .updateProperty('counter', j + 1) .updateProperty('maxCount', maxCount); } } } // to keep proper 'counter' property, we need to walk // on children once again node.children.forEach(unroll); return node; } /** * Optimizes tree node: replaces empty nodes with their children * @param {AbbreviationNode} node * @return {AbbreviationNode} */ function squash(node) { for (var i = node.children.length - 1; i >= 0; i--) { /** @type AbbreviationNode */ var n = node.children[i]; if (n.isGroup()) { n.replace(squash(n).children); } else if (n.isEmpty()) { n.remove(); } } node.children.forEach(squash); return node; } function isAllowedChar(ch) { var charCode = ch.charCodeAt(0); var specialChars = '#.*:$-_!@|%'; return (charCode > 64 && charCode < 91) // uppercase letter || (charCode > 96 && charCode < 123) // lowercase letter || (charCode > 47 && charCode < 58) // number || specialChars.indexOf(ch) != -1; // special character } // XXX add counter replacer function as output processor outputProcessors.push(function(text, node) { return utils.replaceCounter(text, node.counter, node.maxCount); }); // XXX add tabstop updater outputProcessors.push(tabStops.abbrOutputProcessor.bind(tabStops)); // include default pre- and postprocessors [lorem, procResourceMatcher, procAttributes, procPastedContent, procTagName, procHref].forEach(function(mod) { if (mod.preprocessor) { preprocessors.push(mod.preprocessor.bind(mod)); } if (mod.postprocessor) { postprocessors.push(mod.postprocessor.bind(mod)); } }); return { DEFAULT_ATTR_NAME: DEFAULT_ATTR_NAME, /** * Parses abbreviation into tree with respect of groups, * text nodes and attributes. Each node of the tree is a single * abbreviation. Tree represents actual structure of the outputted * result * @memberOf abbreviationParser * @param {String} abbr Abbreviation to parse * @param {Object} options Additional options for parser and processors * * @return {AbbreviationNode} */ parse: function(abbr, options) { options = options || {}; var tree = parseAbbreviation(abbr); var that = this; if (options.contextNode) { // add info about context node – // a parent XHTML node in editor inside which abbreviation is // expanded tree._name = options.contextNode.name; var attrLookup = {}; tree._attributes.forEach(function(attr) { attrLookup[attr.name] = attr; }); options.contextNode.attributes.forEach(function(attr) { if (attr.name in attrLookup) { attrLookup[attr.name].value = attr.value; } else { attr = utils.clone(attr); tree._attributes.push(attr); attrLookup[attr.name] = attr; } }); } // apply preprocessors preprocessors.forEach(function(fn) { fn(tree, options, that); }); if ('counter' in options) { tree.updateProperty('counter', options.counter); } tree = squash(unroll(tree)); // apply postprocessors postprocessors.forEach(function(fn) { fn(tree, options, that); }); return tree; }, /** * Expands given abbreviation into a formatted code structure. * This is the main method that is used for expanding abbreviation * @param {String} abbr Abbreviation to expand * @param {Options} options Additional options for abbreviation * expanding and transformation: `syntax`, `profile`, `contextNode` etc. * @return {String} */ expand: function(abbr, options) { if (!abbr) return ''; if (typeof options == 'string') { throw new Error('Deprecated use of `expand` method: `options` must be object'); } options = options || {}; if (!options.syntax) { options.syntax = utils.defaultSyntax(); } var p = profile.get(options.profile, options.syntax); tabStops.resetTabstopIndex(); var data = filters.extract(abbr); var outputTree = this.parse(data[0], options); var filtersList = filters.composeList(options.syntax, p, data[1]); filters.apply(outputTree, filtersList, p); return outputTree.valueOf(); }, AbbreviationNode: AbbreviationNode, /** * Add new abbreviation preprocessor. Preprocessor is a function * that applies to a parsed abbreviation tree right after it get parsed. * The passed tree is in unoptimized state. * @param {Function} fn Preprocessor function. This function receives * two arguments: parsed abbreviation tree (AbbreviationNode) * and options hash that was passed to parse * method */ addPreprocessor: function(fn) { if (!~preprocessors.indexOf(fn)) { preprocessors.push(fn); } }, /** * Removes registered preprocessor */ removeFilter: function(fn) { var ix = preprocessors.indexOf(fn); if (~ix) { preprocessors.splice(ix, 1); } }, /** * Adds new abbreviation postprocessor. Postprocessor is a * functinon that applies to optimized parsed abbreviation tree * right before it returns from parse() method * @param {Function} fn Postprocessor function. This function receives * two arguments: parsed abbreviation tree (AbbreviationNode) * and options hash that was passed to parse * method */ addPostprocessor: function(fn) { if (!~postprocessors.indexOf(fn)) { postprocessors.push(fn); } }, /** * Removes registered postprocessor function */ removePostprocessor: function(fn) { var ix = postprocessors.indexOf(fn); if (~ix) { postprocessors.splice(ix, 1); } }, /** * Registers output postprocessor. Output processor is a * function that applies to output part (start, * end and content) when * AbbreviationNode.toString() method is called */ addOutputProcessor: function(fn) { if (!~outputProcessors.indexOf(fn)) { outputProcessors.push(fn); } }, /** * Removes registered output processor */ removeOutputProcessor: function(fn) { var ix = outputProcessors.indexOf(fn); if (~ix) { outputProcessors.splice(ix, 1); } }, /** * Check if passed symbol is valid symbol for abbreviation expression * @param {String} ch * @return {Boolean} */ isAllowedChar: function(ch) { ch = String(ch); // convert Java object to JS return isAllowedChar(ch) || ~'>+^[](){}'.indexOf(ch); } }; }); },{"../assets/profile":"assets\\profile.js","../assets/stringStream":"assets\\stringStream.js","../assets/tabStops":"assets\\tabStops.js","../filter/main":"filter\\main.js","../generator/lorem":"generator\\lorem.js","../utils/abbreviation":"utils\\abbreviation.js","../utils/common":"utils\\common.js","./processor/attributes":"parser\\processor\\attributes.js","./processor/href":"parser\\processor\\href.js","./processor/pastedContent":"parser\\processor\\pastedContent.js","./processor/resourceMatcher":"parser\\processor\\resourceMatcher.js","./processor/tagName":"parser\\processor\\tagName.js"}],"parser\\css.js":[function(require,module,exports){ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var session = {tokens: null}; // walks around the source var walker = { init: function (source) { // this.source = source.replace(/\r\n?/g, '\n'); this.source = source; this.ch = ''; this.chnum = -1; // advance this.nextChar(); }, nextChar: function () { return this.ch = this.source.charAt(++this.chnum); }, peek: function() { return this.source.charAt(this.chnum + 1); } }; // utility helpers function isNameChar(c, cc) { cc = cc || c.charCodeAt(0); return ( (cc >= 97 && cc <= 122 /* a-z */) || (cc >= 65 && cc <= 90 /* A-Z */) || /* Experimental: include cyrillic ranges since some letters, similar to latin ones, can accidentally appear in CSS tokens */ (cc >= 1024 && cc <= 1279) || c === '&' || /* selector placeholder (LESS, SCSS) */ c === '_' || c === '<' || /* comparisons (LESS, SCSS) */ c === '>' || c === '=' || c === '-' ); } function isDigit(c, cc) { cc = cc || c.charCodeAt(0); return (cc >= 48 && cc <= 57); } var isOp = (function () { var opsa = "{}[]()+*=.,;:>~|\\%$#@^!".split(''), opsmatcha = "*^|$~".split(''), ops = {}, opsmatch = {}, i = 0; for (; i < opsa.length; i += 1) { ops[opsa[i]] = true; } for (i = 0; i < opsmatcha.length; i += 1) { opsmatch[opsmatcha[i]] = true; } return function (ch, matchattr) { if (matchattr) { return ch in opsmatch; } return ch in ops; }; }()); // creates token objects and pushes them to a list function tokener(value, type) { session.tokens.push({ value: value, type: type || value, start: null, end: null }); } function getPosInfo(w) { var errPos = w.chnum; var source = w.source.replace(/\r\n?/g, '\n'); var part = w.source.substring(0, errPos + 1).replace(/\r\n?/g, '\n'); var lines = part.split('\n'); var ch = (lines[lines.length - 1] || '').length; var fullLine = source.split('\n')[lines.length - 1] || ''; var chunkSize = 100; var offset = Math.max(0, ch - chunkSize); var formattedLine = fullLine.substr(offset, chunkSize * 2) + '\n'; for (var i = 0; i < ch - offset - 1; i++) { formattedLine += '-'; } formattedLine += '^'; return { line: lines.length, ch: ch, text: fullLine, hint: formattedLine }; } function raiseError(message) { var err = error(message); var errObj = new Error(err.message, '', err.line); errObj.line = err.line; errObj.ch = err.ch; errObj.name = err.name; errObj.hint = err.hint; throw errObj; } // oops function error(m) { var w = walker; var info = getPosInfo(walker); var tokens = session.tokens; session.tokens = null; var message = 'CSS parsing error at line ' + info.line + ', char ' + info.ch + ': ' + m; message += '\n' + info.hint; return { name: "ParseError", message: message, hint: info.hint, line: info.line, ch: info.ch }; } // token handlers follow for: // white space, comment, string, identifier, number, operator function white() { var c = walker.ch, token = ''; while (c === " " || c === "\t") { token += c; c = walker.nextChar(); } tokener(token, 'white'); } function comment() { var w = walker, c = w.ch, token = c, cnext; cnext = w.nextChar(); if (cnext === '/') { // inline comment in SCSS and LESS while (c && !(cnext === "\n" || cnext === "\r")) { token += cnext; c = cnext; cnext = w.nextChar(); } } else if (cnext === '*') { // multiline CSS commment while (c && !(c === "*" && cnext === "/")) { token += cnext; c = cnext; cnext = w.nextChar(); } } else { // oops, not a comment, just a / return tokener(token, token); } token += cnext; w.nextChar(); tokener(token, 'comment'); } function eatString() { var w = walker, c = w.ch, q = c, token = c, cnext; c = w.nextChar(); while (c !== q) { if (c === '\n') { cnext = w.nextChar(); if (cnext === "\\") { token += c + cnext; } else { // end of line with no \ escape = bad raiseError("Unterminated string"); } } else if (c === '') { raiseError("Unterminated string"); } else { if (c === "\\") { token += c + w.nextChar(); } else { token += c; } } c = w.nextChar(); } token += c; return token; } function str() { var token = eatString(); walker.nextChar(); tokener(token, 'string'); } function brace() { var w = walker, c = w.ch, depth = 1, token = c, stop = false; c = w.nextChar(); while (c && !stop) { if (c === '(') { depth++; } else if (c === ')') { depth--; if (!depth) { stop = true; } } else if (c === '"' || c === "'") { c = eatString(); } else if (c === '') { raiseError("Unterminated brace"); } token += c; c = w.nextChar(); } tokener(token, 'brace'); } function identifier(pre) { var c = walker.ch; var token = pre ? pre + c : c; c = walker.nextChar(); var cc = c.charCodeAt(0); while (isNameChar(c, cc) || isDigit(c, cc)) { token += c; c = walker.nextChar(); cc = c.charCodeAt(0); } tokener(token, 'identifier'); } function num() { var w = walker, c = w.ch, token = c, point = token === '.', nondigit; c = w.nextChar(); nondigit = !isDigit(c); // .2px or .classname? if (point && nondigit) { // meh, NaN, could be a class name, so it's an operator for now return tokener(token, '.'); } // -2px or -moz-something if (token === '-' && nondigit) { return identifier('-'); } while (c !== '' && (isDigit(c) || (!point && c === '.'))) { // not end of source && digit or first instance of . if (c === '.') { point = true; } token += c; c = w.nextChar(); } tokener(token, 'number'); } function op() { var w = walker, c = w.ch, token = c, next = w.nextChar(); if (next === "=" && isOp(token, true)) { token += next; tokener(token, 'match'); w.nextChar(); return; } tokener(token, token); } // call the appropriate handler based on the first character in a token suspect function tokenize() { var ch = walker.ch; if (ch === " " || ch === "\t") { return white(); } if (ch === '/') { return comment(); } if (ch === '"' || ch === "'") { return str(); } if (ch === '(') { return brace(); } if (ch === '-' || ch === '.' || isDigit(ch)) { // tricky - char: minus (-1px) or dash (-moz-stuff) return num(); } if (isNameChar(ch)) { return identifier(); } if (isOp(ch)) { return op(); } if (ch === '\r') { if (walker.peek() === '\n') { ch += walker.nextChar(); } tokener(ch, 'line'); walker.nextChar(); return; } if (ch === '\n') { tokener(ch, 'line'); walker.nextChar(); return; } raiseError("Unrecognized character '" + ch + "'"); } return { /** * Sprits given source into tokens * @param {String} source * @returns {Array} */ lex: function (source) { walker.init(source); session.tokens = []; // for empty source, return single space token if (!source) { session.tokens.push(this.white()); } else { while (walker.ch !== '') { tokenize(); } } var tokens = session.tokens; session.tokens = null; return tokens; }, /** * Tokenizes CSS source. It's like `lex()` method, * but also stores proper token indexes in source, * so it's a bit slower * @param {String} source * @returns {Array} */ parse: function(source) { // transform tokens var tokens = this.lex(source), pos = 0, token; for (var i = 0, il = tokens.length; i < il; i++) { token = tokens[i]; token.start = pos; token.end = (pos += token.value.length); } return tokens; }, white: function() { return { value: '', type: 'white', start: 0, end: 0 }; }, toSource: function(toks) { var i = 0, max = toks.length, src = ''; for (; i < max; i++) { src += toks[i].value; } return src; } }; }); },{}],"parser\\processor\\attributes.js":[function(require,module,exports){ /** * Resolves node attribute names: moves `default` attribute value * from stub to real attribute. * * This resolver should be applied *after* resource matcher */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var utils = require('../../utils/common'); var findDefault = function(attr) { return attr.isDefault; }; var findImplied = function(attr) { return attr.isImplied; }; var findEmpty = function(attr) { return !attr.value; }; function resolveDefaultAttrs(node, parser) { node.children.forEach(function(item) { var attrList = item.attributeList(); var defaultAttrValue = item.attribute(parser.DEFAULT_ATTR_NAME); if (typeof defaultAttrValue !== 'undefined') { // remove stub attribute item.attribute(parser.DEFAULT_ATTR_NAME, null); if (attrList.length) { // target for default value: // 1. default attribute // 2. implied attribute // 3. first empty attribute // find attribute marked as default var defaultAttr = utils.find(attrList, findDefault) || utils.find(attrList, findImplied) || utils.find(attrList, findEmpty); if (defaultAttr) { var oldVal = item.attribute(defaultAttr.name); var newVal = utils.replaceUnescapedSymbol(oldVal, '|', defaultAttrValue); // no replacement, e.g. default value does not contains | symbol if (oldVal == newVal) { newVal = defaultAttrValue } item.attribute(defaultAttr.name, newVal); } } } else { // if no default attribute value, remove implied attributes attrList.forEach(function(attr) { if (attr.isImplied) { item.attribute(attr.name, null); } }); } resolveDefaultAttrs(item, parser); }); } return { /** * @param {AbbreviationNode} tree * @param {Object} options * @param {abbreviation} parser */ preprocessor: function(tree, options, parser) { resolveDefaultAttrs(tree, parser); } }; }); },{"../../utils/common":"utils\\common.js"}],"parser\\processor\\href.js":[function(require,module,exports){ /** * A preptocessor for <a> tag: tests wrapped content * for common URL patterns and, if matched, inserts it as * `href` attribute */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var prefs = require('../../assets/preferences'); var utils = require('../../utils/common'); var pc = require('./pastedContent'); prefs.define('href.autodetect', true, 'Enables or disables automatic URL recognition when wrapping\ text with <a> tag. With this option enabled,\ if wrapped text matches URL or e-mail pattern it will be automatically\ inserted into href attribute.'); prefs.define('href.urlPattern', '^(?:(?:https?|ftp|file)://|www\\.|ftp\\.)(?:\\([-A-Z0-9+&@#/%=~_|$?!:,.]*\\)|[-A-Z0-9+&@#/%=~_|$?!:,.])*(?:\\([-A-Z0-9+&@#/%=~_|$?!:,.]*\\)|[A-Z0-9+&@#/%=~_|$])', 'RegExp pattern to match wrapped URLs. Matched content will be inserts\ as-is into href attribute, only whitespace will be trimmed.'); prefs.define('href.emailPattern', '^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,5}$', 'RegExp pattern to match wrapped e-mails. Unlike href.urlPattern,\ wrapped content will be prefixed with mailto: in href\ attribute'); return { /** * @param {AbbreviationNode} tree * @param {Object} options */ postprocessor: function(tree, options) { if (!prefs.get('href.autodetect')) { return; } var reUrl = new RegExp(prefs.get('href.urlPattern'), 'i'); var reEmail = new RegExp(prefs.get('href.emailPattern'), 'i'); var reProto = /^([a-z]+:)?\/\//i; tree.findAll(function(item) { if (item.name().toLowerCase() != 'a' || item.attribute('href')) { return; } var pastedContent = utils.trim(pc.pastedContent(item) || options.pastedContent); if (pastedContent) { if (reUrl.test(pastedContent)) { // do we have protocol? if (!reProto.test(pastedContent)) { pastedContent = 'http://' + pastedContent; } item.attribute('href', pastedContent); } else if (reEmail.test(pastedContent)) { item.attribute('href', 'mailto:' + pastedContent); } } }); } }; }); },{"../../assets/preferences":"assets\\preferences.js","../../utils/common":"utils\\common.js","./pastedContent":"parser\\processor\\pastedContent.js"}],"parser\\processor\\pastedContent.js":[function(require,module,exports){ /** * Pasted content abbreviation processor. A pasted content is a content that * should be inserted into implicitly repeated abbreviation nodes. * This processor powers “Wrap With Abbreviation” action */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var utils = require('../../utils/common'); var abbrUtils = require('../../utils/abbreviation'); var stringStream = require('../../assets/stringStream'); var range = require('../../assets/range'); var outputPlaceholder = '$#'; /** * Locates output placeholders inside text * @param {String} text * @returns {Array} Array of ranges of output placeholder in text */ function locateOutputPlaceholder(text) { var result = []; var stream = stringStream.create(text); while (!stream.eol()) { if (stream.peek() == '\\') { stream.next(); } else { stream.start = stream.pos; if (stream.match(outputPlaceholder, true)) { result.push(range.create(stream.start, outputPlaceholder)); continue; } } stream.next(); } return result; } /** * Replaces output placeholders inside source with * value * @param {String} source * @param {String} value * @returns {String} */ function replaceOutputPlaceholders(source, value) { var ranges = locateOutputPlaceholder(source); ranges.reverse().forEach(function(r) { source = utils.replaceSubstring(source, value, r); }); return source; } /** * Check if parsed node contains output placeholder – a target where * pasted content should be inserted * @param {AbbreviationNode} node * @returns {Boolean} */ function hasOutputPlaceholder(node) { if (locateOutputPlaceholder(node.content).length) return true; // check if attributes contains placeholder return !!utils.find(node.attributeList(), function(attr) { return !!locateOutputPlaceholder(attr.value).length; }); } /** * Insert pasted content into correct positions of parsed node * @param {AbbreviationNode} node * @param {String} content * @param {Boolean} overwrite Overwrite node content if no value placeholders * found instead of appending to existing content */ function insertPastedContent(node, content, overwrite) { var nodesWithPlaceholders = node.findAll(function(item) { return hasOutputPlaceholder(item); }); if (hasOutputPlaceholder(node)) nodesWithPlaceholders.unshift(node); if (nodesWithPlaceholders.length) { nodesWithPlaceholders.forEach(function(item) { item.content = replaceOutputPlaceholders(item.content, content); item._attributes.forEach(function(attr) { attr.value = replaceOutputPlaceholders(attr.value, content); }); }); } else { // on output placeholders in subtree, insert content in the deepest // child node var deepest = node.deepestChild() || node; if (overwrite) { deepest.content = content; } else { deepest.content = abbrUtils.insertChildContent(deepest.content, content); } } } return { pastedContent: function(item) { var content = item.data('paste'); if (Array.isArray(content)) { return content[item.counter - 1]; } else if (typeof content === 'function') { return content(item.counter - 1, item.content); } else if (content) { return content; } }, /** * @param {AbbreviationNode} tree * @param {Object} options */ preprocessor: function(tree, options) { if (options.pastedContent) { var lines = utils.splitByLines(options.pastedContent, true).map(utils.trim); // set repeat count for implicitly repeated elements before // tree is unrolled tree.findAll(function(item) { if (item.hasImplicitRepeat) { item.data('paste', lines); return item.repeatCount = lines.length; } }); } }, /** * @param {AbbreviationNode} tree * @param {Object} options */ postprocessor: function(tree, options) { var that = this; // for each node with pasted content, update text data var targets = tree.findAll(function(item) { var pastedContent = that.pastedContent(item); if (pastedContent) { insertPastedContent(item, pastedContent, !!item.data('pasteOverwrites')); } return !!pastedContent; }); if (!targets.length && options.pastedContent) { // no implicitly repeated elements, put pasted content in // the deepest child insertPastedContent(tree, options.pastedContent); } } }; }); },{"../../assets/range":"assets\\range.js","../../assets/stringStream":"assets\\stringStream.js","../../utils/abbreviation":"utils\\abbreviation.js","../../utils/common":"utils\\common.js"}],"parser\\processor\\resourceMatcher.js":[function(require,module,exports){ /** * Processor function that matches parsed AbbreviationNode * against resources defined in resource module */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var resources = require('../../assets/resources'); var elements = require('../../assets/elements'); var utils = require('../../utils/common'); var abbreviationUtils = require('../../utils/abbreviation'); /** * Finds matched resources for child nodes of passed node * element. A matched resource is a reference to snippets.json entry * that describes output of parsed node * @param {AbbreviationNode} node * @param {String} syntax */ function matchResources(node, syntax, parser) { // do a shallow copy because the children list can be modified during // resource matching node.children.slice(0).forEach(function(child) { var r = resources.getMatchedResource(child, syntax); if (typeof r === 'string') { r = elements.create('snippet', r); } child.data('resource', r); var elemType = elements.type(r); if (elemType == 'snippet') { var content = r.data; var curContent = child._text || child.content; if (curContent) { content = abbreviationUtils.insertChildContent(content, curContent); } child.content = content; } else if (elemType == 'element') { child._name = r.name; if (Array.isArray(r.attributes)) { child._attributes = [].concat(r.attributes, child._attributes); } } else if (elemType == 'reference') { // it’s a reference to another abbreviation: // parse it and insert instead of current child /** @type AbbreviationNode */ var subtree = parser.parse(r.data, { syntax: syntax }); // if context element should be repeated, check if we need to // transfer repeated element to specific child node if (child.repeatCount > 1) { var repeatedChildren = subtree.findAll(function(node) { return node.hasImplicitRepeat; }); if (!repeatedChildren.length) { repeatedChildren = subtree.children } repeatedChildren.forEach(function(node) { node.repeatCount = child.repeatCount; node.hasImplicitRepeat = false; }); } // move child‘s children into the deepest child of new subtree var deepestChild = subtree.deepestChild(); if (deepestChild) { child.children.forEach(function(c) { deepestChild.addChild(c); }); deepestChild.content = child.content; } // copy current attributes to children subtree.children.forEach(function(node) { child.attributeList().forEach(function(attr) { node.attribute(attr.name, attr.value); }); }); child.replace(subtree.children); } matchResources(child, syntax, parser); }); } return { preprocessor: function(tree, options, parser) { var syntax = options.syntax || utils.defaultSyntax(); matchResources(tree, syntax, parser); } }; }); },{"../../assets/elements":"assets\\elements.js","../../assets/resources":"assets\\resources.js","../../utils/abbreviation":"utils\\abbreviation.js","../../utils/common":"utils\\common.js"}],"parser\\processor\\tagName.js":[function(require,module,exports){ /** * Resolves tag names in abbreviations with implied name */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var tagName = require('../../resolver/tagName'); /** * Resolves implicit node names in parsed tree * @param {AbbreviationNode} tree */ function resolveNodeNames(tree) { tree.children.forEach(function(node) { if (node.hasImplicitName() || node.data('forceNameResolving')) { node._name = tagName.resolve(node.parent.name()); node.data('nameResolved', true); } resolveNodeNames(node); }); return tree; } return { postprocessor: resolveNodeNames }; }); },{"../../resolver/tagName":"resolver\\tagName.js"}],"parser\\xml.js":[function(require,module,exports){ /** * HTML tokenizer by Marijn Haverbeke * http://codemirror.net/ * @constructor * @memberOf __xmlParseDefine * @param {Function} require * @param {Underscore} _ */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var stringStream = require('../assets/stringStream'); var Kludges = { autoSelfClosers : {}, implicitlyClosed : {}, contextGrabbers : {}, doNotIndent : {}, allowUnquoted : true, allowMissing : true }; // Return variables for tokenizers var tagName = null, type = null; function inText(stream, state) { function chain(parser) { state.tokenize = parser; return parser(stream, state); } var ch = stream.next(); if (ch == "<") { if (stream.eat("!")) { if (stream.eat("[")) { if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>")); else return null; } else if (stream.match("--")) return chain(inBlock("comment", "-->")); else if (stream.match("DOCTYPE", true, true)) { stream.eatWhile(/[\w\._\-]/); return chain(doctype(1)); } else return null; } else if (stream.eat("?")) { stream.eatWhile(/[\w\._\-]/); state.tokenize = inBlock("meta", "?>"); return "meta"; } else { type = stream.eat("/") ? "closeTag" : "openTag"; stream.eatSpace(); tagName = ""; var c; while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c; state.tokenize = inTag; return "tag"; } } else if (ch == "&") { var ok; if (stream.eat("#")) { if (stream.eat("x")) { ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";"); } else { ok = stream.eatWhile(/[\d]/) && stream.eat(";"); } } else { ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";"); } return ok ? "atom" : "error"; } else { stream.eatWhile(/[^&<]/); return "text"; } } function inTag(stream, state) { var ch = stream.next(); if (ch == ">" || (ch == "/" && stream.eat(">"))) { state.tokenize = inText; type = ch == ">" ? "endTag" : "selfcloseTag"; return "tag"; } else if (ch == "=") { type = "equals"; return null; } else if (/[\'\"]/.test(ch)) { state.tokenize = inAttribute(ch); return state.tokenize(stream, state); } else { stream.eatWhile(/[^\s\u00a0=<>\"\'\/?]/); return "word"; } } function inAttribute(quote) { return function(stream, state) { while (!stream.eol()) { if (stream.next() == quote) { state.tokenize = inTag; break; } } return "string"; }; } function inBlock(style, terminator) { return function(stream, state) { while (!stream.eol()) { if (stream.match(terminator)) { state.tokenize = inText; break; } stream.next(); } return style; }; } function doctype(depth) { return function(stream, state) { var ch; while ((ch = stream.next()) !== null) { if (ch == "<") { state.tokenize = doctype(depth + 1); return state.tokenize(stream, state); } else if (ch == ">") { if (depth == 1) { state.tokenize = inText; break; } else { state.tokenize = doctype(depth - 1); return state.tokenize(stream, state); } } } return "meta"; }; } var curState = null, setStyle; function pass() { for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]); } function cont() { pass.apply(null, arguments); return true; } function pushContext(tagName, startOfLine) { var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent); curState.context = { prev : curState.context, tagName : tagName, indent : curState.indented, startOfLine : startOfLine, noIndent : noIndent }; } function popContext() { if (curState.context) curState.context = curState.context.prev; } function element(type) { if (type == "openTag") { curState.tagName = tagName; return cont(attributes, endtag(curState.startOfLine)); } else if (type == "closeTag") { var err = false; if (curState.context) { if (curState.context.tagName != tagName) { if (Kludges.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase())) { popContext(); } err = !curState.context || curState.context.tagName != tagName; } } else { err = true; } if (err) setStyle = "error"; return cont(endclosetag(err)); } return cont(); } function endtag(startOfLine) { return function(type) { if (type == "selfcloseTag" || (type == "endTag" && Kludges.autoSelfClosers .hasOwnProperty(curState.tagName .toLowerCase()))) { maybePopContext(curState.tagName.toLowerCase()); return cont(); } if (type == "endTag") { maybePopContext(curState.tagName.toLowerCase()); pushContext(curState.tagName, startOfLine); return cont(); } return cont(); }; } function endclosetag(err) { return function(type) { if (err) setStyle = "error"; if (type == "endTag") { popContext(); return cont(); } setStyle = "error"; return cont(arguments.callee); }; } function maybePopContext(nextTagName) { var parentTagName; while (true) { if (!curState.context) { return; } parentTagName = curState.context.tagName.toLowerCase(); if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) || !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) { return; } popContext(); } } function attributes(type) { if (type == "word") { setStyle = "attribute"; return cont(attribute, attributes); } if (type == "endTag" || type == "selfcloseTag") return pass(); setStyle = "error"; return cont(attributes); } function attribute(type) { if (type == "equals") return cont(attvalue, attributes); if (!Kludges.allowMissing) setStyle = "error"; return (type == "endTag" || type == "selfcloseTag") ? pass() : cont(); } function attvalue(type) { if (type == "string") return cont(attvaluemaybe); if (type == "word" && Kludges.allowUnquoted) { setStyle = "string"; return cont(); } setStyle = "error"; return (type == "endTag" || type == "selfCloseTag") ? pass() : cont(); } function attvaluemaybe(type) { if (type == "string") return cont(attvaluemaybe); else return pass(); } function startState() { return { tokenize : inText, cc : [], indented : 0, startOfLine : true, tagName : null, context : null }; } function token(stream, state) { if (stream.sol()) { state.startOfLine = true; state.indented = 0; } if (stream.eatSpace()) return null; setStyle = type = tagName = null; var style = state.tokenize(stream, state); state.type = type; if ((style || type) && style != "comment") { curState = state; while (true) { var comb = state.cc.pop() || element; if (comb(type || style)) break; } } state.startOfLine = false; return setStyle || style; } return { /** * @memberOf emmet.xmlParser * @returns */ parse: function(data, offset) { offset = offset || 0; var state = startState(); var stream = stringStream.create(data); var tokens = []; while (!stream.eol()) { tokens.push({ type: token(stream, state), start: stream.start + offset, end: stream.pos + offset }); stream.start = stream.pos; } return tokens; } }; }); },{"../assets/stringStream":"assets\\stringStream.js"}],"plugin\\file.js":[function(require,module,exports){ /** * Module for working with file. Shall implement * IEmmetFile interface. * * Since implementation of this module depends * greatly on current runtime, this module must be * initialized with actual implementation first * before use. E.g. * require('./plugin/file')({ * read: function() {...} * }) * * By default, this module provides Node.JS implementation */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var utils = require('../utils/common'); var _transport = {}; // hide it from Require.JS parser (function(r) { if (typeof define === 'undefined' || !define.amd) { try { fs = r('fs'); path = r('path'); _transport.http = r('http'); _transport.https = r('https'); } catch(e) { } } })(require); // module is a function that can extend itself module.exports = function(obj) { if (obj) { utils.extend(module.exports, obj); } }; function bts(bytes) { var out = []; for (var i = 0, il = bytes.length; i < il; i++) { out.push(String.fromCharCode(bytes[i])); } return out.join(''); } function isURL(path) { var re = /^https?:\/\//; return re.test(path); } return utils.extend(module.exports, { _parseParams: function(args) { var params = { path: args[0], size: 0 }; args = utils.toArray(args, 1); params.callback = args[args.length - 1]; args = args.slice(0, args.length - 1); if (args.length) { params.size = args[0]; } return params; }, _read: function(params, callback) { if (isURL(params.path)) { var req = _transport[/^https:/.test(params.path) ? 'https' : 'http'].get(params.path, function(res) { var bufs = []; var totalLength = 0; var finished = false; res .on('data', function(chunk) { totalLength += chunk.length; bufs.push(chunk); if (params.size && totalLength >= params.size) { finished = true; callback(null, Buffer.concat(bufs)); req.abort(); } }) .on('end', function() { if (!finished) { finished = true; callback(null, Buffer.concat(bufs)); } }); }).on('error', callback); } else { if (params.size) { var fd = fs.openSync(params.path, 'r'); var buf = new Buffer(params.size); fs.read(fd, buf, 0, params.size, null, function(err, bytesRead) { callback(err, buf) }); } else { callback(null, fs.readFileSync(params.path)); } } }, /** * Reads binary file content and return it * @param {String} path File's relative or absolute path * @return {String} */ read: function(path, size, callback) { var params = this._parseParams(arguments); this._read(params, function(err, buf) { params.callback(err, err ? '' : bts(buf)); }); }, /** * Read file content and return it * @param {String} path File's relative or absolute path * @return {String} */ readText: function(path, size, callback) { var params = this._parseParams(arguments); this._read(params, function(err, buf) { params.callback(err, err ? '' : buf.toString()); }); }, /** * Locate file_name file that relates to editor_file. * File name may be absolute or relative path * * Dealing with absolute path. * Many modern editors have a "project" support as information unit, but you * should not rely on project path to find file with absolute path. First, * it requires user to create a project before using this method (and this * is not very convenient). Second, project path doesn't always points to * to website's document root folder: it may point, for example, to an * upper folder which contains server-side scripts. * * For better result, you should use the following algorithm in locating * absolute resources: * 1) Get parent folder for editorFile as a start point * 2) Append required fileName to start point and test if * file exists * 3) If it doesn't exists, move start point one level up (to parent folder) * and repeat step 2. * * @param {String} editorFile * @param {String} fileName * @return {String} Returns null if fileName cannot be located */ locateFile: function(editorFile, fileName, callback) { if (isURL(fileName)) { return callback(fileName); } var dirname = editorFile var filepath; fileName = fileName.replace(/^\/+/, ''); while (dirname && dirname !== path.dirname(dirname)) { dirname = path.dirname(dirname); filepath = path.join(dirname, fileName); if (fs.existsSync(filepath)) return callback(filepath); } callback(null); }, /** * Creates absolute path by concatenating parent and fileName. * If parent points to file, its parent directory is used * @param {String} parent * @param {String} fileName * @return {String} */ createPath: function(parent, fileName, callback) { fs.stat(parent, function(err, stat) { if (err) { return callback(err); } if (stat.isFile()) { parent = path.dirname(parent); } var filepath = path.resolve(parent, fileName); callback(null, filepath); }); }, /** * Saves content as file * @param {String} file File's absolute path * @param {String} content File content */ save: function(file, content, callback) { fs.writeFile(file, content, 'ascii', function(err) { callback(err ? err : null); }); }, /** * Returns file extension in lower case * @param {String} file * @return {String} */ getExt: function(file) { var m = (file || '').match(/\.([\w\-]+)$/); return m ? m[1].toLowerCase() : ''; } }); }); },{"../utils/common":"utils\\common.js"}],"resolver\\css.js":[function(require,module,exports){ /** * Resolver for fast CSS typing. Handles abbreviations with the following * notation:
* * (-vendor prefix)?property(value)*(!)? * *

* Abbreviation handling
* * By default, Emmet searches for matching snippet definition for provided abbreviation. * If snippet wasn't found, Emmet automatically generates element with * abbreviation's name. For example, foo abbreviation will generate * <foo></foo> output. *

* This module will capture all expanded properties and upgrade them with values, * vendor prefixes and !important declarations. All unmatched abbreviations will * be automatically transformed into property-name: ${1} snippets. * * Vendor prefixes
* * If CSS-property is preceded with dash, resolver should output property with * all known vendor prefixes. For example, if brad * abbreviation generates border-radius: ${value}; snippet, * the -brad abbreviation should generate: *

 * -webkit-border-radius: ${value};
 * -moz-border-radius: ${value};
 * border-radius: ${value};
 * 
* Note that o and ms prefixes are omitted since Opera and IE * supports unprefixed property.

* * Users can also provide an explicit list of one-character prefixes for any * CSS property. For example, -wm-float will produce * *

 * -webkit-float: ${1};
 * -moz-float: ${1};
 * float: ${1};
 * 
* * Although this example looks pointless, users can use this feature to write * cutting-edge properties implemented by browser vendors recently. */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var prefs = require('../assets/preferences'); var resources = require('../assets/resources'); var stringStream = require('../assets/stringStream'); var ciu = require('../assets/caniuse'); var utils = require('../utils/common'); var template = require('../utils/template'); var cssEditTree = require('../editTree/css'); var prefixObj = { /** Real vendor prefix name */ prefix: 'emmet', /** * Indicates this prefix is obsolete and should't be used when user * wants to generate all-prefixed properties */ obsolete: false, /** * Returns prefixed CSS property name * @param {String} name Unprefixed CSS property */ transformName: function(name) { return '-' + this.prefix + '-' + name; }, /** * List of unprefixed CSS properties that supported by * current prefix. This list is used to generate all-prefixed property * @returns {Array} */ properties: function() { return getProperties('css.' + this.prefix + 'Properties') || []; }, /** * Check if given property is supported by current prefix * @param name */ supports: function(name) { return ~this.properties().indexOf(name); } }; /** * List of registered one-character prefixes. Key is a one-character prefix, * value is an prefixObj object */ var vendorPrefixes = {}; var defaultValue = '${1};'; // XXX module preferences prefs.define('css.valueSeparator', ': ', 'Defines a symbol that should be placed between CSS property and ' + 'value when expanding CSS abbreviations.'); prefs.define('css.propertyEnd', ';', 'Defines a symbol that should be placed at the end of CSS property ' + 'when expanding CSS abbreviations.'); prefs.define('stylus.valueSeparator', ' ', 'Defines a symbol that should be placed between CSS property and ' + 'value when expanding CSS abbreviations in Stylus dialect.'); prefs.define('stylus.propertyEnd', '', 'Defines a symbol that should be placed at the end of CSS property ' + 'when expanding CSS abbreviations in Stylus dialect.'); prefs.define('sass.propertyEnd', '', 'Defines a symbol that should be placed at the end of CSS property ' + 'when expanding CSS abbreviations in SASS dialect.'); prefs.define('css.syntaxes', 'css, less, sass, scss, stylus, styl', 'List of syntaxes that should be treated as CSS dialects.'); prefs.define('css.autoInsertVendorPrefixes', true, 'Automatically generate vendor-prefixed copies of expanded CSS ' + 'property. By default, Emmet will generate vendor-prefixed ' + 'properties only when you put dash before abbreviation ' + '(e.g. -bxsh). With this option enabled, you don’t ' + 'need dashes before abbreviations: Emmet will produce ' + 'vendor-prefixed properties for you.'); prefs.define('less.autoInsertVendorPrefixes', false, 'Same as css.autoInsertVendorPrefixes but for LESS syntax'); prefs.define('scss.autoInsertVendorPrefixes', false, 'Same as css.autoInsertVendorPrefixes but for SCSS syntax'); prefs.define('sass.autoInsertVendorPrefixes', false, 'Same as css.autoInsertVendorPrefixes but for SASS syntax'); prefs.define('stylus.autoInsertVendorPrefixes', false, 'Same as css.autoInsertVendorPrefixes but for Stylus syntax'); var descTemplate = template('A comma-separated list of CSS properties that may have ' + '<%= vendor %> vendor prefix. This list is used to generate ' + 'a list of prefixed properties when expanding -property ' + 'abbreviations. Empty list means that all possible CSS values may ' + 'have <%= vendor %> prefix.'); var descAddonTemplate = template('A comma-separated list of additional CSS properties ' + 'for css.<%= vendor %>Preperties preference. ' + 'You should use this list if you want to add or remove a few CSS ' + 'properties to original set. To add a new property, simply write its name, ' + 'to remove it, precede property with hyphen.
' + 'For example, to add foo property and remove border-radius one, ' + 'the preference value will look like this: foo, -border-radius.'); // properties list is created from cssFeatures.html file var props = { 'webkit': 'animation, animation-delay, animation-direction, animation-duration, animation-fill-mode, animation-iteration-count, animation-name, animation-play-state, animation-timing-function, appearance, backface-visibility, background-clip, background-composite, background-origin, background-size, border-fit, border-horizontal-spacing, border-image, border-vertical-spacing, box-align, box-direction, box-flex, box-flex-group, box-lines, box-ordinal-group, box-orient, box-pack, box-reflect, box-shadow, color-correction, column-break-after, column-break-before, column-break-inside, column-count, column-gap, column-rule-color, column-rule-style, column-rule-width, column-span, column-width, dashboard-region, font-smoothing, highlight, hyphenate-character, hyphenate-limit-after, hyphenate-limit-before, hyphens, line-box-contain, line-break, line-clamp, locale, margin-before-collapse, margin-after-collapse, marquee-direction, marquee-increment, marquee-repetition, marquee-style, mask-attachment, mask-box-image, mask-box-image-outset, mask-box-image-repeat, mask-box-image-slice, mask-box-image-source, mask-box-image-width, mask-clip, mask-composite, mask-image, mask-origin, mask-position, mask-repeat, mask-size, nbsp-mode, perspective, perspective-origin, rtl-ordering, text-combine, text-decorations-in-effect, text-emphasis-color, text-emphasis-position, text-emphasis-style, text-fill-color, text-orientation, text-security, text-stroke-color, text-stroke-width, transform, transition, transform-origin, transform-style, transition-delay, transition-duration, transition-property, transition-timing-function, user-drag, user-modify, user-select, writing-mode, svg-shadow, box-sizing, border-radius', 'moz': 'animation-delay, animation-direction, animation-duration, animation-fill-mode, animation-iteration-count, animation-name, animation-play-state, animation-timing-function, appearance, backface-visibility, background-inline-policy, binding, border-bottom-colors, border-image, border-left-colors, border-right-colors, border-top-colors, box-align, box-direction, box-flex, box-ordinal-group, box-orient, box-pack, box-shadow, box-sizing, column-count, column-gap, column-rule-color, column-rule-style, column-rule-width, column-width, float-edge, font-feature-settings, font-language-override, force-broken-image-icon, hyphens, image-region, orient, outline-radius-bottomleft, outline-radius-bottomright, outline-radius-topleft, outline-radius-topright, perspective, perspective-origin, stack-sizing, tab-size, text-blink, text-decoration-color, text-decoration-line, text-decoration-style, text-size-adjust, transform, transform-origin, transform-style, transition, transition-delay, transition-duration, transition-property, transition-timing-function, user-focus, user-input, user-modify, user-select, window-shadow, background-clip, border-radius', 'ms': 'accelerator, backface-visibility, background-position-x, background-position-y, behavior, block-progression, box-align, box-direction, box-flex, box-line-progression, box-lines, box-ordinal-group, box-orient, box-pack, content-zoom-boundary, content-zoom-boundary-max, content-zoom-boundary-min, content-zoom-chaining, content-zoom-snap, content-zoom-snap-points, content-zoom-snap-type, content-zooming, filter, flow-from, flow-into, font-feature-settings, grid-column, grid-column-align, grid-column-span, grid-columns, grid-layer, grid-row, grid-row-align, grid-row-span, grid-rows, high-contrast-adjust, hyphenate-limit-chars, hyphenate-limit-lines, hyphenate-limit-zone, hyphens, ime-mode, interpolation-mode, layout-flow, layout-grid, layout-grid-char, layout-grid-line, layout-grid-mode, layout-grid-type, line-break, overflow-style, perspective, perspective-origin, perspective-origin-x, perspective-origin-y, scroll-boundary, scroll-boundary-bottom, scroll-boundary-left, scroll-boundary-right, scroll-boundary-top, scroll-chaining, scroll-rails, scroll-snap-points-x, scroll-snap-points-y, scroll-snap-type, scroll-snap-x, scroll-snap-y, scrollbar-arrow-color, scrollbar-base-color, scrollbar-darkshadow-color, scrollbar-face-color, scrollbar-highlight-color, scrollbar-shadow-color, scrollbar-track-color, text-align-last, text-autospace, text-justify, text-kashida-space, text-overflow, text-size-adjust, text-underline-position, touch-action, transform, transform-origin, transform-origin-x, transform-origin-y, transform-origin-z, transform-style, transition, transition-delay, transition-duration, transition-property, transition-timing-function, user-select, word-break, wrap-flow, wrap-margin, wrap-through, writing-mode', 'o': 'dashboard-region, animation, animation-delay, animation-direction, animation-duration, animation-fill-mode, animation-iteration-count, animation-name, animation-play-state, animation-timing-function, border-image, link, link-source, object-fit, object-position, tab-size, table-baseline, transform, transform-origin, transition, transition-delay, transition-duration, transition-property, transition-timing-function, accesskey, input-format, input-required, marquee-dir, marquee-loop, marquee-speed, marquee-style' }; Object.keys(props).forEach(function(k) { prefs.define('css.' + k + 'Properties', props[k], descTemplate({vendor: k})); prefs.define('css.' + k + 'PropertiesAddon', '', descAddonTemplate({vendor: k})); }); prefs.define('css.unitlessProperties', 'z-index, line-height, opacity, font-weight, zoom', 'The list of properties whose values ​​must not contain units.'); prefs.define('css.intUnit', 'px', 'Default unit for integer values'); prefs.define('css.floatUnit', 'em', 'Default unit for float values'); prefs.define('css.keywords', 'auto, inherit, all', 'A comma-separated list of valid keywords that can be used in CSS abbreviations.'); prefs.define('css.keywordAliases', 'a:auto, i:inherit, s:solid, da:dashed, do:dotted, t:transparent', 'A comma-separated list of keyword aliases, used in CSS abbreviation. ' + 'Each alias should be defined as alias:keyword_name.'); prefs.define('css.unitAliases', 'e:em, p:%, x:ex, r:rem', 'A comma-separated list of unit aliases, used in CSS abbreviation. ' + 'Each alias should be defined as alias:unit_value.'); prefs.define('css.color.short', true, 'Should color values like #ffffff be shortened to ' + '#fff after abbreviation with color was expanded.'); prefs.define('css.color.case', 'keep', 'Letter case of color values generated by abbreviations with color ' + '(like c#0). Possible values are upper, ' + 'lower and keep.'); prefs.define('css.fuzzySearch', true, 'Enable fuzzy search among CSS snippet names. When enabled, every ' + 'unknown snippet will be scored against available snippet ' + 'names (not values or CSS properties!). The match with best score ' + 'will be used to resolve snippet value. For example, with this ' + 'preference enabled, the following abbreviations are equal: ' + 'ov:h == ov-h == o-h == ' + 'oh'); prefs.define('css.fuzzySearchMinScore', 0.3, 'The minium score (from 0 to 1) that fuzzy-matched abbreviation should ' + 'achive. Lower values may produce many false-positive matches, ' + 'higher values may reduce possible matches.'); prefs.define('css.alignVendor', false, 'If set to true, all generated vendor-prefixed properties ' + 'will be aligned by real property name.'); function isNumeric(ch) { var code = ch && ch.charCodeAt(0); return (ch && ch == '.' || (code > 47 && code < 58)); } /** * Check if provided snippet contains only one CSS property and value. * @param {String} snippet * @returns {Boolean} */ function isSingleProperty(snippet) { snippet = utils.trim(snippet); // check if it doesn't contain a comment and a newline if (/\/\*|\n|\r/.test(snippet)) { return false; } // check if it's a valid snippet definition if (!/^[a-z0-9\-]+\s*\:/i.test(snippet)) { return false; } return snippet.replace(/\$\{.+?\}/g, '').split(':').length == 2; } /** * Normalizes abbreviated value to final CSS one * @param {String} value * @returns {String} */ function normalizeValue(value) { if (value.charAt(0) == '-' && !/^\-[\.\d]/.test(value)) { value = value.replace(/^\-+/, ''); } var ch = value.charAt(0); if (ch == '#') { return normalizeHexColor(value); } if (ch == '$') { return utils.escapeText(value); } return getKeyword(value); } function normalizeHexColor(value) { var hex = value.replace(/^#+/, '') || '0'; if (hex.toLowerCase() == 't') { return 'transparent'; } var opacity = ''; hex = hex.replace(/\.(\d+)$/, function(str) { opacity = '0' + str; return ''; }); var repeat = utils.repeatString; var color = null; switch (hex.length) { case 1: color = repeat(hex, 6); break; case 2: color = repeat(hex, 3); break; case 3: color = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2); break; case 4: color = hex + hex.substr(0, 2); break; case 5: color = hex + hex.charAt(0); break; default: color = hex.substr(0, 6); } if (opacity) { return toRgba(color, opacity); } // color must be shortened? if (prefs.get('css.color.short')) { var p = color.split(''); if (p[0] == p[1] && p[2] == p[3] && p[4] == p[5]) { color = p[0] + p[2] + p[4]; } } // should transform case? switch (prefs.get('css.color.case')) { case 'upper': color = color.toUpperCase(); break; case 'lower': color = color.toLowerCase(); break; } return '#' + color; } /** * Transforms HEX color definition into RGBA one * @param {String} color HEX color, 6 characters * @param {String} opacity Opacity value * @return {String} */ function toRgba(color, opacity) { var r = parseInt(color.substr(0, 2), 16); var g = parseInt(color.substr(2, 2), 16); var b = parseInt(color.substr(4, 2), 16); return 'rgba(' + [r, g, b, opacity].join(', ') + ')'; } function getKeyword(name) { var aliases = prefs.getDict('css.keywordAliases'); return name in aliases ? aliases[name] : name; } function getUnit(name) { var aliases = prefs.getDict('css.unitAliases'); return name in aliases ? aliases[name] : name; } function isValidKeyword(keyword) { return ~prefs.getArray('css.keywords').indexOf(getKeyword(keyword)); } /** * Check if passed CSS property support specified vendor prefix * @param {String} property * @param {String} prefix */ function hasPrefix(property, prefix) { var info = vendorPrefixes[prefix]; if (!info) info = utils.find(vendorPrefixes, function(data) { return data.prefix == prefix; }); return info && info.supports(property); } /** * Finds available vendor prefixes for given CSS property. * Search is performed within Can I Use database and internal * property list * @param {String} property CSS property name * @return {Array} Array of resolved prefixes or null if * prefixes are not available for this property at all. * Empty array means prefixes are not available for current * user-define era */ function findVendorPrefixes(property) { var prefixes = ciu.resolvePrefixes(property); if (!prefixes) { // Can I Use database is disabled or prefixes are not // available for this property prefixes = []; Object.keys(vendorPrefixes).forEach(function(key) { if (hasPrefix(property, key)) { prefixes.push(vendorPrefixes[key].prefix); } }); if (!prefixes.length) { prefixes = null; } } return prefixes; } /** * Search for a list of supported prefixes for CSS property. This list * is used to generate all-prefixed snippet * @param {String} property CSS property name * @returns {Array} */ function findInternalPrefixes(property, noAutofill) { var result = []; var prefixes = findVendorPrefixes(property); if (prefixes) { var prefixMap = {}; Object.keys(vendorPrefixes).forEach(function(key) { prefixMap[vendorPrefixes[key].prefix] = key; }); result = prefixes.map(function(prefix) { return prefixMap[prefix]; }); } if (!result.length && !noAutofill) { // add all non-obsolete prefixes Object.keys(vendorPrefixes).forEach(function(prefix) { if (!vendorPrefixes[prefix].obsolete) { result.push(prefix); } }); } return result; } function addPrefix(name, obj) { if (typeof obj === 'string') { obj = {prefix: obj}; } vendorPrefixes[name] = utils.extend({}, prefixObj, obj); } function getSyntaxPreference(name, syntax) { if (syntax) { // hacky alias for Stylus dialect if (syntax == 'styl') { syntax = 'stylus'; } var val = prefs.get(syntax + '.' + name); if (typeof val !== 'undefined') { return val; } } return prefs.get('css.' + name); } /** * Format CSS property according to current syntax dialect * @param {String} property * @param {String} syntax * @returns {String} */ function formatProperty(property, syntax) { var ix = property.indexOf(':'); property = property.substring(0, ix).replace(/\s+$/, '') + getSyntaxPreference('valueSeparator', syntax) + utils.trim(property.substring(ix + 1)); return property.replace(/\s*;\s*$/, getSyntaxPreference('propertyEnd', syntax)); } /** * Transforms snippet value if required. For example, this transformation * may add !important declaration to CSS property * @param {String} snippet * @param {Boolean} isImportant * @returns {String} */ function transformSnippet(snippet, isImportant, syntax) { if (typeof snippet !== 'string') { snippet = snippet.data; } if (!isSingleProperty(snippet)) { return snippet; } if (isImportant) { if (~snippet.indexOf(';')) { snippet = snippet.split(';').join(' !important;'); } else { snippet += ' !important'; } } return formatProperty(snippet, syntax); } function getProperties(key) { var list = prefs.getArray(key); var addon = prefs.getArray(key + 'Addon'); if (addon) { addon.forEach(function(prop) { if (prop.charAt(0) == '-') { list = utils.without(list, prop.substr(1)); } else { if (prop.charAt(0) == '+') prop = prop.substr(1); list.push(prop); } }); } return list; } /** * Tries to produce properties with vendor-prefixed value * @param {Object} snippetObj Parsed snippet object * @return {Array} Array of properties with prefixed values */ function resolvePrefixedValues(snippetObj, isImportant, syntax) { var prefixes = []; var lookup = {}; var parts = cssEditTree.findParts(snippetObj.value); parts.reverse(); parts.forEach(function(p) { var partValue = p.substring(snippetObj.value); (findVendorPrefixes(partValue) || []).forEach(function(prefix) { if (!lookup[prefix]) { lookup[prefix] = snippetObj.value; prefixes.push(prefix); } lookup[prefix] = utils.replaceSubstring(lookup[prefix], '-' + prefix + '-' + partValue, p); }); }); return prefixes.map(function(prefix) { return transformSnippet(snippetObj.name + ':' + lookup[prefix], isImportant, syntax); }); } // TODO refactor, this looks awkward now addPrefix('w', { prefix: 'webkit' }); addPrefix('m', { prefix: 'moz' }); addPrefix('s', { prefix: 'ms' }); addPrefix('o', { prefix: 'o' }); module = module || {}; module.exports = { /** * Adds vendor prefix * @param {String} name One-character prefix name * @param {Object} obj Object describing vendor prefix * @memberOf cssResolver */ addPrefix: addPrefix, /** * Check if passed CSS property supports specified vendor prefix * @param {String} property * @param {String} prefix */ supportsPrefix: hasPrefix, resolve: function(node, syntax) { var cssSyntaxes = prefs.getArray('css.syntaxes'); if (cssSyntaxes && ~cssSyntaxes.indexOf(syntax) && node.isElement()) { return this.expandToSnippet(node.abbreviation, syntax); } return null; }, /** * Returns prefixed version of passed CSS property, only if this * property supports such prefix * @param {String} property * @param {String} prefix * @returns */ prefixed: function(property, prefix) { return hasPrefix(property, prefix) ? '-' + prefix + '-' + property : property; }, /** * Returns list of all registered vendor prefixes * @returns {Array} */ listPrefixes: function() { return vendorPrefixes.map(function(obj) { return obj.prefix; }); }, /** * Returns object describing vendor prefix * @param {String} name * @returns {Object} */ getPrefix: function(name) { return vendorPrefixes[name]; }, /** * Removes prefix object * @param {String} name */ removePrefix: function(name) { if (name in vendorPrefixes) delete vendorPrefixes[name]; }, /** * Extract vendor prefixes from abbreviation * @param {String} abbr * @returns {Object} Object containing array of prefixes and clean * abbreviation name */ extractPrefixes: function(abbr) { if (abbr.charAt(0) != '-') { return { property: abbr, prefixes: null }; } // abbreviation may either contain sequence of one-character prefixes // or just dash, meaning that user wants to produce all possible // prefixed properties var i = 1, il = abbr.length, ch; var prefixes = []; while (i < il) { ch = abbr.charAt(i); if (ch == '-') { // end-sequence character found, stop searching i++; break; } if (ch in vendorPrefixes) { prefixes.push(ch); } else { // no prefix found, meaning user want to produce all // vendor-prefixed properties prefixes.length = 0; i = 1; break; } i++; } // reached end of abbreviation and no property name left if (i == il -1) { i = 1; prefixes.length = 1; } return { property: abbr.substring(i), prefixes: prefixes.length ? prefixes : 'all' }; }, /** * Search for value substring in abbreviation * @param {String} abbr * @returns {String} Value substring */ findValuesInAbbreviation: function(abbr, syntax) { syntax = syntax || 'css'; var i = 0, il = abbr.length, value = '', ch; while (i < il) { ch = abbr.charAt(i); if (isNumeric(ch) || ch == '#' || ch == '$' || (ch == '-' && isNumeric(abbr.charAt(i + 1)))) { value = abbr.substring(i); break; } i++; } // try to find keywords in abbreviation var property = abbr.substring(0, abbr.length - value.length); var keywords = []; // try to extract some commonly-used properties while (~property.indexOf('-') && !resources.findSnippet(syntax, property)) { var parts = property.split('-'); var lastPart = parts.pop(); if (!isValidKeyword(lastPart)) { break; } keywords.unshift(lastPart); property = parts.join('-'); } return keywords.join('-') + value; }, parseValues: function(str) { /** @type StringStream */ var stream = stringStream.create(str); var values = []; var ch = null; while ((ch = stream.next())) { if (ch == '$') { stream.match(/^[^\$]+/, true); values.push(stream.current()); } else if (ch == '#') { stream.match(/^t|[0-9a-f]+(\.\d+)?/i, true); values.push(stream.current()); } else if (ch == '-') { if (isValidKeyword(utils.last(values)) || ( stream.start && isNumeric(str.charAt(stream.start - 1)) ) ) { stream.start = stream.pos; } stream.match(/^\-?[0-9]*(\.[0-9]+)?[a-z%\.]*/, true); values.push(stream.current()); } else { stream.match(/^[0-9]*(\.[0-9]*)?[a-z%]*/, true); values.push(stream.current()); } stream.start = stream.pos; } return values .filter(function(item) { return !!item; }) .map(normalizeValue); }, /** * Extracts values from abbreviation * @param {String} abbr * @returns {Object} Object containing array of values and clean * abbreviation name */ extractValues: function(abbr) { // search for value start var abbrValues = this.findValuesInAbbreviation(abbr); if (!abbrValues) { return { property: abbr, values: null }; } return { property: abbr.substring(0, abbr.length - abbrValues.length).replace(/-$/, ''), values: this.parseValues(abbrValues) }; }, /** * Normalizes value, defined in abbreviation. * @param {String} value * @param {String} property * @returns {String} */ normalizeValue: function(value, property) { property = (property || '').toLowerCase(); var unitlessProps = prefs.getArray('css.unitlessProperties'); return value.replace(/^(\-?[0-9\.]+)([a-z]*)$/, function(str, val, unit) { if (!unit && (val == '0' || ~unitlessProps.indexOf(property))) return val; if (!unit) return val.replace(/\.$/, '') + prefs.get(~val.indexOf('.') ? 'css.floatUnit' : 'css.intUnit'); return val + getUnit(unit); }); }, /** * Expands abbreviation into a snippet * @param {String} abbr Abbreviation name to expand * @param {String} value Abbreviation value * @param {String} syntax Currect syntax or dialect. Default is 'css' * @returns {Object} Array of CSS properties and values or predefined * snippet (string or element) */ expand: function(abbr, value, syntax) { syntax = syntax || 'css'; var autoInsertPrefixes = prefs.get(syntax + '.autoInsertVendorPrefixes'); // check if snippet should be transformed to !important var isImportant = /^(.+)\!$/.test(abbr); if (isImportant) { abbr = RegExp.$1; } // check if we have abbreviated resource var snippet = resources.findSnippet(syntax, abbr); if (snippet && !autoInsertPrefixes) { return transformSnippet(snippet, isImportant, syntax); } // no abbreviated resource, parse abbreviation var prefixData = this.extractPrefixes(abbr); var valuesData = this.extractValues(prefixData.property); var abbrData = utils.extend(prefixData, valuesData); if (!snippet) { snippet = resources.findSnippet(syntax, abbrData.property); } else { abbrData.values = null; } if (!snippet && prefs.get('css.fuzzySearch')) { // let’s try fuzzy search snippet = resources.fuzzyFindSnippet(syntax, abbrData.property, parseFloat(prefs.get('css.fuzzySearchMinScore'))); } if (!snippet) { if (!abbrData.property || abbrData.property.endsWith(':')) { return null; } snippet = abbrData.property + ':' + defaultValue; } else if (typeof snippet !== 'string') { snippet = snippet.data; } if (!isSingleProperty(snippet)) { return snippet; } var snippetObj = this.splitSnippet(snippet); var result = []; if (!value && abbrData.values) { value = abbrData.values.map(function(val) { return this.normalizeValue(val, snippetObj.name); }, this).join(' ') + ';'; } snippetObj.value = value || snippetObj.value; var prefixes = abbrData.prefixes == 'all' || (!abbrData.prefixes && autoInsertPrefixes) ? findInternalPrefixes(snippetObj.name, autoInsertPrefixes && abbrData.prefixes != 'all') : abbrData.prefixes; var names = [], propName; (prefixes || []).forEach(function(p) { if (p in vendorPrefixes) { propName = vendorPrefixes[p].transformName(snippetObj.name); names.push(propName); result.push(transformSnippet(propName + ':' + snippetObj.value, isImportant, syntax)); } }); // put the original property result.push(transformSnippet(snippetObj.name + ':' + snippetObj.value, isImportant, syntax)); names.push(snippetObj.name); result = resolvePrefixedValues(snippetObj, isImportant, syntax).concat(result); if (prefs.get('css.alignVendor')) { var pads = utils.getStringsPads(names); result = result.map(function(prop, i) { return pads[i] + prop; }); } return result; }, /** * Same as expand method but transforms output into * Emmet snippet * @param {String} abbr * @param {String} syntax * @returns {String} */ expandToSnippet: function(abbr, syntax) { var snippet = this.expand(abbr, null, syntax); if (snippet === null) { return null; } if (Array.isArray(snippet)) { return snippet.join('\n'); } if (typeof snippet !== 'string') { return snippet.data; } return snippet + ''; }, /** * Split snippet into a CSS property-value pair * @param {String} snippet */ splitSnippet: function(snippet) { snippet = utils.trim(snippet); if (snippet.indexOf(':') == -1) { return { name: snippet, value: defaultValue }; } var pair = snippet.split(':'); return { name: utils.trim(pair.shift()), // replace ${0} tabstop to produce valid vendor-prefixed values // where possible value: utils.trim(pair.join(':')).replace(/^(\$\{0\}|\$0)(\s*;?)$/, '${1}$2') }; }, getSyntaxPreference: getSyntaxPreference, transformSnippet: transformSnippet, vendorPrefixes: findVendorPrefixes }; return module.exports; }); },{"../assets/caniuse":"assets\\caniuse.js","../assets/preferences":"assets\\preferences.js","../assets/resources":"assets\\resources.js","../assets/stringStream":"assets\\stringStream.js","../editTree/css":"editTree\\css.js","../utils/common":"utils\\common.js","../utils/template":"utils\\template.js"}],"resolver\\cssGradient.js":[function(require,module,exports){ /** * 'Expand Abbreviation' handler that parses gradient definition from under * cursor and updates CSS rule with vendor-prefixed values. */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var prefs = require('../assets/preferences'); var resources = require('../assets/resources'); var utils = require('../utils/common'); var stringStream = require('../assets/stringStream'); var cssResolver = require('./css'); var range = require('../assets/range'); var cssEditTree = require('../editTree/css'); var editorUtils = require('../utils/editor'); var linearGradient = require('./gradient/linear'); var cssSyntaxes = ['css', 'less', 'sass', 'scss', 'stylus', 'styl']; // XXX define preferences prefs.define('css.gradient.prefixes', 'webkit, moz, o', 'A comma-separated list of vendor-prefixes for which values should ' + 'be generated.'); prefs.define('css.gradient.oldWebkit', false, 'Generate gradient definition for old Webkit implementations'); prefs.define('css.gradient.omitDefaultDirection', true, 'Do not output default direction definition in generated gradients.'); prefs.define('css.gradient.defaultProperty', 'background-image', 'When gradient expanded outside CSS value context, it will produce ' + 'properties with this name.'); prefs.define('css.gradient.fallback', false, 'With this option enabled, CSS gradient generator will produce ' + 'background-color property with gradient first color ' + 'as fallback for old browsers.'); /** * Resolves property name (abbreviation): searches for snippet definition in * 'resources' and returns new name of matched property */ function resolvePropertyName(name, syntax) { var snippet = resources.findSnippet(syntax, name); if (!snippet && prefs.get('css.fuzzySearch')) { var minScore = parseFloat(prefs.get('css.fuzzySearchMinScore')); snippet = resources.fuzzyFindSnippet(syntax, name, minScore); } if (snippet) { if (typeof snippet !== 'string') { snippet = snippet.data; } return cssResolver.splitSnippet(snippet).name; } } /** * Returns vendor prefixes for given gradient type * @param {String} type Gradient type (currently, 'linear-gradient' * is the only supported value) * @return {Array} */ function getGradientPrefixes(type) { var prefixes = cssResolver.vendorPrefixes(type); if (!prefixes) { // disabled Can I Use, fallback to property list prefixes = prefs.getArray('css.gradient.prefixes'); } return prefixes || []; } function getPrefixedNames(type) { var prefixes = getGradientPrefixes(type); var names = prefixes ? prefixes.map(function(p) { return '-' + p + '-' + type; }) : []; names.push(type); return names; } /** * Returns list of CSS properties with gradient * @param {Array} gradient List of gradient objects * @param {CSSEditElement} property Original CSS property * @returns {Array} */ function getPropertiesForGradient(gradients, property) { var props = []; var propertyName = property.name(); var omitDir = prefs.get('css.gradient.omitDefaultDirection'); if (prefs.get('css.gradient.fallback') && ~propertyName.toLowerCase().indexOf('background')) { props.push({ name: 'background-color', value: '${1:' + gradients[0].gradient.colorStops[0].color + '}' }); } var value = property.value(); getGradientPrefixes('linear-gradient').forEach(function(prefix) { var name = cssResolver.prefixed(propertyName, prefix); if (prefix == 'webkit' && prefs.get('css.gradient.oldWebkit')) { try { props.push({ name: name, value: insertGradientsIntoCSSValue(gradients, value, { prefix: prefix, oldWebkit: true, omitDefaultDirection: omitDir }) }); } catch(e) {} } props.push({ name: name, value: insertGradientsIntoCSSValue(gradients, value, { prefix: prefix, omitDefaultDirection: omitDir }) }); }); return props.sort(function(a, b) { return b.name.length - a.name.length; }); } /** * Replaces old gradient definitions in given CSS property value * with new ones, preserving original formatting * @param {Array} gradients List of CSS gradients * @param {String} value Original CSS value * @param {Object} options Options for gradient’s stringify() method * @return {String} */ function insertGradientsIntoCSSValue(gradients, value, options) { // gradients *should* passed in order they actually appear in CSS property // iterate over it in backward direction to preserve gradient locations options = options || {}; gradients = utils.clone(gradients); gradients.reverse().forEach(function(item, i) { var suffix = !i && options.placeholder ? options.placeholder : ''; var str = options.oldWebkit ? item.gradient.stringifyOldWebkit(options) : item.gradient.stringify(options); value = utils.replaceSubstring(value, str + suffix, item.matchedPart); }); return value; } /** * Returns list of properties with the same meaning * (e.g. vendor-prefixed + original name) * @param {String} property CSS property name * @return {Array} */ function similarPropertyNames(property) { if (typeof property !== 'string') { property = property.name(); } var similarProps = (cssResolver.vendorPrefixes(property) || []).map(function(prefix) { return '-' + prefix + '-' + property; }); similarProps.push(property); return similarProps; } /** * Pastes gradient definition into CSS rule with correct vendor-prefixes * @param {EditElement} property Matched CSS property * @param {Array} gradients List of gradients to insert */ function pasteGradient(property, gradients) { var rule = property.parent; var alignVendor = prefs.get('css.alignVendor'); var omitDir = prefs.get('css.gradient.omitDefaultDirection'); // we may have aligned gradient definitions: find the smallest value // separator var sep = property.styleSeparator; var before = property.styleBefore; // first, remove all properties within CSS rule with the same name and // gradient definition rule.getAll(similarPropertyNames(property)).forEach(function(item) { if (item != property && /gradient/i.test(item.value())) { if (item.styleSeparator.length < sep.length) { sep = item.styleSeparator; } if (item.styleBefore.length < before.length) { before = item.styleBefore; } rule.remove(item); } }); if (alignVendor) { // update prefix if (before != property.styleBefore) { var fullRange = property.fullRange(); rule._updateSource(before, fullRange.start, fullRange.start + property.styleBefore.length); property.styleBefore = before; } // update separator value if (sep != property.styleSeparator) { rule._updateSource(sep, property.nameRange().end, property.valueRange().start); property.styleSeparator = sep; } } var value = property.value(); // create list of properties to insert var propsToInsert = getPropertiesForGradient(gradients, property); // align prefixed values if (alignVendor) { var names = [], values = []; propsToInsert.forEach(function(item) { names.push(item.name); values.push(item.value); }); values.push(property.value()); names.push(property.name()); var valuePads = utils.getStringsPads(values.map(function(v) { return v.substring(0, v.indexOf('(')); })); var namePads = utils.getStringsPads(names); property.name(namePads[namePads.length - 1] + property.name()); propsToInsert.forEach(function(prop, i) { prop.name = namePads[i] + prop.name; prop.value = valuePads[i] + prop.value; }); property.value(valuePads[valuePads.length - 1] + property.value()); } // put vendor-prefixed definitions before current rule propsToInsert.forEach(function(prop) { rule.add(prop.name, prop.value, rule.indexOf(property)); }); // put vanilla-clean gradient definition into current rule property.value(insertGradientsIntoCSSValue(gradients, value, { placeholder: '${2}', omitDefaultDirection: omitDir })); } /** * Validates caret position relatively to located gradients * in CSS rule. In other words, it checks if it’s safe to * expand gradients for current caret position or not. * * See issue https://github.com/sergeche/emmet-sublime/issues/411 * * @param {Array} gradients List of parsed gradients * @param {Number} caretPos Current caret position * @param {String} syntax Current document syntax * @return {Boolean} */ function isValidCaretPosition(gradients, caretPos, syntax) { syntax = syntax || 'css'; if (syntax == 'css' || syntax == 'less' || syntax == 'scss') { return true; } var offset = gradients.property.valueRange(true).start; var parts = gradients.gradients; // in case of preprocessors where properties are separated with // newlines, make sure there’s no gradient definition past // current caret position. for (var i = parts.length - 1; i >= 0; i--) { if (parts[i].matchedPart.start + offset >= caretPos) { return false; } } return true; } module = module || {}; return module.exports = { /** * Search for gradient definitions inside CSS property value * @returns {Array} Array of matched gradients */ findGradients: function(cssProp) { var value = cssProp.value(); var gradients = []; var that = this; cssProp.valueParts().forEach(function(part) { var partValue = part.substring(value); if (linearGradient.isLinearGradient(partValue)) { var gradient = linearGradient.parse(partValue); if (gradient) { gradients.push({ gradient: gradient, matchedPart: part }); } } }); return gradients.length ? gradients : null; }, /** * Returns list of gradients found in CSS property * of given CSS code in specified (caret) position * @param {String} css CSS code snippet * @param {Number} pos Character index where to start searching for CSS property * @return {Array} */ gradientsFromCSSProperty: function(css, pos) { var cssProp = cssEditTree.propertyFromPosition(css, pos); if (cssProp) { var grd = this.findGradients(cssProp); if (grd) { return { property: cssProp, gradients: grd }; } } return null; }, /** * Handler for “Expand Abbreviation” action * @param {IEmmetEditor} editor * @param {String} syntax * @param {String} profile * return {Boolean} */ expandAbbreviationHandler: function(editor, syntax, profile) { var info = editorUtils.outputInfo(editor, syntax, profile); if (!~cssSyntaxes.indexOf(info.syntax)) { return false; } // let's see if we are expanding gradient definition var caret = editor.getCaretPos(); var content = info.content; var gradients = this.gradientsFromCSSProperty(content, caret); if (gradients) { if (!isValidCaretPosition(gradients, caret, info.syntax)) { return false; } var cssProperty = gradients.property; var cssRule = cssProperty.parent; var ruleStart = cssRule.options.offset || 0; var ruleEnd = ruleStart + cssRule.toString().length; // Handle special case: // user wrote gradient definition between existing CSS // properties and did not finished it with semicolon. // In this case, we have semicolon right after gradient // definition and re-parse rule again if (/[\n\r]/.test(cssProperty.value())) { // insert semicolon at the end of gradient definition var insertPos = cssProperty.valueRange(true).start + utils.last(gradients.gradients).matchedPart.end; content = utils.replaceSubstring(content, ';', insertPos); var _gradients = this.gradientsFromCSSProperty(content, caret); if (_gradients) { gradients = _gradients; cssProperty = gradients.property; cssRule = cssProperty.parent; } } // make sure current property has terminating semicolon cssProperty.end(';'); // resolve CSS property name var resolvedName = resolvePropertyName(cssProperty.name(), syntax); if (resolvedName) { cssProperty.name(resolvedName); } pasteGradient(cssProperty, gradients.gradients); editor.replaceContent(cssRule.toString(), ruleStart, ruleEnd, true); return true; } return this.expandGradientOutsideValue(editor, syntax); }, /** * Tries to expand gradient outside CSS value * @param {IEmmetEditor} editor * @param {String} syntax */ expandGradientOutsideValue: function(editor, syntax) { var propertyName = prefs.get('css.gradient.defaultProperty'); var omitDir = prefs.get('css.gradient.omitDefaultDirection'); if (!propertyName) { return false; } // assuming that gradient definition is written on new line, // do a simplified parsing var content = String(editor.getContent()); /** @type Range */ var lineRange = range.create(editor.getCurrentLineRange()); // get line content and adjust range with padding var line = lineRange.substring(content) .replace(/^\s+/, function(pad) { lineRange.start += pad.length; return ''; }) .replace(/\s+$/, function(pad) { lineRange.end -= pad.length; return ''; }); // trick parser: make it think that we’re parsing actual CSS property var fakeCSS = 'a{' + propertyName + ': ' + line + ';}'; var gradients = this.gradientsFromCSSProperty(fakeCSS, fakeCSS.length - 2); if (gradients) { var props = getPropertiesForGradient(gradients.gradients, gradients.property); props.push({ name: gradients.property.name(), value: insertGradientsIntoCSSValue(gradients.gradients, gradients.property.value(), { placeholder: '${2}', omitDefaultDirection: omitDir }) }); var sep = cssResolver.getSyntaxPreference('valueSeparator', syntax); var end = cssResolver.getSyntaxPreference('propertyEnd', syntax); if (prefs.get('css.alignVendor')) { var pads = utils.getStringsPads(props.map(function(prop) { return prop.value.substring(0, prop.value.indexOf('(')); })); props.forEach(function(prop, i) { prop.value = pads[i] + prop.value; }); } props = props.map(function(item) { return item.name + sep + item.value + end; }); editor.replaceContent(props.join('\n'), lineRange.start, lineRange.end); return true; } return false; }, /** * Handler for “Reflect CSS Value“ action * @param {String} property */ reflectValueHandler: function(property) { var omitDir = prefs.get('css.gradient.omitDefaultDirection'); var gradients = this.findGradients(property); if (!gradients) { return false; } var that = this; var value = property.value(); // reflect value for properties with the same name property.parent.getAll(similarPropertyNames(property)).forEach(function(prop) { if (prop === property) { return; } // make sure current property contains gradient definition, // otherwise – skip it var localGradients = that.findGradients(prop); if (localGradients) { // detect vendor prefix for current property var localValue = prop.value(); var dfn = localGradients[0].matchedPart.substring(localValue); var prefix = ''; if (/^\s*\-([a-z]+)\-/.test(dfn)) { prefix = RegExp.$1; } prop.value(insertGradientsIntoCSSValue(gradients, value, { prefix: prefix, omitDefaultDirection: omitDir })); } }); return true; } }; }); },{"../assets/preferences":"assets\\preferences.js","../assets/range":"assets\\range.js","../assets/resources":"assets\\resources.js","../assets/stringStream":"assets\\stringStream.js","../editTree/css":"editTree\\css.js","../utils/common":"utils\\common.js","../utils/editor":"utils\\editor.js","./css":"resolver\\css.js","./gradient/linear":"resolver\\gradient\\linear.js"}],"resolver\\gradient\\linear.js":[function(require,module,exports){ /** * CSS linear gradient definition */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var stringStream = require('../../assets/stringStream'); var utils = require('../../utils/common'); // all directions are expressed in “new style” degrees var directions = { 'bottom': 0, 'bottom left': 45, 'left': 90, 'top left': 135, 'top': 180, 'top right': 225, 'right': 270, 'bottom right': 315, 'to top': 0, 'to top right': 45, 'to right': 90, 'to bottom right': 135, 'to bottom': 180, 'to bottom left': 225, 'to left': 270, 'to top left': 315 }; var defaultDirections = ['top', 'to bottom', '0deg']; var reLinearGradient = /^\s*(\-[a-z]+\-)?(lg|linear\-gradient)\s*\(/i; var reDeg = /(\d+)deg/i; var reKeyword = /top|bottom|left|right/i; function LinearGradient(dfn) { this.colorStops = []; this.direction = 180; // extract tokens var stream = stringStream.create(utils.trim(dfn)); var ch, cur; while ((ch = stream.next())) { if (stream.peek() == ',') { // Is it a first entry? Check if it’s a direction cur = stream.current(); if (!this.colorStops.length && (reDeg.test(cur) || reKeyword.test(cur))) { this.direction = resolveDirection(cur); } else { this.addColorStop(cur); } stream.next(); stream.eatSpace(); stream.start = stream.pos; } else if (ch == '(') { // color definition, like 'rgb(0,0,0)' stream.skipTo(')'); } } // add last token this.addColorStop(stream.current()); } LinearGradient.prototype = { type: 'linear-gradient', addColorStop: function(color, ix) { color = normalizeSpace(color || ''); if (!color) { return; } color = this.parseColorStop(color); if (typeof ix === 'undefined') { this.colorStops.push(color); } else { this.colorStops.splice(ix, 0, color); } }, /** * Parses color stop definition * @param {String} colorStop * @returns {Object} */ parseColorStop: function(colorStop) { colorStop = normalizeSpace(colorStop); // find color declaration // first, try complex color declaration, like rgb(0,0,0) var color = null; colorStop = colorStop.replace(/^(\w+\(.+?\))\s*/, function(str, c) { color = c; return ''; }); if (!color) { // try simple declaration, like yellow, #fco, #ffffff, etc. var parts = colorStop.split(' '); color = parts[0]; colorStop = parts[1] || ''; } var result = { color: color }; if (colorStop) { // there's position in color stop definition colorStop.replace(/^(\-?[\d\.]+)([a-z%]+)?$/, function(str, pos, unit) { result.position = pos; if (~pos.indexOf('.')) { unit = ''; } else if (!unit) { unit = '%'; } if (unit) { result.unit = unit; } }); } return result; }, stringify: function(options) { options = options || {}; var fn = 'linear-gradient'; if (options.prefix) { fn = '-' + options.prefix + '-' + fn; } // transform color-stops var parts = this.colorStops.map(function(cs) { var pos = cs.position ? ' ' + cs.position + (cs.unit || '') : ''; return cs.color + pos; }); var dir = stringifyDirection(this.direction, !!options.prefix); if (!options.omitDefaultDirection || !~defaultDirections.indexOf(dir)) { parts.unshift(dir); } return fn + '(' + parts.join(', ') + ')'; }, stringifyOldWebkit: function() { var colorStops = this.colorStops.map(function(item) { return utils.clone(item); }); // normalize color-stops position colorStops.forEach(function(cs) { if (!('position' in cs)) // implied position return; if (~cs.position.indexOf('.') || cs.unit == '%') { cs.position = parseFloat(cs.position) / (cs.unit == '%' ? 100 : 1); } else { throw "Can't convert color stop '" + (cs.position + (cs.unit || '')) + "'"; } }); this._fillImpliedPositions(colorStops); // transform color-stops into string representation colorStops = colorStops.map(function(cs, i) { if (!cs.position && !i) { return 'from(' + cs.color + ')'; } if (cs.position == 1 && i == colorStops.length - 1) { return 'to(' + cs.color + ')'; } return 'color-stop(' + (cs.position.toFixed(2).replace(/\.?0+$/, '')) + ', ' + cs.color + ')'; }); return '-webkit-gradient(linear, ' + oldWebkitDirection((this.direction + 180) % 360) + ', ' + colorStops.join(', ') + ')'; }, /** * Fills-out implied positions in color-stops. This function is useful for * old Webkit gradient definitions */ _fillImpliedPositions: function(colorStops) { var from = 0; colorStops.forEach(function(cs, i) { // make sure that first and last positions are defined if (!i) { return cs.position = cs.position || 0; } if (i == colorStops.length - 1 && !('position' in cs)) { cs.position = 1; } if ('position' in cs) { var start = colorStops[from].position || 0; var step = (cs.position - start) / (i - from); colorStops.slice(from, i).forEach(function(cs2, j) { cs2.position = start + step * j; }); from = i; } }); }, valueOf: function() { return this.stringify(); } }; function normalizeSpace(str) { return utils.trim(str).replace(/\s+/g, ' '); } /** * Resolves textual direction to degrees * @param {String} dir Direction to resolve * @return {Number} */ function resolveDirection(dir) { if (typeof dir == 'number') { return dir; } dir = normalizeSpace(dir).toLowerCase(); if (reDeg.test(dir)) { return +RegExp.$1; } var prefix = /^to\s/.test(dir) ? 'to ' : ''; var left = ~dir.indexOf('left') && 'left'; var right = ~dir.indexOf('right') && 'right'; var top = ~dir.indexOf('top') && 'top'; var bottom = ~dir.indexOf('bottom') && 'bottom'; var key = normalizeSpace(prefix + (top || bottom || '') + ' ' + (left || right || '')); return directions[key] || 0; } /** * Tries to find keyword for given direction, expressed in degrees * @param {Number} dir Direction (degrees) * @param {Boolean} oldStyle Use old style keywords (e.g. "top" instead of "to bottom") * @return {String} Keyword or Ndeg expression */ function stringifyDirection(dir, oldStyle) { var reNewStyle = /^to\s/; var keys = Object.keys(directions).filter(function(k) { var hasPrefix = reNewStyle.test(k); return oldStyle ? !hasPrefix : hasPrefix; }); for (var i = 0; i < keys.length; i++) { if (directions[keys[i]] == dir) { return keys[i]; } } if (oldStyle) { dir = (dir + 270) % 360; } return dir + 'deg'; } /** * Creates direction definition for old Webkit gradients * @param {String} direction * @returns {String} */ function oldWebkitDirection(dir) { dir = stringifyDirection(dir, true); if(reDeg.test(dir)) { throw "The direction is an angle that can’t be converted."; } var v = function(pos) { return ~dir.indexOf(pos) ? '100%' : '0'; }; return v('left') + ' ' + v('top') + ', ' + v('right') + ' ' + v('bottom'); } return { /** * Parses gradient definition into an object. * This object can be used to transform gradient into various * forms * @param {String} gradient Gradient definition * @return {LinearGradient} */ parse: function(gradient) { // cut out all redundant data if (this.isLinearGradient(gradient)) { gradient = gradient.replace(/^\s*[\-a-z]+\s*\(|\)\s*$/ig, ''); } else { throw 'Invalid linear gradient definition:\n' + gradient; } return new LinearGradient(gradient); }, /** * Check if given string can be parsed as linear gradient * @param {String} str * @return {Boolean} */ isLinearGradient: function(str) { return reLinearGradient.test(str); }, resolveDirection: resolveDirection, stringifyDirection: stringifyDirection }; }); },{"../../assets/stringStream":"assets\\stringStream.js","../../utils/common":"utils\\common.js"}],"resolver\\tagName.js":[function(require,module,exports){ /** * Module for resolving tag names: returns best matched tag name for child * element based on passed parent's tag name. Also provides utility function * for element type detection (inline, block-level, empty) */ if (typeof module === 'object' && typeof define !== 'function') { var define = function (factory) { module.exports = factory(require, exports, module); }; } define(function(require, exports, module) { var utils = require('../utils/common'); var elementTypes = { // empty: 'area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,keygen,command'.split(','), empty: [], blockLevel: 'address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,link,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul,h1,h2,h3,h4,h5,h6'.split(','), inlineLevel: 'a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'.split(',') }; var elementMap = { 'p': 'span', 'ul': 'li', 'ol': 'li', 'table': 'tr', 'tr': 'td', 'tbody': 'tr', 'thead': 'tr', 'tfoot': 'tr', 'colgroup': 'col', 'select': 'option', 'optgroup': 'option', 'audio': 'source', 'video': 'source', 'object': 'param', 'map': 'area' }; return { /** * Returns best matched child element name for passed parent's * tag name * @param {String} name * @returns {String} * @memberOf tagName */ resolve: function(name) { name = (name || '').toLowerCase(); if (name in elementMap) return this.getMapping(name); if (this.isInlineLevel(name)) return 'span'; return 'div'; }, /** * Returns mapped child element name for passed parent's name * @param {String} name * @returns {String} */ getMapping: function(name) { return elementMap[name.toLowerCase()]; }, /** * Check if passed element name belongs to inline-level element * @param {String} name * @returns {Boolean} */ isInlineLevel: function(name) { return this.isTypeOf(name, 'inlineLevel'); }, /** * Check if passed element belongs to block-level element. * For better matching of unknown elements (for XML, for example), * you should use !this.isInlineLevel(name) * @returns {Boolean} */ isBlockLevel: function(name) { return this.isTypeOf(name, 'blockLevel'); }, /** * Check if passed element is void (i.e. should not have closing tag). * @returns {Boolean} */ isEmptyElement: function(name) { return this.isTypeOf(name, 'empty'); }, /** * Generic function for testing if element name belongs to specified * elements collection * @param {String} name Element name * @param {String} type Collection name * @returns {Boolean} */ isTypeOf: function(name, type) { return ~elementTypes[type].indexOf(name); }, /** * Adds new parent–child mapping * @param {String} parent * @param {String} child */ addMapping: function(parent, child) { elementMap[parent] = child; }, /** * Removes parent-child mapping */ removeMapping: function(parent) { if (parent in elementMap) delete elementMap[parent]; }, /** * Adds new element into collection * @param {String} name Element name * @param {String} collection Collection name */ addElementToCollection: function(name, collection) { if (!elementTypes[collection]) elementTypes[collection] = []; var col = this.getCollection(collection); if (!~col.indexOf(name)) { col.push(name); } }, /** * Removes element name from specified collection * @param {String} name Element name * @param {String} collection Collection name * @returns */ removeElementFromCollection: function(name, collection) { if (collection in elementTypes) { elementTypes[collection] = utils.without(this.getCollection(collection), name); } }, /** * Returns elements name collection * @param {String} name Collection name * @returns {Array} */ getCollection: function(name) { return elementTypes[name]; } }; }); },{"../utils/common":"utils\\common.js"}],"snippets.json":[function(require,module,exports){ module.exports={ "variables": { "lang": "en", "locale": "en-US", "charset": "UTF-8", "indentation": "\t", "newline": "\n" }, "css": { "filters": "css", "profile": "css", "snippets": { "@i": "@import url(|);", "@import": "@import url(|);", "@m": "@media ${1:screen} {\n\t|\n}", "@media": "@media ${1:screen} {\n\t|\n}", "@f": "@font-face {\n\tfont-family:|;\n\tsrc:url(|);\n}", "@f+": "@font-face {\n\tfont-family: '${1:FontName}';\n\tsrc: url('${2:FileName}.eot');\n\tsrc: url('${2:FileName}.eot?#iefix') format('embedded-opentype'),\n\t\t url('${2:FileName}.woff') format('woff'),\n\t\t url('${2:FileName}.ttf') format('truetype'),\n\t\t url('${2:FileName}.svg#${1:FontName}') format('svg');\n\tfont-style: ${3:normal};\n\tfont-weight: ${4:normal};\n}", "@kf": "@-webkit-keyframes ${1:identifier} {\n\t${2:from} { ${3} }${6}\n\t${4:to} { ${5} }\n}\n@-o-keyframes ${1:identifier} {\n\t${2:from} { ${3} }${6}\n\t${4:to} { ${5} }\n}\n@-moz-keyframes ${1:identifier} {\n\t${2:from} { ${3} }${6}\n\t${4:to} { ${5} }\n}\n@keyframes ${1:identifier} {\n\t${2:from} { ${3} }${6}\n\t${4:to} { ${5} }\n}", "anim": "animation:|;", "anim-": "animation:${1:name} ${2:duration} ${3:timing-function} ${4:delay} ${5:iteration-count} ${6:direction} ${7:fill-mode};", "animdel": "animation-delay:${1:time};", "animdir": "animation-direction:${1:normal};", "animdir:n": "animation-direction:normal;", "animdir:r": "animation-direction:reverse;", "animdir:a": "animation-direction:alternate;", "animdir:ar": "animation-direction:alternate-reverse;", "animdur": "animation-duration:${1:0}s;", "animfm": "animation-fill-mode:${1:both};", "animfm:f": "animation-fill-mode:forwards;", "animfm:b": "animation-fill-mode:backwards;", "animfm:bt": "animation-fill-mode:both;", "animfm:bh": "animation-fill-mode:both;", "animic": "animation-iteration-count:${1:1};", "animic:i": "animation-iteration-count:infinite;", "animn": "animation-name:${1:none};", "animps": "animation-play-state:${1:running};", "animps:p": "animation-play-state:paused;", "animps:r": "animation-play-state:running;", "animtf": "animation-timing-function:${1:linear};", "animtf:e": "animation-timing-function:ease;", "animtf:ei": "animation-timing-function:ease-in;", "animtf:eo": "animation-timing-function:ease-out;", "animtf:eio": "animation-timing-function:ease-in-out;", "animtf:l": "animation-timing-function:linear;", "animtf:cb": "animation-timing-function:cubic-bezier(${1:0.1}, ${2:0.7}, ${3:1.0}, ${3:0.1});", "ap": "appearance:${none};", "!": "!important", "pos": "position:${1:relative};", "pos:s": "position:static;", "pos:a": "position:absolute;", "pos:r": "position:relative;", "pos:f": "position:fixed;", "t": "top:|;", "t:a": "top:auto;", "r": "right:|;", "r:a": "right:auto;", "b": "bottom:|;", "b:a": "bottom:auto;", "l": "left:|;", "l:a": "left:auto;", "z": "z-index:|;", "z:a": "z-index:auto;", "fl": "float:${1:left};", "fl:n": "float:none;", "fl:l": "float:left;", "fl:r": "float:right;", "cl": "clear:${1:both};", "cl:n": "clear:none;", "cl:l": "clear:left;", "cl:r": "clear:right;", "cl:b": "clear:both;", "colm": "columns:|;", "colmc": "column-count:|;", "colmf": "column-fill:|;", "colmg": "column-gap:|;", "colmr": "column-rule:|;", "colmrc": "column-rule-color:|;", "colmrs": "column-rule-style:|;", "colmrw": "column-rule-width:|;", "colms": "column-span:|;", "colmw": "column-width:|;", "d": "display:${1:block};", "d:n": "display:none;", "d:b": "display:block;", "d:f": "display:flex;", "d:if": "display:inline-flex;", "d:i": "display:inline;", "d:ib": "display:inline-block;", "d:ib+": "display: inline-block;\n*display: inline;\n*zoom: 1;", "d:li": "display:list-item;", "d:ri": "display:run-in;", "d:cp": "display:compact;", "d:tb": "display:table;", "d:itb": "display:inline-table;", "d:tbcp": "display:table-caption;", "d:tbcl": "display:table-column;", "d:tbclg": "display:table-column-group;", "d:tbhg": "display:table-header-group;", "d:tbfg": "display:table-footer-group;", "d:tbr": "display:table-row;", "d:tbrg": "display:table-row-group;", "d:tbc": "display:table-cell;", "d:rb": "display:ruby;", "d:rbb": "display:ruby-base;", "d:rbbg": "display:ruby-base-group;", "d:rbt": "display:ruby-text;", "d:rbtg": "display:ruby-text-group;", "v": "visibility:${1:hidden};", "v:v": "visibility:visible;", "v:h": "visibility:hidden;", "v:c": "visibility:collapse;", "ov": "overflow:${1:hidden};", "ov:v": "overflow:visible;", "ov:h": "overflow:hidden;", "ov:s": "overflow:scroll;", "ov:a": "overflow:auto;", "ovx": "overflow-x:${1:hidden};", "ovx:v": "overflow-x:visible;", "ovx:h": "overflow-x:hidden;", "ovx:s": "overflow-x:scroll;", "ovx:a": "overflow-x:auto;", "ovy": "overflow-y:${1:hidden};", "ovy:v": "overflow-y:visible;", "ovy:h": "overflow-y:hidden;", "ovy:s": "overflow-y:scroll;", "ovy:a": "overflow-y:auto;", "ovs": "overflow-style:${1:scrollbar};", "ovs:a": "overflow-style:auto;", "ovs:s": "overflow-style:scrollbar;", "ovs:p": "overflow-style:panner;", "ovs:m": "overflow-style:move;", "ovs:mq": "overflow-style:marquee;", "zoo": "zoom:1;", "zm": "zoom:1;", "cp": "clip:|;", "cp:a": "clip:auto;", "cp:r": "clip:rect(${1:top} ${2:right} ${3:bottom} ${4:left});", "bxz": "box-sizing:${1:border-box};", "bxz:cb": "box-sizing:content-box;", "bxz:bb": "box-sizing:border-box;", "bxsh": "box-shadow:${1:inset }${2:hoff} ${3:voff} ${4:blur} ${5:color};", "bxsh:r": "box-shadow:${1:inset }${2:hoff} ${3:voff} ${4:blur} ${5:spread }rgb(${6:0}, ${7:0}, ${8:0});", "bxsh:ra": "box-shadow:${1:inset }${2:h} ${3:v} ${4:blur} ${5:spread }rgba(${6:0}, ${7:0}, ${8:0}, .${9:5});", "bxsh:n": "box-shadow:none;", "m": "margin:|;", "m:a": "margin:auto;", "mt": "margin-top:|;", "mt:a": "margin-top:auto;", "mr": "margin-right:|;", "mr:a": "margin-right:auto;", "mb": "margin-bottom:|;", "mb:a": "margin-bottom:auto;", "ml": "margin-left:|;", "ml:a": "margin-left:auto;", "p": "padding:|;", "pt": "padding-top:|;", "pr": "padding-right:|;", "pb": "padding-bottom:|;", "pl": "padding-left:|;", "w": "width:|;", "w:a": "width:auto;", "h": "height:|;", "h:a": "height:auto;", "maw": "max-width:|;", "maw:n": "max-width:none;", "mah": "max-height:|;", "mah:n": "max-height:none;", "miw": "min-width:|;", "mih": "min-height:|;", "mar": "max-resolution:${1:res};", "mir": "min-resolution:${1:res};", "ori": "orientation:|;", "ori:l": "orientation:landscape;", "ori:p": "orientation:portrait;", "ol": "outline:|;", "ol:n": "outline:none;", "olo": "outline-offset:|;", "olw": "outline-width:|;", "olw:tn": "outline-width:thin;", "olw:m": "outline-width:medium;", "olw:tc": "outline-width:thick;", "ols": "outline-style:|;", "ols:n": "outline-style:none;", "ols:dt": "outline-style:dotted;", "ols:ds": "outline-style:dashed;", "ols:s": "outline-style:solid;", "ols:db": "outline-style:double;", "ols:g": "outline-style:groove;", "ols:r": "outline-style:ridge;", "ols:i": "outline-style:inset;", "ols:o": "outline-style:outset;", "olc": "outline-color:#${1:000};", "olc:i": "outline-color:invert;", "bfv": "backface-visibility:|;", "bfv:h": "backface-visibility:hidden;", "bfv:v": "backface-visibility:visible;", "bd": "border:|;", "bd+": "border:${1:1px} ${2:solid} ${3:#000};", "bd:n": "border:none;", "bdbk": "border-break:${1:close};", "bdbk:c": "border-break:close;", "bdcl": "border-collapse:|;", "bdcl:c": "border-collapse:collapse;", "bdcl:s": "border-collapse:separate;", "bdc": "border-color:#${1:000};", "bdc:t": "border-color:transparent;", "bdi": "border-image:url(|);", "bdi:n": "border-image:none;", "bdti": "border-top-image:url(|);", "bdti:n": "border-top-image:none;", "bdri": "border-right-image:url(|);", "bdri:n": "border-right-image:none;", "bdbi": "border-bottom-image:url(|);", "bdbi:n": "border-bottom-image:none;", "bdli": "border-left-image:url(|);", "bdli:n": "border-left-image:none;", "bdci": "border-corner-image:url(|);", "bdci:n": "border-corner-image:none;", "bdci:c": "border-corner-image:continue;", "bdtli": "border-top-left-image:url(|);", "bdtli:n": "border-top-left-image:none;", "bdtli:c": "border-top-left-image:continue;", "bdtri": "border-top-right-image:url(|);", "bdtri:n": "border-top-right-image:none;", "bdtri:c": "border-top-right-image:continue;", "bdbri": "border-bottom-right-image:url(|);", "bdbri:n": "border-bottom-right-image:none;", "bdbri:c": "border-bottom-right-image:continue;", "bdbli": "border-bottom-left-image:url(|);", "bdbli:n": "border-bottom-left-image:none;", "bdbli:c": "border-bottom-left-image:continue;", "bdf": "border-fit:${1:repeat};", "bdf:c": "border-fit:clip;", "bdf:r": "border-fit:repeat;", "bdf:sc": "border-fit:scale;", "bdf:st": "border-fit:stretch;", "bdf:ow": "border-fit:overwrite;", "bdf:of": "border-fit:overflow;", "bdf:sp": "border-fit:space;", "bdlen": "border-length:|;", "bdlen:a": "border-length:auto;", "bdsp": "border-spacing:|;", "bds": "border-style:|;", "bds:n": "border-style:none;", "bds:h": "border-style:hidden;", "bds:dt": "border-style:dotted;", "bds:ds": "border-style:dashed;", "bds:s": "border-style:solid;", "bds:db": "border-style:double;", "bds:dtds": "border-style:dot-dash;", "bds:dtdtds": "border-style:dot-dot-dash;", "bds:w": "border-style:wave;", "bds:g": "border-style:groove;", "bds:r": "border-style:ridge;", "bds:i": "border-style:inset;", "bds:o": "border-style:outset;", "bdw": "border-width:|;", "bdtw": "border-top-width:|;", "bdrw": "border-right-width:|;", "bdbw": "border-bottom-width:|;", "bdlw": "border-left-width:|;", "bdt": "border-top:|;", "bt": "border-top:|;", "bdt+": "border-top:${1:1px} ${2:solid} ${3:#000};", "bdt:n": "border-top:none;", "bdts": "border-top-style:|;", "bdts:n": "border-top-style:none;", "bdtc": "border-top-color:#${1:000};", "bdtc:t": "border-top-color:transparent;", "bdr": "border-right:|;", "br": "border-right:|;", "bdr+": "border-right:${1:1px} ${2:solid} ${3:#000};", "bdr:n": "border-right:none;", "bdrst": "border-right-style:|;", "bdrst:n": "border-right-style:none;", "bdrc": "border-right-color:#${1:000};", "bdrc:t": "border-right-color:transparent;", "bdb": "border-bottom:|;", "bb": "border-bottom:|;", "bdb+": "border-bottom:${1:1px} ${2:solid} ${3:#000};", "bdb:n": "border-bottom:none;", "bdbs": "border-bottom-style:|;", "bdbs:n": "border-bottom-style:none;", "bdbc": "border-bottom-color:#${1:000};", "bdbc:t": "border-bottom-color:transparent;", "bdl": "border-left:|;", "bl": "border-left:|;", "bdl+": "border-left:${1:1px} ${2:solid} ${3:#000};", "bdl:n": "border-left:none;", "bdls": "border-left-style:|;", "bdls:n": "border-left-style:none;", "bdlc": "border-left-color:#${1:000};", "bdlc:t": "border-left-color:transparent;", "bdrs": "border-radius:|;", "bdtrrs": "border-top-right-radius:|;", "bdtlrs": "border-top-left-radius:|;", "bdbrrs": "border-bottom-right-radius:|;", "bdblrs": "border-bottom-left-radius:|;", "bg": "background:#${1:000};", "bg+": "background:${1:#fff} url(${2}) ${3:0} ${4:0} ${5:no-repeat};", "bg:n": "background:none;", "bg:ie": "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='${1:x}.png',sizingMethod='${2:crop}');", "bgc": "background-color:#${1:fff};", "bgc:t": "background-color:transparent;", "bgi": "background-image:url(|);", "bgi:n": "background-image:none;", "bgr": "background-repeat:|;", "bgr:n": "background-repeat:no-repeat;", "bgr:x": "background-repeat:repeat-x;", "bgr:y": "background-repeat:repeat-y;", "bgr:sp": "background-repeat:space;", "bgr:rd": "background-repeat:round;", "bga": "background-attachment:|;", "bga:f": "background-attachment:fixed;", "bga:s": "background-attachment:scroll;", "bgp": "background-position:${1:0} ${2:0};", "bgpx": "background-position-x:|;", "bgpy": "background-position-y:|;", "bgbk": "background-break:|;", "bgbk:bb": "background-break:bounding-box;", "bgbk:eb": "background-break:each-box;", "bgbk:c": "background-break:continuous;", "bgcp": "background-clip:${1:padding-box};", "bgcp:bb": "background-clip:border-box;", "bgcp:pb": "background-clip:padding-box;", "bgcp:cb": "background-clip:content-box;", "bgcp:nc": "background-clip:no-clip;", "bgo": "background-origin:|;", "bgo:pb": "background-origin:padding-box;", "bgo:bb": "background-origin:border-box;", "bgo:cb": "background-origin:content-box;", "bgsz": "background-size:|;", "bgsz:a": "background-size:auto;", "bgsz:ct": "background-size:contain;", "bgsz:cv": "background-size:cover;", "c": "color:#${1:000};", "c:r": "color:rgb(${1:0}, ${2:0}, ${3:0});", "c:ra": "color:rgba(${1:0}, ${2:0}, ${3:0}, .${4:5});", "cm": "/* |${child} */", "cnt": "content:'|';", "cnt:n": "content:normal;", "cnt:oq": "content:open-quote;", "cnt:noq": "content:no-open-quote;", "cnt:cq": "content:close-quote;", "cnt:ncq": "content:no-close-quote;", "cnt:a": "content:attr(|);", "cnt:c": "content:counter(|);", "cnt:cs": "content:counters(|);", "tbl": "table-layout:|;", "tbl:a": "table-layout:auto;", "tbl:f": "table-layout:fixed;", "cps": "caption-side:|;", "cps:t": "caption-side:top;", "cps:b": "caption-side:bottom;", "ec": "empty-cells:|;", "ec:s": "empty-cells:show;", "ec:h": "empty-cells:hide;", "lis": "list-style:|;", "lis:n": "list-style:none;", "lisp": "list-style-position:|;", "lisp:i": "list-style-position:inside;", "lisp:o": "list-style-position:outside;", "list": "list-style-type:|;", "list:n": "list-style-type:none;", "list:d": "list-style-type:disc;", "list:c": "list-style-type:circle;", "list:s": "list-style-type:square;", "list:dc": "list-style-type:decimal;", "list:dclz": "list-style-type:decimal-leading-zero;", "list:lr": "list-style-type:lower-roman;", "list:ur": "list-style-type:upper-roman;", "lisi": "list-style-image:|;", "lisi:n": "list-style-image:none;", "q": "quotes:|;", "q:n": "quotes:none;", "q:ru": "quotes:'\\00AB' '\\00BB' '\\201E' '\\201C';", "q:en": "quotes:'\\201C' '\\201D' '\\2018' '\\2019';", "ct": "content:|;", "ct:n": "content:normal;", "ct:oq": "content:open-quote;", "ct:noq": "content:no-open-quote;", "ct:cq": "content:close-quote;", "ct:ncq": "content:no-close-quote;", "ct:a": "content:attr(|);", "ct:c": "content:counter(|);", "ct:cs": "content:counters(|);", "coi": "counter-increment:|;", "cor": "counter-reset:|;", "va": "vertical-align:${1:top};", "va:sup": "vertical-align:super;", "va:t": "vertical-align:top;", "va:tt": "vertical-align:text-top;", "va:m": "vertical-align:middle;", "va:bl": "vertical-align:baseline;", "va:b": "vertical-align:bottom;", "va:tb": "vertical-align:text-bottom;", "va:sub": "vertical-align:sub;", "ta": "text-align:${1:left};", "ta:l": "text-align:left;", "ta:c": "text-align:center;", "ta:r": "text-align:right;", "ta:j": "text-align:justify;", "ta-lst": "text-align-last:|;", "tal:a": "text-align-last:auto;", "tal:l": "text-align-last:left;", "tal:c": "text-align-last:center;", "tal:r": "text-align-last:right;", "td": "text-decoration:${1:none};", "td:n": "text-decoration:none;", "td:u": "text-decoration:underline;", "td:o": "text-decoration:overline;", "td:l": "text-decoration:line-through;", "te": "text-emphasis:|;", "te:n": "text-emphasis:none;", "te:ac": "text-emphasis:accent;", "te:dt": "text-emphasis:dot;", "te:c": "text-emphasis:circle;", "te:ds": "text-emphasis:disc;", "te:b": "text-emphasis:before;", "te:a": "text-emphasis:after;", "th": "text-height:|;", "th:a": "text-height:auto;", "th:f": "text-height:font-size;", "th:t": "text-height:text-size;", "th:m": "text-height:max-size;", "ti": "text-indent:|;", "ti:-": "text-indent:-9999px;", "tj": "text-justify:|;", "tj:a": "text-justify:auto;", "tj:iw": "text-justify:inter-word;", "tj:ii": "text-justify:inter-ideograph;", "tj:ic": "text-justify:inter-cluster;", "tj:d": "text-justify:distribute;", "tj:k": "text-justify:kashida;", "tj:t": "text-justify:tibetan;", "tov": "text-overflow:${ellipsis};", "tov:e": "text-overflow:ellipsis;", "tov:c": "text-overflow:clip;", "to": "text-outline:|;", "to+": "text-outline:${1:0} ${2:0} ${3:#000};", "to:n": "text-outline:none;", "tr": "text-replace:|;", "tr:n": "text-replace:none;", "tt": "text-transform:${1:uppercase};", "tt:n": "text-transform:none;", "tt:c": "text-transform:capitalize;", "tt:u": "text-transform:uppercase;", "tt:l": "text-transform:lowercase;", "tw": "text-wrap:|;", "tw:n": "text-wrap:normal;", "tw:no": "text-wrap:none;", "tw:u": "text-wrap:unrestricted;", "tw:s": "text-wrap:suppress;", "tsh": "text-shadow:${1:hoff} ${2:voff} ${3:blur} ${4:#000};", "tsh:r": "text-shadow:${1:h} ${2:v} ${3:blur} rgb(${4:0}, ${5:0}, ${6:0});", "tsh:ra": "text-shadow:${1:h} ${2:v} ${3:blur} rgba(${4:0}, ${5:0}, ${6:0}, .${7:5});", "tsh+": "text-shadow:${1:0} ${2:0} ${3:0} ${4:#000};", "tsh:n": "text-shadow:none;", "trf": "transform:|;", "trf:skx": "transform: skewX(${1:angle});", "trf:sky": "transform: skewY(${1:angle});", "trf:sc": "transform: scale(${1:x}, ${2:y});", "trf:scx": "transform: scaleX(${1:x});", "trf:scy": "transform: scaleY(${1:y});", "trf:scz": "transform: scaleZ(${1:z});", "trf:sc3": "transform: scale3d(${1:x}, ${2:y}, ${3:z});", "trf:r": "transform: rotate(${1:angle});", "trf:rx": "transform: rotateX(${1:angle});", "trf:ry": "transform: rotateY(${1:angle});", "trf:rz": "transform: rotateZ(${1:angle});", "trf:t": "transform: translate(${1:x}, ${2:y});", "trf:tx": "transform: translateX(${1:x});", "trf:ty": "transform: translateY(${1:y});", "trf:tz": "transform: translateZ(${1:z});", "trf:t3": "transform: translate3d(${1:tx}, ${2:ty}, ${3:tz});", "trfo": "transform-origin:|;", "trfs": "transform-style:${1:preserve-3d};", "trs": "transition:${1:prop} ${2:time};", "trsde": "transition-delay:${1:time};", "trsdu": "transition-duration:${1:time};", "trsp": "transition-property:${1:prop};", "trstf": "transition-timing-function:${1:tfunc};", "lh": "line-height:|;", "whs": "white-space:|;", "whs:n": "white-space:normal;", "whs:p": "white-space:pre;", "whs:nw": "white-space:nowrap;", "whs:pw": "white-space:pre-wrap;", "whs:pl": "white-space:pre-line;", "whsc": "white-space-collapse:|;", "whsc:n": "white-space-collapse:normal;", "whsc:k": "white-space-collapse:keep-all;", "whsc:l": "white-space-collapse:loose;", "whsc:bs": "white-space-collapse:break-strict;", "whsc:ba": "white-space-collapse:break-all;", "wob": "word-break:|;", "wob:n": "word-break:normal;", "wob:k": "word-break:keep-all;", "wob:ba": "word-break:break-all;", "wos": "word-spacing:|;", "wow": "word-wrap:|;", "wow:nm": "word-wrap:normal;", "wow:n": "word-wrap:none;", "wow:u": "word-wrap:unrestricted;", "wow:s": "word-wrap:suppress;", "wow:b": "word-wrap:break-word;", "wm": "writing-mode:${1:lr-tb};", "wm:lrt": "writing-mode:lr-tb;", "wm:lrb": "writing-mode:lr-bt;", "wm:rlt": "writing-mode:rl-tb;", "wm:rlb": "writing-mode:rl-bt;", "wm:tbr": "writing-mode:tb-rl;", "wm:tbl": "writing-mode:tb-lr;", "wm:btl": "writing-mode:bt-lr;", "wm:btr": "writing-mode:bt-rl;", "lts": "letter-spacing:|;", "lts-n": "letter-spacing:normal;", "f": "font:|;", "f+": "font:${1:1em} ${2:Arial,sans-serif};", "fw": "font-weight:|;", "fw:n": "font-weight:normal;", "fw:b": "font-weight:bold;", "fw:br": "font-weight:bolder;", "fw:lr": "font-weight:lighter;", "fs": "font-style:${italic};", "fs:n": "font-style:normal;", "fs:i": "font-style:italic;", "fs:o": "font-style:oblique;", "fv": "font-variant:|;", "fv:n": "font-variant:normal;", "fv:sc": "font-variant:small-caps;", "fz": "font-size:|;", "fza": "font-size-adjust:|;", "fza:n": "font-size-adjust:none;", "ff": "font-family:|;", "ff:s": "font-family:serif;", "ff:ss": "font-family:sans-serif;", "ff:c": "font-family:cursive;", "ff:f": "font-family:fantasy;", "ff:m": "font-family:monospace;", "ff:a": "font-family: Arial, \"Helvetica Neue\", Helvetica, sans-serif;", "ff:t": "font-family: \"Times New Roman\", Times, Baskerville, Georgia, serif;", "ff:v": "font-family: Verdana, Geneva, sans-serif;", "fef": "font-effect:|;", "fef:n": "font-effect:none;", "fef:eg": "font-effect:engrave;", "fef:eb": "font-effect:emboss;", "fef:o": "font-effect:outline;", "fem": "font-emphasize:|;", "femp": "font-emphasize-position:|;", "femp:b": "font-emphasize-position:before;", "femp:a": "font-emphasize-position:after;", "fems": "font-emphasize-style:|;", "fems:n": "font-emphasize-style:none;", "fems:ac": "font-emphasize-style:accent;", "fems:dt": "font-emphasize-style:dot;", "fems:c": "font-emphasize-style:circle;", "fems:ds": "font-emphasize-style:disc;", "fsm": "font-smooth:|;", "fsm:a": "font-smooth:auto;", "fsm:n": "font-smooth:never;", "fsm:aw": "font-smooth:always;", "fst": "font-stretch:|;", "fst:n": "font-stretch:normal;", "fst:uc": "font-stretch:ultra-condensed;", "fst:ec": "font-stretch:extra-condensed;", "fst:c": "font-stretch:condensed;", "fst:sc": "font-stretch:semi-condensed;", "fst:se": "font-stretch:semi-expanded;", "fst:e": "font-stretch:expanded;", "fst:ee": "font-stretch:extra-expanded;", "fst:ue": "font-stretch:ultra-expanded;", "op": "opacity:|;", "op+": "opacity: $1;\nfilter: alpha(opacity=$2);", "op:ie": "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);", "op:ms": "-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)';", "rsz": "resize:|;", "rsz:n": "resize:none;", "rsz:b": "resize:both;", "rsz:h": "resize:horizontal;", "rsz:v": "resize:vertical;", "cur": "cursor:${pointer};", "cur:a": "cursor:auto;", "cur:d": "cursor:default;", "cur:c": "cursor:crosshair;", "cur:ha": "cursor:hand;", "cur:he": "cursor:help;", "cur:m": "cursor:move;", "cur:p": "cursor:pointer;", "cur:t": "cursor:text;", "fxd": "flex-direction:|;", "fxd:r": "flex-direction:row;", "fxd:rr": "flex-direction:row-reverse;", "fxd:c": "flex-direction:column;", "fxd:cr": "flex-direction:column-reverse;", "fxw": "flex-wrap: |;", "fxw:n": "flex-wrap:nowrap;", "fxw:w": "flex-wrap:wrap;", "fxw:wr": "flex-wrap:wrap-reverse;", "fxf": "flex-flow:|;", "jc": "justify-content:|;", "jc:fs": "justify-content:flex-start;", "jc:fe": "justify-content:flex-end;", "jc:c": "justify-content:center;", "jc:sb": "justify-content:space-between;", "jc:sa": "justify-content:space-around;", "ai": "align-items:|;", "ai:fs": "align-items:flex-start;", "ai:fe": "align-items:flex-end;", "ai:c": "align-items:center;", "ai:b": "align-items:baseline;", "ai:s": "align-items:stretch;", "ac": "align-content:|;", "ac:fs": "align-content:flex-start;", "ac:fe": "align-content:flex-end;", "ac:c": "align-content:center;", "ac:sb": "align-content:space-between;", "ac:sa": "align-content:space-around;", "ac:s": "align-content:stretch;", "ord": "order:|;", "fxg": "flex-grow:|;", "fxsh": "flex-shrink:|;", "fxb": "flex-basis:|;", "fx": "flex:|;", "as": "align-self:|;", "as:a": "align-self:auto;", "as:fs": "align-self:flex-start;", "as:fe": "align-self:flex-end;", "as:c": "align-self:center;", "as:b": "align-self:baseline;", "as:s": "align-self:stretch;", "pgbb": "page-break-before:|;", "pgbb:au": "page-break-before:auto;", "pgbb:al": "page-break-before:always;", "pgbb:l": "page-break-before:left;", "pgbb:r": "page-break-before:right;", "pgbi": "page-break-inside:|;", "pgbi:au": "page-break-inside:auto;", "pgbi:av": "page-break-inside:avoid;", "pgba": "page-break-after:|;", "pgba:au": "page-break-after:auto;", "pgba:al": "page-break-after:always;", "pgba:l": "page-break-after:left;", "pgba:r": "page-break-after:right;", "orp": "orphans:|;", "us": "user-select:${none};", "wid": "widows:|;", "wfsm": "-webkit-font-smoothing:${antialiased};", "wfsm:a": "-webkit-font-smoothing:antialiased;", "wfsm:s": "-webkit-font-smoothing:subpixel-antialiased;", "wfsm:sa": "-webkit-font-smoothing:subpixel-antialiased;", "wfsm:n": "-webkit-font-smoothing:none;" } }, "html": { "filters": "html", "profile": "html", "snippets": { "!!!": "", "!!!4t": "", "!!!4s": "", "!!!xt": "", "!!!xs": "", "!!!xxs": "", "c": "", "cc:ie6": "", "cc:ie": "", "cc:noie": "\n\t${child}|\n" }, "abbreviations": { "!": "html:5", "a": "", "a:link": "", "a:mail": "", "abbr": "", "acr|acronym": "", "base": "", "basefont": "", "br": "
", "frame": "", "hr": "
", "bdo": "", "bdo:r": "", "bdo:l": "", "col": "", "link": "", "link:css": "", "link:print": "", "link:favicon": "", "link:touch": "", "link:rss": "", "link:atom": "", "link:im|link:import": "", "meta": "", "meta:utf": "", "meta:win": "", "meta:edge": "", "meta:vp": "", "meta:compat": "", "meta:redirect": "", "style": "