Template Upload

This commit is contained in:
SOUTHERNCO\x2mjbyrn
2017-05-17 13:45:25 -04:00
parent 415b9c25f3
commit 7efe7605b8
11476 changed files with 2170865 additions and 34 deletions

250
node_modules/browser-sync-ui/lib/UI.js generated vendored Normal file
View File

@ -0,0 +1,250 @@
var fs = require("fs");
var path = require("path");
var config = require("./config");
var eachSeries = require("async-each-series");
var asyncTasks = require("./async-tasks");
var hooks = require("./hooks");
var merge = require("./opts").merge;
var defaultPlugins = {
"sync-options": require("./plugins/sync-options/sync-options.plugin"),
"overview": require("./plugins/overview/overview.plugin"),
"history": require("./plugins/history/history.plugin"),
"plugins": require("./plugins/plugins/plugins.plugin"),
"remote-debug": require("./plugins/remote-debug/remote-debug.plugin"),
"help": require("./plugins/help/help.plugin"),
"connections": require("./plugins/connections/connections.plugin"),
"network-throttle": require("./plugins/network-throttle/network-throttle.plugin")
};
/**
* @param {Object} opts - Any options specifically
* passed to the control panel
* @param {BrowserSync} bs
* @param {EventEmitter} emitter
* @constructor
* @returns {UI}
*/
var UI = function (opts, bs, emitter) {
var ui = this;
ui.bs = bs;
ui.config = config.merge();
ui.events = emitter;
ui.options = merge(opts);
ui.logger = bs.getLogger(ui.config.get("pluginName"));
ui.defaultPlugins = defaultPlugins;
ui.listeners = {};
ui.clients = bs.io.of(bs.options.getIn(["socket", "namespace"]));
ui.socket = bs.io.of(ui.config.getIn(["socket", "namespace"]));
if (ui.options.get("logLevel")) {
ui.logger.setLevel(ui.options.get("logLevel"));
}
/**
*
*/
ui.pluginManager = new bs.utils.easyExtender(defaultPlugins, hooks).init();
/**
* Transform/save data RE: plugins
* @type {*}
*/
ui.bsPlugins = require("./resolve-plugins")(bs.getUserPlugins());
return ui;
};
/**
* Detect an available port
* @returns {UI}
*/
UI.prototype.init = function () {
var ui = this;
eachSeries(
asyncTasks,
taskRunner(ui),
tasksComplete(ui)
);
return this;
};
/**
* @param cb
*/
UI.prototype.getServer = function (cb) {
var ui = this;
if (ui.server) {
return ui.server;
}
this.events.on("ui:running", function () {
cb(null, ui.server);
});
};
/**
* @returns {Array}
*/
UI.prototype.getInitialTemplates = function () {
var prefix = path.resolve(__dirname, "../templates/directives");
return fs.readdirSync(prefix)
.map(function (name) {
return path.resolve(prefix, name);
});
};
/**
* @param event
*/
UI.prototype.delegateEvent = function (event) {
var ui = this;
var listeners = ui.listeners[event.namespace];
if (listeners) {
if (listeners.event) {
listeners.event.call(ui, event);
} else {
if (event.event && listeners[event.event]) {
listeners[event.event].call(ui, event.data);
}
}
}
};
/**
* @param cb
*/
UI.prototype.listen = function (ns, events) {
var ui = this;
if (Array.isArray(ns)) {
ns = ns.join(":");
}
if (!ui.listeners[ns]) {
ui.listeners[ns] = events;
}
};
/**
* @param name
* @param value
* @returns {Map|*}
*/
UI.prototype.setOption = function (name, value) {
var ui = this;
ui.options = ui.options.set(name, value);
return ui.options;
};
/**
* @param path
* @param value
* @returns {Map|*}
*/
UI.prototype.setOptionIn = function (path, value) {
this.options = this.options.setIn(path, value);
return this.options;
};
/**
* @param fn
*/
UI.prototype.setMany = function (fn) {
this.options = this.options.withMutations(fn);
return this.options;
};
/**
* @param path
* @returns {any|*}
*/
UI.prototype.getOptionIn = function (path) {
return this.options.getIn(path);
};
/**
* Run each setup task in sequence
* @param ui
* @returns {Function}
*/
function taskRunner (ui) {
return function (item, cb) {
ui.logger.debug("Starting Step: " + item.step);
/**
* Give each step access to the UI Instance
*/
item.fn(ui, function (err, out) {
if (err) {
return cb(err);
}
if (out) {
handleOut(ui, out);
}
ui.logger.debug("{green:Step Complete: " + item.step);
cb();
});
};
}
/**
* Setup tasks may return options or instance properties to be set
* @param {UI} ui
* @param {Object} out
*/
function handleOut (ui, out) {
if (out.options) {
Object.keys(out.options).forEach(function (key) {
ui.options = ui.options.set(key, out.options[key]);
});
}
if (out.optionsIn) {
out.optionsIn.forEach(function (item) {
ui.options = ui.options.setIn(item.path, item.value);
});
}
if (out.instance) {
Object.keys(out.instance).forEach(function (key) {
ui[key] = out.instance[key];
});
}
}
/**
* All async tasks complete at this point
* @param ui
*/
function tasksComplete (ui) {
return function (err) {
/**
* Log any error according to BrowserSync's Logging level
*/
if (err) {
ui.logger.setOnce("useLevelPrefixes", true).error(err.message || err);
}
/**
* Running event
*/
ui.events.emit("ui:running", {instance: ui, options: ui.options});
/**
* Finally call the user-provided callback
*/
ui.cb(null, ui);
};
}
module.exports = UI;

36
node_modules/browser-sync-ui/lib/async-tasks.js generated vendored Normal file
View File

@ -0,0 +1,36 @@
var async = require("./async");
module.exports = [
{
step: "Setting default plugins",
fn: async.initDefaultHooks
},
{
step: "Finding a free port",
fn: async.findAFreePort
},
{
step: "Setting options also relevant to UI from BS",
fn: async.setBsOptions
},
{
step: "Setting available URLS for UI",
fn: async.setUrlOptions
},
{
step: "Starting the Control Panel Server",
fn: async.startServer
},
{
step: "Add element events",
fn: async.addElementEvents
},
{
step: "Registering default plugins",
fn: async.registerPlugins
},
{
step: "Add options setting event",
fn: async.addOptionsEvent
}
];

204
node_modules/browser-sync-ui/lib/async.js generated vendored Normal file
View File

@ -0,0 +1,204 @@
var Immutable = require("immutable");
var url = require("url");
module.exports = {
/**
* The UI uses it's own server/port
* @param ui
* @param done
*/
findAFreePort: function (ui, done) {
var port = ui.options.get("port");
ui.bs.utils.portscanner.findAPortNotInUse(port, port + 100, {
host: "localhost",
timeout: 1000
}, function (err, port) {
if (err) {
return done(err);
}
done(null, {
options: {
port: port
}
});
});
},
/**
* Default hooks do things like creating/joining JS files &
* building angular config
* @param ui
* @param done
*/
initDefaultHooks: function (ui, done) {
var out = ui.pluginManager.hook("page", ui);
done(null, {
instance: {
clientJs: ui.pluginManager.hook("client:js", ui),
templates: ui.pluginManager.hook("templates", ui.getInitialTemplates(), ui),
pagesConfig: out.pagesConfig,
pages: out.pagesObj,
pageMarkup: out.pageMarkup
}
});
},
setBsOptions: function (ui, done) {
done(null, {
options: {
bs: Immutable.Map({
mode: ui.bs.options.get("mode"),
port: ui.bs.options.get("port")
})
}
});
},
/**
* @param ui
* @param done
*/
setUrlOptions: function (ui, done) {
var port = ui.options.get("port");
var bsUrls = ui.bs.getOptionIn(["urls"]).toJS();
var urls = {
ui: "http://localhost:" + port
};
if (bsUrls.external) {
urls["ui-external"] = ["http://", url.parse(bsUrls.external).hostname, ":", port].join("");
}
done(null, {
options: {
urls: Immutable.fromJS(urls)
}
});
},
/**
* Simple static file server with some middlewares for custom
* scripts/routes.
* @param ui
* @param done
*/
startServer: function (ui, done) {
var bs = ui.bs;
var port = ui.options.get("port");
ui.logger.debug("Using port %s", port);
var server = require("./server")(ui, {
middleware: {
socket: bs.getMiddleware("socket-js"),
connector: bs.getSocketConnector(bs.options.get("port"), {
path: bs.options.getIn(["socket", "path"]),
namespace: ui.config.getIn(["socket", "namespace"])
})
}
});
require('server-destroy')(server.server);
bs.registerCleanupTask(function () {
if (server.server) {
server.server.destroy();
}
if (ui.servers) {
Object.keys(ui.servers).forEach(function (key) {
if (ui.servers[key].server) {
ui.servers[key].server.destroy();
}
});
}
});
done(null, {
instance: {
server: server.server.listen(port),
app: server.app
}
});
},
/**
* Allow an API for adding/removing elements to clients
* @param ui
* @param done
*/
addElementEvents: function (ui, done) {
var elems = ui.pluginManager.hook("elements");
var bs = ui.bs;
if (!Object.keys(elems).length) {
return done();
}
ui.setOption("clientFiles", Immutable.fromJS(elems));
done(null, {
instance: {
enableElement: require("./client-elements").enable(ui.clients, ui, bs),
disableElement: require("./client-elements").disable(ui.clients, ui, bs),
addElement: require("./client-elements").addElement
}
});
},
/**
* Run default plugins
* @param ui
* @param done
*/
registerPlugins: function (ui, done) {
Object.keys(ui.defaultPlugins).forEach(function (key) {
ui.pluginManager.get(key)(ui, ui.bs);
});
done();
},
/**
* The most important event is the initial connection where
* the options are received from the socket
* @param ui
* @param done
*/
addOptionsEvent: function (ui, done) {
var bs = ui.bs;
ui.clients.on("connection", function (client) {
client.emit("ui:connection", ui.options.toJS());
ui.options.get("clientFiles").map(function (item) {
if (item.get("active")) {
ui.addElement(client, item.toJS());
}
});
});
ui.socket.on("connection", function (client) {
client.emit("connection", bs.getOptions().toJS());
client.emit("ui:connection", ui.options.toJS());
client.on("ui:get:options", function () {
client.emit("ui:receive:options", {
bs: bs.getOptions().toJS(),
ui: ui.options.toJS()
});
});
// proxy client events
client.on("ui:client:proxy", function (evt) {
ui.clients.emit(evt.event, evt.data);
});
client.on("ui", function (data) {
ui.delegateEvent(data);
});
});
done();
}
};

94
node_modules/browser-sync-ui/lib/client-elements.js generated vendored Normal file
View File

@ -0,0 +1,94 @@
var fs = require("fs");
const CLIENT_FILES_OPT = "clientFiles";
/**
* Enable a element on clients
* @param clients
* @param ui
* @param bs
* @returns {Function}
*/
var types = {
"css": "text/css",
"js": "application/javascript"
};
function enableElement (clients, ui, bs) {
return function (file) {
var uiItem = ui.getOptionIn([CLIENT_FILES_OPT, file.name]);
var item = uiItem.toJS();
var enableFn = uiItem.getIn(["callbacks", "enable"]);
if (item.active) {
return;
}
ui.setOptionIn([CLIENT_FILES_OPT, item.name, "active"], true, {silent: true});
if (enableFn) {
enableFn.call(ui, item);
}
if (item.file && !item.served) {
ui.setOptionIn([CLIENT_FILES_OPT, item.name, "served"], true, {silent: true});
bs.serveFile(item.src, {
type: types[item.type],
content: fs.readFileSync(item.file)
});
}
addElement(clients, ui.getOptionIn([CLIENT_FILES_OPT, item.name]).toJS());
};
}
/**
* @param clients
* @param ui
* @returns {Function}
*/
function disableElement (clients, ui) {
return function (file) {
var uiItem = ui.getOptionIn([CLIENT_FILES_OPT, file.name]);
var item = uiItem.toJS();
var disableFn = uiItem.getIn(["callbacks", "disable"]);
if (disableFn) {
disableFn.call(ui, item);
}
ui.setOptionIn([CLIENT_FILES_OPT, item.name, "active"], false, {silent: true});
removeElement(clients, item.id);
};
}
/**
* @param clients
* @param item
*/
function addElement (clients, item) {
clients.emit("ui:element:add", item);
}
/**
* @param clients
* @param id
*/
function removeElement(clients, id) {
clients.emit("ui:element:remove", {id: id});
}
module.exports.addElement = addElement;
module.exports.removeElement = removeElement;
module.exports.enable = enableElement;
module.exports.disable = disableElement;

120
node_modules/browser-sync-ui/lib/client-js.js generated vendored Normal file
View File

@ -0,0 +1,120 @@
"use strict";
(function (window, document, bs, undefined) {
var socket = bs.socket;
var uiOptions = {
bs: {}
};
socket.on("ui:connection", function (options) {
uiOptions = options;
bs.socket.emit("ui:history:connected", {
href: window.location.href
});
});
socket.on("ui:element:remove", function (data) {
if (data.id) {
var elem = document.getElementById(data.id);
if (elem) {
removeElement(elem);
}
}
});
socket.on("highlight", function () {
var id = "__browser-sync-highlight__";
var elem = document.getElementById(id);
if (elem) {
return removeElement(elem);
}
(function (e) {
e.style.position = "fixed";
e.style.zIndex = "1000";
e.style.width = "100%";
e.style.height = "100%";
e.style.borderWidth = "5px";
e.style.borderColor = "red";
e.style.borderStyle = "solid";
e.style.top = "0";
e.style.left = "0";
e.setAttribute("id", id);
document.getElementsByTagName("body")[0].appendChild(e);
})(document.createElement("div"));
});
socket.on("ui:element:add", function (data) {
var elem = document.getElementById(data.id);
if (!elem) {
if (data.type === "css") {
return addCss(data);
}
if (data.type === "js") {
return addJs(data);
}
if (data.type === "dom") {
return addDomNode(data);
}
}
});
bs.addDomNode = addDomNode;
bs.addJs = addJs;
bs.addCss = addJs;
function addJs(data) {
(function (e) {
e.setAttribute("src", getAbsoluteUrl(data.src));
e.setAttribute("id", data.id);
document.getElementsByTagName("body")[0].appendChild(e);
})(document.createElement("script"));
}
function addCss(data) {
(function (e) {
e.setAttribute("rel", "stylesheet");
e.setAttribute("type", "text/css");
e.setAttribute("id", data.id);
e.setAttribute("media", "all");
e.setAttribute("href", getAbsoluteUrl(data.src));
document.getElementsByTagName("head")[0].appendChild(e);
})(document.createElement("link"));
}
function addDomNode(data) {
var elem = document.createElement(data.tagName);
for (var attr in data.attrs) {
elem.setAttribute(attr, data.attrs[attr]);
}
if (data.placement) {
document.getElementsByTagName(data.placement)[0].appendChild(elem);
} else {
document.getElementsByTagName("body")[0].appendChild(elem);
}
return elem;
}
function removeElement(element) {
if (element && element.parentNode) {
element.parentNode.removeChild(element);
}
}
function getAbsoluteUrl(path) {
if (path.match(/^h/)) {
return path;
}
return [window.location.protocol, "//", getHost(), path].join("");
}
function getHost () {
return uiOptions.bs.mode === "snippet" ? window.location.hostname + ":" + uiOptions.bs.port : window.location.host;
}
})(window, document, ___browserSync___);

