Example #1
0
	/** 
	 *  Called when there is a text change in the inline editor.
	 *
	 *  @params instance    : the codemirror instance,
	 *  @params change      : the change object.
	 */
	function inlineChange(instance, change) {

		console.log("inlineChange", instance, change);
		
		// Make sure that the change is even worth looking at.
		if (change.text.length < 2 && change.from.line !== 0) {

			var currentQuery = QueryManager.getCurrentQueryMark();
			var inlineWidget = EditorManager.getFocusedInlineWidget();

			// Add the changed rule to the current query object.
			currentQuery.addRule(inlineWidget.currentSelector, instance.getLine(change.from.line));

			// If a previous query had this prop set, remove its background highlight.
			instance.removeLineClass(change.from.line, "background");

			// Add the new line highlight with the color of the current query.
			instance.addLineClass(change.from.line, "background", "pq" + currentQuery.colorIndex);

			// Write out the changes to the style block and the media queries CSS file.
			refreshIFrameMediaQueries();
		}

		// Adjust the highlight according to the new CSS value.
		if (inspectController) {
			inspectController.positionDomHighlight(inlineElement);
		}
	}
Example #2
0
	/**
	 * Responsible for closing the response mode. This can be invoked in a number of situations
	 *   - when the user clicks on the 'response' icon in the main toolbar on the left
	 *   - when the user switches between projects
	 */
	function closeResponseMode() {

		function _removeDocumentHandlers() {
			
			MainViewManager.off("currentFileChange", handleCurrentFilechange);
	
			DocumentManager
				.off('dirtyFlagChange', handleDirtyFlagChange)
				.off('documentSaved', handleDocumentSaved)
				.off('documentRefreshed', handleDocumentRefreshed);
			
			// if the user switches to a new project, then close the reponse mode
			ProjectManager.off("beforeProjectClose", closeResponseMode);
		}
		
		console.info('closing response mode');

		// close any open inline editors and close responsemode
		closeOpenInlineEditors();

		// remove any document event handlers
		_removeDocumentHandlers();
		
		// close docReloadBar if it is still open
		docReloadBar.close();

		// deselect the current query and queries
		QueryManager.clearQueryMarks();
		
		// remove the #response view
		var element = document.getElementById("response");
		if (element) {

			// ensure inspect mode is off so handlers are removed 
			// but don't update inspect mode menu item
			inspectController.close();

			// remove the response dom element
			element.parentNode.removeChild(element);

			// Manually fire the window resize event to position everything correctly.
			handleWindowResize(null);
			response = null;

			// refresh layout
			WorkspaceManager.recomputeLayout(true);
		}

		// update toolbar icon and menu state to indicate we are leaving responsive mode
		iconLink.style.backgroundPosition = '0 0';
		document.body.classList.remove('responsive-mode');

		var command = CommandManager.get(Strings.CMD_RESPONSEMODE_ID);
		command.setChecked(false);
	}
Example #3
0
	/** 
	 *  Called when the iframe DOM has fully loaded.
	 */
	function handleFrameLoaded(e) {

		if (e) {
			e.stopImmediatePropagation();
		}

		console.log("frame loaded event fired");
		
		// Store a reference to the iframe document.
		frameDOM = document.getElementById("frame").contentWindow.document;
		
		// refresh the dom cache
		DomCache.rebuildCache();
		
		// handle the case if we are using preview url and it does not load correctly
		if (!frameDOM.body.firstElementChild) {
			
			// Configure the twipsy
			var options = {
				placement: "left",
				trigger: "manual",
				autoHideDelay: 5000,
				title: function () {
					return Strings.ERR_PREVURL_NOTLOADED;
				}
			};

			// Show the twipsy with the explanation
			$("#response-icon").twipsy(options).twipsy("show");
			
			return;
		}
		
		frameDOM.body.firstElementChild.style.overflowX = 'hidden';

		// Add an empty style block in the iframe head tag. This is where we
		// will write the CSS changes so they update live in the preview.
		style = frameDOM.head.appendChild(document.createElement('style'));
		style = style.appendChild(document.createTextNode(""));

		inspectController.open();
		
		// update the layout based on vert/horz mode
		var horzCmd = CommandManager.get(Strings.CMD_HORZLAYOUT_ID);
		if (horzCmd.getChecked()) {
			showHorizontalLayout();
		} else {
			showVerticalLayout();
		}
		
		// inject frame with media queries as inline style element
		toolbar.refreshQueryMarkTracks();
		refreshIFrameMediaQueries(false);
	}
Example #4
0
	/** 
	 *  Function that will update an already opened inline editor. This is called when
	 *  a new query is created or if one of the colored query marks has been clicked.
	 *  NOTE: There is quite a bit of duplicated code here from the inlineEditorProvider function.
	 */
	function updateInlineWidgets() {

		// get the inline widgets for the currently open document
		var hostEditor = EditorManager.getCurrentFullEditor();
		var inlineWidgets = hostEditor.getInlineWidgets();

		// Update the highlight.
		if (inspectController) {
			inspectController.positionDomHighlight(inlineElement);
		}

		var cq = QueryManager.getCurrentQueryMark(),
			i,
			j,
			len;

		for (j = 0; j < inlineWidgets.length; j++) {

			var inlineCodeMirror = inlineWidgets[j].editor._codeMirror;

			// update the background colour of the inline mark
			inlineWidgets[j].refreshMediaQueryInfo(cq);
			
			var existingEdits = [];

/* BR: issue74 - stop using cached editorNode dom element as it is not longer valid if user reloads the iframe for any reason */

			// Refresh rules for current query and loop through.
			cssResults = ResponseUtils.getAuthorCSSRules(frameDOM, inlineWidgets[j].editorNode);
			inlineWidgets[j].refreshSelectorDropdown(cssResults);

			// Build the editor contents.
			// Note: For some reason count is 0 when refreshed but 4 when editor is created
			var editorContents = refreshCodeEditor(cq, cssResults);

			// Set the text in the inline editor to our new string.
			inlineCodeMirror.setValue(editorContents.contents);

			// Loop through the existingEdits array and highlight lines appropriately.
			existingEdits = editorContents.existingEdits;

			for (i = 0, len = existingEdits.length; i < len; i++) {
				inlineCodeMirror.removeLineClass(existingEdits[i].line, "background");
				inlineCodeMirror.addLineClass(existingEdits[i].line, "background", "pq" + existingEdits[i].query.colorIndex);
			}
		}
	}
