前端代码

This commit is contained in:
ChloeChen0423
2025-05-12 16:42:36 +09:00
commit 7c63f2f07b
4531 changed files with 656010 additions and 0 deletions

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,160 @@
var Immutable = require("immutable");
module.exports.init = function (ui) {
var optPath = ["network-throttle"];
var serverOptPath = optPath.concat(["servers"]);
var listenHost = ui.options.get("listen");
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, listenHost);
require('server-destroy')(args.server);
args.server.listen(port, listenHost);
saveThrottleInfo(args);
}
/**
* Try for a free port
*/
ui.bs.utils.portscanner.findAPortNotInUse(data.port, data.port + 100, (listenHost || "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, listenHost) {
var options = {
local_host: listenHost,
remote_host: listenHost,
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,40 @@
var files = [
{
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 <span style='text-decoration: line-through'>Pesticide.io</span>)",
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 <span style='text-decoration: line-through'>Pesticide.io</span>)",
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,144 @@
(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.active) {
return ctrl.enable(item);
}
return ctrl.disable(item);
};
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,82 @@
//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) {
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 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,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 when 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";
var chalk = require("chalk");
/**
* @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: %s:%s", chalk.magenta(data.path.join(".")), chalk.cyan(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");
}