40
node_modules/browser-sync-ui/lib/config.js generated vendored Normal file
View File

@ -0,0 +1,40 @@
var Immutable = require("immutable");
/**
* Any configurable paths/config
* @type {{pluginName: string, indexPage: string, socketJs: string, appJs: string, connector: string}}
*/
var defaults = {
pluginName: "UI",
indexPage: "/index.html",
socketJs: "/js/vendor/socket.js",
appJs: "/js/dist/app.js",
app: "/app.js",
appExtraJs: "/js/app-extra.js",
connector: "/js/connector.js",
pagesConfig: "/js/pages-config.js",
public: {
svg: "/img/icons/icons.svg",
css: "/css/core.min.css"
},
clientJs: "/lib/client-js.js",
socket: {
namespace: "/browser-sync-cp"
},
components: {
header: "/components/header.html",
footer: "/components/footer.html"
}
};
module.exports.defaults = defaults;
/**
* @param [userConfig]
* @returns {Map}
*/
module.exports.merge = function (userConfig) {
return Immutable
.fromJS(defaults)
.mergeDeep(userConfig);
};

86
node_modules/browser-sync-ui/lib/directive-stripper.js generated vendored Normal file
View File

@ -0,0 +1,86 @@
var tokenize = require("html-tokenize");
var through2 = require("through2");
var vinyl = require("vinyl");
var select = require("html-select");
/**
* @param config
* @param item
* @param markup
* @param done
*/
function directiveStripper(config, item, markup, done) {
var replacer = getReplacer(item, config);
var chunks = [];
new vinyl({
contents: new Buffer(markup)
})
.pipe(tokenize())
.pipe(replacer)
.pipe(through2.obj(function (row, buf, next) {
chunks.push(row[1]);
next();
}, function () {
done(null, chunks.join(""));
}));
replacer.resume();
}
/**
* @param name
* @param item
* @returns {*|exports}
*/
function getReplacer (name, markup) {
return select(name, function (e) {
var tr = through2.obj(function (row, buf, next) {
if (row[0] === "open") {
this.push([row[0], directive(name, String(row[1]), markup)]);
} else {
this.push([ row[0], "" ]);
}
next();
});
tr.pipe(e.createStream()).pipe(tr);
});
}
/**
* @param name
* @param content
* @param item
* @returns {*|string}
*/
function directive (name, content, item) {
var angularDir;
try {
angularDir = require("../src/scripts/directives/" + name)();
} catch (e) {
console.log("Directive not found, cannot re-use");
return content;
}
var scope = item;
scope = angularDir.link(scope, {}, {});
return angularDir.template.replace(/\{\{(.+?)\}\}/, function ($1, $2) {
if ($2 in scope) {
return scope[$2];
}
return $1;
});
}
module.exports.getReplacer = getReplacer;
module.exports.directive = directive;
module.exports.directiveStripper = directiveStripper;

271
node_modules/browser-sync-ui/lib/hooks.js generated vendored Normal file
View File

@ -0,0 +1,271 @@
var fs = require("fs");
var path = require("path");
var pluginTmpl = templateFile("/plugin.tmpl");
var configTmpl = templateFile("/config.tmpl");
var configItem = templateFile("/config.item.tmpl");
var inlineTemp = templateFile("/inline.template.tmpl");
var pluginItemTmpl = fs.readFileSync(path.resolve(__dirname, "../", "templates/plugin.item.tmpl"), "utf-8");
function templateFile (filepath) {
return fs.readFileSync(path.join(__dirname, "/../templates", filepath || ""), "utf-8");
}
/**
* @type {{page: Function, markup: Function, client:js: Function, templates: Function}}
*/
module.exports = {
/**
* Create the url config for each section of the ui
* @param hooks
* @param ui
*/
"page": function (hooks, ui) {
var config = hooks
.map(transformConfig)
.reduce(createConfigItem, {});
return {
/**
* pagesConfig - This is the angular configuration such as routes
*/
pagesConfig: configTmpl
.replace("%when%", hooks.reduce(
createAngularRoutes,
""
))
.replace("%pages%", JSON.stringify(
config,
null,
4
)),
/**
* pagesConfig in object form
*/
pagesObj: config,
pageMarkup: function () {
return preAngular(ui.pluginManager.plugins, config, ui);
}
};
},
/**
* Controller markup for each plugin
* @param hooks
* @returns {*}
*/
"markup": function (hooks) {
return hooks.reduce(pluginTemplate, "");
},
/**
* @param hooks
* @param {UI} ui
* @returns {*|string}
*/
"client:js": function (hooks, ui) {
/**
* Add client JS from Browsersync Plugins
*/
ui.bsPlugins.forEach(function (plugin) {
if (plugin.has("client:js")) {
plugin.get("client:js").forEach(function (value) {
hooks.push(value);
});
}
});
var out = hooks.reduce(function (all, item) {
if (typeof item === "string") {
all += ";" + item;
} else if (Array.isArray(item)) {
item.forEach(function (item) {
all += ";" + item;
});
}
return all;
}, "");
return out;
},
/**
* @param hooks
* @param initial
* @param {UI} ui
* @returns {String}
*/
"templates": function (hooks, initial, ui) {
/**
* Add templates from each Browsersync registered plugin
* @type {string}
*/
var pluginDirectives = ui.bsPlugins.reduce(function (all, plugin) {
if (!plugin.has("templates")) {
return all;
}
/**
* Slugify-ish the plugin name
* eg: Test Browsersync Plugin
* = test-browsersync-plugin
* @type {string}
*/
var slug = plugin.get("name")
.trim()
.split(" ")
.map(function (word) {
return word.trim().toLowerCase();
})
.join("-");
/**
* For every plugin that has templates, wrap
* the markup in the <script type="text/ng-template" id="{{slug}}"></script>
* markup to result in the single output string.
*/
plugin.get("templates").forEach(function (value, key) {
all += angularWrap([slug, path.basename(key)].join("/"), value);
});
return all;
}, "");
/**
* Combine the markup from the plugins done above with any
* others registered via hooks + initial
* to create the final markup
*/
return [pluginDirectives, createInlineTemplates(hooks.concat([initial]))].join("");
},
/**
* Allow plugins to register toggle-able elements
* @param hooks
* @returns {{}}
*/
"elements": function (hooks) {
var obj = {};
hooks.forEach(function (elements) {
elements.forEach(function (item) {
if (!obj[item.name]) {
obj[item.name] = item;
}
});
});
return obj;
}
};
/**
* @param hooks
* @returns {String}
*/
function createInlineTemplates (hooks) {
return hooks.reduce(function (combined, item) {
return combined + item.reduce(function (all, filepath) {
return all + angularWrap(
path.basename(filepath),
fs.readFileSync(filepath));
}, "");
}, "");
}
/**
* @param item
* @returns {*}
*/
function transformConfig (item) {
return item;
}
/**
* @param {String} all
* @param {Object} item
* @returns {*}
*/
function createAngularRoutes(all, item) {
return all + configItem.replace(/%(.+)%/g, function () {
var key = arguments[1];
if (item[key]) {
return item[key];
}
});
}
/**
* @param joined
* @param item
* @returns {*}
*/
function createConfigItem (joined, item) {
if (item.path === "/") {
joined["overview"] = item;
} else {
joined[item.path.slice(1)] = item;
}
return joined;
}
/**
* @returns {*}
*/
function pluginTemplate (combined, item) {
return [combined, pluginTmpl.replace("%markup%", item)].join("\n");
}
/**
* @param plugins
* @param config
* @returns {*}
*/
function preAngular (plugins, config, ui) {
return Object.keys(plugins)
.filter(function (key) {
return config[key]; // only work on plugins that have pages
})
.map(function (key) {
if (key === "plugins") {
var pluginMarkup = ui.bsPlugins.reduce(function (all, item, i) {
all += pluginItemTmpl
.replace("%content%", item.get("markup") || "")
.replace(/%index%/g, i)
.replace(/%name%/g, item.get("name"));
return all;
}, "");
plugins[key].hooks.markup = plugins[key].hooks.markup.replace("%pluginlist%", pluginMarkup);
}
return angularWrap(config[key].template, bindOnce(plugins[key].hooks.markup, config[key]));
})
.reduce(function (combined, item) {
return combined + item;
}, "");
}
/**
* @param templateName
* @param markup
* @returns {*}
*/
function angularWrap (templateName, markup) {
return inlineTemp
.replace("%content%", markup)
.replace("%id%", templateName);
}
/**
* @param markup
* @param config
* @returns {*|string}
*/
function bindOnce (markup, config) {
return markup.toString().replace(/\{\{ctrl.section\.(.+?)\}\}/g, function ($1, $2) {
return config[$2] || "";
});
}
module.exports.bindOnce = bindOnce;

31
node_modules/browser-sync-ui/lib/opts.js generated vendored Normal file
View File

@ -0,0 +1,31 @@
var Immutable = require("immutable");
var defaults = Immutable.fromJS({
port: 3001,
weinre: {
port: 8080
}
});
/**
* @param {Object} obj
* @returns {Map}
*/
module.exports.merge = function (obj) {
return defaults.mergeDeep(Immutable.fromJS(obj));
};
/**
* @param {Immutable.Map} obj
* @returns {*}
*/
//function transformOptions(obj) {
//
// var out;
//
// Object.keys(transforms).forEach(function (key) {
// out = obj.set(key, transforms[key](obj));
// });
//
// return out;
//}

View File

@ -0,0 +1,69 @@
(function (angular) {
const SECTION_NAME = "connections";
angular
.module("BrowserSync")
.controller("ConnectionsController", [
"pagesConfig",
ConnectionsControllers
]);
/**
* @param pagesConfig
* @constructor
*/
function ConnectionsControllers(pagesConfig) {
var ctrl = this;
ctrl.section = pagesConfig[SECTION_NAME];
}
angular
.module("BrowserSync")
.directive("connectionList", function () {
return {
restrict: "E",
scope: {
options: "="
},
templateUrl: "connections.directive.html",
controller: ["$scope", "Clients", "Socket", connectionListDirective],
controllerAs: "ctrl"
};
});
/**
* Controller for the URL sync
* @param $scope - directive scope
* @param Clients
* @param Socket
*/
function connectionListDirective($scope, Clients, Socket) {
var ctrl = this;
ctrl.connections = [];
ctrl.update = function (data) {
ctrl.connections = data;
$scope.$digest();
};
// Always try to retreive the sockets first time.
Socket.getData("clients").then(function (data) {
ctrl.connections = data;
});
// Listen to events to update the list on the fly
Socket.on("ui:connections:update", ctrl.update);
$scope.$on("$destroy", function () {
Socket.off("ui:connections:update", ctrl.update);
});
ctrl.highlight = function (connection) {
Clients.highlight(connection);
};
}
})(angular);

View File

@ -0,0 +1,10 @@
<ul bs-list="basic" ng-show="ctrl.connections" id="bs-connection-list">
<li ng-repeat="connection in ctrl.connections track by connection.id">
<p>{{connection.browser.name}} - ({{connection.browser.version}})</p>
<!--<span bs-multi-controls="right">
<a href="#" ng-click="highlight(connection)" bs-button>
<svg bs-svg-icon><use xlink:href="#svg-target"></use></svg> Highlight
</a>
</span>-->
</li>
</ul>

View File

@ -0,0 +1,18 @@
<div bs-panel="controls outline">
<h1 bs-heading><icon icon="{{section.icon}}"></icon> {{section.title}}</h1>
</div>
<div bs-panel ng-if="!ui.connections.length">
<div bs-panel-content="basic">
<p>Connected devices/browsers will be listed here. If you are not seeing your device in the list,
it's probably because the Browsersync script tag is not being loaded on your page.</p>
<p>
Browsersync works by injecting an asynchronous script tag (<code>&lt;script async&gt;...&lt;/script&gt;</code>) right after the &lt;body&gt; tag during initial request. In order for this to work properly the &lt;body&gt; tag must be present. Alternatively you can provide a custom rule for the snippet using snippetOptions
</p>
</div>
</div>
<div bs-skinny>
<connection-list ng-if="ui.connections"
options="options"
connections="ui.connections"></connection-list>
</div>

View File

@ -0,0 +1,45 @@
var connections = require("./lib/connections");
const PLUGIN_NAME = "Connections";
/**
* @type {{plugin: Function, plugin:name: string, markup: string}}
*/
module.exports = {
/**
* @param {UI} ui
* @param {BrowserSync} bs
*/
"plugin": function (ui, bs) {
connections.init(ui, bs);
},
/**
* Hooks
*/
"hooks": {
"client:js": fileContent("/connections.client.js"),
"templates": [
getPath("/connections.directive.html")
]
},
/**
* Plugin name
*/
"plugin:name": PLUGIN_NAME
};
/**
* @param filepath
* @returns {*}
*/
function getPath (filepath) {
return require("path").join(__dirname, filepath);
}
/**
* @param filepath
* @returns {*}
*/
function fileContent (filepath) {
return require("fs").readFileSync(getPath(filepath), "utf-8");
}

View File

