/
githubmirror
/
brackets
Обзор
Документация
Войти
/
githubmirror
/
brackets
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/extensions/default/QuickView/main.js
841 строка
33 KB
nyteksf
#7723 - Fixed indentation error
14 мар 2017, 15:09
14 мар 2017, 15:09
e83621f
Код
Авторство
О чём код?
/* * Copyright (c) 2013 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*jslint regexp: true */ define(function (require, exports, module) { "use strict"; // Brackets modules var ColorUtils = brackets.getModule("utils/ColorUtils"), CommandManager = brackets.getModule("command/CommandManager"), Commands = brackets.getModule("command/Commands"), CSSUtils = brackets.getModule("language/CSSUtils"), EditorManager = brackets.getModule("editor/EditorManager"), ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), FileUtils = brackets.getModule("file/FileUtils"), Menus = brackets.getModule("command/Menus"), PreferencesManager = brackets.getModule("preferences/PreferencesManager"), LanguageManager = brackets.getModule("language/LanguageManager"), Strings = brackets.getModule("strings"), ViewUtils = brackets.getModule("utils/ViewUtils"), TokenUtils = brackets.getModule("utils/TokenUtils"), PathUtils = brackets.getModule("thirdparty/path-utils/path-utils"); var previewContainerHTML = require("text!QuickViewTemplate.html"); var enabled, // Only show preview if true prefs = null, // Preferences $previewContainer, // Preview container $previewContent, // Preview content holder lastMousePos, // Last mouse position animationRequest, // Request for animation frame extensionlessImagePreview; // Whether to try and preview extensionless URLs // Constants var CMD_ENABLE_QUICK_VIEW = "view.enableQuickView", HOVER_DELAY = 350, // Time (ms) mouse must remain over a provider's matched text before popover appears POINTER_HEIGHT = 15, // Pointer height, used to shift popover above pointer (plus a little bit of space) POPOVER_HORZ_MARGIN = 5; // Horizontal margin var styleLanguages = ["css", "text/x-less", "sass", "text/x-scss", "stylus"]; // List of protocols which we will support for image preview urls var validProtocols = ["data:", "http:", "https:", "ftp:", "file:"]; prefs = PreferencesManager.getExtensionPrefs("quickview"); prefs.definePreference("enabled", "boolean", true, { description: Strings.DESCRIPTION_QUICK_VIEW_ENABLED }); // Whether or not to try and show image previews for URLs missing extensions // (e.g., https://avatars2.githubusercontent.com/u/476009?v=3&s=200) prefs.definePreference("extensionlessImagePreview", "boolean", true, { description: Strings.DESCRIPTION_EXTENSION_LESS_IMAGE_PREVIEW }); /** * There are three states for this var: * 1. If null, there is no provider result for the given mouse position. * 2. If non-null, and visible==true, there is a popover currently showing. * 3. If non-null, but visible==false, we're waiting for HOVER_DELAY, which * is tracked by hoverTimer. The state changes to visible==true as soon as * there is a provider. If the mouse moves before then, timer is restarted. * * @type {{ * visible: boolean, * editor: !Editor, * hoverTimer: number, - setTimeout() token * start: !{line, ch}, - start of matched text range * end: !{line, ch}, - end of matched text range * content: !string, - HTML content to display in popover * onShow: ?function():void, - called once popover content added to the DOM (may never be called) * - if specified, must call positionPreview() * xpos: number, - x of center of popover * ytop: number, - y of top of matched text (when popover placed above text, normally) * ybot: number, - y of bottom of matched text (when popover moved below text, avoiding window top) * marker: ?CodeMirror.TextMarker - only set once visible==true * }} */ var popoverState = null; // Popover widget management ---------------------------------------------- /** * Cancels whatever popoverState was currently pending and sets it back to null. If the popover was visible, * hides it; if the popover was invisible and still pending, cancels hoverTimer so it will never be shown. */ function hidePreview() { if (!popoverState) { return; } if (popoverState.visible) { popoverState.marker.clear(); $previewContent.empty(); $previewContainer.hide(); $previewContainer.removeClass("active"); } else { window.clearTimeout(popoverState.hoverTimer); } popoverState = null; } function positionPreview(editor, xpos, ypos, ybot) { var previewWidth = $previewContainer.outerWidth(), top = ypos - $previewContainer.outerHeight() - POINTER_HEIGHT, left = xpos - previewWidth / 2, elementRect = { top: top, left: left - POPOVER_HORZ_MARGIN, height: $previewContainer.outerHeight() + POINTER_HEIGHT, width: previewWidth + 2 * POPOVER_HORZ_MARGIN }, clip = ViewUtils.getElementClipSize($(editor.getRootElement()), elementRect); // Prevent horizontal clipping if (clip.left > 0) { left += clip.left; } else if (clip.right > 0) { left -= clip.right; } // If clipped on top, flip popover below line if (clip.top > 0) { top = ybot + POINTER_HEIGHT; $previewContainer .removeClass("preview-bubble-above") .addClass("preview-bubble-below"); } else { $previewContainer .removeClass("preview-bubble-below") .addClass("preview-bubble-above"); } $previewContainer .css({ left: left, top: top }) .addClass("active"); } function divContainsMouse($div, mousePos) { var offset = $div.offset(); return (mousePos.clientX >= offset.left && mousePos.clientX <= offset.left + $div.width() && mousePos.clientY >= offset.top && mousePos.clientY <= offset.top + $div.height()); } // Color & gradient preview provider -------------------------------------- function colorAndGradientPreviewProvider(editor, pos, token, line) { // Check for gradient. -webkit-gradient() can have parens in parameters // nested 2 levels. Other gradients can only nest 1 level. var gradientRegEx = /-webkit-gradient\((?:[^\(]*?(?:\((?:[^\(]*?(?:\([^\)]*?\))*?)*?\))*?)*?\)|(?:(?:-moz-|-ms-|-o-|-webkit-|:|\s)((repeating-)?linear-gradient)|(?:-moz-|-ms-|-o-|-webkit-|:|\s)((repeating-)?radial-gradient))(\((?:[^\)]*?(?:\([^\)]*?\))*?)*?\))/gi, colorRegEx = new RegExp(ColorUtils.COLOR_REGEX), mode = TokenUtils.getModeAt(editor._codeMirror, pos, false), isStyleSheet = (styleLanguages.indexOf(mode) !== -1); function areParensBalanced(str) { var i, nestLevel = 0, len; if (isStyleSheet) { // Remove comments & strings from style sheets str = CSSUtils.reduceStyleSheetForRegExParsing(str); } len = str.length; for (i = 0; i < len; i++) { switch (str[i]) { case "(": nestLevel++; break; case ")": nestLevel--; break; case "\\": i++; // next char is escaped, so skip it break; } } // if parens are balanced, nest level will be 0 return (nestLevel === 0); } function execGradientMatch(line, parensBalanced) { // Unbalanced parens cause infinite loop (see issue #4650) var gradientMatch = (parensBalanced ? gradientRegEx.exec(line) : null), prefix = "", colorValue; if (gradientMatch) { if (gradientMatch[0].indexOf("@") !== -1) { // If the gradient match has "@" in it, it is most likely a less or // sass variable. Ignore it since it won't be displayed correctly. gradientMatch = null; } else { // If it was a linear-gradient or radial-gradient variant with a vendor prefix // add "-webkit-" so it shows up correctly in Brackets. if (gradientMatch[0].match(/-o-|-moz-|-ms-|-webkit-/i)) { prefix = "-webkit-"; } // For prefixed gradients, use the non-prefixed value as the color value. // "-webkit-" will be added before this value later if (gradientMatch[1]) { colorValue = gradientMatch[1] + gradientMatch[5]; // linear gradiant } else if (gradientMatch[3]) { colorValue = gradientMatch[3] + gradientMatch[5]; // radial gradiant } else if (gradientMatch[0]) { colorValue = gradientMatch[0]; // -webkit-gradient prefix = ""; // do not prefix } } } return { match: gradientMatch, prefix: prefix, colorValue: colorValue }; } function execColorMatch(editor, line, pos) { var colorMatch, ignoreNamedColors; function hyphenOnMatchBoundary(match, line) { var beforeIndex, afterIndex; if (match) { beforeIndex = match.index - 1; if (beforeIndex >= 0 && line[beforeIndex] === "-") { return true; } else { afterIndex = match.index + match[0].length; if (afterIndex < line.length && line[afterIndex] === "-") { return true; } } } return false; } function isNamedColor(match) { if (match && match[0] && /^[a-z]+$/i.test(match[0])) { // only for color names, not for hex-/rgb-values return true; } } // Hyphens do not count as a regex word boundary (\b), so check for those here do { colorMatch = colorRegEx.exec(line); if (!colorMatch) { break; } if (ignoreNamedColors === undefined) { var mode = TokenUtils.getModeAt(editor._codeMirror, pos, false).name; ignoreNamedColors = styleLanguages.indexOf(mode) === -1; } } while (hyphenOnMatchBoundary(colorMatch, line) || (ignoreNamedColors && isNamedColor(colorMatch))); return colorMatch; } // simple css property splitter (used to find color stop arguments in gradients) function splitStyleProperty(property) { var token = /((?:[^"']|".*?"|'.*?')*?)([(,)]|$)/g; var recurse = function () { var array = []; for (;;) { var result = token.exec(property); if (result[2] === "(") { var str = result[1].trim() + "(" + recurse().join(",") + ")"; result = token.exec(property); str += result[1]; array.push(str); } else { array.push(result[1].trim()); } if (result[2] !== ",") { return array; } } }; return (recurse()); } // color stop helpers function isGradientColorStop(args) { return (args.length > 0 && args[0].match(colorRegEx) !== null); } function hasLengthInPixels(args) { return (args.length > 1 && args[1].indexOf("px") > 0); } // Ensures that input is in usable hex format function ensureHexFormat(str) { return (/^0x/).test(str) ? str.replace("0x","#") : str; } // Normalizes px color stops to % function normalizeGradientExpressionForQuickview(expression) { if (expression.indexOf("px") > 0) { var paramStart = expression.indexOf("(") + 1, paramEnd = expression.lastIndexOf(")"), parameters = expression.substring(paramStart, paramEnd), params = splitStyleProperty(parameters), lowerBound = 0, upperBound = $previewContainer.width(), args, thisSize, i; // find lower bound for (i = 0; i < params.length; i++) { args = params[i].split(" "); if (hasLengthInPixels(args)) { thisSize = parseFloat(args[1]); upperBound = Math.max(upperBound, thisSize); // we really only care about converting negative // pixel values -- so take the smallest negative pixel // value and use that as baseline for display purposes if (thisSize < 0) { lowerBound = Math.min(lowerBound, thisSize); } } } // convert negative lower bound to positive and adjust all pixel values // so that -20px is now 0px and 100px is now 120px lowerBound = Math.abs(lowerBound); // Offset the upperbound by the lowerBound to give us a corrected context upperBound += lowerBound; // convert to % for (i = 0; i < params.length; i++) { args = params[i].split(" "); if (isGradientColorStop(args) && hasLengthInPixels(args)) { if (upperBound === 0) { thisSize = 0; } else { thisSize = ((parseFloat(args[1]) + lowerBound) / upperBound) * 100; } args[1] = thisSize + "%"; } params[i] = args.join(" "); } // put it back together. expression = expression.substring(0, paramStart) + params.join(", ") + expression.substring(paramEnd); } return expression; } var parensBalanced = areParensBalanced(line), gradientMatch = execGradientMatch(line, parensBalanced), match = gradientMatch.match || execColorMatch(editor, line, pos), cm = editor._codeMirror; while (match) { if (pos.ch < match.index) { // Gradients are matched first, then colors, so... if (gradientMatch.match) { // ... gradient match is past cursor -- stop looking for gradients, start searching for colors gradientMatch = { match: null, prefix: "", colorValue: null }; } else { // ... color match is past cursor -- stop looping break; } } else if (pos.ch <= match.index + match[0].length) { // build the css for previewing the gradient from the regex result var previewCSS = gradientMatch.prefix + (gradientMatch.colorValue || match[0]); // normalize the arguments to something that we can display to the user // NOTE: we need both the div and the popover's _previewCSS member // (used by unit tests) to match so normalize the css for both previewCSS = normalizeGradientExpressionForQuickview(ensureHexFormat(previewCSS)); var preview = "<div class='color-swatch' style='background:" + previewCSS + "'>" + "</div>"; var startPos = {line: pos.line, ch: match.index}, endPos = {line: pos.line, ch: match.index + match[0].length}, startCoords = cm.charCoords(startPos), xPos; xPos = (cm.charCoords(endPos).left - startCoords.left) / 2 + startCoords.left; return { start: startPos, end: endPos, content: preview, xpos: xPos, ytop: startCoords.top, ybot: startCoords.bottom, _previewCSS: previewCSS }; } // Get next match if (gradientMatch.match) { gradientMatch = execGradientMatch(line, parensBalanced); } match = gradientMatch.match || execColorMatch(editor, line, pos); } return null; } // Image preview provider ------------------------------------------------- function imagePreviewProvider(editor, pos, token, line) { var cm = editor._codeMirror; // Check for image name var urlRegEx = /url\(([^\)]*)\)/gi, tokenString, urlMatch; if (token.type === "string") { tokenString = token.string; } else { urlMatch = urlRegEx.exec(line); while (urlMatch) { if (pos.ch < urlMatch.index) { // match is past cursor, so stop looping break; } else if (pos.ch <= urlMatch.index + urlMatch[0].length) { tokenString = urlMatch[1]; break; } urlMatch = urlRegEx.exec(line); } } if (!tokenString) { return null; } // Strip leading/trailing quotes, if present tokenString = tokenString.replace(/(^['"])|(['"]$)/g, ""); var sPos, ePos; var docPath = editor.document.file.fullPath; var imgPath; // Determine whether or not this URL/path is likely to be an image. var parsed = PathUtils.parseUrl(tokenString); // If the URL has a protocol, check if it's one of the supported protocols var hasProtocol = parsed.protocol !== "" && validProtocols.indexOf(parsed.protocol.trim().toLowerCase()) !== -1; var ext = parsed.filenameExtension.replace(/^\./, ''); var language = LanguageManager.getLanguageForExtension(ext); var id = language && language.getId(); var isImage = id === "image" || id === "svg"; // Use this URL if this is an absolute URL and either points to a // filename with a known image extension, or lacks an extension (e.g., // a web service that returns an image). Honour the extensionlessImagePreview // preference as well in the latter case. if (hasProtocol && (isImage || (!ext && extensionlessImagePreview))) { imgPath = tokenString; } // Use this filename if this is a path with a known image extension. else if (!hasProtocol && isImage) { imgPath = "file:///" + FileUtils.getDirectoryPath(docPath) + tokenString; } if (!imgPath) { return null; } if (urlMatch) { sPos = {line: pos.line, ch: urlMatch.index}; ePos = {line: pos.line, ch: urlMatch.index + urlMatch[0].length}; } else { sPos = {line: pos.line, ch: token.start}; ePos = {line: pos.line, ch: token.end}; } var imgPreview = "<div class='image-preview'>" + " <img src=\"" + imgPath + "\">" + "</div>"; var coord = cm.charCoords(sPos); var xpos = (cm.charCoords(ePos).left - coord.left) / 2 + coord.left; var showHandler = function () { // Hide the preview container until the image is loaded. $previewContainer.hide(); $previewContainer.find(".image-preview > img").on("load", function () { $previewContent .append("<div class='img-size'>" + this.naturalWidth + " × " + this.naturalHeight + " " + Strings.UNIT_PIXELS + "</div>" ); $previewContainer.show(); positionPreview(editor, popoverState.xpos, popoverState.ytop, popoverState.ybot); }).on("error", function (e) { e.preventDefault(); hidePreview(); }); }; return { start: sPos, end: ePos, content: imgPreview, onShow: showHandler, xpos: xpos, ytop: coord.top, ybot: coord.bottom, _imgPath: imgPath }; } // Preview hide/show logic ------------------------------------------------ /** * Returns a 'ready for use' popover state object: * { visible: false, editor, start, end, content, ?onShow, xpos, ytop, ybot } * Lacks only hoverTimer (supplied by handleMouseMove()) and marker (supplied by showPreview()). */ function queryPreviewProviders(editor, pos, token) { var line = editor.document.getLine(pos.line); // FUTURE: Support plugin providers. For now we just hard-code... var popover = colorAndGradientPreviewProvider(editor, pos, token, line) || imagePreviewProvider(editor, pos, token, line); if (popover) { // Providers return just { start, end, content, ?onShow, xpos, ytop, ybot } popover.visible = false; popover.editor = editor; return popover; } return null; } function getHoveredEditor(mousePos) { // Figure out which editor we are over var fullEditor = EditorManager.getCurrentFullEditor(); if (!fullEditor || !mousePos) { return; } // Check for inline Editor instances first var inlines = fullEditor.getInlineWidgets(), i, editor; for (i = 0; i < inlines.length; i++) { var $inlineEditorRoot = inlines[i].editor && $(inlines[i].editor.getRootElement()), // see MultiRangeInlineEditor $otherDiv = inlines[i].$htmlContent; if ($inlineEditorRoot && divContainsMouse($inlineEditorRoot, mousePos)) { editor = inlines[i].editor; break; } else if ($otherDiv && divContainsMouse($otherDiv, mousePos)) { // Mouse inside unsupported inline editor like Quick Docs or Color Editor return; } } // Check main editor if (!editor) { if (divContainsMouse($(fullEditor.getRootElement()), mousePos)) { editor = fullEditor; } } return editor; } /** * Changes the current hidden popoverState to visible, showing it in the UI and highlighting * its matching text in the editor. */ function showPreview(editor, popover) { var token, cm; // Figure out which editor we are over if (!editor) { editor = getHoveredEditor(lastMousePos); } if (!editor || !editor._codeMirror) { hidePreview(); return; } cm = editor._codeMirror; // Find char mouse is over var pos = cm.coordsChar({left: lastMousePos.clientX, top: lastMousePos.clientY}); // No preview if mouse is past last char on line if (pos.ch >= editor.document.getLine(pos.line).length) { return; } if (popover) { popoverState = popover; } else { // Query providers and append to popoverState token = TokenUtils.getTokenAt(cm, pos); popoverState = $.extend({}, popoverState, queryPreviewProviders(editor, pos, token)); } if (popoverState && popoverState.start && popoverState.end) { popoverState.marker = cm.markText( popoverState.start, popoverState.end, {className: "quick-view-highlight"} ); $previewContent.append(popoverState.content); $previewContainer.show(); popoverState.visible = true; if (popoverState.onShow) { popoverState.onShow(); } else { positionPreview(editor, popoverState.xpos, popoverState.ytop, popoverState.ybot); } } } function processMouseMove() { animationRequest = null; if (!lastMousePos) { return; // should never get here, but safety first! } var showImmediately = false, editor = null; if (popoverState && popoverState.visible) { // Only figure out which editor we are over when there is already a popover // showing (otherwise wait until after delay to minimize processing) editor = getHoveredEditor(lastMousePos); if (editor && editor._codeMirror) { // Find char mouse is over var cm = editor._codeMirror, pos = cm.coordsChar({left: lastMousePos.clientX, top: lastMousePos.clientY}); if (popoverState.start && popoverState.end && editor.posWithinRange(pos, popoverState.start, popoverState.end, true) && (pos.ch < editor.document.getLine(pos.line).length)) { // That one's still relevant - nothing more to do // Note: posWithinRange() includes mouse past end of line, so need to check for that case return; } } // That one doesn't cover this pos - hide it and start anew showImmediately = true; } // Initialize popoverState hidePreview(); popoverState = {}; // Set timer to scan and show. This will get cancelled (in hidePreview()) // if mouse movement rendered this popover inapplicable before timer fires. // When showing "immediately", still use setTimeout() to make this async // so we return from this mousemove event handler ASAP. popoverState.hoverTimer = window.setTimeout(function () { showPreview(editor); }, showImmediately ? 0 : HOVER_DELAY); } function handleMouseMove(event) { lastMousePos = null; if (!enabled) { return; } if (event.which) { // Button is down - don't show popovers while dragging hidePreview(); return; } // Keep track of last mouse position lastMousePos = { clientX: event.clientX, clientY: event.clientY }; // Prevent duplicate animation frame requests if (!animationRequest) { animationRequest = window.requestAnimationFrame(processMouseMove); } } function onActiveEditorChange(event, current, previous) { // Hide preview when editor changes hidePreview(); if (previous && previous.document) { previous.document.off("change", hidePreview); } if (current && current.document) { current.document.on("change", hidePreview); } } // Menu command handlers function updateMenuItemCheckmark() { CommandManager.get(CMD_ENABLE_QUICK_VIEW).setChecked(enabled); } function setExtensionlessImagePreview(_extensionlessImagePreview, doNotSave) { if (extensionlessImagePreview !== _extensionlessImagePreview) { extensionlessImagePreview = _extensionlessImagePreview; if (!doNotSave) { prefs.set("extensionlessImagePreview", enabled); prefs.save(); } } } function setEnabled(_enabled, doNotSave) { if (enabled !== _enabled) { enabled = _enabled; var editorHolder = $("#editor-holder")[0]; if (enabled) { // Note: listening to "scroll" also catches text edits, which bubble a scroll event up from the hidden text area. This means // we auto-hide on text edit, which is probably actually a good thing. editorHolder.addEventListener("mousemove", handleMouseMove, true); editorHolder.addEventListener("scroll", hidePreview, true); editorHolder.addEventListener("mouseout", hidePreview, true); // Setup doc "change" listener onActiveEditorChange(null, EditorManager.getActiveEditor(), null); EditorManager.on("activeEditorChange", onActiveEditorChange); } else { editorHolder.removeEventListener("mousemove", handleMouseMove, true); editorHolder.removeEventListener("scroll", hidePreview, true); editorHolder.removeEventListener("mouseout", hidePreview, true); // Cleanup doc "change" listener onActiveEditorChange(null, null, EditorManager.getActiveEditor()); EditorManager.off("activeEditorChange", onActiveEditorChange); hidePreview(); } if (!doNotSave) { prefs.set("enabled", enabled); prefs.save(); } } // Always update the checkmark, even if the enabled flag hasn't changed. updateMenuItemCheckmark(); } function toggleEnableQuickView() { setEnabled(!enabled); } function _forceShow(popover) { hidePreview(); lastMousePos = { clientX: popover.xpos, clientY: Math.floor((popover.ybot + popover.ytop) / 2) }; showPreview(popover.editor, popover); } // Create the preview container $previewContainer = $(previewContainerHTML).appendTo($("body")); $previewContent = $previewContainer.find(".preview-content"); // Load our stylesheet ExtensionUtils.loadStyleSheet(module, "QuickView.less"); // Register command // Insert menu at specific pos since this may load before OR after code folding extension CommandManager.register(Strings.CMD_ENABLE_QUICK_VIEW, CMD_ENABLE_QUICK_VIEW, toggleEnableQuickView); Menus.getMenu(Menus.AppMenuBar.VIEW_MENU).addMenuItem(CMD_ENABLE_QUICK_VIEW, null, Menus.AFTER, Commands.VIEW_TOGGLE_INSPECTION); // Setup initial UI state setEnabled(prefs.get("enabled"), true); setExtensionlessImagePreview(prefs.get("extensionlessImagePreview"), true); prefs.on("change", "enabled", function () { setEnabled(prefs.get("enabled"), true); }); prefs.on("change", "extensionlessImagePreview", function () { setExtensionlessImagePreview(prefs.get("extensionlessImagePreview")); }); // For unit testing exports._queryPreviewProviders = queryPreviewProviders; exports._forceShow = _forceShow; });