Example #5
0
define(function (require, exports, module) {
	"use strict";

	/*====================  Define constants  =====================*/

	var EXT_PREFIX				= "responsive",

		// The 'constant' for vertical or horizontal mode.
		VERTICAL = 0,
		HORIZONTAL = 1,

	/*================ Load needed brackets modules ================*/

		CommandManager			= brackets.getModule("command/CommandManager"),
		Menus					= brackets.getModule("command/Menus"),
		DocumentManager			= brackets.getModule("document/DocumentManager"),
		MainViewManager			= brackets.getModule("view/MainViewManager"),
		WorkspaceManager		= brackets.getModule("view/WorkspaceManager"),
		FileUtils				= brackets.getModule("file/FileUtils"),
		FileSystem				= brackets.getModule("filesystem/FileSystem"),
		ProjectManager			= brackets.getModule("project/ProjectManager"),
		EditorManager			= brackets.getModule("editor/EditorManager"),
		ExtensionUtils			= brackets.getModule("utils/ExtensionUtils"),
		AppInit					= brackets.getModule("utils/AppInit"),
		CSSUtils				= brackets.getModule("language/CSSUtils"),
		HTMLUtils				= brackets.getModule("language/HTMLUtils"),
		PreferencesManager		= brackets.getModule("preferences/PreferencesManager"),
	
	/*================  Load custom modules  ================*/

		// This is a much lighter-weight version of the MultiRangeInlineTextEditor.
		// Ideally I could would be able to use the InlineTextEditor we can't yet.
		ResponseInlineEdit		= require("widgets/ResponseInlineEdit").ResponseInlineEdit,

		// Used to ask users if they want to refresh preview pane when switching
		// between HTML documents
		DocReloadBar			= require("widgets/DocReloadBar").DocReloadBar,

		// This much lighter-weight version of the Resizer utility
		Splitter				= require("widgets/Splitter").Splitter,

		// Represents the toolbar at the top of the preview pane
		ResponseToolbar			= require("widgets/ResponseToolbar").ResponseToolbar,
		
		// Set of DOM and CSS utility methods.
		ResponseUtils			= require("utils/ResponseUtils"),

		// Set of DOM and CSS utility methods.
		DomCache				= require("utils/DomCache"),

		// represents a media query and its custom selectors/rules
		Query					= require("query/Query").Query,
		QueryManager			= require("query/QueryManager"),

		// responsible for controlling the inspect feature
		InspectController		= require("InspectController").InspectController,

		// Load the nls string module for this plugin. 
		Strings					= require("strings"),

	/*================  Define module properties  ================*/
	
		// Reference to the DocReloadBar
		docReloadBar,
	
		// Reference to the ResponseToolbar in the preview pane
		toolbar,
		
		// Reference to the InspectController
		inspectController = new InspectController(),
		
		// Configure preferences for the extension
		prefs = PreferencesManager.getExtensionPrefs(EXT_PREFIX),

		// Path to this extension.
		modulePath,

		// Path to the current open project.
		projectRoot,

		// Document for the generated media-queries.css file.
		mediaQueryDoc,

		// TODO: should be removed from global scope
		// Element whose CSS rules are being show in the inline editor.
		inlineElement,
		
		// Iframe containing the live HTML preview.
		frame,

		// The .main-view div in Brackets core.
		mainView,

		// Main container for the response tools and iFrame.
		response,

		// The current layout mode.
		mode = VERTICAL,

		// Document object of iframe.
		frameDOM,

		// TODO: should be removed from global scope
		// Results returned from ResponseUtils.getAuthorCSSRules().
		cssResults,

		// A style block we will inject into the iframe.
		style,

		// The splitter that allows resizing of the split view.
		splitter,

		// indicates whether we are currently working with livePreviewUrl or local files
		workingMode,

		// reference to the left hand toolbar icon to open/close response mode
		iconLink;
	
	/*================  Begin function definitions  ================*/


	/**
	 * Responsible for closing any open inline editors.
	 *
	 * Note, we are making use of Document._masterEditor in order to get the editor
	 * associated to the document. This may not be 'legel' but seems to be the only
	 * way to get the editor associated to a document
	 */
	function closeOpenInlineEditors() {

		var i, len;

		try {
			var openDocs = DocumentManager.getAllOpenDocuments();
			for (i = 0; i < openDocs.length; i++) {

				var editor = openDocs[i]._masterEditor;

				if (editor !== null) {
					var inlineWidgets = editor.getInlineWidgets();

					// when closing widgets, the array is being modified so need to 
					// iterate by modifying the length value
					len = inlineWidgets.length;
					while (len--) {
						EditorManager.closeInlineWidget(editor, inlineWidgets[len]);
					}
				}
			}
		} catch (err) {
			console.error("unexpected error occurred trying to close inline widgets", err);
		}
	}

	/** 
	 *  Function that goes through all of the media query data and writes it to the 
	 *  style block in the iframe and also to the media-queries.css file.
	 */
	function refreshIFrameMediaQueries(writeToFile) {

		// only update if the reference to the style element has been set
		if (style) {
			// Defining some vars we'll need.
			var s = "",
				sortedQueries = QueryManager.getSortedQueryMarks(),
				i = sortedQueries.length,
				query,
				sel,
				k;

			// Loop through the queries and write them to the output string.
			while (i--) {

				// We need to sort the queries so the larger widths are written first
				// in order for inheritance to work properly.
				query = sortedQueries[i];

				s += '@media only screen and (max-width:';
				s += query.width;
				s += 'px) {\n\n';
				for (sel in query.selectors) {
					if (query.selectors.hasOwnProperty(sel)) {
						s += '\t' + sel + ' {\n';
						for (k in query.selectors[sel].rules) {
							if (query.selectors[sel].rules.hasOwnProperty(k)) {
								s += '\t\t' + k + ": " + query.selectors[sel].rules[k] + '\n';
							}
						}
						s += '\t}\n\n';
					}
				}
				s += '}\n';
			}

			// Set the style block in the iframe using the output string. 
			style.textContent = s;

			// Write the new text to the media-queries.css file.
			if (writeToFile === undefined || writeToFile) {
				FileUtils.writeText(mediaQueryDoc.file, s);
			}
		}
	}

	function showHorizontalLayout() {
		
		// Update only if the response element exists
		if (document.querySelector('#response')) {

			// update the global class to indicate layout
			document.body.classList.remove('response-vert');
			document.body.classList.add('response-horz');

			// clear any inline css rules on div#response and div.main-view
			response.style.cssText = null;
			mainView.style.cssText = null;

			// Remove the current panel splitter
			if (splitter !== undefined) {
				response.removeChild(splitter);
			}

			// Create a new splitter for this mode
			var cm = EditorManager.getCurrentFullEditor()._codeMirror;
			Splitter.makeResizable(response, 'horz', 344, cm);
			splitter = document.querySelector('.horz-splitter');
			splitter.style.right = '-16px';
			
			var w = window.innerWidth;

			// Change to a left/right layout
			response.style.width = (w * 0.5) + 'px';
			mainView.style.left = (response.offsetWidth + 15) + 'px';
			mainView.style.height = '100%';
			
			toolbar.resize(response.offsetWidth);
			
			// refresh layout
			WorkspaceManager.recomputeLayout(true);
		}
	}

	/** 
	 *  Called when the user clicks on one of the editor layout
	 *  toggle buttons (either vertical or horizontal)
	 *
	 * note: the buttons are not named correctly. the 'horzButt' is actually 
	 * when the user is in vertical layout (up and down) while 'vertButt' is
	 * when the user is in horizontal layout (left to right). the code should
	 * be updated at some point to remove this confusion
	 */

	function handleHorzLayoutToggle(e) {

		var btnClicked = false;
		
		// if e is defined then it means the click came from the button in the preview pane. 
		// need to check if it is not already 'active' and signal it was clicked if it is 
		// not active
		if (e) {
			e.stopImmediatePropagation();
			btnClicked = !document.body.classList.contains('response-horz');
		}

		// check if the layout state has changed. making sure not clicking on an already
		// active menu
		var horzCmd = CommandManager.get(Strings.CMD_HORZLAYOUT_ID);
		if (btnClicked || !horzCmd.getChecked()) {
			
			// update menu state if not already correct
			horzCmd.setChecked(true);

			var vertCmd = CommandManager.get(Strings.CMD_VERTLAYOUT_ID);
			vertCmd.setChecked(false);
		
			// set the mode. would like to get rid of this variable and use menu state instead
			mode = HORIZONTAL;
			
			// update the layout if the preview pane is visible
			showHorizontalLayout();
		}
	}

	function showVerticalLayout() {
		
		// Update only if the response element exists
		if (document.querySelector('#response')) {

			// update the global class to indicate layout
			document.body.classList.remove('response-horz');
			document.body.classList.add('response-vert');

			// clear any inline css rules on div#response and div.main-view
			response.style.cssText = null;
			mainView.style.cssText = null;

			// Remove the current panel splitter
			if (splitter !== undefined) {
				response.removeChild(splitter);
			}

			// Create a new splitter for this mode
			var cm = EditorManager.getCurrentFullEditor()._codeMirror;
			Splitter.makeResizable(response, 'vert', 100, cm);

			splitter = document.querySelector('.vert-splitter');

			var h = window.innerHeight;

			// Change to a top/bottom layout
			response.style.height = (h * 0.6) + 'px';
			mainView.style.height = (h - response.offsetHeight - 16) + 'px';
			
			toolbar.resize(response.offsetWidth);
			
			// refresh layout
			WorkspaceManager.recomputeLayout(true);
		}
	}

	function handleVertLayoutToggle(e) {

		var btnClicked = false;
		
		// if e is defined then it means the click came from the button in the preview pane. 
		// need to check if it is not already 'active' and signal it was clicked if it is 
		// not active
		if (e) {
			e.stopImmediatePropagation();
			btnClicked = !document.body.classList.contains('response-vert');
		}

		// check if the layout state has changed. making sure not clicking on an already
		// active menu
		var vertCmd = CommandManager.get(Strings.CMD_VERTLAYOUT_ID);
		if (btnClicked || !vertCmd.getChecked()) {
			
			// update menu state if not already correct
			vertCmd.setChecked(true);

			var horzCmd = CommandManager.get(Strings.CMD_HORZLAYOUT_ID);
			horzCmd.setChecked(false);
		
			// set the mode. would like to get rid of this variable and use menu state instead
			mode = VERTICAL;
			
			// update the layout if the preview pane is visible
			showVerticalLayout();
		}
	}

	/*================  Methods to handle document changes  ================*/
	
	/**
	 * responsible for handling when the current main document changes. It will show 
	 * a message to reload the preview pane bar
	 */
	function handleCurrentFilechange(e, newFile, newPaneId, oldFile, oldPaneId) {

		try {
			console.debug("currentFileChange event triggered", newFile, oldFile);
			
			var currentDoc = DocumentManager.getCurrentDocument();
			if (document.querySelector('#response') && workingMode === 'local' && currentDoc !== null && currentDoc.language.getId() === "html") {
				// open the doc reload bar so user can decide if the preview pane should be reloaded
				docReloadBar.open();
			}
		} catch (err) {
			console.error("unexpected error occurred trying to handle currentFileChange event", err);
		}
	}

	/**
	 * handles when the file has been modified from user editing. It will show 
	 * a message to reload the preview pane bar
	 */
	function handleDirtyFlagChange(e, doc) {

		try {
			console.debug("dirtyFlagChange event triggered", doc);
			
			var currentDoc = DocumentManager.getCurrentDocument();
			if (doc.isDirty && doc === currentDoc && workingMode === 'local' && currentDoc.language.getId() === "html") {
				// open the doc reload bar so user can decide if the preview pane should be reloaded
				docReloadBar.open();
			}

		} catch (err) {
			console.error("unexpected error occurred trying to handle currentFileChange event", err);
		}
	}

	/**
	 * handles when the document has been saved. It will refresh the preview pane and close
	 * any open doc reload bars
	 */
	function handleDocumentSaved(e, doc) {

		try {
			console.debug("documentSaved event triggered", doc);

			if (workingMode === 'local' && doc.language.getId() === "html") {
				// refresh the preview pane and close the reload bar
				ResponseUtils.refreshPreviewPane();
				docReloadBar.close();
			}

		} catch (err) {
			console.error("unexpected error occurred trying to handle currentFileChange event", err);
		}
	}
	
	function handleDocumentRefreshed(e, doc) {

		try {
			console.debug("documentRefreshed event triggered", doc);

			var currentDoc = DocumentManager.getCurrentDocument();
			if (doc === currentDoc && workingMode === 'local' && currentDoc.language.getId() === "html") {
				// open the doc reload bar so user can decide if the preview pane should be reloaded
				docReloadBar.open();
			}

		} catch (err) {
			console.error("unexpected error occurred trying to handle currentFileChange event", err);
		}
	}
 
	/** 
	 *  Builds the UI for responsive mode. Lots of DOM injecting here.
	 */
	function createResponseUI(previewPaneUrl) {

		var doc = document;
		doc.body.backgroundColor = "#303030";

		var cm = EditorManager.getCurrentFullEditor()._codeMirror;

		// create response main container and add to body
		response = $('<div id="response" class="quiet-scrollbars"/>')[0];
		doc.body.insertBefore(response, doc.body.firstChild);

		// create toolbar and add to response div element
		toolbar = new ResponseToolbar();
		toolbar.resize(response.offsetWidth, true);
		toolbar.$toolbar.appendTo(response);

		toolbar.on('queryWidthChanged', function (e, newVal) {
			console.log("queryWidthChanged triggered: " + newVal);
		});

		// add click handler for vertical/horizontal layout buttons
		var horzLayoutBtn = document.getElementById("horzButt");
		horzLayoutBtn.addEventListener('click', handleHorzLayoutToggle, false);
		var vertLayoutBtn = document.getElementById("vertButt");
		vertLayoutBtn.addEventListener('click', handleVertLayoutToggle, false);

		// Here I add the live preview iframe wrapped in a div.
		var domArray = [{tag: "div", attr: {id: "fwrap"}, parent: -1},
					{tag: "iframe", attr: {id: "frame", class: "quiet-scrollbars", name: "frame", src: previewPaneUrl}, parent: 0}];

		var frag = ResponseUtils.createDOMFragment(domArray);
		response.appendChild(frag);

		frame = doc.getElementById('frame');
		
		var h = window.innerHeight;

		// Set the initial heights of the panels to 60% response / 40% code editor.
		response.style.height = (h * 0.6) + 'px';
		mainView.style.height = (h - response.offsetHeight - 16) + 'px';

		// Create a vertical splitter to divide the editor and the response UI
		Splitter.makeResizable(response, 'vert', 100, cm);
		splitter = document.querySelector('.vert-splitter');
		
		// Manually fire the window resize event to position everything correctly.
		handleWindowResize(null);

		// Refresh codemirror
		cm.refresh();
	 
		setupEventHandlers();
	}

	/**
	 * Responsible for open the response mode which happens when the user clicks on the response icon
	 * in the main toolbar along the left
	 */
	function openResponseMode() {

		console.info('opening response mode');
		
		/**
		 * determines which URL to use in the iframe preview pane
		 */
		function _getPreviewPaneUrl() {
			
			var previewPaneUrl;
			
			workingMode = null;
			
			// check if we should be using the live preview url
			var command = CommandManager.get(Strings.CMD_PREVIEWURL_ID);
			if (command.getChecked()) {
				if (ProjectManager.getBaseUrl()) {
					previewPaneUrl = ProjectManager.getBaseUrl();
					workingMode = 'livePreviewUrl';
				} else {
					console.info("Live Preview Base URL not set under File > Project Settings. Need to let user know. defaulting to HTML file if it is open");
				}
			}
			
			// not configured to use live preview url. use current doc if it is an HTML
			if (!previewPaneUrl) {
				var currentDoc = DocumentManager.getCurrentDocument();
			
				// Only switch to responsive mode if the current document is HTML or 
				// a Live Preview Base URL has been defined under File > Project Settings and user
				// has chosen to open with Live Preview Base URL in the menu
				if (currentDoc !== null && currentDoc.language.getId() === "html") {
					previewPaneUrl = "file://" + currentDoc.file.fullPath;
					workingMode = 'local';
				} else {
					console.info("Unable to switch to Responsive mode as the current document is not HTML");
				}
			}

			// display message to user if unable to determine preview pane url
			if (!previewPaneUrl) {

				// Configure the twipsy
				var options = {
					placement: "left",
					trigger: "manual",
					autoHideDelay: 5000,
					title: function () {
						return Strings.ERR_PREVURL_UNAVAILABLE;
					}
				};


				// Show the twipsy with the explanation
				$("#response-icon").twipsy(options).twipsy("show");
			}
			
			return previewPaneUrl;
		}
		
		function _addDocumentHandlers() {
			
			MainViewManager.on("currentFileChange", handleCurrentFilechange);
	
			DocumentManager
				.on('dirtyFlagChange', handleDirtyFlagChange)
				.on('documentSaved', handleDocumentSaved)
				.on('documentRefreshed', handleDocumentRefreshed);
			
			// if the user switches to a new project, then close the reponse mode
			ProjectManager.on("beforeProjectClose", closeResponseMode);
		}
		
		function _getMediaQueryDocument(previewPaneUrl) {
			
			console.log("getting document for media query");
			DocumentManager.getDocumentForPath(projectRoot + prefs.get("mediaQueryFile"))
				.done(function (doc) {
					console.log("retrieved document");

					// close any open inline editors
					closeOpenInlineEditors();

					// enable event handlers for documents
					_addDocumentHandlers();
				
					// Save reference to the new files document.
					mediaQueryDoc = doc;
					MainViewManager.addToWorkingSet(MainViewManager.ACTIVE_PANE, doc.file);

					// refresh media queries from file if they exist
					QueryManager.parseMediaQueries(doc.getText(), doc.getLanguage().getMode());
				
					// now we are ready to create the response UI
					createResponseUI(previewPaneUrl);

					// update toolbar icon to indicate we are in responsive mode
					iconLink.style.backgroundPosition = '0 -26px';
					document.body.classList.add('responsive-mode');

					var command = CommandManager.get(Strings.CMD_RESPONSEMODE_ID);
					command.setChecked(true);
				})
				.fail(function (error) {
					console.error("an unexpedted error occurred trying to get the media query css file", error);
				});
		}
		
		// Ensure we can create a preview pane. Either the currently main
		// document needs to be an HTML doc or use the Live Preview URL if
		// it has been set
		var previewPaneUrl = _getPreviewPaneUrl();
		if (!previewPaneUrl) {
			return;
		}

		projectRoot = ProjectManager.getProjectRoot().fullPath;
		mainView = document.querySelector('.main-view');

		var mediaQueryFilePath = projectRoot + prefs.get("mediaQueryFile");

		// Check if the media-queries css file exists. If it doesn't, then create a
		// new file. If it does, then reload and refresh UI
		FileSystem.resolve(mediaQueryFilePath, function (result, file, fileSystemStats) {
			console.log("resolved path to media query file");

			// create an empty file as one doesn't exist yet                
			if ('NotFound' === result) {
				console.log("creating media query file: " + prefs.get("mediaQueryFile"));

				var mediaQueryFile = FileSystem.getFileForPath(mediaQueryFilePath);

				// create the parent dir if it doesn't yet exist. currently only supports a single node
				console.log("creating parent dir if it doesn't exist");
				var parentDir = FileSystem.getDirectoryForPath(mediaQueryFile.parentPath);
				parentDir.exists(function (error, exists) {
					if (!exists) {
						parentDir.create();
					}
				});

				console.log("writing to media query file to force create");
				mediaQueryFile.write('', function (error, stats) {
					console.log("error: " + error + "; stats: " + stats);
					if (error === null) {
						_getMediaQueryDocument(previewPaneUrl);
					}
				});
				console.log("write completed");

			} else {
				_getMediaQueryDocument(previewPaneUrl);
			}
		});
	}

	/**
	 * Responsible for closing the response mode. This can be invoked in a number of situations
	 *   - when the user clicks on the 'response' icon in the main toolbar on the left
	 *   - when the user switches between projects
	 */
	function closeResponseMode() {

		function _removeDocumentHandlers() {
			
			MainViewManager.off("currentFileChange", handleCurrentFilechange);
	
			DocumentManager
				.off('dirtyFlagChange', handleDirtyFlagChange)
				.off('documentSaved', handleDocumentSaved)
				.off('documentRefreshed', handleDocumentRefreshed);
			
			// if the user switches to a new project, then close the reponse mode
			ProjectManager.off("beforeProjectClose", closeResponseMode);
		}
		
		console.info('closing response mode');

		// close any open inline editors and close responsemode
		closeOpenInlineEditors();

		// remove any document event handlers
		_removeDocumentHandlers();
		
		// close docReloadBar if it is still open
		docReloadBar.close();

		// deselect the current query and queries
		QueryManager.clearQueryMarks();
		
		// remove the #response view
		var element = document.getElementById("response");
		if (element) {

			// ensure inspect mode is off so handlers are removed 
			// but don't update inspect mode menu item
			inspectController.close();

			// remove the response dom element
			element.parentNode.removeChild(element);

			// Manually fire the window resize event to position everything correctly.
			handleWindowResize(null);
			response = null;

			// refresh layout
			WorkspaceManager.recomputeLayout(true);
		}

		// update toolbar icon and menu state to indicate we are leaving responsive mode
		iconLink.style.backgroundPosition = '0 0';
		document.body.classList.remove('responsive-mode');

		var command = CommandManager.get(Strings.CMD_RESPONSEMODE_ID);
		command.setChecked(false);
	}
	
	/** 
	 *  Main entry point of extension that is called when responsive mode is launched.
	 */
	function handleResponseIconClick(e) {

		if (e) { e.stopImmediatePropagation(); }
		
		// Prevent creating UI more than once
		if (document.querySelector('#response')) {
			closeResponseMode();
		} else {
			openResponseMode();
		}
	}

	/** 
	 *  Sets up all of the event listeners we need
	 */
	function setupEventHandlers() {

		// using jquery load event handling as this will trigger when iframe is reloaded
		// instead of only on the first time it is loaded.
		$(frame).on("load", handleFrameLoaded);
		
		window.addEventListener('resize', handleWindowResize, false);
		$(response).on('panelResizeUpdate', handlePanelResize);
	}


	/**
	 * Called when user selects live preview menu item. If the menu item
	 * is enabled then the preview pane will load with the url specified under
	 * File > Project Settings
	 */
	function handleLivePreviewToggle(e) {
		
		if (e) {
			e.stopImmediatePropagation();
		}

		// update the inspect menu state
		var command = CommandManager.get(Strings.CMD_PREVIEWURL_ID);
		command.setChecked(!command.getChecked());
	}
	
	/** 
	 *  Called when the iframe DOM has fully loaded.
	 */
	function handleFrameLoaded(e) {

		if (e) {
			e.stopImmediatePropagation();
		}

		console.log("frame loaded event fired");
		
		// Store a reference to the iframe document.
		frameDOM = document.getElementById("frame").contentWindow.document;
		
		// refresh the dom cache
		DomCache.rebuildCache();
		
		// handle the case if we are using preview url and it does not load correctly
		if (!frameDOM.body.firstElementChild) {
			
			// Configure the twipsy
			var options = {
				placement: "left",
				trigger: "manual",
				autoHideDelay: 5000,
				title: function () {
					return Strings.ERR_PREVURL_NOTLOADED;
				}
			};

			// Show the twipsy with the explanation
			$("#response-icon").twipsy(options).twipsy("show");
			
			return;
		}
		
		frameDOM.body.firstElementChild.style.overflowX = 'hidden';

		// Add an empty style block in the iframe head tag. This is where we
		// will write the CSS changes so they update live in the preview.
		style = frameDOM.head.appendChild(document.createElement('style'));
		style = style.appendChild(document.createTextNode(""));

		inspectController.open();
		
		// update the layout based on vert/horz mode
		var horzCmd = CommandManager.get(Strings.CMD_HORZLAYOUT_ID);
		if (horzCmd.getChecked()) {
			showHorizontalLayout();
		} else {
			showVerticalLayout();
		}
		
		// inject frame with media queries as inline style element
		toolbar.refreshQueryMarkTracks();
		refreshIFrameMediaQueries(false);
	}

	/** 
	 *  Called when the user resizes the brackets window.
	 */
	function handleWindowResize(e) {

		if (e) {
			e.stopImmediatePropagation();
		}

		var w = window.innerWidth;
		var h = window.innerHeight;

		// Get the width and height of the response UI
		var responseWidth = response.offsetWidth;
		var responseHeight = response.offsetHeight;

		toolbar.resize(responseWidth);

		// This gets called if we are in horizontal mode. Since the event can
		// be fired excessively, I use a bitwise operator to eek out some perf.
		if (mode & 1) {
			mainView.style.left = (responseWidth + 15) + 'px';
		} else {
			mainView.style.height = (h - responseHeight - 16) + 'px';
		}
	}

	/** 
	 *  Called when the user resizes the panels using the splitter.
	 */
	function handlePanelResize(e, size) {
  
		// Only refresh codemirror every other call (perf).    
		if (size & 1) {
			var cm = EditorManager.getCurrentFullEditor()._codeMirror;
			cm.refresh();
		}
		
		// Adjust things properly if in horizontal mode.
		if (mode & 1) {
			mainView.style.left = (parseInt(size, 10) + 15) + 'px';

			// resize the toolbar
			toolbar.resize(size);
			
			return;
		}

		// Were in vertical mode so adjust things accordingly.
		mainView.style.height = (window.innerHeight - size - 16) + 'px';
	}

	/** 
	 *  Called when the user chooses a CSS selector from the select box
	 *  that appears in the inline editor.
	 */
	function handleSelectorChange(e) {
		
		var newSelector = e.target.value,
			i,
			len;

		var inlineWidget = EditorManager.getFocusedInlineWidget();
		inlineWidget.currentSelector = newSelector;
		
		// Build the editor contents. 
		// Note: For some reason count is 0 when refreshed but 4 when editor is created
		var editorContents = refreshCodeEditor(QueryManager.getCurrentQueryMark(), cssResults, newSelector);

		// Set the text in the inline editor to our new string.
		var inlineCm = inlineWidget.editor._codeMirror;
		inlineCm.setValue(editorContents.contents);

		// Loop through the existingEdits array and highlight lines appropriately.
		var existingEdits = editorContents.existingEdits;
		for (i = 0, len = existingEdits.length; i < len; i++) {
			inlineCm.removeLineClass(existingEdits[i].line, "background");
			inlineCm.addLineClass(existingEdits[i].line, "background", "pq" + existingEdits[i].query.colorIndex);
		}
	}

	/** 
	 *  This is where we setup and display the inline editor for doing quick edits.
	 *  @params: these 2 get sent when you register as an inline provider. The first
	 *  is the main or host editor and the second is the cursor position.
	 */
	function inlineEditorProvider(hostEditor, pos) {

		// uses the tagInfo from the editor to create adom element in the frame document
		// that needs to be parsed for editing. we don't look up the element as we need
		// more control in what is not included when getting the css rules associated to the
		// element
		function _getFrameElement(frameDom, tagInfo) {

			var element = frameDom.createElement(tagInfo.tagName);

			if (tagInfo.position.tokenType === HTMLUtils.ATTR_NAME || tagInfo.position.tokenType === HTMLUtils.ATTR_VALUE) {
				if (tagInfo.attr.name === "class") {
					// Class selector
					element.className = tagInfo.attr.value.trim();

				} else if (tagInfo.attr.name === "id") {
					// ID selector
					element.id = tagInfo.attr.value.trim();
				}
			}

			return element;
		}

		// Only provide a CSS editor when cursor is in HTML content
		if (hostEditor.getLanguageForSelection().getId() !== "html") {
			return null;
		}
				
		// Only provide CSS editor if the selection is within a single line
		var sel = hostEditor.getSelection();
		if (sel.start.line !== sel.end.line) {
			return null;
		}

		// We are not in responsive mode yet (toolbar icon not selected). Fallback
		// to the default CSS inline editor
		if (!document.querySelector('#response')) {
			return null;
		}
		
		// If there isn't a media query, show the message that a query has not been selected
		if (!QueryManager.getCurrentQueryMark()) {
			hostEditor.displayErrorMessageAtCursor("There have not been any media queries defined.");
			return $.Deferred().promise();
		}
		
		// We are now going to write the string the temporary CSS file so we can display
		// it in the inline editor. A jQuery deffered object is used for async.
		var result = new $.Deferred();
		
		// get code mirror from main editor
		var cm = EditorManager.getCurrentFullEditor()._codeMirror;
        var cursor = cm.getCursor();
        
        // Find out the tag name they were on when they hit Cmd-E. If could not
        // be determined then return so message is displayed to user
        var tag = cm.getTokenAt(cursor).state.htmlState.tagName;
        if (tag ===  null) {
            return null;
        }
        
        // Get a reference to the DOM element in the iframe.
		var domCache = DomCache.getCache();
        var el = domCache.frameDom[tag][domCache.codeDom[tag].indexOf(cursor.line)];
		
		// Set this element to the inlineElement property that is used elsewhere.
		inlineElement = el;

		// Call my utility method that finds all of the CSS rules that are
		// currently set for this element. See the comments in ResponseUtils.js.
		cssResults = ResponseUtils.getAuthorCSSRules(frameDOM, el);
		
		var count = 4,
			i,
			len,
			cq = QueryManager.getCurrentQueryMark();

		// build the editor contents
		// The line count starts at 4 because of the selector, whitespace, etc.  
		// Note: For some reason count is 0 when refreshed but 4 when editor is created
		var editorContents = refreshCodeEditor(cq, cssResults);

		// Create a new inline editor. This is my stripped-down version of the
		// MultiRangeInlineEditor module.
		var inlineEditor = new ResponseInlineEdit();
		inlineEditor.editorNode = el;

		// Load the editor with the CSS we generated.
		inlineEditor.load(hostEditor, 0, count + 2, editorContents.contents);

		// Called when the editor is added to the DOM.
		inlineEditor.onAdded = function () {

			// Get a reference to the codemirror instance of the inline editor.
			var inlineCm = this.editor._codeMirror;

			// Loops through the existingEdits array and highlights the appropriate lines
			// in the inline editor.
			var existingEdits = editorContents.existingEdits;
			for (i = 0, len = existingEdits.length; i < len; i++) {
				inlineCm.removeLineClass(existingEdits[i].line, "background");
				inlineCm.addLineClass(existingEdits[i].line, "background", "pq" + existingEdits[i].query.colorIndex);
			}

			// Sets cursor to the end of line 2 in the inline editor.
			this.editor.setCursorPos(1, 0);

			/* BR: could use inlineEditor change event instead of code mirror in effort to stop using code mirror */
			// Listen for changes in the inline editor.
			inlineCm.on("change", inlineChange);
			inlineEditor.doc.on("change", function (e, instance, change) {
				console.log("inlineEditor change event triggered", e, instance, change);
			});
			
			this.refreshMediaQueryInfo(cq);
			this.refreshSelectorDropdown(cssResults);
			this.$selectorSelect[0].addEventListener('change', handleSelectorChange, false);
		};

		// Called when the inline editor is closed.
		inlineEditor.onClosed = function () {

			// Call parent function first.
			ResponseInlineEdit.prototype.parentClass.onAdded.apply(this, arguments);
		};

		// I had to mod the EditorManager module so it always chooses me.
		result.resolve(inlineEditor);

		return result.promise();
	}

	/**
	 *  refreshes the contents of the inline widget, showing the css rules of the
	 *  current css selector (from dropdown)
	 *
	 *  @params cq              : the current media query that has been selected from slider
	 *  @params res             : the css rules that were retrieved from the selected element in the
	 *                            main editor
	 *  @params currentSelector : the current css selector. If not supplied it will default to
	 *                            first css selector for the current element
	 */
	function refreshCodeEditor(cq, res, currentSelector) {

		currentSelector = currentSelector || res.selectors[0];
		
		// Array to hold information about whether a rule has already been set by this or another query.
		var existingEdits = [],

			// indicates the current line number. setting for 1 as the first line (0) is the selector
			lineNumber = 0,
			
			// used in iterator for properties
			prop,
			index,

			// Here we begin writing the string that we will use to populate the inline editor.
			str = currentSelector + " {\n";

		// Go through all of the returned CSS rules and write to the output string.
		if (res.rules[currentSelector] !== null) {
			for (prop in res.rules[currentSelector]) {

				var pvalue = null;
				lineNumber++;

				// Here we loop through all of the defined media queries to see if this rule
				// has already been set by one of them. This is used to show inheritance.
				var queries = QueryManager.getSortedQueryMarks();
				for (index in queries) {

					var q = queries[index];

					// If the media query (q) has a width greater than the currently selected
					// query and has already set a value for this property, then the current
					// query will inherit that value.
					if (q !== cq && parseInt(q.width, 10) > parseInt(cq.width, 10) &&
							q.selectors[currentSelector]) {

						// Check if it has the property set and if so, add it to the existingEdits
						// array so we can highlight it appropriately. Also stores the value.
						if (q.selectors[currentSelector].rules[prop]) {
							pvalue = q.selectors[currentSelector].rules[prop];
							existingEdits.push({query: q, line: lineNumber});
							pvalue = pvalue.replace(/;/, '');
							break;
						}

					} else if (cq === q && q.selectors[currentSelector]) {
						// Check if the currently selected query has this property already set.
						// If so then we add it to the existingEdits array for highlighting purposes.
						// It also stores the value 'pvalue' so we can use that in the output.

						if (q.selectors[currentSelector].rules[prop]) {
							pvalue = q.selectors[currentSelector].rules[prop];
							existingEdits.push({query: q, line: lineNumber});
							pvalue = pvalue.replace(/;/, '');
							break;
						}
					}
				}

				// If this property hasn't been set by anyone, we use the original value returned.
				if (!pvalue) {
					pvalue = res.rules[currentSelector][prop];
				}

				// Finally we add the CSS rule to the output string.
				str += "\t" + prop + ": " + pvalue.trim() + ";\n";
			}
		} else {
			// no rules so create an empty line
			str += "\t\n";
		}

		// Closing curly brace = we're done!
		str += "}";
		
		return { contents: str, existingEdits: existingEdits, numLines: lineNumber };
	}
	
	/** 
	 *  Called when there is a text change in the inline editor.
	 *
	 *  @params instance    : the codemirror instance,
	 *  @params change      : the change object.
	 */
	function inlineChange(instance, change) {

		console.log("inlineChange", instance, change);
		
		// Make sure that the change is even worth looking at.
		if (change.text.length < 2 && change.from.line !== 0) {

			var currentQuery = QueryManager.getCurrentQueryMark();
			var inlineWidget = EditorManager.getFocusedInlineWidget();

			// Add the changed rule to the current query object.
			currentQuery.addRule(inlineWidget.currentSelector, instance.getLine(change.from.line));

			// If a previous query had this prop set, remove its background highlight.
			instance.removeLineClass(change.from.line, "background");

			// Add the new line highlight with the color of the current query.
			instance.addLineClass(change.from.line, "background", "pq" + currentQuery.colorIndex);

			// Write out the changes to the style block and the media queries CSS file.
			refreshIFrameMediaQueries();
		}

		// Adjust the highlight according to the new CSS value.
		if (inspectController) {
			inspectController.positionDomHighlight(inlineElement);
		}
	}

	/** 
	 *  Function that will update an already opened inline editor. This is called when
	 *  a new query is created or if one of the colored query marks has been clicked.
	 *  NOTE: There is quite a bit of duplicated code here from the inlineEditorProvider function.
	 */
	function updateInlineWidgets() {

		// get the inline widgets for the currently open document
		var hostEditor = EditorManager.getCurrentFullEditor();
		var inlineWidgets = hostEditor.getInlineWidgets();

		// Update the highlight.
		if (inspectController) {
			inspectController.positionDomHighlight(inlineElement);
		}

		var cq = QueryManager.getCurrentQueryMark(),
			i,
			j,
			len;

		for (j = 0; j < inlineWidgets.length; j++) {

			var inlineCodeMirror = inlineWidgets[j].editor._codeMirror;

			// update the background colour of the inline mark
			inlineWidgets[j].refreshMediaQueryInfo(cq);
			
			var existingEdits = [];

/* BR: issue74 - stop using cached editorNode dom element as it is not longer valid if user reloads the iframe for any reason */

			// Refresh rules for current query and loop through.
			cssResults = ResponseUtils.getAuthorCSSRules(frameDOM, inlineWidgets[j].editorNode);
			inlineWidgets[j].refreshSelectorDropdown(cssResults);

			// Build the editor contents.
			// Note: For some reason count is 0 when refreshed but 4 when editor is created
			var editorContents = refreshCodeEditor(cq, cssResults);

			// Set the text in the inline editor to our new string.
			inlineCodeMirror.setValue(editorContents.contents);

			// Loop through the existingEdits array and highlight lines appropriately.
			existingEdits = editorContents.existingEdits;

			for (i = 0, len = existingEdits.length; i < len; i++) {
				inlineCodeMirror.removeLineClass(existingEdits[i].line, "background");
				inlineCodeMirror.addLineClass(existingEdits[i].line, "background", "pq" + existingEdits[i].query.colorIndex);
			}
		}
	}
	
	function buildMenuSystem() {
		
		// Build commands and menu system
		var customMenu = Menus.addMenu(Strings.MENU_MAIN, Strings.MENU_RESPONSE_ID, Menus.AFTER, Menus.AppMenuBar.NAVIGATE_MENU);

		CommandManager.register(Strings.SUBMENU_RESPSONSEMODE, Strings.CMD_RESPONSEMODE_ID, handleResponseIconClick);
		customMenu.addMenuItem(Strings.CMD_RESPONSEMODE_ID, "Shift-Alt-R");

		// Toggle inspect mode.
		CommandManager.register(Strings.SUBMENU_INSPECTMODE, Strings.CMD_INSPECTMODE_ID, inspectController.handleInspectToggle);
		customMenu.addMenuItem(Strings.CMD_INSPECTMODE_ID, "Shift-Alt-I");

		customMenu.addMenuDivider();

		// add menu items to indicate if horizontal or vertical layout should be used for the preview
		// pane
		CommandManager.register(Strings.SUBMENU_HORZLAYOUT, Strings.CMD_HORZLAYOUT_ID, handleHorzLayoutToggle);
		customMenu.addMenuItem(Strings.CMD_HORZLAYOUT_ID, "Shift-Alt-H");

		CommandManager.register(Strings.SUBMENU_VERTLAYOUT, Strings.CMD_VERTLAYOUT_ID, handleVertLayoutToggle);
		customMenu.addMenuItem(Strings.CMD_VERTLAYOUT_ID, "Shift-Alt-V");

		customMenu.addMenuDivider();

		// Add menu item to indicate if live preview url setting should be used for preview pane
		CommandManager.register(Strings.SUBMENU_PREVIEWURL, Strings.CMD_PREVIEWURL_ID, handleLivePreviewToggle);
		customMenu.addMenuItem(Strings.CMD_PREVIEWURL_ID, "Shift-Alt-U");
	}
	

	/** 
	 *  Called whenever the current query changes. It is responsible for updating the inline editors
	 */
	QueryManager.on("currentQueryChanged", function (e, cq) {

		try {
			console.debug("currentQueryChanged event called", cq);
			
			// In horizontal mode the code editor also snaps to the query width to give more space.      
			if (mode === HORIZONTAL) {
				Splitter.updateElement(cq.width);
			}

			// Refresh codemirror
			var cm = EditorManager.getCurrentFullEditor()._codeMirror;
			cm.refresh();

			// update the inline editor with the newly selected query.
			updateInlineWidgets();

			// Calling this function will write the new query to the style block 
			// in the iframe and also to the media-queries.css file.
			refreshIFrameMediaQueries();
			
		} catch (err) {
			console.error("an unexpected exception occurred trying to handle currentQueryChanged event", err);
		}
	});
	
	/** 
	 *  Called when brackets has opened and is ready.
	 */
	AppInit.appReady(function () {
		
		// Here we add the toolbar icon that launches you into responsive mode.
		iconLink = document.createElement('a');
		iconLink.href = "#";
		iconLink.id = "response-icon";

		var iconURL = require.toUrl('./images/toolbar-icon.png');
		iconLink.style.cssText = "content: ''; background: url('" + iconURL + "') 0 0 no-repeat;";

		document.querySelector('#main-toolbar .buttons').appendChild(iconLink);
		iconLink.addEventListener('click', handleResponseIconClick, false);

		docReloadBar = new DocReloadBar();
	});

	modulePath = FileUtils.getNativeModuleDirectoryPath(module);

	// Is there a brackets function for loading non-module scripts?
	// I couldn't find one so I wrote a simple one.
	ResponseUtils.loadExternalScript(modulePath + "/js/TweenMax.min.js", document.head);

	// Load in the main CSS for the responsive UI.
	ExtensionUtils.addLinkedStyleSheet(modulePath + "/css/respond.css");
	
	prefs.definePreference("mediaQueryFile", "string", "css/media-queries.css");
	prefs.definePreference("preferredLayout", "string", "vertical").on("change", function () {
		
		if (prefs.get("preferredLayout").toLowerCase() === "horizontal") {
			handleHorzLayoutToggle();
		} else {
			handleVertLayoutToggle();
		}
	});
	
	prefs.definePreference("useLivePreviewUrl", "boolean", false).on("change", function () {

		var command = CommandManager.get(Strings.CMD_PREVIEWURL_ID);

		// update the live preview url menu state
		if (prefs.get("useLivePreviewUrl")) {
			command.setChecked(true);
		} else {
			command.setChecked(false);
		}
	});

	buildMenuSystem();

	// Register as an inline provider.
	EditorManager.registerInlineEditProvider(inlineEditorProvider, 9);
});