@ -0,0 +1,132 @@
var Immutable = require("immutable");
/**
* Track connected clients
* @param {UI} ui
* @param {BrowserSync} bs
*/
module.exports.init = function (ui, bs) {
var uaParser = new bs.utils.UAParser();
var currentConnections = [];
ui.clients.on("connection", function (client) {
client.on("client:heartbeat", function (data) {
var match;
if (currentConnections.some(function (item, index) {
if (item.id === client.id) {
match = index;
return true;
}
return false;
})) {
if (typeof match === "number") {
currentConnections[match].timestamp = new Date().getTime();
currentConnections[match].data = data;
}
} else {
currentConnections.push({
id: client.id,
timestamp: new Date().getTime(),
browser: uaParser.setUA(client.handshake.headers["user-agent"]).getBrowser(),
data: data
});
}
});
});
var registry;
var temp;
var initialSent;
var int = setInterval(function () {
var sockets = ui.clients.sockets;
var keys = Object.keys(sockets);
if (keys.length) {
temp = Immutable.List(keys.map(function (clientKey) {
var currentClient = sockets[clientKey];
return Immutable.fromJS({
id: currentClient.id,
browser: uaParser.setUA(currentClient.handshake.headers["user-agent"]).getBrowser()
});
}));
if (!registry) {
registry = temp;
sendUpdated(ui.socket, decorateClients(registry.toJS(), currentConnections));
} else {
if (Immutable.is(registry, temp)) {
if (!initialSent) {
sendUpdated(ui.socket, decorateClients(registry.toJS(), currentConnections));
initialSent = true;
}
} else {
registry = temp;
sendUpdated(ui.socket, decorateClients(registry.toJS(), currentConnections));
}
}
} else {
sendUpdated(ui.socket, []);
}
}, 1000);
bs.registerCleanupTask(function () {
clearInterval(int);
});
};
/**
* Use heart-beated data to decorate clients
* @param clients
* @param clientsInfo
* @returns {*}
*/
function decorateClients(clients, clientsInfo) {
return clients.map(function (item) {
clientsInfo.forEach(function (client) {
if (client.id === item.id) {
item.data = client.data;
return false;
}
});
return item;
});
}
/**
* @param socket
* @param connectedClients
*/
function sendUpdated(socket, connectedClients) {
socket.emit("ui:connections:update", connectedClients);
}
/**
* @param clients
* @param data
*/
//function highlightClient (clients, data) {
// var socket = getClientById(clients, data.id);
// if (socket) {
// socket.emit("highlight");
// }
//}
/**
* @param clients
* @param id
*/
//function getClientById (clients, id) {
// var match;
// clients.sockets.some(function (item, i) {
// if (item.id === id) {
// match = clients.sockets[i];
// return true;
// }
// });
// return match;
//}

View File

@ -0,0 +1,24 @@
(function (angular) {
const SECTION_NAME = "history";
angular
.module("BrowserSync")
.controller("HelpAboutController", [
"options",
"pagesConfig",
helpAboutController
]);
/**
* @param options
* @param pagesConfig
*/
function helpAboutController(options, pagesConfig) {
var ctrl = this;
ctrl.options = options.bs;
ctrl.section = pagesConfig[SECTION_NAME];
}
})(angular);

View File

View File

@ -0,0 +1,8 @@
<div bs-panel="controls outline">
<h1 bs-heading><icon icon="{{ctrl.section.icon}}"></icon> {{ctrl.section.title}}</h1>
</div>
<div bs-panel id="bs-help">
<div bs-panel-content="basic">
<p>Help page</p>
</div>
</div>

View File

@ -0,0 +1,49 @@
const PLUGIN_NAME = "Help / About";
/**
* @type {{plugin: Function, plugin:name: string, markup: string}}
*/
module.exports = {
/**
* Plugin init
*/
"plugin": function () {},
/**
* Hooks
*/
"hooks": {
"markup": fileContent("/../../../static/content/help.content.html"),
"client:js": fileContent("/help.client.js"),
"templates": [
getPath("/help.directive.html")
],
"page": {
path: "/help",
title: PLUGIN_NAME,
template: "help.html",
controller: "HelpAboutController",
order: 6,
icon: "help"
}
},
/**
* Plugin name
*/
"plugin:name": PLUGIN_NAME
};
/**
* @param filepath
* @returns {*}
*/
function getPath (filepath) {
return require("path").join(__dirname, filepath);
}
/**
* @param filepath
* @returns {*}
*/
function fileContent (filepath) {
return require("fs").readFileSync(getPath(filepath), "utf-8");
}

View File

@ -0,0 +1,111 @@
(function (angular) {
const SECTION_NAME = "history";
angular
.module("BrowserSync")
.controller("HistoryController", [
"$scope",
"options",
"History",
"pagesConfig",
historyController
]);
/**
* @param $scope
* @param options
* @param History
* @param pagesConfig
*/
function historyController($scope, options, History, pagesConfig) {
var ctrl = this;
ctrl.options = options.bs;
ctrl.section = pagesConfig[SECTION_NAME];
ctrl.visited = [];
ctrl.update = function (items) {
ctrl.visited = items;
$scope.$digest();
};
History.get().then(function (items) {
ctrl.visited = items;
});
History.on("change", ctrl.update);
$scope.$on("$destroy", function () {
History.off(ctrl.update);
});
ctrl.clearVisited = function () {
History.clear();
};
}
angular
.module("BrowserSync")
.directive("historyList", function () {
return {
restrict: "E",
scope: {
options: "=",
visited: "="
},
templateUrl: "history.directive.html",
controller: ["$scope", "History", "Clients", historyDirective],
controllerAs: "ctrl"
};
});
/**
* Controller for the URL sync
* @param $scope - directive scope
* @param History
* @param Clients
*/
function historyDirective($scope, History, Clients) {
var ctrl = this;
ctrl.visited = [];
ctrl.utils = {};
ctrl.utils.localUrl = function (path) {
return [$scope.options.urls.local, path].join("");
};
ctrl.updateVisited = function (data) {
ctrl.visited = data;
$scope.$digest();
};
ctrl.sendAllTo = function (url) {
url.success = true;
Clients.sendAllTo(url.path);
setTimeout(function () {
url.success = false;
$scope.$digest();
}, 1000);
};
ctrl.removeVisited = function (item) {
History.remove(item);
};
History.get().then(function (items) {
ctrl.visited = items;
});
History.on("change", ctrl.updateVisited);
$scope.$on("$destroy", function () {
History.off(ctrl.updateVisited);
});
}
})(angular);

View File

@ -0,0 +1,20 @@
<ul bs-list="bordered inline-controls" ng-if="ctrl.visited" id="bs-history-list">
<li ng-repeat="url in ctrl.visited track by $index">
<p>{{url.path}}</p>
<div bs-button-group>
<new-tab url="{{ctrl.utils.localUrl(url.path)}}" mode="options.mode"></new-tab>
<a href="#"
title="Sync all devices to this address."
bs-button="subtle-alt icon-left"
ng-click="ctrl.sendAllTo(url)"
ng-class="{success: url.success}"
>
<icon icon="circle-ok" bs-state="success"></icon>
<icon icon="syncall" bs-state="default"></icon> Sync all
</a>
<a href="#" bs-button="subtle-alt icon" bs-remove ng-click="ctrl.removeVisited(url)">
<icon icon="bin"></icon>
</a>
</div>
</li>
</ul>

View File

@ -0,0 +1,16 @@
<div bs-panel="controls outline">
<h1 bs-heading><icon icon="{{ctrl.section.icon}}"></icon> {{ctrl.section.title}}</h1>
</div>
<div bs-button-row ng-if="ctrl.visited.length">
<button bs-button="icon-left inline" ng-click="ctrl.clearVisited()" ng-show="ctrl.visited.length">
<svg bs-svg-icon><use xlink:href="#svg-bin"></use></svg>
Clear all
</button>
</div>
<div bs-panel ng-if="!ctrl.visited.length" id="bs-history-empty">
<div bs-panel-content="basic">
<p>Pages you navigate to will appear here - making it easy
to sync all devices to a specific page</p>
</div>
</div>
<history-list options="ctrl.options"></history-list>

View File

@ -0,0 +1,132 @@
var url = require("url");
var Immutable = require("immutable");
module.exports.init = function (ui, bs) {
var validUrls = Immutable.OrderedSet();
var methods = {
/**
* Send the url list to UI
* @param urls
*/
sendUpdatedUrls: function (urls) {
ui.socket.emit("ui:history:update", decorateUrls(urls));
},
/**
* Only send to UI if list changed
* @param current
* @param temp
*/
sendUpdatedIfChanged: function (current, temp) {
if (!Immutable.is(current, temp)) {
validUrls = temp;
methods.sendUpdatedUrls(validUrls);
}
},
/**
* Send all clients to a URL - this is a proxy
* in case we need to limit/check anything.
* @param data
*/
sendToUrl: function (data) {
var parsed = url.parse(data.path);
data.override = true;
data.path = parsed.path;
data.url = parsed.href;
ui.clients.emit("browser:location", data);
},
/**
* Add a new path
* @param data
*/
addPath: function (data) {
var temp = addPath(validUrls, url.parse(data.href), bs.options.get("mode"));
methods.sendUpdatedIfChanged(validUrls, temp, ui.socket);
},
/**
* Remove a path
* @param data
*/
removePath: function (data) {
var temp = removePath(validUrls, data.path);
methods.sendUpdatedIfChanged(validUrls, temp, ui.socket);
},
/**
* Get the current list
*/
getVisited: function () {
ui.socket.emit("ui:receive:visited", decorateUrls(validUrls));
}
};
ui.clients.on("connection", function (client) {
client.on("ui:history:connected", methods.addPath);
});
ui.socket.on("connection", function (uiClient) {
/**
* Send urls on first connection
*/
uiClient.on("ui:get:visited", methods.getVisited);
methods.sendUpdatedUrls(validUrls);
});
ui.listen("history", {
"sendAllTo": methods.sendToUrl,
"remove": methods.removePath,
"clear": function () {
validUrls = Immutable.OrderedSet([]);
methods.sendUpdatedUrls(validUrls);
}
});
return methods;
};
/**
* @param {Immutable.Set} urls
* @returns {Array}
*/
function decorateUrls (urls) {
var count = 0;
return urls.map(function (value) {
count += 1;
return {
path: value,
key: count
};
}).toJS().reverse();
}
/**
* If snippet mode, add the full URL
* if server/proxy, add JUST the path
* @param immSet
* @param urlObj
* @param mode
* @returns {Set}
*/
function addPath(immSet, urlObj, mode) {
return immSet.add(
mode === "snippet"
? urlObj.href
: urlObj.path
);
}
module.exports.addPath = addPath;
/**
* @param immSet
* @param urlPath
* @returns {*}
*/
function removePath(immSet, urlPath) {
return immSet.remove(url.parse(urlPath).path);
}
module.exports.removePath = removePath;

View File

@ -0,0 +1,54 @@
var historyPlugin = require("./history");
const PLUGIN_NAME = "History";
/**
* @type {{plugin: Function, plugin:name: string, markup: string}}
*/
module.exports = {
/**
* @param ui
* @param bs
*/
"plugin": function (ui, bs) {
ui.history = historyPlugin.init(ui, bs);
},
/**
* Hooks
*/
"hooks": {
"markup": fileContent("history.html"),
"client:js": fileContent("/history.client.js"),
"templates": [
getPath("/history.directive.html")
],
"page": {
path: "/history",
title: PLUGIN_NAME,
template: "history.html",
controller: PLUGIN_NAME + "Controller",
order: 3,
icon: "list2"
}
},
/**
* Plugin name
*/
"plugin:name": PLUGIN_NAME
};
/**
* @param filepath
* @returns {*}
*/
function getPath (filepath) {
return require("path").join(__dirname, filepath);
}
/**
* @param filepath
* @returns {*}
*/
function fileContent (filepath) {
return require("fs").readFileSync(getPath(filepath), "utf-8");
}

View File

@ -0,0 +1,201 @@
(function (angular) {
const SECTION_NAME = "network-throttle";
angular
.module("BrowserSync")
.controller("NetworkThrottleController", [
"options",
"pagesConfig",
"Socket",
"$scope",
NetworkThrottleController
]);
/**
* @param options
* @param pagesConfig
* @param Socket
* @param $scope
*/
function NetworkThrottleController (options, pagesConfig, Socket, $scope) {
var ctrl = this;
ctrl.section = pagesConfig[SECTION_NAME];
ctrl.options = options.bs;
ctrl.uiOptions = options.ui;
ctrl.clientFiles = options.ui.clientFiles || {};
ctrl.section = pagesConfig[SECTION_NAME];
ctrl.throttle = ctrl.uiOptions[SECTION_NAME];
ctrl.selected = ctrl.throttle.targets[0].id;
ctrl.servers = ctrl.throttle.servers;
ctrl.port = "";
ctrl.portEntry = "auto";
ctrl.serverCount = Object.keys(ctrl.servers).length;
ctrl.blurs = [];
ctrl.state = {
success: false,
waiting: false,
classname: "ready"
};
ctrl.createServer = function (selected, event) {
if (ctrl.blurs.indexOf(event.target) === -1) {
ctrl.blurs.push(event.target);
}
var item = getByProp(ctrl.throttle.targets, "id", ctrl.selected);
if (ctrl.portEntry === "auto") {
return send("");
}
if (!ctrl.port || !ctrl.port.length) {
setError();
return;
}
if (!ctrl.port.match(/\d{4,5}/)) {
setError();
return;
}
var port = parseInt(ctrl.port, 10);
if (port < 1024 || port > 65535) {
setError();
return;
}
send(ctrl.port);
function setError() {
ctrl.state.waiting = false;
ctrl.state.portError = true;
}
function send (port) {
ctrl.state.classname = "waiting";
ctrl.state.waiting = true;
Socket.uiEvent({
namespace: SECTION_NAME,
event: "server:create",
data: {
speed: item,
port: port
}
});
}
};
ctrl.destroyServer = function (item, port) {
Socket.uiEvent({
namespace: SECTION_NAME,
event: "server:destroy",
data: {
speed: item,
port: port
}
});
};
ctrl.toggleSpeed = function (item) {
if (!item.active) {
item.urls = [];
}
};
ctrl.update = function (data) {
ctrl.servers = data.servers;
ctrl.serverCount = Object.keys(ctrl.servers).length;
if (data.event === "server:create") {
updateButtonState();
}
$scope.$digest();
};
function updateButtonState() {
ctrl.state.success = true;
ctrl.state.classname = "success";
setTimeout(function () {
ctrl.blurs.forEach(function (elem) {
elem.blur();
});
setTimeout(function () {
ctrl.state.success = false;
ctrl.state.waiting = false;
ctrl.state.classname = "ready";
$scope.$digest();
}, 500);
}, 300);
}
/**
* @param collection
* @param prop
* @returns {*}
*/
function getByProp (collection, prop, name) {
var match = collection.filter(function (item) {
return item[prop] === name;
});
if (match.length) {
return match[0];
}
return false;
}
Socket.on("ui:network-throttle:update", ctrl.update);
$scope.$on("$destroy", function () {
Socket.off("ui:network-throttle:update", ctrl.update);
});
}
/**
* Display the snippet when in snippet mode
*/
angular
.module("BrowserSync")
.directive("throttle", function () {
return {
restrict: "E",
replace: true,
scope: {
"target": "=",
"options": "="
},
templateUrl: "network-throttle.directive.html",
controller: ["$scope", "Socket", throttleDirectiveControlller],
controllerAs: "ctrl"
};
});
/**
* @param $scope
*/
function throttleDirectiveControlller ($scope) {
var ctrl = this;
ctrl.throttle = $scope.options[SECTION_NAME];
}
})(angular);

