c9-core/plugins/c9.ide.preview/preview.js

929 wiersze
36 KiB
JavaScript

define(function(require, exports, module) {
main.consumes = [
"Editor", "editors", "settings", "ui", "proc", "c9", "util",
"preferences", "layout", "tabManager", "tree", "commands", "menus",
"dialog.error", "dialog.alert", "save", "Menu", "MenuItem", "Divider"
];
main.provides = ["preview"];
return main;
// @todo - Add XML Plugin
// @todo - Add JSON Plugin
// @todo - Add Coffee Plugin
// @todo - Add Jade Plugin
// @todo - Fix the activate/deactivate events on session. They leak / are not cleaned up
function main(options, imports, register) {
var Editor = imports.Editor;
var editors = imports.editors;
var ui = imports.ui;
var c9 = imports.c9;
var settings = imports.settings;
var commands = imports.commands;
var menus = imports.menus;
var layout = imports.layout;
var tree = imports.tree;
var save = imports.save;
var proc = imports.proc;
var util = imports.util;
var tabs = imports.tabManager;
var prefs = imports.preferences;
var Menu = imports.Menu;
var MenuItem = imports.MenuItem;
var Divider = imports.Divider;
var showError = imports["dialog.error"].show;
var showAlert = imports["dialog.alert"].show;
var basename = require("path").basename;
var extensions = ["pdf", "swf"];
var previewUrl = options.previewUrl.replace(/^[/]/, function() {
return c9.location.replace(/^(\w+:[/]+[^/#?]+).*/, "$1/");
}).replace(/[/]$/, "");
/***** Initialization *****/
var handle = editors.register("preview", "Preview", Preview, extensions);
var handleEmit = handle.getEmitter();
var BGCOLOR = {
"flat-light": "#F1F1F1",
"flat-dark": "#303130",
"light": "#d6d5d5",
"light-gray": "#d6d5d5",
"dark": "#303130",
"dark-gray": "#303130"
};
var previewers = {};
var menu, liveMenuItem, mnuSettings;
function load() {
var parent = layout.findParent({ name: "preview" });
if (!options.hideButton) {
var submenu = new ui.menu({
"onprop.visible": function(e) {
var tab = tabs.focussedTab;
var isKnown = false;
if (tab && tab.path) {
var path = tab.path;
for (var name in previewers) {
if (previewers[name].matcher(path)) {
isKnown = true;
break;
}
}
liveMenuItem.setAttribute("caption", isKnown
? "Live Preview File (" + basename(path) + ")"
: "Raw Content of " + basename(path)
);
liveMenuItem.enable();
}
else {
liveMenuItem.disable();
}
}
});
var button = new ui.button({
skin: "c9-toolbarbutton-glossy",
"class": "preview",
// tooltip : "Preview the current document",
caption: "Preview",
submenu: submenu
});
button && ui.insertByIndex(parent, button, 10, handle);
menus.addItemByPath("Tools/Preview/", submenu, 1000, handle);
liveMenuItem = new ui.item({
onclick: function(e) { commands.exec("preview", { newTab: e && e.button == 1 }); }
});
menus.addItemByPath("Tools/Preview/Live Preview Files",
liveMenuItem, 100, handle);
menus.addItemByPath("Tools/Preview/Preview Running Application", new ui.item({
onclick: function(e) {
commands.exec("preview", null, {
server: true,
newTab: e && e.button == 1
});
}
}), 200, handle);
}
settings.on("read", function(e) {
settings.setDefaults("user/preview", [
["running_app", options.defaultRunApp || "false"],
["default", options.defaultPreviewer || "raw"],
["onSave", "false"]
]);
}, handle);
// Context menu for tree
var itemCtxTreePreview = new ui.item({
match: "file",
caption: "Preview",
isAvailable: function() {
return tree.selectedNode && !tree.selectedNode.isFolder
&& (options.local || util.normalizePath(tree.selectedNode.path).charAt(0) != "~");
},
onclick: function() {
openPreview(tree.selected);
}
});
tree.getElement("mnuCtxTree", function(mnuCtxTree) {
ui.insertByIndex(mnuCtxTree, itemCtxTreePreview, 160, handle);
});
// Context menu for settings
mnuSettings = new Menu({}, handle);
// Command
commands.addCommand({
name: "preview",
exec: function(editor, args) {
var path, pane;
var tab = tabs.focussedTab;
function findPane() {
if (!tab) return;
// Find a good location to open preview side-by-side
var pane;
var otherPreview = search();
if (otherPreview && tab.pane != otherPreview) {
pane = otherPreview;
}
else if (args.pane) {
pane = args.pane;
}
else {
var nodes = tab.pane.group;
if (!nodes)
pane = tab.pane.hsplit(true);
else {
pane = nodes[nodes.indexOf(tab.pane) === 0 ? 1 : 0];
}
}
return pane;
}
if (args.server) {
var hostname = c9.hostname || "localhost:8080";
var cb = function(err, stderr, stdout) {
if (err && err.code != 1)
showError("Could not check if server is running.");
else if (stderr || !stdout || !stdout.length) {
// Check for project run config
var json = settings.getJson("project/run/configs") || {};
for (var name in json) {
if (json[name]["default"]) {
commands.exec("run", null, {
callback: function(proc) {
proc.on("started", function() {
setTimeout(done, 1000);
});
}
});
return;
}
}
warnNoServer(hostname);
}
done();
};
function done() {
var path = (options.local ? "http" : "https")
+ "://" + hostname;
if (args.newTab)
return util.openNewWindow(path);
// Open Pane
pane = findPane();
// Open Preview
openPreview(path, pane, args && args.active);
}
if (args.nocheck)
done();
else if (options.local) {
proc.execFile("lsof", {
args: ["-i", ":8080"]
}, cb);
}
else {
proc.execFile("nc", {
args: ["-zv", hostname, "80"]
}, cb);
}
return;
}
else if (args.path) {
path = args.path;
}
else {
if (!tab || (tab.editor.type === "preview" || !tab.path))
return;
pane = findPane();
path = tab.path.replace(/[#?]/g, escape);
}
if (searchTab(path))
return commands.exec("reloadpreview");
// Open Preview
openPreview(path, pane, args && args.active);
}
}, handle);
commands.addCommand({
name: "reloadpreview",
bindKey: { mac: "Command-Enter", win: "Ctrl-Enter" },
isAvailable: function() {
var path = tabs.focussedTab && tabs.focussedTab.path;
var tab = searchTab(path) || searchTab() || searchTab(-1);
return tab ? true : false;
},
exec: function() {
var path = tabs.focussedTab && tabs.focussedTab.path;
var tab = searchTab(path) || searchTab() || searchTab(-1);
if (tab) {
if (tabs.focussedTab && tabs.focussedTab.document.changed) {
save.save(tabs.focussedTab, null, function() {
tab.editor.reload();
});
}
else {
tab.editor.reload();
}
}
}
}, handle);
save.on("afterSave", function(e) {
if (settings.get("user/preview/@onSave") !== "true")
return;
var tab = searchTab(e.path) || searchTab() || searchTab(-1);
tab && tab.editor.reload();
});
menu = new Menu({}, handle);
// Preferences
var key = commands.getHotkey("reloadpreview");
if (commands.platform == "mac")
key = apf.hotkeys.toMacNotation(key);
prefs.add({
"Run": {
position: 600,
"Preview": {
position: 200,
"Preview Running Apps": {
type: "checkbox",
path: "user/preview/@running_app",
position: 400
},
"Default Previewer": {
type: "dropdown",
path: "user/preview/@default",
position: 500,
items: [
// @todo this should come from plugin api
{ caption: "Raw", value: "preview.raw" },
{ caption: "Browser", value: "preview.browser" }
]
},
"When Saving Reload Preview": {
type: "dropdown",
path: "user/preview/@onSave",
position: 600,
items: [
{ caption: "Only on " + key, value: "false" },
{ caption: "Always", value: "true" },
]
}
}
}
}, handle);
}
var drawn = false;
function drawHandle() {
if (drawn) return;
drawn = true;
// Import CSS
var css = require("text!./preview.css");
ui.insertCss(css, options.staticPrefix, handle);
handleEmit.sticky("draw");
}
//Search through pages
function search() {
var pane;
tabs.getTabs().every(function(tab) {
if (tab.editorType == "preview") {
pane = tab.pane;
return false;
}
return true;
});
return pane;
}
function searchTab(path) {
var pane;
tabs.getTabs().every(function(tab) {
if (tab.editorType == "preview"
&& (!path && tab.isActive()
|| path && path != -1
&& path == (tab.document.getSession() || {}).path)) {
pane = tab;
return false;
}
return true;
});
return pane;
}
function registerPlugin(plugin, matcher) {
previewers[plugin.name] = {
plugin: plugin,
matcher: matcher
};
}
function unregisterPlugin(plugin) {
delete previewers[plugin.name];
}
function openPreview(path, pane, active, callback) {
return tabs.open({
name: "preview-" + path,
editorType: "preview",
pane: pane,
active: active !== false,
document: {
title: "[P] " + path,
preview: {
path: path
}
}
}, function(err, tab, done, existing) {
if (existing)
tab.editor.reload();
callback && callback(err, tab);
});
}
function findPreviewer(path, id) {
if (id) return previewers[id].plugin;
else if (path) {
for (id in previewers) {
if (previewers[id].matcher(path))
return previewers[id].plugin;
}
}
id = settings.get("user/preview/@default");
return previewers[id].plugin;
}
function warnNoServer(hostname) {
showAlert("Could not find a server running.",
"No server running at " + hostname,
"Please start your server at " + hostname + " to enable preview "
+ "via this menu. Alternatively you can start a regular preview "
+ "and change the hostname in the location bar.");
}
/**
* The preview handle, responsible for managing preview plugins.
* This is the object you get when you request the preview
* service in your plugin.
*
* Example:
*
* define(function(require, exports, module) {
* main.consumes = ["preview"];
* main.provides = ["myplugin"];
* return main;
*
* function main(options, imports, register) {
* var preview = imports.preview;
*
* var previewer = preview.findPreviewer("preview.browser");
* });
* });
*
* @class preview
* @extends Plugin
* @singleton
*/
handle.freezePublicAPI({
/**
* The base URL for previewing files
* @property {String} previewUrl
*/
get previewUrl() { return previewUrl; },
/**
* The menu shown to select the previewer
* @property {Menu} previewMenu
*/
get previewMenu() { return menu; },
/**
*
*/
get settingsMenu() { return mnuSettings; },
/**
*
*/
openPreview: openPreview,
/**
* Adds a previewer to the list of known previewers.
*
* *N.B. The {@link Previewer} base class already calls this method.*
*
* @param {Previewer} previewer the previewer to register.
* @private
*/
register: registerPlugin,
/**
* Removes a previewer from the list of known previewers.
*
* *N.B. The {@link Previewer} base class already calls this method.*
*
* @param {Previewer} previewer the previewer to unregister.
* @private
*/
unregister: unregisterPlugin,
/**
* Retrieves a previewer based on a file path or id.
* @param {String} path The path of the file that is to be previewed
* @param {String} id The unique name of the previewer to retrieve
* @return {Previewer}
*/
findPreviewer: findPreviewer,
});
handle.on("load", load);
function Preview() {
var plugin = new Editor("Ajax.org", main.consumes, extensions);
var emit = plugin.getEmitter();
var currentDocument, currentSession;
var container, txtPreview, btnMode, btnBack, btnForward;
var btnPopOut, btnSettings;
plugin.on("draw", function(e) {
drawHandle();
var buttons = options.local ? [
btnBack = new ui.button({
skin: "c9-toolbarbutton-glossy",
"class": "goback",
tooltip: "Back",
width: "29",
disabled: true,
onclick: function(e) { goBack(); }
}),
btnForward = new ui.button({
skin: "c9-toolbarbutton-glossy",
"class": "goforward",
tooltip: "Forward",
disabled: true,
width: "29",
onclick: function(e) { goForward(); }
})
] : [];
buttons.push(
new ui.button({
skin: "c9-toolbarbutton-glossy",
"class": "refresh",
tooltip: "Refresh",
width: "29",
onclick: function(e) { reload(); }
})
);
// Create UI elements
var bar = e.tab.appendChild(new ui.vsplitbox({
anchors: "0 0 0 0",
childNodes: [
new ui.hsplitbox({
"class": "toolbar-top previewbar",
height: 35,
edge: "4",
padding: 3,
childNodes: [
new ui.bar({
width: options.local ? 87 : 29,
"class": "fakehbox aligncenter",
childNodes: buttons
}),
new ui.hbox({
padding: 3,
childNodes: [
new ui.bar({
id: "locationbar",
"class": "locationbar",
flex: 1,
childNodes: [
new ui.textbox({
id: "txtPreview",
class: "ace_searchbox tb_textbox searchbox searchTxt tb_console",
value: "",
focusselect: true
}),
new ui.button({
id: "btnMode",
submenu: menu.aml,
icon: "page_white.png",
skin: "btn-switcher",
caption: "browser"
})
]
}),
btnPopOut = new ui.button({
id: "btnPopOut",
skin: "c9-toolbarbutton-glossy",
"class": "popout",
tooltip: "Pop Out Into New Window",
width: "30",
onclick: function(e) { popout(); }
}),
btnSettings = new ui.button({
id: "btnSettings",
skin: "c9-toolbarbutton-glossy",
"class": "settings",
tooltip: "Preview Settings",
width: "30",
submenu: mnuSettings.aml
})
]
})
]
}),
new ui.bar({
id: "container"
})
]
}));
plugin.addElement(bar);
btnMode = plugin.getElement("btnMode");
txtPreview = plugin.getElement("txtPreview");
container = plugin.getElement("container").$int;
txtPreview.$input.onkeydown = function(e) {
if (e.keyCode == 13) {
currentSession.previewer.navigate({ url: this.value });
txtPreview.blur();
}
};
txtPreview.addEventListener("contextmenu", function(e) {
e.cancelBubble = true;
return true;
});
});
/***** Method *****/
function reload() {
var session = currentSession;
if (session)
session.previewer.reload();
}
function popout() {
currentSession.previewer.popout();
}
function setPreviewer(id) {
var session = currentSession;
if (session) {
// Check if previewer is available
if (!previewers[id])
return showError("Could not find previewer:" + id);
// If this previewer is already active, do nothing
if (session.previewer.name == id)
return;
var doc = currentDocument;
var state = plugin.getState(doc);
// Unload the previous previewer
if (session.previewer) {
session.previewer.unloadDocument(doc);
session.cleanUp();
session.destroy && session.destroy();
}
// Enable the new previewer
var previewer = previewers[id].plugin;
session.previewer = previewer;
btnSettings.show();
btnPopOut.show();
previewer.loadDocument(doc, plugin, state);
previewer.activateDocument(doc);
previewer.navigate({ url: session.path });
}
}
function goBack() {
currentSession.previewer.navigate({
url: currentSession.back()
});
updateButtons();
}
function goForward() {
currentSession.previewer.navigate({
url: currentSession.forward()
});
updateButtons();
}
function setLocation(value, visualOnly) {
if (!value || !currentSession) return;
if (!visualOnly) {
var session = currentSession;
var current = session.stack[session.position];
if (current != value)
session.add(value);
}
txtPreview.setValue(value);
updateButtons();
}
function setButtonStyle(caption, icon) {
btnMode.setCaption(caption);
btnMode.setIcon(icon);
}
function updateButtons() {
if (!btnBack) return;
btnBack.setAttribute("disabled", currentSession.position < 1);
btnForward.setAttribute("disabled",
currentSession.position == currentSession.stack.length - 1);
}
/***** Lifecycle *****/
plugin.on("load", function() {
});
plugin.on("documentLoad", function(e) {
var doc = e.doc;
var tab = doc.tab;
var session = doc.getSession();
session.doc = doc;
session.tab = tab;
if (session.inited) {
session.previewer.loadDocument(doc, plugin);
return;
}
function setTheme(e) {
var isDark = e.theme == "dark";
tab.backgroundColor = BGCOLOR[e.theme];
if (isDark) tab.classList.add("dark");
else tab.classList.remove("dark");
}
layout.on("themeChange", setTheme, doc);
setTheme({ theme: settings.get("user/general/@skin") });
// session.path = session.path || e.state.path;
session.initPath = session.path || e.state.path || doc.tab.path;
session.inited = true;
if (e.state.trusted || e.state.trustedPath)
session.trustedPath = e.state.trustedPath || e.state.path;
session.previewer = findPreviewer(session.initPath, (e.state || 0).previewer);
session.previewer.loadDocument(doc, plugin, e.state);
var handler = function(type, e) {
if (currentSession == e.session)
emit(type, {
previewer: session.previewer,
session: session,
url: e.url
});
};
session.previewer.on("navigate", handler.bind(null, "navigate"), session);
session.previewer.on("reload", handler.bind(null, "reload"), session);
session.stack = [];
session.position = -1;
session.add = function(value) {
session.stack.splice(session.position + 1);
session.stack.push(value);
session.position++;
};
session.back = function() {
if (session.position === 0)
return false;
session.position--;
return session.stack[session.position];
};
session.forward = function() {
if (session.position === session.stack.length - 1)
return false;
session.position++;
return session.stack[session.position];
};
tabs.on("open", function(e) {
if (!session.previewTab && e.options && e.options.path == session.path) {
session.previewTab = e.tab;
session.previewer.navigate({ url: session.path, tab: e.tab });
}
}, doc);
});
plugin.on("documentActivate", function(e) {
if (currentDocument)
currentSession.previewer.deactivateDocument(currentDocument);
currentDocument = e.doc;
currentSession = e.doc.getSession();
btnSettings.show();
btnPopOut.show();
var previewer = currentSession.previewer;
previewer.activateDocument(currentDocument);
// @todo shouldn't previewTab be set here?
if (currentSession.initPath) {
previewer.navigate({ url: currentSession.initPath });
delete currentSession.initPath;
}
updateButtons();
});
plugin.on("documentUnload", function(e) {
var doc = e.doc;
var session = doc.getSession();
session.previewer.navigate(doc, true); // Remove the listener
session.previewer.unloadDocument(doc, e);
if (session == currentSession) {
currentDocument = null;
currentSession = null;
}
});
plugin.on("getState", function(e) {
var state = e.state;
var session = e.doc.getSession();
state.path = session.path;
if (!session.previewer)
return;
state.previewer = session.previewer.name;
session.previewer.getState(e.doc, state);
});
plugin.on("setState", function(e) {
var state = e.state;
var session = e.doc.getSession();
session.path = state.path;
// session.previewer = state.previewer;
session.previewer.setState(e.doc, state);
});
plugin.on("clear", function() {
});
plugin.on("focus", function(e) {
if (currentSession)
currentSession.previewer.focus(e);
});
plugin.on("blur", function(e) {
if (currentSession)
currentSession.previewer.blur(e);
});
plugin.on("enable", function() {
});
plugin.on("disable", function() {
});
plugin.on("unload", function() {
// unload all previewers?
});
/***** Register and define API *****/
/**
* Preview pane for previewing files and content in a Cloud9 tab.
*
* There are a few default previewers (i.e.
* {@link preview.browser browser}, {@link preview.raw raw},
* {@link preview.markdown markdown}).
*
* It's easy to make additional previewers. See {@link Previewer}.
*
* Plugins can open a preview tab using the {@link tab} API:
*
* tabManager.open({
* editorType : "preview",
* active : true,
* document : {
* preview : {
* path: "https://c9.io"
* }
* }
* }, function(err, tab) {});
*
* Alternatively, use an urlView to open just the page without
* the browser controls and URL bar:
*
* tabManager.open({
* value : "http://www.c9.io",
* editorType : "urlview",
* active : true,
* document : {
* urlview : {
* backgroundColor : "#FF0000",
* dark : true
* }
* }
* }, function(err, tab) {})
**/
plugin.freezePublicAPI({
/**
* The HTML element to attach your custom previewer to.
* @property {HTMLElement} container
*/
get container() { return container; },
/**
* Trigger a reload of the content displayed in the previewer.
*/
reload: reload,
/**
* Pop the previewer out of the Cloud9 tab into a new window.
* @ignore Not implemented
*/
popout: popout,
/**
* Change to a different previewer for the displayed content.
* @param {String} name The name of the previewer to show (e.g. "previewer.browser").
*/
setPreviewer: setPreviewer,
/**
* Set the value of the location bar of the preview pane.
* @param {String} value The value of the location bar.
*/
setLocation: setLocation,
/**
* Set the icon and label of the button in the preview bar that
* allows users to choose which previewer to use.
* @param {String} caption The caption of the button.
* @param {String} icon The icon of the button.
*/
setButtonStyle: setButtonStyle
});
plugin.load(null, "preview");
return plugin;
}
register(null, {
preview: handle
});
}
});