/**
* @author Jeremy England
* @license MIT
* @description Adds tabs, views, and controls to specified containers in node.js electron.
* @requires electron, jquery, color.js, electron-context-menu, url-regex
* @see https://github.com/simply-coded/electron-navigation
* @tutorial
* Add these IDs to your html (containers don't have to be divs).
*
*
*
* Add these scripts to your html (at the end of the body tag).
*
* Add a theme file to your html (at the end of the head tag)(optional).
*
*/
import WebviewTag = Electron.WebviewTag;
/**
* DEPENDENCIES
*/
var jq = require('jquery');
var Color = require('color.js');
var urlRegex = require('url-regex');
const contextMenu = require('electron-context-menu')
var globalCloseableTabsOverride;
/**
* OBJECT
*/
function Navigation(options) {
/**
* OPTIONS
*/
var defaults = {
showBackButton: true,
showForwardButton: true,
showReloadButton: true,
showUrlBar: true,
showAddTabButton: true,
closableTabs: true,
verticalTabs: false,
defaultFavicons: false,
newTabCallback: null,
changeTabCallback: null,
newTabParams: null
};
options = options ? Object.assign(defaults,options) : defaults;
/**
* GLOBALS & ICONS
*/
globalCloseableTabsOverride = options.closableTabs;
const NAV = this;
this.newTabCallback = options.newTabCallback;
this.changeTabCallback = options.changeTabCallback;
this.SESSION_ID = 1;
this.SVG_BACK = '';
this.SVG_FORWARD = '';
this.SVG_RELOAD = '';
this.SVG_FAVICON = '';
this.SVG_ADD = '';
this.SVG_CLEAR = '';
/**
* ADD ELEMENTS
*/
if (options.showBackButton) {
jq('#nav-body-ctrls').append('' + this.SVG_BACK + '');
}
if (options.showForwardButton) {
jq('#nav-body-ctrls').append('' + this.SVG_FORWARD + '');
}
if (options.showReloadButton) {
jq('#nav-body-ctrls').append('' + this.SVG_RELOAD + '');
}
if (options.showUrlBar) {
jq('#nav-body-ctrls').append('')
}
if (options.showAddTabButton) {
jq('#nav-body-tabs').append('' + this.SVG_ADD + '');
}
/**
* ADD CORE STYLE
*/
if (options.verticalTabs) {
jq('head').append('');
} else {
jq('head').append('');
}
/**
* EVENTS
*/
//
// switch active view and tab on click
//
jq('#nav-body-tabs').on('click', '.nav-tabs-tab', function () {
jq('.nav-tabs-tab, .nav-views-view').removeClass('active');
var sessionID = jq(this).data('session');
jq('.nav-tabs-tab, .nav-views-view')
.filter('[data-session="' + sessionID + '"]')
.addClass('active');
var session = jq('.nav-views-view[data-session="' + sessionID + '"]')[0];
(NAV.changeTabCallback || (() => {}))(session);
NAV._updateUrl((session as WebviewTag).getURL());
NAV._updateCtrls();
//
// close tab and view
//
}).on('click', '.nav-tabs-close', function() {
var sessionID = jq(this).parent('.nav-tabs-tab').data('session');
var session = jq('.nav-tabs-tab, .nav-views-view').filter('[data-session="' + sessionID + '"]');
if (session.hasClass('active')) {
if (session.next('.nav-tabs-tab').length) {
session.next().addClass('active');
(NAV.changeTabCallback || (() => {}))(session.next()[1]);
} else {
session.prev().addClass('active');
(NAV.changeTabCallback || (() => {}))(session.prev()[1]);
}
}
session.remove();
NAV._updateUrl();
NAV._updateCtrls();
return false;
});
//
// add a tab, default to google.com
//
jq('#nav-body-tabs').on('click', '#nav-tabs-add', function () {
let params;
if(typeof options.newTabParams === "function"){
params = options.newTabParams();
}
else if(options.newTabParams instanceof Array){
params = options.newTabParams
} else {
params = ['http://www.google.com/', {
close: options.closableTabs,
icon: NAV.TAB_ICON
}];
}
NAV.newTab(...params);
});
//
// go back
//
jq('#nav-body-ctrls').on('click', '#nav-ctrls-back', function () {
NAV.back();
});
//
// go forward
//
jq('#nav-body-ctrls').on('click', '#nav-ctrls-forward', function () {
NAV.forward();
});
//
// reload page
//
jq('#nav-body-ctrls').on('click', '#nav-ctrls-reload', function () {
if (jq(this).find('#nav-ready').length) {
NAV.reload();
} else {
NAV.stop();
}
});
//
// highlight address input text on first select
//
jq('#nav-ctrls-url').on('focus', function (e) {
jq(this)
.one('mouseup', function () {
jq(this).select();
return false;
})
.select();
});
//
// load or search address on enter / shift+enter
//
jq('#nav-ctrls-url').keyup(function (this: HTMLInputElement, e) {
if (e.keyCode == 13) {
if (e.shiftKey) {
NAV.newTab(this.value, {
close: options.closableTabs,
icon: NAV.TAB_ICON
});
} else {
if (jq('.nav-tabs-tab').length) {
NAV.changeTab(this.value);
} else {
NAV.newTab(this.value, {
close: options.closableTabs,
icon: NAV.TAB_ICON
});
}
}
}
});
/**
* FUNCTIONS
*/
//
// update controls like back, forward, etc...
//
this._updateCtrls = function () {
let webview = jq('.nav-views-view.active')[0] as WebviewTag;
if (!webview) {
jq('#nav-ctrls-back').addClass('disabled');
jq('#nav-ctrls-forward').addClass('disabled');
jq('#nav-ctrls-reload').html(this.SVG_RELOAD).addClass('disabled');
return;
}
if (webview.canGoBack()) {
jq('#nav-ctrls-back').removeClass('disabled');
} else {
jq('#nav-ctrls-back').addClass('disabled');
}
if (webview.canGoForward()) {
jq('#nav-ctrls-forward').removeClass('disabled');
} else {
jq('#nav-ctrls-forward').addClass('disabled');
}
if (webview.isLoading()) {
this._loading();
} else {
this._stopLoading();
}
if (webview.getAttribute('data-readonly') == 'true') {
jq('#nav-ctrls-url').attr('readonly', 'readonly');
} else {
jq('#nav-ctrls-url').removeAttr('readonly');
}
} //:_updateCtrls()
//
// start loading animations
//
this._loading = function (tab) {
tab = tab || null;
if (tab == null) {
tab = jq('.nav-tabs-tab.active');
}
tab.find('.nav-tabs-favicon').css('animation', 'nav-spin 2s linear infinite');
jq('#nav-ctrls-reload').html(this.SVG_CLEAR);
} //:_loading()
//
// stop loading animations
//
this._stopLoading = function (tab) {
tab = tab || null;
if (tab == null) {
tab = jq('.nav-tabs-tab.active');
}
tab.find('.nav-tabs-favicon').css('animation', '');
jq('#nav-ctrls-reload').html(this.SVG_RELOAD);
} //:_stopLoading()
//
// auto add http protocol to url input or do a search
//
this._purifyUrl = function (url) {
if (urlRegex({
strict: false,
exact: true
}).test(url)) {
url = (url.match(/^https?:\/\/.*/)) ? url : 'http://' + url;
} else {
url = (!url.match(/^[a-zA-Z]+:\/\//)) ? 'https://www.google.com/search?q=' + url.replace(' ', '+') : url;
}
return url;
} //:_purifyUrl()
//
// set the color of the tab based on the favicon
//
this._setTabColor = function (url, currtab) {
const getHexColor = new Color(url, {
amount: 1,
format: 'hex'
});
getHexColor.mostUsed(result => {
currtab.find('.nav-tabs-favicon svg').attr('fill', result);
});
} //:_setTabColor()
//
// add event listeners to current webview
//
this._addEvents = function (sessionID, options) {
let currtab = jq('.nav-tabs-tab[data-session="' + sessionID + '"]');
let webview = jq('.nav-views-view[data-session="' + sessionID + '"]') as JQuery;
webview.on('dom-ready', function () {
if (options.contextMenu) {
contextMenu({
window: webview[0],
labels: {
cut: 'Cut',
copy: 'Copy',
paste: 'Paste',
save: 'Save',
copyLink: 'Copy Link',
inspect: 'Inspect'
}
});
}
});
webview.on('page-title-updated', function () {
if (options.title == 'default') {
currtab.find('.nav-tabs-title').text(webview[0].getTitle());
currtab.find('.nav-tabs-title').attr('title', webview[0].getTitle());
}
});
webview.on('did-start-loading', function () {
NAV._loading(currtab);
});
webview.on('did-stop-loading', function () {
NAV._stopLoading(currtab);
});
webview.on('enter-html-full-screen', function () {
jq('.nav-views-view.active').siblings().not('script').hide();
jq('.nav-views-view.active').parents().not('script').siblings().hide();
});
webview.on('leave-html-full-screen', function () {
jq('.nav-views-view.active').siblings().not('script').show();
jq('.nav-views-view.active').parents().siblings().not('script').show();
});
webview.on('load-commit', function () {
NAV._updateCtrls();
});
webview[0].addEventListener('did-navigate', (res) => {
if(currtab[0] === jq('.nav-tabs-tab.active')[0])
NAV._updateUrl(res.url);
});
webview[0].addEventListener('did-fail-load', (res) => {
if(currtab[0] === jq('.nav-tabs-tab.active')[0])
NAV._updateUrl(res.validatedURL);
});
webview[0].addEventListener('did-navigate-in-page', (res) => {
if(currtab[0] === jq('.nav-tabs-tab.active')[0])
NAV._updateUrl(res.url);
});
webview[0].addEventListener("new-window", (res) => {
if (!(options.newWindowFrameNameBlacklistExpression instanceof RegExp && options.newWindowFrameNameBlacklistExpression.test(res.frameName))) {
NAV.newTab(res.url, {
icon: NAV.TAB_ICON
});
}
});
webview[0].addEventListener('page-favicon-updated', (res) => {
currtab.find('.nav-tabs-favicon').replaceWith(jq(''));
});
webview[0].addEventListener('did-fail-load', (res) => {
if (res.validatedURL == jq('#nav-ctrls-url').val() && res.errorCode != -3) {
this.executeJavaScript('document.body.innerHTML=' +
'