View File

@ -0,0 +1,12 @@
<section bs-panel-content>
<div ng-if="target.active">
<p ng-if="!target.urls.length">
Creating a throttled server, please wait...
</p>
<div ng-if="target.urls.length">
<ul bs-list>
<li ng-repeat="url in target.urls"><a href="{{url}}">{{url}}</a></li>
</ul>
</div>
</div>
</section>

View File

@ -0,0 +1,93 @@
<article>
<div bs-panel="controls outline">
<h1 bs-heading>
<icon icon="{{ctrl.section.icon}}"></icon>
{{ctrl.section.title}}
</h1>
</div>
<div bs-panel="no-border" ng-if="ctrl.options.mode === 'snippet'">
<div bs-panel-content="basic">
<p class="lede">Sorry, Network Throttling is only available in Server or Proxy mode.</p>
</div>
</div>
<div bs-panel="no-border" ng-if="ctrl.options.mode !== 'snippet'">
<div bs-panel-content="basic">
<div bs-inputs bs-grid="wide-3 desk-2">
<div bs-grid-item>
<p bs-label-heading>Speed</p>
<div bs-input="inline" ng-repeat="(key, item) in ctrl.throttle.targets | orderObjectBy:'order'">
<input
type="radio"
id="speed-{{item.id}}"
checked name="speed"
ng-model="ctrl.selected"
value="{{item.id}}">
<label for="speed-{{item.id}}" bs-input-label="light">{{item.title}}</label>
</div>
</div>
<div bs-grid-item>
<p bs-label-heading>Port</p>
<div bs-input="text">
<div bs-input="inline">
<input type="radio" name="port-select" id="port-auto" checked value="auto"
ng-model="ctrl.portEntry">
<label for="port-auto" bs-input-label="light">Auto Detection</label>
</div>
<div bs-input="inline">
<input type="radio" id="port-manual" name="port-select" value="manual" ng-model="ctrl.portEntry">
<label for="port-manual" bs-input-label="light">User specified <span ng-if="ctrl.state.portError">(between
1024 & 65535)</span></label>
</div>
<input id="server-port"
type="text"
value=""
placeholder="Eg: 1024"
ng-model="ctrl.port"
ng-focus="ctrl.portEntry = 'manual'"
custom-validation>
</div>
<br/>
<div ng-class="[ctrl.state.classname]" bs-state-wrapper>
<button
id="create-server"
bs-button="size-small subtle-alt icon-left"
ng-click="ctrl.createServer(ctrl.selected, $event)"
ng-disabled="ctrl.state.waiting"
>
<icon icon="circle-plus"></icon>
Create Server
</button>
<div bs-state-icons>
<icon icon="circle-ok" bs-state="success inline"></icon>
<icon icon="circle-minus" bs-state="waiting inline" bs-anim="spin"></icon>
</div>
</div>
</div>
<div bs-grid-item>
</div>
</div>
</div>
<br/>
<div bs-panel-content="basic">
<h3 ng-if="ctrl.serverCount">Your Servers:</h3>
<h3 ng-if="!ctrl.serverCount">Your Servers will appear here...</h3>
</div>
<ul bs-list="bordered inline-controls" bs-offset="basic" id="throttle-server-list">
<li ng-repeat="(key, item) in ctrl.servers track by key">
<p bs-width="5">{{$index + 1}}.</p>
<p bs-width="10"><b>{{item.speed.id | uppercase}}</b></p>
<p><a href="{{item.urls[0]}}">{{item.urls[0]}}</a></p>
<p><a href="{{item.urls[1]}}">{{item.urls[1]}}</a></p>
<div bs-button-group>
<button href="#" bs-button="subtle-alt icon" ng-click="ctrl.destroyServer(item, key)">
<svg bs-svg-icon><use xlink:href="#svg-bin"></use></svg>
</button>
</div>
</li>
</ul>
</div>
</article>

View File

@ -0,0 +1,159 @@
var Immutable = require("immutable");
module.exports.init = function (ui) {
var optPath = ["network-throttle"];
var serverOptPath = optPath.concat(["servers"]);
ui.servers = {};
ui.setOptionIn(optPath, Immutable.fromJS({
name: "network-throttle",
title: "Network Throttle",
active: false,
targets: require("./targets")
}));
ui.setOptionIn(serverOptPath, Immutable.Map({}));
/**
* @param input
* @returns {number}
*/
function getPortArg(input) {
input = input.trim();
if (input.length && input.match(/\d{3,5}/)) {
input = parseInt(input, 10);
} else {
input = ui.bs.options.get("port") + 1;
}
return input;
}
/**
* @returns {string}
*/
function getTargetUrl() {
return require("url").parse(ui.bs.options.getIn(["urls", "local"]));
}
var methods = {
/**
* @param data
*/
"server:create": function (data) {
data.port = getPortArg(data.port);
data.cb = data.cb || function () { /* noop */};
/**
* @param opts
*/
function saveThrottleInfo (opts) {
var urls = getUrls(ui.bs.options.set("port", opts.port).toJS());
ui.setOptionIn(serverOptPath.concat([opts.port]), Immutable.fromJS({
urls: urls,
speed: opts.speed
}));
setTimeout(function () {
ui.socket.emit("ui:network-throttle:update", {
servers: ui.getOptionIn(serverOptPath).toJS(),
event: "server:create"
});
ui.servers[opts.port] = opts.server;
data.cb(null, opts);
}, 300);
}
/**
* @param err
* @param port
*/
function createThrottle (err, port) {
var target = getTargetUrl();
var args = {
port: port,
target: target,
speed: data.speed
};
if (ui.bs.getOption("scheme") === "https") {
var httpsOpts = require("browser-sync/lib/server/utils").getHttpsOptions(ui.bs.options);
args.key = httpsOpts.key;
args.cert = httpsOpts.cert;
}
args.server = require("./throttle-server")(args);
require('server-destroy')(args.server);
args.server.listen(port);
saveThrottleInfo(args);
}
/**
* Try for a free port
*/
ui.bs.utils.portscanner.findAPortNotInUse(data.port, data.port + 100, "127.0.0.1", function (err, port) {
if (err) {
return createThrottle(err);
} else {
createThrottle(null, port);
}
});
},
/**
* @param data
*/
"server:destroy": function (data) {
if (ui.servers[data.port]) {
ui.servers[data.port].destroy();
ui.setMany(function (item) {
item.deleteIn(serverOptPath.concat([parseInt(data.port, 10)]));
});
delete ui.servers[data.port];
}
ui.socket.emit("ui:network-throttle:update", {
servers: ui.getOptionIn(serverOptPath).toJS(),
event: "server:destroy"
});
},
/**
* @param event
*/
event: function (event) {
methods[event.event](event.data);
}
};
return methods;
};
/**
* Get local + external urls with a different port
* @param opts
* @returns {List<T>|List<any>}
*/
function getUrls (opts) {
var list = [];
var bsLocal = require("url").parse(opts.urls.local);
list.push([bsLocal.protocol + "//", bsLocal.hostname, ":", opts.port].join(""));
if (opts.urls.external) {
var external = require("url").parse(opts.urls.external);
list.push([bsLocal.protocol + "//", external.hostname, ":", opts.port].join(""));
}
return Immutable.List(list);
}

View File

@ -0,0 +1,53 @@
var networkThrottle = require("./network-throttle");
const PLUGIN_NAME = "Network Throttle";
/**
* @type {{plugin: Function, plugin:name: string, markup: string}}
*/
module.exports = {
/**
* Plugin init
*/
"plugin": function (ui, bs) {
ui.throttle = networkThrottle.init(ui, bs);
ui.listen("network-throttle", ui.throttle);
},
/**
* Hooks
*/
"hooks": {
"markup": fileContent("/network-throttle.html"),
"client:js": [fileContent("/network-throttle.client.js")],
"templates": [],
"page": {
path: "/network-throttle",
title: PLUGIN_NAME,
template: "network-throttle.html",
controller: "NetworkThrottleController",
order: 5,
icon: "time"
}
},
/**
* Plugin name
*/
"plugin:name": PLUGIN_NAME
};
/**
* @param filepath
* @returns {*}
*/
function getPath (filepath) {
return require("path").join(__dirname, filepath);
}
/**
* @param filepath
* @returns {*}
*/
function fileContent (filepath) {
return require("fs").readFileSync(getPath(filepath));
}

View File

@ -0,0 +1,57 @@
module.exports = [
{
active: false,
title: "DSL (2Mbs, 5ms RTT)",
id: "dsl",
speed: 200,
latency: 5,
urls: [],
order: 1
},
{
active: false,
title: "4G (4Mbs, 20ms RTT)",
id: "4g",
speed: 400,
latency: 10,
urls: [],
order: 2
},
{
active: false,
title: "3G (750kbs, 100ms RTT)",
id: "3g",
speed: 75,
latency: 50,
urls: [],
order: 3
},
{
active: false,
id: "good-2g",
title: "Good 2G (450kbs, 150ms RTT)",
speed: 45,
latency: 75,
urls: [],
order: 4
},
{
active: false,
id: "2g",
title: "Regular 2G (250kbs, 300ms RTT)",
speed: 25,
latency: 150,
urls: [],
order: 5
},
{
active: false,
id: "gprs",
title: "GPRS (50kbs, 500ms RTT)",
speed: 5,
latency: 250,
urls: [],
order: 6
}
];

View File

@ -0,0 +1,70 @@
var ThrottleGroup = require("stream-throttle").ThrottleGroup;
module.exports = throttle;
/**
*
*/
function throttle (opts) {
var options = {
local_host: "localhost",
remote_host: "localhost",
upstream: 10*1024,
downstream: opts.speed.speed * 1024,
keepalive: false
};
var serverOpts = {
allowHalfOpen: true,
rejectUnauthorized: false
};
var module = "net";
var method = "createConnection";
if (opts.key) {
module = "tls";
method = "connect";
serverOpts.key = opts.key;
serverOpts.cert = opts.cert;
}
return require(module).createServer(serverOpts, function (local) {
var remote = require(module)[method]({
host: opts.target.hostname,
port: opts.target.port,
allowHalfOpen: true,
rejectUnauthorized: false
});
var upThrottle = new ThrottleGroup({ rate: options.upstream });
var downThrottle = new ThrottleGroup({ rate: options.downstream });
var localThrottle = upThrottle.throttle();
var remoteThrottle = downThrottle.throttle();
setTimeout(function () {
local
.pipe(localThrottle)
.pipe(remote);
}, opts.speed.latency);
setTimeout(function () {
remote
.pipe(remoteThrottle)
.pipe(local);
}, opts.speed.latency);
local.on("error", function() {
remote.destroy();
local.destroy();
});
remote.on("error", function() {
local.destroy();
remote.destroy();
});
});
}

View File

@ -0,0 +1,131 @@
(function (angular) {
const SECTION_NAME = "overview";
angular
.module("BrowserSync")
.controller("OverviewController", [
"options",
"pagesConfig",
OverviewController
]);
/**
* @param options
* @param pagesConfig
*/
function OverviewController (options, pagesConfig) {
var ctrl = this;
ctrl.section = pagesConfig[SECTION_NAME];
ctrl.options = options.bs;
ctrl.ui = {
snippet: !ctrl.options.server && !ctrl.options.proxy
};
}
/**
* Url Info - this handles rendering of each server
* info item
*/
angular
.module("BrowserSync")
.directive("urlInfo", function () {
return {
restrict: "E",
replace: true,
scope: {
"options": "="
},
templateUrl: "url-info.html",
controller: [
"$scope",
"$rootScope",
"Clients",
urlInfoController
]
};
});
/**
* @param $scope
* @param $rootScope
* @param Clients
*/
function urlInfoController($scope, $rootScope, Clients) {
var options = $scope.options;
var urls = options.urls;
$scope.ui = {
server: false,
proxy: false
};
if ($scope.options.mode === "server") {
$scope.ui.server = true;
if (!Array.isArray($scope.options.server.baseDir)) {
$scope.options.server.baseDir = [$scope.options.server.baseDir];
}
}
if ($scope.options.mode === "proxy") {
$scope.ui.proxy = true;
}
$scope.urls = [];
$scope.urls.push({
title: "Local",
tagline: "URL for the machine you are running BrowserSync on",
url: urls.local,
icon: "imac"
});
if (urls.external) {
$scope.urls.push({
title: "External",
tagline: "Other devices on the same wifi network",
url: urls.external,
icon: "wifi"
});
}
if (urls.tunnel) {
$scope.urls.push({
title: "Tunnel",
tagline: "Secure HTTPS public url",
url: urls.tunnel,
icon: "globe"
});
}
/**
*
*/
$scope.sendAllTo = function (path) {
Clients.sendAllTo(path);
$rootScope.$emit("notify:flash", {
heading: "Instruction sent:",
message: "Sync all Browsers to: " + path
});
};
}
/**
* Display the snippet when in snippet mode
*/
angular
.module("BrowserSync")
.directive("snippetInfo", function () {
return {
restrict: "E",
replace: true,
scope: {
"options": "="
},
templateUrl: "snippet-info.html",
controller: ["$scope", function snippetInfoController() {/*noop*/}]
};
});
})(angular);

View File

@ -0,0 +1,25 @@
<article>
<div bs-panel="controls outline">
<h1 bs-heading>
<icon icon="{{ctrl.section.icon}}"></icon>
{{ctrl.section.title}}
</h1>
</div>
<url-info ng-if="ctrl.options.server || ctrl.options.proxy" options="ctrl.options"></url-info>
<snippet-info ng-if="ctrl.options && ctrl.ui.snippet" options="ctrl.options"></snippet-info>
<div bs-panel="full">
<div bs-panel-content>
<div bs-panel-icon>
<svg bs-svg-icon><use xlink:href="#svg-devices"></use></svg>
</div>
<p bs-text="lede">Current Connections</p>
<p>Connected browsers will be listed here.</p>
<connection-list options="ctrl.options"></connection-list>
</div>
</div>
</article>

View File

@ -0,0 +1,51 @@
const PLUGIN_NAME = "Overview";
/**
* @type {{plugin: Function, plugin:name: string, markup: string}}
*/
module.exports = {
/**
* Plugin init
*/
"plugin": function () { /* noop */ },
/**
* Hooks
*/
"hooks": {
"markup": fileContent("/overview.html"),
"client:js": fileContent("/overview.client.js"),
"templates": [
getPath("/snippet-info.html"),
getPath("/url-info.html")
],
"page": {
path: "/",
title: PLUGIN_NAME,
template: "overview.html",
controller: PLUGIN_NAME.replace(" ", "") + "Controller",
order: 1,
icon: "cog"
}
},
/**
* Plugin name
*/
"plugin:name": PLUGIN_NAME
};
/**
* @param filepath
* @returns {*}
*/
function getPath (filepath) {
return require("path").join(__dirname, filepath);
}
/**
* @param filepath
* @returns {*}
*/
function fileContent (filepath) {
return require("fs").readFileSync(getPath(filepath), "utf-8");
}

View File

@ -0,0 +1,10 @@
<div bs-panel="full">
<div bs-panel-content>
<div bs-panel-icon>
<svg bs-svg-icon=""><use xlink:href="#svg-code"></use></svg>
</div>
<p bs-text="lede">Place this snippet somewhere before the closing <code>&lt;/body&gt;</code> tag in your website</p>
<pre><code>{{options.snippet}}</code></pre>
</div>
</div>

View File

@ -0,0 +1,45 @@
<div>
<section bs-panel ng-repeat="url in urls">
<div bs-panel-content>
<div bs-panel-icon>
<icon icon="{{url.icon}}"></icon>
</div>
<p bs-text="lede">{{url.title}}</p>
<p><a href="{{url.url}}">{{url.url}}</a></p>
<div bs-button-group>
<a href="{{url.url}}" target="_blank" bs-button="size-small subtle-alt icon-left">
<icon icon="newtab"></icon>
new tab
</a>
<a href="#" ng-click="sendAllTo(url.url)" bs-button="size-small subtle-alt icon-left">
<icon icon="syncall"></icon>
sync all
</a>
</div>
</div>
</section>
<section ng-if="ui.server">
<div bs-panel="full">
<div bs-panel-content>
<div bs-panel-icon>
<icon icon="terminal"></icon>
</div>
<p bs-text="lede">Serving files from</p>
<ul bs-list="basic">
<li ng-repeat="url in options.server.baseDir">{{url}}</li>
</ul>
</div>
</div>
</section>
<section bs-panel ng-if="ui.proxy">
<div bs-panel-content>
<div bs-panel-icon>
<icon icon="target"></icon></svg>
</div>
<p bs-text="lede">Proxying:</p>
<p>
<a href="{{options.proxy.target}}" target="_blank">{{options.proxy.target}}</a>
</p>
</div>
</section>
</div>

View File

@ -0,0 +1,85 @@
/**
*
*/
(function (angular) {
var SECTION_NAME = "plugins";
angular
.module("BrowserSync")
.controller("PluginsController", [
"options",
"Socket",
"pagesConfig",
PluginsPageController
]);
/**
* @param options
* @param Socket
* @param pagesConfig
* @constructor
*/
function PluginsPageController(options, Socket, pagesConfig) {
var ctrl = this;
ctrl.section = pagesConfig[SECTION_NAME];
ctrl.options = options.bs;
ctrl.uiOptions = options.ui;
/**
* Don't show this UI as user plugin
*/
var filtered = ctrl.options.userPlugins.filter(function (item) {
return item.name !== "UI";
}).map(function (item) {
item.title = item.name;
return item;
});
var named = filtered.reduce(function (all, item) {
all[item.name] = item;
return all;
}, {});
/**
* @type {{loading: boolean}}
*/
ctrl.ui = {
loading: false,
plugins: filtered,
named: named
};
/**
* Toggle a pluginrs
*/
ctrl.togglePlugin = function (plugin) {
Socket.uiEvent({
namespace: SECTION_NAME,
event: "set",
data: plugin
});
};
/**
* Set the state of many options
* @param value
*/
ctrl.setMany = function (value) {
Socket.uiEvent({
namespace: SECTION_NAME,
event: "setMany",
data: value
});
ctrl.ui.plugins = ctrl.ui.plugins.map(function (item) {
item.active = value;
return item;
});
};
}
})(angular);

View File

@ -0,0 +1,33 @@
<div bs-panel="controls outline">
<h1 bs-heading>
<icon icon="{{ctrl.section.icon}}"></icon>
{{ctrl.section.title}}
</h1>
<div ng-if="ui.plugins.length" bs-button-row>
<button bs-button="inline success" ng-click="ctrl.setMany(true)">
<svg bs-svg-icon="">
<use xlink:href="#svg-circle-ok"></use>
</svg>
Enable All
</button>
<button bs-button="inline" ng-click="ctrl.setMany(false)">
<svg bs-svg-icon="">
<use xlink:href="#svg-circle-delete"></use>
</svg>
Disable all
</button>
</div>
</div>
%pluginlist%
<section ng-if="!ctrl.ui.plugins.length">
<div bs-panel>
<div bs-panel-content="basic">
<p bs-text="lede">Sorry, no plugins were loaded</p>
<p>You can either write your own plugin (guide coming soon!) or <a href="https://www.npmjs.org/search?q=browser%20sync%20plugin" target="_blank">Search NPM</a>
for packages that contain the keywords <code>browser sync plugin</code>
</p>
</div>
</div>
</section>

View File

@ -0,0 +1,74 @@
const PLUGIN_NAME = "Plugins";
/**
* @type {{plugin: Function, plugin:name: string, markup: string}}
*/
module.exports = {
/**
* @param ui
* @param bs
*/
"plugin": function (ui, bs) {
ui.listen("plugins", {
"set": function (data) {
bs.events.emit("plugins:configure", data);
},
"setMany": function (data) {
if (data.value !== true) {
data.value = false;
}
bs.getUserPlugins()
.filter(function (item) {
return item.name !== "UI "; // todo dupe code server/client
})
.forEach(function (item) {
item.active = data.value;
bs.events.emit("plugins:configure", item);
});
}
});
},
/**
* Hooks
*/
"hooks": {
"markup": fileContent("plugins.html"),
"client:js": fileContent("/plugins.client.js"),
"templates": [
//getPath("plugins.directive.html")
],
"page": {
path: "/plugins",
title: PLUGIN_NAME,
template: "plugins.html",
controller: PLUGIN_NAME + "Controller",
order: 4,
icon: "plug"
}
},
/**
* Plugin name
*/
"plugin:name": PLUGIN_NAME
};
/**
* @param filepath
* @returns {*}
*/
function getPath (filepath) {
return require("path").join(__dirname, filepath);
}
/**
* @param filepath
* @returns {*}
*/
function fileContent (filepath) {
return require("fs").readFileSync(getPath(filepath), "utf-8");
}

View File

@ -0,0 +1,48 @@
var files = [
{
name: "weinre",
context: "remote-debug",
active: false,
title: "Remote Debugger (weinre)",
tagline: "",
hidden: "<a href=\"%s\" target=\"_blank\">Access remote debugger (opens in a new tab)</a></p>"
},
{
type: "css",
context: "remote-debug",
id: "__browser-sync-pesticide__",
active: false,
file: __dirname + "/css/pesticide.min.css",
title: "CSS Outlining",
served: false,
name: "pesticide",
src: "/browser-sync/pesticide.css",
tagline: "Add simple CSS outlines to all elements. (powered by <a href=\"http://pesticide.io\" target=\"_blank\">Pesticide.io</a>)",
hidden: ""
},
{
type: "css",
context: "remote-debug",
id: "__browser-sync-pesticidedepth__",
active: false,
file: __dirname + "/css/pesticide-depth.css",
title: "CSS Depth Outlining",
served: false,
name: "pesticide-depth",
src: "/browser-sync/pesticide-depth.css",
tagline: "Add CSS box-shadows to all elements. (powered by <a href=\"http://pesticide.io\" target=\"_blank\">Pesticide.io</a>)",
hidden: ""
},
{
type: "js",
context: "n/a",
id: "__browser-sync-gridoverlay__",
active: false,
file: __dirname + "/overlay-grid/js/grid-overlay.js",
served: false,
name: "overlay-grid-js",
src: "/browser-sync/grid-overlay-js.js"
}
];
module.exports.files = files;

View File

@ -0,0 +1,19 @@
<div bs-panel="switch" ng-class="{'disabled': !ctrl.compression.active}">
<div bs-panel-content>
<div bs-panel-icon="switch">
<div class="switch">
<input id="cmn-form-{{ctrl.compression.name}}"
ng-model="ctrl.compression.active"
ng-change="ctrl.toggleLatency(ctrl.compression)"
class="cmn-toggle cmn-toggle-round"
type="checkbox"
checked="">
<label for="cmn-form-{{ctrl.compression.name}}"></label>
</div>
</div>
<div>
<p bs-Text="lede">{{ctrl.compression.title}}</p>
<p ng-if="ctrl.compression.tagline.length" ng-bind-html="ctrl.compression.tagline"></p>
</div>
</div>
</div>

View File

@ -0,0 +1,33 @@
var Immutable = require("immutable");
module.exports.init = function (ui, bs) {
var optPath = ["remote-debug", "compression"];
ui.setOptionIn(optPath, Immutable.Map({
name: "compression",
title: "Compression",
active: false,
tagline: "Add Gzip Compression to all responses"
}));
var methods = {
toggle: function (value) {
if (value !== true) {
value = false;
}
if (value) {
ui.setOptionIn(optPath.concat("active"), true);
bs.addMiddleware("", require("compression")(), {id: "ui-compression", override: true});
} else {
ui.setOptionIn(optPath.concat("active"), false);
bs.removeMiddleware("ui-compression");
}
},
event: function (event) {
methods[event.event](event.data);
}
};
return methods;
};

View File

@ -0,0 +1,498 @@
/*
pesticide v1.0.0 . @mrmrs . MIT
*/
body {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
article {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
nav {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
aside {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
section {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
header {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
footer {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
h1 {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
h2 {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
h3 {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
h4 {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
h5 {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
h6 {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
main {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
address {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
div {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
p {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
hr {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
pre {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
blockquote {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
ol {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
ul {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
li {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
dl {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
dt {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
dd {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
figure {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
figcaption {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
table {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
caption {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
thead {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
tbody {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
tfoot {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
tr {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
th {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
td {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
col {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
colgroup {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
button {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
datalist {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
fieldset {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
form {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
input {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
keygen {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
label {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
legend {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
meter {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
optgroup {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
option {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
output {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
progress {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
select {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
textarea {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
details {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
summary {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
command {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
menu {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
del {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
ins {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
img {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
iframe {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
embed {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
object {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
param {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
video {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
audio {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
source {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
canvas {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
track {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
map {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
area {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
a {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
em {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
strong {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
i {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
b {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
u {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
s {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
small {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
abbr {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
q {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
cite {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
dfn {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
sub {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
sup {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
time {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
code {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
kbd {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
samp {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
var {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
mark {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
bdi {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
bdo {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
ruby {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
rt {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
rp {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
span {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
br {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}
wbr {
-webkit-box-shadow: 0 0 1rem rgba(0,0,0,0.6);
box-shadow: 0 0 1rem rgba(0,0,0,0.6);
background-color: rgba(255,255,255,0.25);
}

View File

@ -0,0 +1,201 @@
/*
pesticide v1.0.0 . @mrmrs . MIT
*/
{
body
outline: 1px solid #2980b9 !important;
article
outline: 1px solid #3498db !important;
nav
outline: 1px solid #0088c3 !important;
aside
outline: 1px solid #33a0ce !important;
section
outline: 1px solid #66b8da !important;
header
outline: 1px solid #99cfe7 !important;
footer
outline: 1px solid #cce7f3 !important;
h1
outline: 1px solid #162544 !important;
h2
outline: 1px solid #314e6e !important;
h3
outline: 1px solid #3e5e85 !important;
h4
outline: 1px solid #449baf !important;
h5
outline: 1px solid #c7d1cb !important;
h6
outline: 1px solid #4371d0 !important;
main
outline: 1px solid #2f4f90 !important;
address
outline: 1px solid #1a2c51 !important;
div
outline: 1px solid #036cdb !important;
outline: 1px solid #ac050b !important;
hr
outline: 1px solid #ff063f !important;
pre
outline: 1px solid #850440 !important;
blockquote
outline: 1px solid #f1b8e7 !important;
ol
outline: 1px solid #ff050c !important;
ul
outline: 1px solid #d90416 !important;
li
outline: 1px solid #d90416 !important;
dl
outline: 1px solid #fd3427 !important;
dt
outline: 1px solid #ff0043 !important;
dd
outline: 1px solid #e80174 !important;
figure
outline: 1px solid #f0b !important;
figcaption
outline: 1px solid #bf0032 !important;
table
outline: 1px solid #0c9 !important;
caption
outline: 1px solid #37ffc4 !important;
thead
outline: 1px solid #98daca !important;
tbody
outline: 1px solid #64a7a0 !important;
tfoot
outline: 1px solid #22746b !important;
tr
outline: 1px solid #86c0b2 !important;
th
outline: 1px solid #a1e7d6 !important;
td
outline: 1px solid #3f5a54 !important;
col
outline: 1px solid #6c9a8f !important;
colgroup
outline: 1px solid #6c9a9d !important;
button
outline: 1px solid #da8301 !important;
datalist
outline: 1px solid #c06000 !important;
fieldset
outline: 1px solid #d95100 !important;
form
outline: 1px solid #d23600 !important;
input
outline: 1px solid #fca600 !important;
keygen
outline: 1px solid #b31e00 !important;
label
outline: 1px solid #ee8900 !important;
legend
outline: 1px solid #de6d00 !important;
meter
outline: 1px solid #e8630c !important;
optgroup
outline: 1px solid #b33600 !important;
option
outline: 1px solid #ff8a00 !important;
output
outline: 1px solid #ff9619 !important;
progress
outline: 1px solid #e57c00 !important;
select
outline: 1px solid #e26e0f !important;
textarea
outline: 1px solid #cc5400 !important;
details
outline: 1px solid #33848f !important;
summary
outline: 1px solid #60a1a6 !important;
command
outline: 1px solid #438da1 !important;
menu
outline: 1px solid #449da6 !important;
del
outline: 1px solid #bf0000 !important;
ins
outline: 1px solid #400000 !important;
img
outline: 1px solid #22746b !important;
iframe
outline: 1px solid #64a7a0 !important;
embed
outline: 1px solid #98daca !important;
object
outline: 1px solid #0c9 !important;
param
outline: 1px solid #37ffc4 !important;
video
outline: 1px solid #6ee866 !important;
audio
outline: 1px solid #027353 !important;
source
outline: 1px solid #012426 !important;
canvas
outline: 1px solid #a2f570 !important;
track
outline: 1px solid #59a600 !important;
map
outline: 1px solid #7be500 !important;
area
outline: 1px solid #305900 !important;
a
outline: 1px solid #ff62ab !important;
em
outline: 1px solid #800b41 !important;
strong
outline: 1px solid #ff1583 !important;
i
outline: 1px solid #803156 !important;
b
outline: 1px solid #cc1169 !important;
u
outline: 1px solid #ff0430 !important;
outline: 1px solid #f805e3 !important;
small
outline: 1px solid #d107b2 !important;
abbr
outline: 1px solid #4a0263 !important;
q
outline: 1px solid #240018 !important;
cite
outline: 1px solid #64003c !important;
dfn
outline: 1px solid #b4005a !important;
sub
outline: 1px solid #dba0c8 !important;
sup
outline: 1px solid #cc0256 !important;
time
outline: 1px solid #d6606d !important;
code
outline: 1px solid #e04251 !important;
kbd
outline: 1px solid #5e001f !important;
samp
outline: 1px solid #9c0033 !important;
var
outline: 1px solid #d90047 !important;
mark
outline: 1px solid #ff0053 !important;
bdi
outline: 1px solid #bf3668 !important;
bdo
outline: 1px solid #6f1400 !important;
ruby
outline: 1px solid #ff7b93 !important;
rt
outline: 1px solid #ff2f54 !important;
rp
outline: 1px solid #803e49 !important;
span
outline: 1px solid #cc2643 !important;
br
outline: 1px solid #db687d !important;
wbr
outline: 1px solid #db175b !important;
}

View File

@ -0,0 +1,395 @@
body {
outline: 1px solid #2980b9 !important
}
article {
outline: 1px solid #3498db !important
}
nav {
outline: 1px solid #0088c3 !important
}
aside {
outline: 1px solid #33a0ce !important
}
section {
outline: 1px solid #66b8da !important
}
header {
outline: 1px solid #99cfe7 !important
}
footer {
outline: 1px solid #cce7f3 !important
}
h1 {
outline: 1px solid #162544 !important
}
h2 {
outline: 1px solid #314e6e !important
}
h3 {
outline: 1px solid #3e5e85 !important
}
h4 {
outline: 1px solid #449baf !important
}
h5 {
outline: 1px solid #c7d1cb !important
}
h6 {
outline: 1px solid #4371d0 !important
}
main {
outline: 1px solid #2f4f90 !important
}
address {
outline: 1px solid #1a2c51 !important
}
div {
outline: 1px solid #036cdb !important
}
p {
outline: 1px solid #ac050b !important
}
hr {
outline: 1px solid #ff063f !important
}
pre {
outline: 1px solid #850440 !important
}
blockquote {
outline: 1px solid #f1b8e7 !important
}
ol {
outline: 1px solid #ff050c !important
}
ul {
outline: 1px solid #d90416 !important
}
li {
outline: 1px solid #d90416 !important
}
dl {
outline: 1px solid #fd3427 !important
}
dt {
outline: 1px solid #ff0043 !important
}
dd {
outline: 1px solid #e80174 !important
}
figure {
outline: 1px solid #f0b !important
}
figcaption {
outline: 1px solid #bf0032 !important
}
table {
outline: 1px solid #0c9 !important
}
caption {
outline: 1px solid #37ffc4 !important
}
thead {
outline: 1px solid #98daca !important
}
tbody {
outline: 1px solid #64a7a0 !important
}
tfoot {
outline: 1px solid #22746b !important
}
tr {
outline: 1px solid #86c0b2 !important
}
th {
outline: 1px solid #a1e7d6 !important
}
td {
outline: 1px solid #3f5a54 !important
}
col {
outline: 1px solid #6c9a8f !important
}
colgroup {
outline: 1px solid #6c9a9d !important
}
button {
outline: 1px solid #da8301 !important
}
datalist {
outline: 1px solid #c06000 !important
}
fieldset {
outline: 1px solid #d95100 !important
}
form {
outline: 1px solid #d23600 !important
}
input {
outline: 1px solid #fca600 !important
}
keygen {
outline: 1px solid #b31e00 !important
}
label {
outline: 1px solid #ee8900 !important
}
legend {
outline: 1px solid #de6d00 !important
}
meter {
outline: 1px solid #e8630c !important
}
optgroup {
outline: 1px solid #b33600 !important
}
option {
outline: 1px solid #ff8a00 !important
}
output {
outline: 1px solid #ff9619 !important
}
progress {
outline: 1px solid #e57c00 !important
}
select {
outline: 1px solid #e26e0f !important
}
textarea {
outline: 1px solid #cc5400 !important
}
details {
outline: 1px solid #33848f !important
}
summary {
outline: 1px solid #60a1a6 !important
}
command {
outline: 1px solid #438da1 !important
}
menu {
outline: 1px solid #449da6 !important
}
del {
outline: 1px solid #bf0000 !important
}
ins {
outline: 1px solid #400000 !important
}
img {
outline: 1px solid #22746b !important
}
iframe {
outline: 1px solid #64a7a0 !important
}
embed {
outline: 1px solid #98daca !important
}
object {
outline: 1px solid #0c9 !important
}
param {
outline: 1px solid #37ffc4 !important
}
video {
outline: 1px solid #6ee866 !important
}
audio {
outline: 1px solid #027353 !important
}
source {
outline: 1px solid #012426 !important
}
canvas {
outline: 1px solid #a2f570 !important
}
track {
outline: 1px solid #59a600 !important
}
map {
outline: 1px solid #7be500 !important
}
area {
outline: 1px solid #305900 !important
}
a {
outline: 1px solid #ff62ab !important
}
em {
outline: 1px solid #800b41 !important
}
strong {
outline: 1px solid #ff1583 !important
}
i {
outline: 1px solid #803156 !important
}
b {
outline: 1px solid #cc1169 !important
}
u {
outline: 1px solid #ff0430 !important
}
s {
outline: 1px solid #f805e3 !important
}
small {
outline: 1px solid #d107b2 !important
}
abbr {
outline: 1px solid #4a0263 !important
}
q {
outline: 1px solid #240018 !important
}
cite {
outline: 1px solid #64003c !important
}
dfn {
outline: 1px solid #b4005a !important
}
sub {
outline: 1px solid #dba0c8 !important
}
sup {
outline: 1px solid #cc0256 !important
}
time {
outline: 1px solid #d6606d !important
}
code {
outline: 1px solid #e04251 !important
}
kbd {
outline: 1px solid #5e001f !important
}
samp {
outline: 1px solid #9c0033 !important
}
var {
outline: 1px solid #d90047 !important
}
mark {
outline: 1px solid #ff0053 !important
}
bdi {
outline: 1px solid #bf3668 !important
}
bdo {
outline: 1px solid #6f1400 !important
}
ruby {
outline: 1px solid #ff7b93 !important
}
rt {
outline: 1px solid #ff2f54 !important
}
rp {
outline: 1px solid #803e49 !important
}
span {
outline: 1px solid #cc2643 !important
}
br {
outline: 1px solid #db687d !important
}
wbr {
outline: 1px solid #db175b !important
}

View File

@ -0,0 +1,43 @@
(function (angular) {
const SECTION_NAME = "remote-debug";
/**
* Display the snippet when in snippet mode
*/
angular
.module("BrowserSync")
.directive("latency", function () {
return {
restrict: "E",
replace: true,
scope: {
"options": "="
},
templateUrl: "latency.html",
controller: ["$scope", "Socket", latencyDirectiveControlller],
controllerAs: "ctrl"
};
});
/**
* @param $scope
* @param Socket
*/
function latencyDirectiveControlller($scope, Socket) {
var ctrl = this;
var ns = SECTION_NAME + ":latency";
ctrl.latency = $scope.options[SECTION_NAME]["latency"];
ctrl.alterLatency = function () {
Socket.emit("ui", {
namespace: ns,
event: "adjust",
data: {
rate: ctrl.latency.rate
}
});
};
}
})(angular);

View File

@ -0,0 +1,12 @@
<div ng-show="ctrl.latency.active" bs-panel-content>
<input type="range"
max="5"
min="0"
step=".50"
id="latency-rate"
ng-change="ctrl.alterLatency()"
ng-model="ctrl.latency.rate"/>
<label for="latency-rate">{{ctrl.latency.rate | number:1}}s</label>
</div>

View File

@ -0,0 +1,44 @@
var Immutable = require("immutable");
module.exports.init = function (ui, bs) {
var timeout = 0;
var optPath = ["remote-debug", "latency"];
ui.setOptionIn(optPath, Immutable.Map({
name: "latency",
title: "Latency",
active: false,
tagline: "Simulate slower connections by throttling the response time of each request.",
rate: 0
}));
var methods = {
toggle: function (value) {
if (value !== true) {
value = false;
}
if (value) {
ui.setOptionIn(optPath.concat("active"), true);
bs.addMiddleware("*", function (req, res, next) {
setTimeout(next, timeout);
}, {id: "cp-latency", override: true});
} else {
ui.setOptionIn(optPath.concat("active"), false);
bs.removeMiddleware("cp-latency");
}
},
adjust: function (data) {
timeout = parseFloat(data.rate) * 1000;
var saved = ui.options.getIn(optPath.concat("rate"));
if (saved !== data.rate) {
ui.setOptionIn(optPath.concat("rate"), timeout/1000);
}
},
event: function (event) {
methods[event.event](event.data);
}
};
return methods;
};

View File

@ -0,0 +1,19 @@
<div bs-panel="switch" ng-class="{'disabled': !ctrl.noCache.active}">
<div bs-panel-content>
<div bs-panel-icon="switch">
<div class="switch">
<input id="cmn-form-{{ctrl.noCache.name}}"
ng-model="ctrl.noCache.active"
ng-change="ctrl.toggleLatency(ctrl.noCache)"
class="cmn-toggle cmn-toggle-round"
type="checkbox"
checked="">
<label for="cmn-form-{{ctrl.noCache.name}}"></label>
</div>
</div>
<div>
<p bs-Text="lede">{{ctrl.noCache.title}}</p>
<p ng-if="ctrl.noCache.tagline.length" ng-bind-html="ctrl.noCache.tagline"></p>
</div>
</div>
</div>

View File

@ -0,0 +1,38 @@
var Immutable = require("immutable");
module.exports.init = function (ui, bs) {
var optPath = ["remote-debug", "no-cache"];
ui.setOptionIn(optPath, Immutable.Map({
name: "no-cache",
title: "No Cache",
active: false,
tagline: "Disable all Browser Caching"
}));
var methods = {
toggle: function (value) {
if (value !== true) {
value = false;
}
if (value) {
ui.setOptionIn(optPath.concat("active"), true);
bs.addMiddleware("*", function (req, res, next) {
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
res.setHeader("Pragma", "no-cache");
res.setHeader("Expires", "0");
next();
}, {id: "ui-no-cache", override: true});
} else {
ui.setOptionIn(optPath.concat("active"), false);
bs.removeMiddleware("ui-no-cache");
}
},
event: function (event) {
methods[event.event](event.data);
}
};
return methods;
};

View File

@ -0,0 +1,16 @@
{{selector}}:after {
position: absolute;
width: auto;
height: auto;
z-index: 9999;
content: '';
display: block;
pointer-events: none;
top: {{offsetY}};
right: 0;
bottom: 0;
left: {{offsetX}};
background-color: transparent;
background-image: linear-gradient({{color}} 1px, transparent 1px);
background-size: 100% {{size}};
}

View File

@ -0,0 +1,16 @@
{{selector}}:before {
position: absolute;
width: auto;
height: auto;
z-index: 9999;
content: '';
display: block;
pointer-events: none;
top: {{offsetY}};
right: 0;
bottom: 0;
left: {{offsetX}};
background-color: transparent;
background-image: linear-gradient(90deg, {{color}} 1px, transparent 1px);
background-size: {{size}} 100%;
}

View File

@ -0,0 +1,18 @@
(function (window, bs, undefined) {
var styleElem = bs.addDomNode({
placement: "head",
attrs: {
"type": "text/css",
id: "__bs_overlay-grid-styles__"
},
tagName: "style"
});
bs.socket.on("ui:remote-debug:css-overlay-grid", function (data) {
styleElem.innerHTML = data.innerHTML;
});
bs.socket.emit("ui:remote-debug:css-overlay-grid:ready");
}(window, window.___browserSync___));

View File

@ -0,0 +1,56 @@
(function (angular) {
const SECTION_NAME = "remote-debug";
/**
* Display the snippet when in snippet mode
*/
angular
.module("BrowserSync")
.directive("cssGrid", function () {
return {
restrict: "E",
replace: true,
scope: {
"options": "="
},
templateUrl: "overlay-grid.html",
controller: ["$scope", "Socket", overlayGridDirectiveControlller],
controllerAs: "ctrl"
};
});
/**
* @param $scope
* @param Socket
*/
function overlayGridDirectiveControlller($scope, Socket) {
var ctrl = this;
ctrl.overlayGrid = $scope.options[SECTION_NAME]["overlay-grid"];
ctrl.size = ctrl.overlayGrid.size;
var ns = SECTION_NAME + ":overlay-grid";
ctrl.alter = function (value) {
Socket.emit("ui", {
namespace: ns,
event: "adjust",
data: value
});
};
ctrl.toggleAxis = function (axis, value) {
Socket.emit("ui", {
namespace: ns,
event: "toggle:axis",
data: {
axis: axis,
value: value
}
});
};
}
})(angular);

View File

@ -0,0 +1,106 @@
<div ng-show="ctrl.overlayGrid.active" bs-panel-content>
<div bs-inputs bs-grid="wide-4 desk-2">
<div bs-grid-item>
<div bs-input="text">
<label for="grid-size" bs-input-label>Grid Size</label>
<div bs-input>
<input type="text"
max="100"
min="0"
step="1"
id="grid-size"
size="20"
ng-change="ctrl.alter(ctrl.overlayGrid)"
ng-model="ctrl.overlayGrid.size"/>
</div>
</div>
</div>
<div bs-grid-item>
<div bs-input="text">
<label for="grid-color" bs-input-label>Grid Colour</label>
<div bs-input>
<input type="text"
max="100"
min="0"
step="1"
id="grid-color"
size="20"
ng-change="ctrl.alter(ctrl.overlayGrid)"
ng-model="ctrl.overlayGrid.color"/>
</div>
</div>
</div>
<div bs-grid-item>
<div bs-input="text">
<label for="grid-selector" bs-input-label>CSS Selector</label>
<div bs-input>
<input type="text"
max="100"
min="0"
step="1"
id="grid-selector"
size="20"
ng-change="ctrl.alter(ctrl.overlayGrid)"
ng-model="ctrl.overlayGrid.selector"/>
</div>
</div>
</div>
</div>
<div bs-inputs bs-grid="wide-4 desk-2">
<div bs-grid-item>
<div bs-input="text">
<label for="grid-offsetY" bs-input-label>Offset Top</label>
<div bs-input>
<input type="text"
id="grid-offsetY"
size="20"
ng-change="ctrl.alter(ctrl.overlayGrid)"
ng-model="ctrl.overlayGrid.offsetY"/>
</div>
</div>
</div>
<div bs-grid-item>
<div bs-input="text">
<label for="grid-offsetX" bs-input-label>Offset Left</label>
<div bs-input>
<input type="text"
id="grid-offsetX"
size="20"
ng-change="ctrl.alter(ctrl.overlayGrid)"
ng-model="ctrl.overlayGrid.offsetX"/>
</div>
</div>
</div>
</div>
<div bs-inputs bs-grid="wide-4 desk-2">
<div bs-grid-item>
<div bs-input="inline">
<input
type="checkbox"
id="grid-axis-y"
ng-model="ctrl.overlayGrid.vertical"
ng-change="ctrl.toggleAxis('vertical', ctrl.overlayGrid.vertical)"/>
<label for="grid-axis-y" bs-input-label>Vertical Axis</label>
</div>
</div>
<div bs-grid-item>
<div bs-input="inline">
<input
type="checkbox"
id="grid-axis-x"
ng-model="ctrl.overlayGrid.horizontal"
ng-change="ctrl.toggleAxis('horizontal', ctrl.overlayGrid.horizontal)"/>
<label for="grid-axis-x" bs-input-label>Horizontal Axis</label>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,101 @@
var Immutable = require("immutable");
var fs = require("fs");
var path = require("path");
var baseHorizontal = fs.readFileSync(path.resolve(__dirname, "css/grid-overlay-horizontal.css"), "utf8");
var baseVertical = fs.readFileSync(path.resolve(__dirname, "css/grid-overlay-vertical.css"), "utf8");
function template (string, obj) {
obj = obj || {};
return string.replace(/\{\{(.+?)\}\}/g, function () {
if (obj[arguments[1]]) {
return obj[arguments[1]];
}
return "";
});
}
function getCss(opts) {
var base = opts.selector + " {position:relative;}";
if (opts.horizontal) {
base += baseHorizontal;
}
if (opts.vertical) {
base += baseVertical;
}
return template(base, opts);
}
module.exports.init = function (ui) {
const TRANSMIT_EVENT = "ui:remote-debug:css-overlay-grid";
const READY_EVENT = "ui:remote-debug:css-overlay-grid:ready";
const OPT_PATH = ["remote-debug", "overlay-grid"];
var defaults = {
offsetY: "0",
offsetX: "0",
size: "16px",
selector: "body",
color: "rgba(0, 0, 0, .2)",
horizontal: true,
vertical: true
};
ui.clients.on("connection", function (client) {
client.on(READY_EVENT, function () {
client.emit(TRANSMIT_EVENT, {
innerHTML: getCss(ui.options.getIn(OPT_PATH).toJS())
});
});
});
ui.setOptionIn(OPT_PATH, Immutable.Map({
name: "overlay-grid",
title: "Overlay CSS Grid",
active: false,
tagline: "Add an adjustable CSS overlay grid to your webpage",
innerHTML: ""
}).merge(defaults));
var methods = {
toggle: function (value) {
if (value !== true) {
value = false;
}
if (value) {
ui.setOptionIn(OPT_PATH.concat("active"), true);
ui.enableElement({name: "overlay-grid-js"});
} else {
ui.setOptionIn(OPT_PATH.concat("active"), false);
ui.disableElement({name: "overlay-grid-js"});
ui.clients.emit("ui:element:remove", {id: "__bs_overlay-grid-styles__"});
}
},
adjust: function (data) {
ui.setOptionIn(OPT_PATH, ui.getOptionIn(OPT_PATH).merge(data));
ui.clients.emit(TRANSMIT_EVENT, {
innerHTML: getCss(ui.options.getIn(OPT_PATH).toJS())
});
},
"toggle:axis": function (item) {
ui.setOptionIn(OPT_PATH.concat([item.axis]), item.value);
ui.clients.emit(TRANSMIT_EVENT, {
innerHTML: getCss(ui.options.getIn(OPT_PATH).toJS())
});
},
event: function (event) {
methods[event.event](event.data);
}
};
return methods;
};

View File

@ -0,0 +1,155 @@
(function (angular) {
const SECTION_NAME = "remote-debug";
angular
.module("BrowserSync")
.controller("RemoteDebugController", [
"options",
"Socket",
"pagesConfig",
RemoteDebugController
]);
/**
* @param options
* @param Socket
* @param pagesConfig
*/
function RemoteDebugController(options, Socket, pagesConfig) {
var ctrl = this;
ctrl.options = options.bs;
ctrl.uiOptions = options.ui;
ctrl.clientFiles = options.ui.clientFiles || {};
ctrl.section = pagesConfig[SECTION_NAME];
ctrl.overlayGrid = options.ui[SECTION_NAME]["overlay-grid"];
ctrl.items = [];
if (Object.keys(ctrl.clientFiles).length) {
Object.keys(ctrl.clientFiles).forEach(function (key) {
if (ctrl.clientFiles[key].context === SECTION_NAME) {
ctrl.items.push(ctrl.clientFiles[key]);
}
});
}
ctrl.toggleClientFile = function (item) {
if (item.name === "weinre") {
return ctrl.toggleWeinre(item);
}
if (item.active) {
return ctrl.enable(item);
}
return ctrl.disable(item);
};
ctrl.toggleWeinre = function (item) {
Socket.uiEvent({
namespace: SECTION_NAME + ":weinre",
event: "toggle",
data: item.active
});
};
ctrl.toggleOverlayGrid = function (item) {
var ns = SECTION_NAME + ":overlay-grid";
Socket.uiEvent({
namespace: ns,
event: "toggle",
data: item.active
});
};
ctrl.enable = function (item) {
Socket.uiEvent({
namespace: SECTION_NAME + ":files",
event: "enableFile",
data: item
});
};
ctrl.disable = function (item) {
Socket.uiEvent({
namespace: SECTION_NAME + ":files",
event: "disableFile",
data: item
});
};
}
/**
* Display the snippet when in snippet mode
*/
angular
.module("BrowserSync")
.directive("noCache", function () {
return {
restrict: "E",
replace: true,
scope: {
"options": "="
},
templateUrl: "no-cache.html",
controller: ["$scope", "Socket", noCacheDirectiveControlller],
controllerAs: "ctrl"
};
});
/**
* @param $scope
* @param Socket
*/
function noCacheDirectiveControlller ($scope, Socket) {
var ctrl = this;
ctrl.noCache = $scope.options[SECTION_NAME]["no-cache"];
ctrl.toggleLatency = function (item) {
Socket.emit("ui:no-cache", {
event: "toggle",
data: item.active
});
};
}
/**
* Display the snippet when in snippet mode
*/
angular
.module("BrowserSync")
.directive("compression", function () {
return {
restrict: "E",
replace: true,
scope: {
"options": "="
},
templateUrl: "compression.html",
controller: ["$scope", "Socket", compressionDirectiveControlller],
controllerAs: "ctrl"
};
});
/**
* @param $scope
* @param Socket
*/
function compressionDirectiveControlller ($scope, Socket) {
var ctrl = this;
ctrl.compression = $scope.options[SECTION_NAME]["compression"];
ctrl.toggleLatency = function (item) {
Socket.emit("ui:compression", {
event: "toggle",
data: item.active
});
};
}
})(angular);

View File

@ -0,0 +1,23 @@
<div bs-panel="controls outline">
<h1 bs-heading>
<icon icon="{{ctrl.section.icon}}"></icon>
{{ctrl.section.title}}
</h1>
</div>
<switch toggle="ctrl.toggleClientFile(item)"
switchid="plugins-{{$index}}"
active="item.active"
prop="active"
ng-repeat="item in ctrl.items track by $index"
item="item">
<div bs-panel-content ng-if="item.active && item.hidden.length" ng-bind-html="item.hidden"></div>
</switch>
<switch toggle="ctrl.toggleOverlayGrid(ctrl.overlayGrid)"
switchid="css-overlay-grid"
active="ctrl.overlayGrid.active"
prop="active"
item="ctrl.overlayGrid">
<css-grid options="ctrl.uiOptions" ng-if="ctrl.options.mode !== 'snippet'"></css-grid>
</switch>

View File

@ -0,0 +1,96 @@
var weinre = require("./weinre");
//var compression = require("./compression");
//var noCachePlugin = require("./no-cache");
var overlayPlugin = require("./overlay-grid/overlay-grid");
var clientFiles = require("./client-files");
const PLUGIN_NAME = "Remote Debug";
/**
* @type {{plugin: Function, plugin:name: string, markup: string}}
*/
module.exports = {
/**
* @param ui
* @param bs
*/
"plugin": function (ui, bs) {
if (bs.options.get("scheme") === "https") {
ui.setMany(function (item) {
item.deleteIn(["clientFiles", "weinre"]);
});
} else {
ui.weinre = weinre.init(ui);
}
ui.overlayGrid = overlayPlugin.init(ui, bs);
//ui.noCache = noCachePlugin.init(ui, bs);
//ui.compression = compression.init(ui, bs);
/**
* Listen for file events
*/
ui.listen("remote-debug:files", {
"enableFile": function (file) {
ui.enableElement(file);
},
"disableFile": function (file) {
ui.disableElement(file);
}
});
/**
* Listen for weinre toggles
*/
ui.listen("remote-debug:weinre", ui.weinre);
/**
* Listen for overlay-grid events
*/
ui.listen("remote-debug:overlay-grid", ui.overlayGrid);
},
/**
* Hooks
*/
"hooks": {
"markup": fileContent("remote-debug.html"),
"client:js": [
fileContent("/remote-debug.client.js"),
fileContent("/overlay-grid/overlay-grid.client.js")
],
"templates": [
getPath("/overlay-grid/overlay-grid.html")
],
"page": {
path: "/remote-debug",
title: PLUGIN_NAME,
template: "remote-debug.html",
controller: PLUGIN_NAME.replace(" ", "") + "Controller",
order: 4,
icon: "bug"
},
elements: clientFiles.files
},
/**
* Plugin name
*/
"plugin:name": PLUGIN_NAME
};
/**
* @param filepath
* @returns {*}
*/
function getPath (filepath) {
return require("path").join(__dirname, filepath);
}
/**
* @param filepath
* @returns {*}
*/
function fileContent (filepath) {
return require("fs").readFileSync(getPath(filepath), "utf-8");
}

View File

@ -0,0 +1,201 @@
var url = require("url");
var Immutable = require("immutable");
var path = require("path");
var weinreApp;
const WEINRE_NAME = "weinre-debug";
const WEINRE_ID = "#browsersync";
const WEINRE_ELEM_ID = "__browser-sync-weinre__";
var weinreTargetUrl = {
protocol: "http:",
pathname: "/target/target-script-min.js",
hash: WEINRE_ID
};
var weinreClientUrl = {
protocol: "http:",
pathname: "/client/",
hash: WEINRE_ID
};
/**
* Prepare weinre for later possible use.
* @param ui
*/
function init (ui) {
var hostUrl = getHostUrl(ui, ui.bs);
var weinrePort = ui.getOptionIn(["weinre", "port"]);
weinreTargetUrl.hostname = hostUrl.hostname;
weinreClientUrl.hostname = hostUrl.hostname;
weinreClientUrl.port = weinrePort;
weinreTargetUrl.port = weinrePort;
ui.setOption(WEINRE_NAME, Immutable.fromJS({
name: WEINRE_NAME,
active: false,
url: false,
targetUrl: url.format(weinreTargetUrl),
clientUrl: url.format(weinreClientUrl),
port: weinrePort
}));
setWeinreClientUrl(ui, url.format(weinreClientUrl));
var methods = {
toggle: function (data) {
toggleWeinre(ui.socket, ui.clients, ui, ui.bs, data);
},
event: function (event) {
methods[event.event](event.data);
}
};
return methods;
}
/**
* Get a suitable host URL for weinre
* @param ui
* @param bs
* @returns {*}
*/
function getHostUrl(ui, bs) {
var url = bs.getOptionIn(["urls", "external"]);
if (!url) {
url = bs.getOptionIn(["urls", "local"]);
}
return require("url").parse(url);
}
/**
* @param ui
* @param weinreClientUrl
*/
function setWeinreClientUrl(ui, weinreClientUrl) {
var weinre = ui.options.getIn(["clientFiles", "weinre"]).toJS();
ui.setMany(function (item) {
item.setIn(["clientFiles", "weinre", "hidden"], weinre.hidden.replace("%s", weinreClientUrl));
return item;
});
}
/**
* @param socket
* @param clients
* @param ui
* @param bs
* @param value
*/
function toggleWeinre (socket, clients, ui, bs, value) {
if (value !== true) {
value = false;
}
if (value) {
var _debugger = enableWeinre(ui, bs);
// set the state of weinre
ui.setMany(function (item) {
item.setIn([WEINRE_NAME, "active"], true);
item.setIn([WEINRE_NAME, "url"], _debugger.url);
item.setIn([WEINRE_NAME, "active"], true);
item.setIn(["clientFiles", "weinre", "active"], true);
}, {silent: true});
// Let the UI know about it
socket.emit("ui:weinre:enabled", _debugger);
var fileitem = {
type: "js",
src: ui.getOptionIn([WEINRE_NAME, "targetUrl"]),
id: WEINRE_ELEM_ID
};
// Add the element to all clients
ui.addElement(clients, fileitem);
// Save for page refreshes
//clientScripts = clientScripts.set("weinre", fileitem);
} else {
// Stop it
disableWeinre(ui, bs);
//clientScripts = clientScripts.remove("weinre");
// Reset the state
ui.setOptionIn([WEINRE_NAME, "active"], false, {silent: false}); // Force a reload here
ui.setOptionIn(["clientFiles", "weinre", "active"], false); // Force a reload here
// Let the UI know
socket.emit("ui:weinre:disabled");
// Reload all browsers to remove weinre elements/JS
clients.emit("browser:reload");
}
}
/**
* Enable the debugger
* @param ui
* @param bs
* @returns {{url: string, port: number}}
*/
function enableWeinre (ui, bs) {
if (weinreApp && typeof weinreApp.destroy === "function") {
weinreApp.destroy();
weinreApp = undefined;
}
var port = ui.getOptionIn([WEINRE_NAME, "port"]);
var logger = require(path.join(path.dirname(require.resolve("weinre")), "utils.js"));
logger.log = function (message) {
ui.logger.debug("[weinre]: %s", message);
};
var weinre = require("weinre");
var external = getHostUrl(ui, bs);
weinreApp = weinre.run({
httpPort: port,
boundHost: external.hostname,
verbose: false,
debug: false,
readTimeout: 5,
deathTimeout: 15 });
require("server-destroy")(weinreApp);
return ui.options.get(WEINRE_NAME).toJS();
}
/**
* @param ui
* @returns {any|*}
*/
function disableWeinre (ui) {
if (weinreApp) {
weinreApp.close();
weinreApp = false;
}
return ui.options.get(WEINRE_NAME).toJS();
}
module.exports.init = init;
module.exports.toggleWeinre = toggleWeinre;

View File

@ -0,0 +1,94 @@
(function (angular) {
const SECTION_NAME = "sync-options";
angular
.module("BrowserSync")
.controller("SyncOptionsController", [
"Socket",
"options",
"pagesConfig",
SyncOptionsController
]);
/**
* @param Socket
* @param options
* @param pagesConfig
* @constructor
*/
function SyncOptionsController(Socket, options, pagesConfig) {
var ctrl = this;
ctrl.options = options.bs;
ctrl.section = pagesConfig[SECTION_NAME];
ctrl.setMany = function (value) {
Socket.uiEvent({
namespace: SECTION_NAME,
event: "setMany",
data: {
value: value
}
});
ctrl.syncItems = ctrl.syncItems.map(function (item) {
item.value = value;
return item;
});
};
/**
* Toggle Options
* @param item
*/
ctrl.toggleSyncItem = function (item) {
Socket.uiEvent({
namespace: SECTION_NAME,
event: "set",
data: {
path: item.path,
value: item.value
}
});
};
ctrl.syncItems = [];
var taglines = {
clicks: "Mirror clicks across devices",
scroll: "Mirror scroll position across devices",
"ghostMode.submit": "Form Submissions will be synced",
"ghostMode.inputs": "Text inputs (including text-areas) will be synced",
"ghostMode.toggles": "Radio + Checkboxes changes will be synced",
codeSync: "Reload or Inject files they change"
};
// If watching files, add the code-sync toggle
ctrl.syncItems.push(addItem("codeSync", ["codeSync"], ctrl.options.codeSync, taglines["codeSync"]));
Object.keys(ctrl.options.ghostMode).forEach(function (item) {
if (item !== "forms" && item !== "location") {
ctrl.syncItems.push(addItem(item, ["ghostMode", item], ctrl.options.ghostMode[item], taglines[item]));
}
});
Object.keys(ctrl.options.ghostMode.forms).forEach(function (item) {
ctrl.syncItems.push(addItem("Forms: " + item, ["ghostMode", "forms", item], ctrl.options.ghostMode["forms"][item], taglines["ghostMode." + item]));
});
function addItem (item, path, value, tagline) {
return {
value: value,
name: item,
path: path,
title: ucfirst(item),
tagline: tagline
};
}
}
function ucfirst (string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
})(angular);

View File

@ -0,0 +1,25 @@
<article>
<div bs-panel="controls outline">
<h1 bs-heading>
<icon icon="{{ctrl.section.icon}}"></icon>
{{ctrl.section.title}}
</h1>
</div>
<div bs-button-row>
<button bs-button="icon-left inline success" ng-click="ctrl.setMany(true)">
<svg bs-svg-icon><use xlink:href="#svg-circle-ok"></use></svg>
Enable All
</button>
<button bs-button="icon-left inline" ng-click="ctrl.setMany(false)">
<svg bs-svg-icon><use xlink:href="#svg-circle-delete"></use></svg>
Disable all
</button>
</div>
<switch toggle="ctrl.toggleSyncItem(item)"
switchid="sync-options-{{$index}}"
active="item.value"
prop="value"
ng-repeat="item in ctrl.syncItems track by $index"
item="item"></switch>
</article>

View File

@ -0,0 +1,66 @@
const PLUGIN_NAME = "Sync Options";
/**
* @type {{plugin: Function, plugin:name: string, hooks: object}}
*/
module.exports = {
"plugin": function (ui, bs) {
ui.listen("sync-options", {
"set": function (data) {
ui.logger.debug("Setting option: {magenta:%s}:{cyan:%s}", data.path.join("."), data.value);
bs.setOptionIn(data.path, data.value);
},
"setMany": function (data) {
ui.logger.debug("Setting Many options...");
if (data.value !== true) {
data.value = false;
}
bs.setMany(function (item) {
[
["codeSync"],
["ghostMode", "clicks"],
["ghostMode", "scroll"],
["ghostMode", "forms", "inputs"],
["ghostMode", "forms", "toggles"],
["ghostMode", "forms", "submit"]
].forEach(function (option) {
item.setIn(option, data.value);
});
});
return bs;
}
});
},
"hooks": {
"markup": fileContent("sync-options.html"),
"client:js": fileContent("sync-options.client.js"),
"templates": [],
"page": {
path: "/sync-options",
title: PLUGIN_NAME,
template: "sync-options.html",
controller: PLUGIN_NAME.replace(" ", "") + "Controller",
order: 2,
icon: "sync"
}
},
"plugin:name": PLUGIN_NAME
};
function getPath (filepath) {
return require("path").join(__dirname, filepath);
}
function fileContent (filepath) {
return require("fs").readFileSync(getPath(filepath), "utf-8");
}

117
node_modules/browser-sync-ui/lib/resolve-plugins.js generated vendored Normal file
View File

@ -0,0 +1,117 @@
var fs = require("fs");
var path = require("path");
var Immutable = require("immutable");
/**
* Take Browsersync plugins and determine if
* any UI is provided by looking at data in the the
* modules package.json file
* @param plugins
* @returns {*}
*/
module.exports = function (plugins) {
return require("immutable")
.fromJS(plugins)
/**
* Exclude the UI
*/
.filter(function (plugin) {
return plugin.get("name") !== "UI";
})
/**
* Attempt to retrieve a plugins package.json file
*/
.map(function (plugin) {
var moduleName = plugin.getIn(["opts", "moduleName"]);
var pkg = {};
if (!moduleName) {
return plugin;
}
try {
pkg = require("immutable").fromJS(require(path.join(moduleName, "package.json")));
} catch (e) {
console.error(e);
return plugin;
}
plugin = plugin.set("pkg", pkg);
return plugin.set("relpath", path.dirname(require.resolve(moduleName)));
})
/**
* Try to load markup for each plugin
*/
.map(function (plugin) {
if (!plugin.hasIn(["pkg", "browser-sync:ui"])) {
return plugin;
}
var markup = plugin.getIn(["pkg", "browser-sync:ui", "hooks", "markup"]);
if (markup) {
plugin = plugin.set("markup", fs.readFileSync(path.resolve(plugin.get("relpath"), markup), "utf8"));
}
return plugin;
})
/**
* Load any template files for the plugin
*/
.map(function (plugin) {
if (!plugin.hasIn(["pkg", "browser-sync:ui"])) {
return plugin;
}
return resolveIfPluginHas(["pkg", "browser-sync:ui", "hooks", "templates"], "templates", plugin);
})
/**
* Try to load Client JS for each plugin
*/
.map(function (plugin) {
if (!plugin.hasIn(["pkg", "browser-sync:ui"])) {
return plugin;
}
return resolveIfPluginHas(["pkg", "browser-sync:ui", "hooks", "client:js"], "client:js", plugin);
});
};
/**
* If a plugin contains this option path, resolve/read the files
* @param {Array} optPath - How to access the collection
* @param {String} propName - Key for property access
* @param {Immutable.Map} plugin
* @returns {*}
*/
function resolveIfPluginHas(optPath, propName, plugin) {
var opt = plugin.getIn(optPath);
if (opt.size) {
return plugin.set(
propName,
resolvePluginFiles(opt, plugin.get("relpath"))
);
}
return plugin;
}
/**
* Read & store a file from a plugin
* @param {Array|Immutable.List} collection
* @param {String} relPath
* @returns {any}
*/
function resolvePluginFiles (collection, relPath) {
return Immutable.fromJS(collection.reduce(function (all, item) {
var full = path.join(relPath, item);
if (fs.existsSync(full)) {
all[full] = fs.readFileSync(full, "utf8");
}
return all;
}, {}));
}

221
node_modules/browser-sync-ui/lib/server.js generated vendored Normal file
View File

@ -0,0 +1,221 @@
var http = require("http");
var fs = require("fs");
var path = require("path");
var config = require("./config");
var svg = publicFile(config.defaults.public.svg);
var indexPage = publicFile(config.defaults.indexPage);
//var css = publicFile(config.defaults.public.css);
var header = staticFile(config.defaults.components.header);
var footer = staticFile(config.defaults.components.footer);
var zlib = require("zlib");
/**
* @param {UI} ui
* @returns {*}
*/
function startServer(ui) {
var connect = ui.bs.utils.connect;
var serveStatic = ui.bs.utils.serveStatic;
/**
* Create a connect server
*/
var app = connect();
var socketJs = getSocketJs(ui);
var jsFilename = "/" + md5(socketJs, 10) + ".js";
//var cssFilename = "/" + md5(css, 10) + ".css";
/**
* Create a single big file with all deps
*/
//app.use(serveFile(jsFilename, "js", socketJs));
app.use(serveFile(config.defaults.socketJs, "js", socketJs));
// also serve for convenience/testing
app.use(serveFile(config.defaults.pagesConfig, "js", ui.pagesConfig));
//
app.use(serveFile(config.defaults.clientJs, "js", ui.clientJs));
/**
* Add any markup from plugins/hooks/templates
*/
insertPageMarkupFromHooks(
app,
ui.pages,
indexPage
.replace("%pageMarkup%", ui.pageMarkup)
.replace("%templates%", ui.templates)
.replace("%svg%", svg)
.replace("%header%", header)
.replace(/%footer%/g, footer)
);
/**
* gzip css
*/
//app.use(serveFile(cssFilename, "css", css));
app.use(serveStatic(path.join(__dirname, "../public")));
/**
* all public dir as static
*/
app.use(serveStatic(publicDir("")));
/**
* History API fallback
*/
app.use(require("connect-history-api-fallback"));
/**
* Development use
*/
app.use("/node_modules", serveStatic(packageDir("node_modules")));
/**
* Return the server.
*/
return {
server: http.createServer(app),
app: app
};
}
/**
* @param app
* @param pages
* @param markup
*/
function insertPageMarkupFromHooks(app, pages, markup) {
var cached;
app.use(function (req, res, next) {
if (req.url === "/" || pages[req.url.slice(1)]) {
res.writeHead(200, {"Content-Type": "text/html", "Content-Encoding": "gzip"});
if (!cached) {
var buf = new Buffer(markup, "utf-8");
zlib.gzip(buf, function (_, result) {
cached = result;
res.end(result);
});
} else {
res.end(cached);
}
} else {
next();
}
});
}
/**
* Serve Gzipped files & cache them
* @param app
* @param all
*/
var gzipCache = {};
function serveFile(path, type, string) {
var typemap = {
js: "application/javascript",
css: "text/css"
};
return function (req, res, next) {
if (req.url !== path) {
return next();
}
res.writeHead(200, {
"Content-Type": typemap[type],
"Content-Encoding": "gzip",
"Cache-Control": "no-cache, no-store, must-revalidate",
"Expires": 0,
"Pragma": "no-cache"
});
if (gzipCache[path]) {
return res.end(gzipCache[path]);
}
var buf = new Buffer(string, "utf-8");
zlib.gzip(buf, function (_, result) {
gzipCache[path] = result;
res.end(result);
});
};
}
/**
* @param cp
* @returns {string}
*/
function getSocketJs (cp) {
return [
cp.bs.getSocketIoScript(),
cp.bs.getExternalSocketConnector({namespace: "/browser-sync-cp"})
].join(";");
}
///**
// * @returns {*}
// * @param filepath
// */
//function fileContent (filepath) {
// return fs.readFileSync(require.resolve(filepath), "utf8");
//}
/**
* @param src
* @param length
*/
function md5(src, length) {
var crypto = require("crypto");
var hash = crypto.createHash("md5").update(src, "utf8").digest("hex");
return hash.slice(0, length);
}
/**
* CWD directory helper for static dir
* @param {string} filepath
* @returns {string}
*/
function publicDir (filepath) {
return path.join(__dirname, "/../public" + filepath) || "";
}
/**
* @param {string} filepath
* @returns {string|string}
*/
function staticDir (filepath) {
return path.join(__dirname, "/../static" + filepath) || "";
}
/**
* @param {string} filepath
* @returns {*}
*/
function publicFile(filepath) {
return fs.readFileSync(publicDir(filepath), "utf-8");
}
/**
* @param filepath
* @returns {*}
*/
function staticFile(filepath) {
return fs.readFileSync(staticDir(filepath), "utf-8");
}
/**
* @param {string} filepath
* @returns {string}
*/
function packageDir (filepath) {
return path.join(__dirname, "/../" + filepath);
}
module.exports = startServer;

25
node_modules/browser-sync-ui/lib/transform.options.js generated vendored Normal file
View File

@ -0,0 +1,25 @@
var path = require("path");
module.exports = function (bs) {
/**
* Transform server options to offer additional functionality
* @param bs
*/
var options = bs.options;
var server = options.server;
var cwd = bs.cwd;
/**
* Transform server option
*/
if (server) {
if (Array.isArray(server.baseDir)) {
server.baseDirs = options.server.baseDir.map(function (item) {
return path.join(cwd, item);
});
} else {
server.baseDirs = [path.join(cwd, server.baseDir)];
}
}
};

11
node_modules/browser-sync-ui/lib/transforms.js generated vendored Normal file
View File

@ -0,0 +1,11 @@
module.exports = {
"mode": function (obj) {
if (obj.get("server")) {
return "Server";
}
if (obj.get("proxy")) {
return "Proxy";
}
return "Snippet";
}
};

0
node_modules/browser-sync-ui/lib/urls.js generated vendored Normal file
View File

35
node_modules/browser-sync-ui/lib/utils.js generated vendored Normal file
View File

@ -0,0 +1,35 @@
var url = require("url");
var http = require("http");
/**
* @param localUrl
* @param urlPath
* @returns {*}
*/
function createUrl(localUrl, urlPath) {
return url.parse(url.resolve(localUrl, urlPath));
}
/**
* @param url
* @param cb
*/
function verifyUrl(url, cb) {
url.headers = {
"accept": "text/html"
};
http.get(url, function (res) {
if (res.statusCode === 200) {
cb(null, res);
} else {
cb("not 200");
}
}).on("error", function(e) {
console.log("Got error: " + e.message);
});
}
module.exports.createUrl = createUrl;
module.exports.verifyUrl = verifyUrl;