add files
29
assets/wvbrowser_ui/commands.js
Normal file
@ -0,0 +1,29 @@
|
||||
const commands = {
|
||||
MG_NAVIGATE: 1,
|
||||
MG_UPDATE_URI: 2,
|
||||
MG_GO_FORWARD: 3,
|
||||
MG_GO_BACK: 4,
|
||||
MG_NAV_STARTING: 5,
|
||||
MG_NAV_COMPLETED: 6,
|
||||
MG_RELOAD: 7,
|
||||
MG_CANCEL: 8,
|
||||
MG_CREATE_TAB: 10,
|
||||
MG_UPDATE_TAB: 11,
|
||||
MG_SWITCH_TAB: 12,
|
||||
MG_CLOSE_TAB: 13,
|
||||
MG_CLOSE_WINDOW: 14,
|
||||
MG_SHOW_OPTIONS: 15,
|
||||
MG_HIDE_OPTIONS: 16,
|
||||
MG_OPTIONS_LOST_FOCUS: 17,
|
||||
MG_OPTION_SELECTED: 18,
|
||||
MG_SECURITY_UPDATE: 19,
|
||||
MG_UPDATE_FAVICON: 20,
|
||||
MG_GET_SETTINGS: 21,
|
||||
MG_GET_FAVORITES: 22,
|
||||
MG_REMOVE_FAVORITE: 23,
|
||||
MG_CLEAR_CACHE: 24,
|
||||
MG_CLEAR_COOKIES: 25,
|
||||
MG_GET_HISTORY: 26,
|
||||
MG_REMOVE_HISTORY_ITEM: 27,
|
||||
MG_CLEAR_HISTORY: 28
|
||||
};
|
||||
16
assets/wvbrowser_ui/content_ui/favorites.html
Normal file
@ -0,0 +1,16 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Favorites</title>
|
||||
<link rel="shortcut icon" href="img/favorites.png">
|
||||
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||
<link rel="stylesheet" type="text/css" href="items.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="main-title">Favorites</h1>
|
||||
<div id="entries-container">
|
||||
You don't have any favorites.
|
||||
</div>
|
||||
<script src="../commands.js"></script>
|
||||
<script src="favorites.js"></script>
|
||||
<body>
|
||||
</html>
|
||||
95
assets/wvbrowser_ui/content_ui/favorites.js
Normal file
@ -0,0 +1,95 @@
|
||||
const messageHandler = event => {
|
||||
var message = event.data.message;
|
||||
var args = event.data.args;
|
||||
|
||||
switch (message) {
|
||||
case commands.MG_GET_FAVORITES:
|
||||
loadFavorites(args.favorites);
|
||||
break;
|
||||
default:
|
||||
console.log(`Unexpected message: ${JSON.stringify(event.data)}`);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
function requestFavorites() {
|
||||
let message = {
|
||||
message: commands.MG_GET_FAVORITES,
|
||||
args: {}
|
||||
};
|
||||
|
||||
window.chrome.webview.postMessage(message);
|
||||
}
|
||||
|
||||
function removeFavorite(uri) {
|
||||
let message = {
|
||||
message: commands.MG_REMOVE_FAVORITE,
|
||||
args: {
|
||||
uri: uri
|
||||
}
|
||||
};
|
||||
|
||||
window.chrome.webview.postMessage(message);
|
||||
}
|
||||
|
||||
function loadFavorites(payload) {
|
||||
let fragment = document.createDocumentFragment();
|
||||
|
||||
if (payload.length > 0) {
|
||||
let container = document.getElementById('entries-container');
|
||||
container.textContent = '';
|
||||
}
|
||||
|
||||
payload.map(favorite => {
|
||||
let favoriteContainer = document.createElement('div');
|
||||
favoriteContainer.className = 'item-container';
|
||||
let favoriteElement = document.createElement('div');
|
||||
favoriteElement.className = 'item';
|
||||
|
||||
let faviconElement = document.createElement('div');
|
||||
faviconElement.className = 'favicon';
|
||||
let faviconImage = document.createElement('img');
|
||||
faviconImage.src = favorite.favicon;
|
||||
faviconElement.appendChild(faviconImage);
|
||||
|
||||
let labelElement = document.createElement('div');
|
||||
labelElement.className = 'label-title';
|
||||
let linkElement = document.createElement('a');
|
||||
linkElement.textContent = favorite.title;
|
||||
linkElement.href = favorite.uri;
|
||||
linkElement.title = favorite.title;
|
||||
labelElement.appendChild(linkElement);
|
||||
|
||||
let uriElement = document.createElement('div');
|
||||
uriElement.className = 'label-uri';
|
||||
let textElement = document.createElement('p');
|
||||
textElement.textContent = favorite.uriToShow || favorite.uri;
|
||||
textElement.title = favorite.uriToShow || favorite.uri;
|
||||
uriElement.appendChild(textElement);
|
||||
|
||||
let buttonElement = document.createElement('div');
|
||||
buttonElement.className = 'btn-close';
|
||||
buttonElement.addEventListener('click', function(e) {
|
||||
favoriteContainer.parentNode.removeChild(favoriteContainer);
|
||||
removeFavorite(favorite.uri);
|
||||
});
|
||||
|
||||
favoriteElement.appendChild(faviconElement);
|
||||
favoriteElement.appendChild(labelElement);
|
||||
favoriteElement.appendChild(uriElement);
|
||||
favoriteElement.appendChild(buttonElement);
|
||||
|
||||
favoriteContainer.appendChild(favoriteElement);
|
||||
fragment.appendChild(favoriteContainer);
|
||||
});
|
||||
|
||||
let container = document.getElementById('entries-container');
|
||||
container.appendChild(fragment);
|
||||
}
|
||||
|
||||
function init() {
|
||||
window.chrome.webview.addEventListener('message', messageHandler);
|
||||
requestFavorites();
|
||||
}
|
||||
|
||||
init();
|
||||
81
assets/wvbrowser_ui/content_ui/history.css
Normal file
@ -0,0 +1,81 @@
|
||||
.header-date {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: rgb(16, 16, 16);
|
||||
line-height: 20px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 4px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#btn-clear {
|
||||
font-size: 14px;
|
||||
color: rgb(0, 97, 171);
|
||||
cursor: pointer;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
#btn-clear.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
#overlay.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#prompt-box {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
left: calc(50% - 130px);
|
||||
top: calc(50% - 70px);
|
||||
width: 260px;
|
||||
height: 140px;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
background-color: white;
|
||||
|
||||
box-shadow: rgba(0, 0, 0, 0.13) 0px 1.6px 20px, rgba(0, 0, 0, 0.11) 0px 0.3px 10px;
|
||||
}
|
||||
|
||||
#prompt-options {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.prompt-btn {
|
||||
flex: 1;
|
||||
flex-grow: 0;
|
||||
align-self: flex-end;
|
||||
cursor: pointer;
|
||||
font-family: 'system-ui', sans-serif;
|
||||
display: inline-block;
|
||||
padding: 2px 7px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
border-radius: 3px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
#prompt-true {
|
||||
background-color: rgb(0, 112, 198);
|
||||
color: white;
|
||||
}
|
||||
|
||||
#prompt-false {
|
||||
background-color: rgb(210, 210, 210);
|
||||
margin-right: 5px;
|
||||
}
|
||||
30
assets/wvbrowser_ui/content_ui/history.html
Normal file
@ -0,0 +1,30 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>History</title>
|
||||
<link rel="shortcut icon" href="img/history.png">
|
||||
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||
<link rel="stylesheet" type="text/css" href="items.css">
|
||||
<link rel="stylesheet" type="text/css" href="history.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="overlay" class="hidden">
|
||||
<div id="prompt-box">
|
||||
<span class="prompt-text">Clear history?</span>
|
||||
<div id="prompt-options">
|
||||
<div class="prompt-btn" id="prompt-false">Cancel</div>
|
||||
<div class="prompt-btn" id="prompt-true">Clear</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="main-title">History</h1>
|
||||
<div>
|
||||
<span id="btn-clear" class="hidden">Clear history</span>
|
||||
</div>
|
||||
<div id="entries-container">
|
||||
Loading...
|
||||
</div>
|
||||
|
||||
<script src="../commands.js"></script>
|
||||
<script src="history.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
273
assets/wvbrowser_ui/content_ui/history.js
Normal file
@ -0,0 +1,273 @@
|
||||
const DEFAULT_HISTORY_ITEM_COUNT = 20;
|
||||
const EMPTY_HISTORY_MESSAGE = `You haven't visited any sites yet.`;
|
||||
let requestedTop = 0;
|
||||
let lastRequestSize = 0;
|
||||
let itemHeight = 48;
|
||||
|
||||
const dateStringFormat = new Intl.DateTimeFormat('default', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
|
||||
const timeStringFormat = new Intl.DateTimeFormat('default', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
|
||||
const messageHandler = event => {
|
||||
var message = event.data.message;
|
||||
var args = event.data.args;
|
||||
|
||||
switch (message) {
|
||||
case commands.MG_GET_HISTORY:
|
||||
let entriesContainer = document.getElementById('entries-container');
|
||||
if (args.from == 0 && args.items.length) {
|
||||
entriesContainer.textContent = '';
|
||||
|
||||
let clearButton = document.getElementById('btn-clear');
|
||||
clearButton.classList.remove('hidden');
|
||||
}
|
||||
|
||||
loadItems(args.items);
|
||||
if (args.items.length == lastRequestSize) {
|
||||
document.addEventListener('scroll', requestTrigger);
|
||||
} else if (entriesContainer.childElementCount == 0) {
|
||||
loadUIForEmptyHistory();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.log(`Unexpected message: ${JSON.stringify(event.data)}`);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const requestTrigger = function(event) {
|
||||
let triggerRange = 50;
|
||||
let element = document.body;
|
||||
|
||||
if (element.scrollTop + element.clientHeight >= element.scrollHeight - triggerRange) {
|
||||
getMoreHistoryItems();
|
||||
event.target.removeEventListener('scroll', requestTrigger);
|
||||
}
|
||||
};
|
||||
|
||||
function requestHistoryItems(from, count) {
|
||||
let message = {
|
||||
message: commands.MG_GET_HISTORY,
|
||||
args: {
|
||||
from: from,
|
||||
count: count || DEFAULT_HISTORY_ITEM_COUNT
|
||||
}
|
||||
};
|
||||
|
||||
window.chrome.webview.postMessage(message);
|
||||
}
|
||||
|
||||
function removeItem(id) {
|
||||
let message = {
|
||||
message: commands.MG_REMOVE_HISTORY_ITEM,
|
||||
args: {
|
||||
id: id
|
||||
}
|
||||
};
|
||||
|
||||
window.chrome.webview.postMessage(message);
|
||||
}
|
||||
|
||||
function createItemElement(item, id, date) {
|
||||
let itemContainer = document.createElement('div');
|
||||
itemContainer.id = id;
|
||||
itemContainer.className = 'item-container';
|
||||
|
||||
let itemElement = document.createElement('div');
|
||||
itemElement.className = 'item';
|
||||
|
||||
// Favicon
|
||||
let faviconElement = document.createElement('div');
|
||||
faviconElement.className = 'favicon';
|
||||
let faviconImage = document.createElement('img');
|
||||
faviconImage.src = item.favicon;
|
||||
faviconElement.append(faviconImage);
|
||||
itemElement.append(faviconElement);
|
||||
|
||||
// Title
|
||||
let titleLabel = document.createElement('div');
|
||||
titleLabel.className = 'label-title';
|
||||
let linkElement = document.createElement('a');
|
||||
linkElement.href = item.uri;
|
||||
linkElement.title = item.title;
|
||||
linkElement.textContent = item.title;
|
||||
titleLabel.append(linkElement);
|
||||
itemElement.append(titleLabel);
|
||||
|
||||
// URI
|
||||
let uriLabel = document.createElement('div');
|
||||
uriLabel.className = 'label-uri';
|
||||
let textElement = document.createElement('p');
|
||||
textElement.title = item.uri;
|
||||
textElement.textContent = item.uri;
|
||||
uriLabel.append(textElement);
|
||||
itemElement.append(uriLabel);
|
||||
|
||||
// Time
|
||||
let timeLabel = document.createElement('div');
|
||||
timeLabel.className = 'label-time';
|
||||
let timeText = document.createElement('p');
|
||||
timeText.textContent = timeStringFormat.format(date);
|
||||
timeLabel.append(timeText);
|
||||
itemElement.append(timeLabel);
|
||||
|
||||
// Close button
|
||||
let closeButton = document.createElement('div');
|
||||
closeButton.className = 'btn-close';
|
||||
closeButton.addEventListener('click', function(e) {
|
||||
if (itemContainer.parentNode.children.length <= 2) {
|
||||
itemContainer.parentNode.remove();
|
||||
} else {
|
||||
itemContainer.remove();
|
||||
}
|
||||
|
||||
let entriesContainer = document.getElementById('entries-container');
|
||||
if (entriesContainer.childElementCount == 0) {
|
||||
loadUIForEmptyHistory();
|
||||
}
|
||||
removeItem(parseInt(id.split('-')[1]));
|
||||
});
|
||||
itemElement.append(closeButton);
|
||||
itemContainer.append(itemElement);
|
||||
|
||||
return itemContainer;
|
||||
}
|
||||
|
||||
function createDateContainer(id, date) {
|
||||
let dateContainer = document.createElement('div');
|
||||
dateContainer.id = id;
|
||||
|
||||
let dateLabel = document.createElement('h3');
|
||||
dateLabel.className = 'header-date';
|
||||
dateLabel.textContent = dateStringFormat.format(date);
|
||||
dateContainer.append(dateLabel);
|
||||
|
||||
return dateContainer;
|
||||
}
|
||||
|
||||
function loadItems(items) {
|
||||
let dateContainer;
|
||||
let fragment;
|
||||
|
||||
items.map((entry) => {
|
||||
let id = entry.id;
|
||||
let item = entry.item;
|
||||
let itemContainerId = `item-${id}`;
|
||||
|
||||
// Skip the item if already loaded. This could happen if the user
|
||||
// visits an item for the current date again before requesting more
|
||||
// history items.
|
||||
let itemContainer = document.getElementById(itemContainerId);
|
||||
if (itemContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
let date = new Date(item.timestamp);
|
||||
let day = date.getDate();
|
||||
let month = date.getMonth();
|
||||
let year = date.getFullYear();
|
||||
let dateContainerId = `entries-${month}-${day}-${year}`;
|
||||
|
||||
// If entry belongs to a new date, append buffered items for previous
|
||||
// date.
|
||||
if (dateContainer && dateContainer.id != dateContainerId) {
|
||||
dateContainer.append(fragment);
|
||||
}
|
||||
|
||||
dateContainer = document.getElementById(dateContainerId);
|
||||
if (!dateContainer) {
|
||||
dateContainer = createDateContainer(dateContainerId, date);
|
||||
fragment = document.createDocumentFragment();
|
||||
|
||||
let entriesContainer = document.getElementById('entries-container');
|
||||
entriesContainer.append(dateContainer);
|
||||
} else if (!fragment) {
|
||||
fragment = document.createDocumentFragment();
|
||||
}
|
||||
|
||||
itemContainer = createItemElement(item, itemContainerId, date);
|
||||
fragment.append(itemContainer);
|
||||
});
|
||||
|
||||
// Append remaining items in buffer
|
||||
if (fragment) {
|
||||
dateContainer.append(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
function getMoreHistoryItems(n) {
|
||||
n = n ? n : DEFAULT_HISTORY_ITEM_COUNT;
|
||||
|
||||
requestHistoryItems(requestedTop, n);
|
||||
requestedTop += n;
|
||||
lastRequestSize = n;
|
||||
document.removeEventListener('scroll', requestTrigger);
|
||||
}
|
||||
|
||||
function addUIListeners() {
|
||||
let confirmButton = document.getElementById('prompt-true');
|
||||
confirmButton.addEventListener('click', function(event) {
|
||||
clearHistory();
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
let cancelButton = document.getElementById('prompt-false');
|
||||
cancelButton.addEventListener('click', function(event) {
|
||||
toggleClearPrompt();
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
let promptBox = document.getElementById('prompt-box');
|
||||
promptBox.addEventListener('click', function(event) {
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
let promptOverlay = document.getElementById('overlay');
|
||||
promptOverlay.addEventListener('click', toggleClearPrompt);
|
||||
|
||||
let clearButton = document.getElementById('btn-clear');
|
||||
clearButton.addEventListener('click', toggleClearPrompt);
|
||||
}
|
||||
|
||||
function toggleClearPrompt() {
|
||||
let promptOverlay = document.getElementById('overlay');
|
||||
promptOverlay.classList.toggle('hidden');
|
||||
}
|
||||
|
||||
function loadUIForEmptyHistory() {
|
||||
let entriesContainer = document.getElementById('entries-container');
|
||||
entriesContainer.textContent = EMPTY_HISTORY_MESSAGE;
|
||||
|
||||
let clearButton = document.getElementById('btn-clear');
|
||||
clearButton.classList.add('hidden');
|
||||
}
|
||||
|
||||
function clearHistory() {
|
||||
toggleClearPrompt();
|
||||
loadUIForEmptyHistory();
|
||||
|
||||
let message = {
|
||||
message: commands.MG_CLEAR_HISTORY,
|
||||
args: {}
|
||||
};
|
||||
|
||||
window.chrome.webview.postMessage(message);
|
||||
}
|
||||
|
||||
function init() {
|
||||
window.chrome.webview.addEventListener('message', messageHandler);
|
||||
|
||||
let viewportItemsCapacity = Math.round(window.innerHeight / itemHeight);
|
||||
addUIListeners();
|
||||
getMoreHistoryItems(viewportItemsCapacity);
|
||||
}
|
||||
|
||||
init();
|
||||
BIN
assets/wvbrowser_ui/content_ui/img/close.png
Normal file
|
After Width: | Height: | Size: 715 B |
BIN
assets/wvbrowser_ui/content_ui/img/favorites.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
assets/wvbrowser_ui/content_ui/img/history.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
assets/wvbrowser_ui/content_ui/img/settings.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
92
assets/wvbrowser_ui/content_ui/items.css
Normal file
@ -0,0 +1,92 @@
|
||||
.item-container {
|
||||
box-sizing: border-box;
|
||||
padding: 4px 0;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: 820px;
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: rgba(0, 0, 0, 0.13) 0px 1.6px 3.6px, rgba(0, 0, 0, 0.11) 0px 0.3px 0.9px;
|
||||
align-items: center;
|
||||
background: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
box-shadow: 0px 4.8px 10.8px rgba(0,0,0,0.13), 0px 0.9px 2.7px rgba(0,0,0,0.11);
|
||||
}
|
||||
|
||||
.favicon {
|
||||
display: flex;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.favicon img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.label-title, .label-uri {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 40px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.label-title a {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgb(16, 16, 16);
|
||||
border-width: initial;
|
||||
border-style: none;
|
||||
border-color: initial;
|
||||
border-image: initial;
|
||||
overflow: hidden;
|
||||
background: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.label-title a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.label-uri, .label-time {
|
||||
margin: 0 6px;
|
||||
line-height: 16px;
|
||||
font-size: 12px;
|
||||
color: rgb(115, 115, 115);
|
||||
}
|
||||
|
||||
.label-uri p, .label-time p {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
margin: 6px;
|
||||
border-radius: 2px;
|
||||
background-image: url('img/close.png');
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.btn-close:hover {
|
||||
background-color: rgb(243, 243, 243);
|
||||
}
|
||||
54
assets/wvbrowser_ui/content_ui/settings.css
Normal file
@ -0,0 +1,54 @@
|
||||
.settings-entry {
|
||||
display: block;
|
||||
height: 48px;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 4px 0;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#entry-script, #entry-popups {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.entry {
|
||||
display: block;
|
||||
height: 100%;
|
||||
text-align: left;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.entry:hover {
|
||||
background-color: rgb(220, 220, 220);
|
||||
}
|
||||
|
||||
.entry:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.entry-name, .entry-value {
|
||||
display: inline-flex;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.entry-name span, .entry-value span {
|
||||
flex: 1;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.entry-name {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.entry-value {
|
||||
float: right;
|
||||
vertical-align: middle;
|
||||
margin: 0 15px;
|
||||
font-size: 0.8em;
|
||||
color: gray;
|
||||
}
|
||||
56
assets/wvbrowser_ui/content_ui/settings.html
Normal file
@ -0,0 +1,56 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Settings</title>
|
||||
<link rel="shortcut icon" href="img/settings.png">
|
||||
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||
<link rel="stylesheet" type="text/css" href="settings.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="main-title">Settings</h1>
|
||||
<div class="page-content">
|
||||
<button class="settings-entry" id="entry-cache">
|
||||
<div class="entry">
|
||||
<div class="entry-name">
|
||||
<span>Clear cache</span>
|
||||
</div>
|
||||
<div class="entry-value">
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<button class="settings-entry" id="entry-cookies">
|
||||
<div class="entry">
|
||||
<div class="entry-name">
|
||||
<span>Clear cookies</span>
|
||||
</div>
|
||||
<div class="entry-value">
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<button class="settings-entry" id="entry-script">
|
||||
<div class="entry">
|
||||
<div class="entry-name">
|
||||
<span>Toggle JavaScript</span>
|
||||
</div>
|
||||
<div class="entry-value">
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<button class="settings-entry" id="entry-popups">
|
||||
<div class="entry">
|
||||
<div class="entry-name">
|
||||
<span>Block pop-ups</span>
|
||||
</div>
|
||||
<div class="entry-value">
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<script src="../commands.js"></script>
|
||||
<script src="settings.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
107
assets/wvbrowser_ui/content_ui/settings.js
Normal file
@ -0,0 +1,107 @@
|
||||
const messageHandler = event => {
|
||||
var message = event.data.message;
|
||||
var args = event.data.args;
|
||||
|
||||
switch (message) {
|
||||
case commands.MG_GET_SETTINGS:
|
||||
loadSettings(args.settings);
|
||||
break;
|
||||
case commands.MG_CLEAR_CACHE:
|
||||
if (args.content && args.controls) {
|
||||
updateLabelForEntry('entry-cache', 'Cleared');
|
||||
} else {
|
||||
updateLabelForEntry('entry-cache', 'Try again');
|
||||
}
|
||||
break;
|
||||
case commands.MG_CLEAR_COOKIES:
|
||||
if (args.content && args.controls) {
|
||||
updateLabelForEntry('entry-cookies', 'Cleared');
|
||||
} else {
|
||||
updateLabelForEntry('entry-cookies', 'Try again');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.log(`Unexpected message: ${JSON.stringify(event.data)}`);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
function addEntriesListeners() {
|
||||
let cacheEntry = document.getElementById('entry-cache');
|
||||
cacheEntry.addEventListener('click', function(e) {
|
||||
let message = {
|
||||
message: commands.MG_CLEAR_CACHE,
|
||||
args: {}
|
||||
};
|
||||
|
||||
window.chrome.webview.postMessage(message);
|
||||
});
|
||||
|
||||
let cookiesEntry = document.getElementById('entry-cookies');
|
||||
cookiesEntry.addEventListener('click', function(e) {
|
||||
let message = {
|
||||
message: commands.MG_CLEAR_COOKIES,
|
||||
args: {}
|
||||
};
|
||||
|
||||
window.chrome.webview.postMessage(message);
|
||||
});
|
||||
|
||||
let scriptEntry = document.getElementById('entry-script');
|
||||
scriptEntry.addEventListener('click', function(e) {
|
||||
// Toggle script support
|
||||
});
|
||||
|
||||
let popupsEntry = document.getElementById('entry-popups');
|
||||
popupsEntry.addEventListener('click', function(e) {
|
||||
// Toggle popups
|
||||
});
|
||||
}
|
||||
|
||||
function requestBrowserSettings() {
|
||||
let message = {
|
||||
message: commands.MG_GET_SETTINGS,
|
||||
args: {}
|
||||
};
|
||||
|
||||
window.chrome.webview.postMessage(message);
|
||||
}
|
||||
|
||||
function loadSettings(settings) {
|
||||
if (settings.scriptsEnabled) {
|
||||
updateLabelForEntry('entry-script', 'Enabled');
|
||||
} else {
|
||||
updateLabelForEntry('entry-script', 'Disabled');
|
||||
}
|
||||
|
||||
if (settings.blockPopups) {
|
||||
updateLabelForEntry('entry-popups', 'Blocked');
|
||||
} else {
|
||||
updateLabelForEntry('entry-popups', 'Allowed');
|
||||
}
|
||||
}
|
||||
|
||||
function updateLabelForEntry(elementId, label) {
|
||||
let entryElement = document.getElementById(elementId);
|
||||
if (!entryElement) {
|
||||
console.log(`Element with id ${elementId} does not exist`);
|
||||
return;
|
||||
}
|
||||
|
||||
let labelSpan = entryElement.querySelector(`.entry-value span`);
|
||||
|
||||
if (!labelSpan) {
|
||||
console.log(`${elementId} does not have a label`);
|
||||
return;
|
||||
}
|
||||
|
||||
labelSpan.textContent = label;
|
||||
}
|
||||
|
||||
function init() {
|
||||
window.chrome.webview.addEventListener('message', messageHandler);
|
||||
requestBrowserSettings();
|
||||
addEntriesListeners();
|
||||
}
|
||||
|
||||
init();
|
||||
25
assets/wvbrowser_ui/content_ui/styles.css
Normal file
@ -0,0 +1,25 @@
|
||||
html, body {
|
||||
margin: 0;
|
||||
border: none;
|
||||
padding: 0;
|
||||
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
background-color: rgb(240, 240, 242);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 32px 50px;
|
||||
font-family: 'system-ui', sans-serif;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
display: block;
|
||||
margin-bottom: 18px;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: rgb(16, 16, 16);
|
||||
}
|
||||
135
assets/wvbrowser_ui/controls_ui/address-bar.css
Normal file
@ -0,0 +1,135 @@
|
||||
#address-bar-container {
|
||||
display: flex;
|
||||
height: calc(100% - 10px);
|
||||
width: 80%;
|
||||
max-width: calc(100% - 160px);
|
||||
|
||||
background-color: white;
|
||||
border: 1px solid gray;
|
||||
border-radius: 5px;
|
||||
|
||||
position: relative;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
#address-bar-container:focus-within {
|
||||
outline: none;
|
||||
box-shadow: 0 0 3px dodgerblue;
|
||||
}
|
||||
|
||||
#address-bar-container:focus-within #btn-clear {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#security-label {
|
||||
display: inline-flex;
|
||||
height: 100%;
|
||||
margin-left: 2px;
|
||||
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#security-label span {
|
||||
font-family: Arial;
|
||||
font-size: 0.9em;
|
||||
color: gray;
|
||||
vertical-align: middle;
|
||||
flex: 1;
|
||||
align-self: center;
|
||||
text-align: left;
|
||||
padding-left: 5px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.icn {
|
||||
display: inline-block;
|
||||
margin: 2px 0;
|
||||
border-radius: 5px;
|
||||
top: 0;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
#icn-lock {
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
#security-label.label-unknown .icn {
|
||||
background-image: url('img/unknown.png');
|
||||
}
|
||||
|
||||
#security-label.label-insecure .icn {
|
||||
background-image: url('img/insecure.png');
|
||||
}
|
||||
|
||||
#security-label.label-insecure span {
|
||||
color: rgb(192, 0, 0);
|
||||
}
|
||||
|
||||
#security-label.label-neutral .icn {
|
||||
background-image: url('img/neutral.png');
|
||||
}
|
||||
|
||||
#security-label.label-secure .icn {
|
||||
background-image: url('img/secure.png');
|
||||
}
|
||||
|
||||
#security-label.label-secure span, #security-label.label-neutral span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#icn-favicon {
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
#img-favicon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
#address-form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#address-field {
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
margin: 0;
|
||||
|
||||
line-height: 30px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#address-field:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#btn-fav {
|
||||
margin: 2px 5px;
|
||||
background-size: 100%;
|
||||
background-image: url('img/favorite.png');
|
||||
}
|
||||
|
||||
#btn-fav:hover, #btn-clear:hover {
|
||||
background-color: rgb(230, 230, 230);
|
||||
}
|
||||
|
||||
#btn-fav.favorited {
|
||||
background-image: url('img/favorited.png');
|
||||
}
|
||||
|
||||
#btn-clear {
|
||||
display: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: none;
|
||||
align-self: center;
|
||||
background-color: transparent;
|
||||
background-image: url(img/cancel.png);
|
||||
background-size: 100%;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
}
|
||||
66
assets/wvbrowser_ui/controls_ui/controls.css
Normal file
@ -0,0 +1,66 @@
|
||||
#controls-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: row;
|
||||
height: 40px;
|
||||
background-color: rgb(230, 230, 230);
|
||||
}
|
||||
|
||||
.btn, .btn-disabled, .btn-cancel, .btn-active {
|
||||
display: inline-block;
|
||||
border: none;
|
||||
margin: 5px 0;
|
||||
border-radius: 5px;
|
||||
outline: none;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
#btn-forward {
|
||||
background-image: url('img/goForward.png');
|
||||
}
|
||||
|
||||
.btn-disabled#btn-forward {
|
||||
background-image: url('img/goForward_disabled.png');
|
||||
}
|
||||
|
||||
#btn-back {
|
||||
background-image: url('img/goBack.png');
|
||||
}
|
||||
|
||||
.btn-disabled#btn-back {
|
||||
background-image: url('img/goBack_disabled.png');
|
||||
}
|
||||
|
||||
#btn-reload {
|
||||
background-image: url('img/reload.png');
|
||||
}
|
||||
|
||||
.btn-cancel#btn-reload {
|
||||
background-image: url('img/cancel.png');
|
||||
}
|
||||
|
||||
#btn-options {
|
||||
background-image: url('img/options.png');
|
||||
}
|
||||
|
||||
.controls-group {
|
||||
display: inline-block;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
#nav-controls-container {
|
||||
align-self: flex-start;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
#manage-controls-container {
|
||||
align-self: flex-end;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.btn:hover, .btn-cancel:hover, .btn-active {
|
||||
background-color: rgb(200, 200, 200);
|
||||
}
|
||||
3
assets/wvbrowser_ui/controls_ui/default.css
Normal file
@ -0,0 +1,3 @@
|
||||
html, body {
|
||||
height: 70px;
|
||||
}
|
||||
17
assets/wvbrowser_ui/controls_ui/default.html
Normal file
@ -0,0 +1,17 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||
<link rel="stylesheet" type="text/css" href="default.css">
|
||||
<link rel="stylesheet" type="text/css" href="controls.css">
|
||||
<link rel="stylesheet" type="text/css" href="address-bar.css">
|
||||
<link rel="stylesheet" type="text/css" href="strip.css">
|
||||
</head>
|
||||
<body>
|
||||
<script src="../commands.js"></script>
|
||||
<script src="tabs.js"></script>
|
||||
<script src="storage.js"></script>
|
||||
<script src="favorites.js"></script>
|
||||
<script src="history.js"></script>
|
||||
<script src="default.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
736
assets/wvbrowser_ui/controls_ui/default.js
Normal file
@ -0,0 +1,736 @@
|
||||
const WORD_REGEX = /^[^//][^.]*$/;
|
||||
const VALID_URI_REGEX = /^[-:.&#+()[\]$'*;@~!,?%=\/\w]+$/; // Will check that only RFC3986 allowed characters are included
|
||||
const SCHEMED_URI_REGEX = /^\w+:.+$/;
|
||||
|
||||
let settings = {
|
||||
scriptsEnabled: true,
|
||||
blockPopups: true
|
||||
};
|
||||
|
||||
const messageHandler = event => {
|
||||
var message = event.data.message;
|
||||
var args = event.data.args;
|
||||
|
||||
switch (message) {
|
||||
case commands.MG_UPDATE_URI:
|
||||
if (isValidTabId(args.tabId)) {
|
||||
const tab = tabs.get(args.tabId);
|
||||
let previousURI = tab.uri;
|
||||
|
||||
// Update the tab state
|
||||
tab.uri = args.uri;
|
||||
tab.uriToShow = args.uriToShow;
|
||||
tab.canGoBack = args.canGoBack;
|
||||
tab.canGoForward = args.canGoForward;
|
||||
|
||||
// If the tab is active, update the controls UI
|
||||
if (args.tabId == activeTabId) {
|
||||
updateNavigationUI(message);
|
||||
}
|
||||
|
||||
isFavorite(tab.uri, (isFavorite) => {
|
||||
tab.isFavorite = isFavorite;
|
||||
updateFavoriteIcon();
|
||||
});
|
||||
|
||||
// Don't add history entry if URI has not changed
|
||||
if (tab.uri == previousURI) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Filter URIs that should not appear in history
|
||||
if (!tab.uri || tab.uri == 'about:blank') {
|
||||
tab.historyItemId = INVALID_HISTORY_ID;
|
||||
break;
|
||||
}
|
||||
|
||||
if (tab.uriToShow && tab.uriToShow.substring(0, 10) == 'browser://') {
|
||||
tab.historyItemId = INVALID_HISTORY_ID;
|
||||
break;
|
||||
}
|
||||
|
||||
addHistoryItem(historyItemFromTab(args.tabId), (id) => {
|
||||
tab.historyItemId = id;
|
||||
});
|
||||
}
|
||||
break;
|
||||
case commands.MG_NAV_STARTING:
|
||||
if (isValidTabId(args.tabId)) {
|
||||
// Update the tab state
|
||||
tabs.get(args.tabId).isLoading = true;
|
||||
|
||||
// If the tab is active, update the controls UI
|
||||
if (args.tabId == activeTabId) {
|
||||
updateNavigationUI(message);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case commands.MG_NAV_COMPLETED:
|
||||
if (isValidTabId(args.tabId)) {
|
||||
// Update tab state
|
||||
tabs.get(args.tabId).isLoading = false;
|
||||
|
||||
// If the tab is active, update the controls UI
|
||||
if (args.tabId == activeTabId) {
|
||||
updateNavigationUI(message);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case commands.MG_UPDATE_TAB:
|
||||
if (isValidTabId(args.tabId)) {
|
||||
const tab = tabs.get(args.tabId);
|
||||
const tabElement = document.getElementById(`tab-${args.tabId}`);
|
||||
|
||||
if (!tabElement) {
|
||||
refreshTabs();
|
||||
return;
|
||||
}
|
||||
|
||||
// Update tab label
|
||||
// Use given title or fall back to a generic tab title
|
||||
tab.title = args.title || 'Tab';
|
||||
const tabLabel = tabElement.firstChild;
|
||||
const tabLabelSpan = tabLabel.firstChild;
|
||||
tabLabelSpan.textContent = tab.title;
|
||||
|
||||
// Update title in history item
|
||||
// Browser pages will keep an invalid history ID
|
||||
if (tab.historyItemId != INVALID_HISTORY_ID) {
|
||||
updateHistoryItem(tab.historyItemId, historyItemFromTab(args.tabId));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case commands.MG_OPTIONS_LOST_FOCUS:
|
||||
let optionsButton = document.getElementById('btn-options');
|
||||
if (optionsButton) {
|
||||
if (optionsButton.className = 'btn-active') {
|
||||
toggleOptionsDropdown();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case commands.MG_SECURITY_UPDATE:
|
||||
if (isValidTabId(args.tabId)) {
|
||||
const tab = tabs.get(args.tabId);
|
||||
tab.securityState = args.state;
|
||||
|
||||
if (args.tabId == activeTabId) {
|
||||
updateNavigationUI(message);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case commands.MG_UPDATE_FAVICON:
|
||||
if (isValidTabId(args.tabId)) {
|
||||
updateFaviconURI(args.tabId, args.uri);
|
||||
}
|
||||
break;
|
||||
case commands.MG_CLOSE_WINDOW:
|
||||
closeWindow();
|
||||
break;
|
||||
case commands.MG_CLOSE_TAB:
|
||||
if (isValidTabId(args.tabId)) {
|
||||
closeTab(args.tabId);
|
||||
}
|
||||
break;
|
||||
case commands.MG_GET_FAVORITES:
|
||||
if (isValidTabId(args.tabId)) {
|
||||
getFavoritesAsJson((payload) => {
|
||||
args.favorites = payload;
|
||||
window.chrome.webview.postMessage(event.data);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case commands.MG_REMOVE_FAVORITE:
|
||||
removeFavorite(args.uri);
|
||||
break;
|
||||
case commands.MG_GET_SETTINGS:
|
||||
if (isValidTabId(args.tabId)) {
|
||||
args.settings = settings;
|
||||
window.chrome.webview.postMessage(event.data);
|
||||
}
|
||||
break;
|
||||
case commands.MG_GET_HISTORY:
|
||||
if (isValidTabId(args.tabId)) {
|
||||
getHistoryItems(args.from, args.count, (payload) => {
|
||||
args.items = payload;
|
||||
window.chrome.webview.postMessage(event.data);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case commands.MG_REMOVE_HISTORY_ITEM:
|
||||
removeHistoryItem(args.id);
|
||||
break;
|
||||
case commands.MG_CLEAR_HISTORY:
|
||||
clearHistory();
|
||||
break;
|
||||
default:
|
||||
console.log(`Received unexpected message: ${JSON.stringify(event.data)}`);
|
||||
}
|
||||
};
|
||||
|
||||
function processAddressBarInput() {
|
||||
var text = document.querySelector('#address-field').value;
|
||||
tryNavigate(text);
|
||||
}
|
||||
|
||||
function tryNavigate(text) {
|
||||
try {
|
||||
var uriParser = new URL(text);
|
||||
|
||||
// URL creation succeeded, verify protocol is allowed
|
||||
switch (uriParser.protocol) {
|
||||
case 'http:':
|
||||
case 'https:':
|
||||
case 'file:':
|
||||
case 'ftp:':
|
||||
// Allowed protocol, navigate
|
||||
navigateActiveTab(uriParser.href, false);
|
||||
break;
|
||||
default:
|
||||
// Protocol not allowed, search Bing
|
||||
navigateActiveTab(getSearchURI(text), true);
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
// URL creation failed, check for invalid characters
|
||||
if (containsIlegalCharacters(text) || isSingleWord(text)) {
|
||||
// Search Bing
|
||||
navigateActiveTab(getSearchURI(text), true);
|
||||
} else {
|
||||
// Try with HTTP
|
||||
if (!hasScheme(text)) {
|
||||
tryNavigate(`http:${text}`);
|
||||
} else {
|
||||
navigateActiveTab(getSearchURI(text), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function navigateActiveTab(uri, isSearch) {
|
||||
var message = {
|
||||
message: commands.MG_NAVIGATE,
|
||||
args: {
|
||||
uri: uri,
|
||||
encodedSearchURI: isSearch ? uri : getSearchURI(uri)
|
||||
}
|
||||
};
|
||||
|
||||
window.chrome.webview.postMessage(message);
|
||||
}
|
||||
|
||||
function reloadActiveTabContent() {
|
||||
var message = {
|
||||
message: commands.MG_RELOAD,
|
||||
args: {}
|
||||
};
|
||||
window.chrome.webview.postMessage(message);
|
||||
}
|
||||
|
||||
function containsIlegalCharacters(query) {
|
||||
return !VALID_URI_REGEX.test(query);
|
||||
}
|
||||
|
||||
function isSingleWord(query) {
|
||||
return WORD_REGEX.test(query);
|
||||
}
|
||||
|
||||
function hasScheme(query) {
|
||||
return SCHEMED_URI_REGEX.test(query);
|
||||
}
|
||||
|
||||
function getSearchURI(query) {
|
||||
return `https://www.bing.com/search?q=${encodeURIComponent(query)}`;
|
||||
}
|
||||
|
||||
function closeWindow() {
|
||||
var message = {
|
||||
message: commands.MG_CLOSE_WINDOW,
|
||||
args: {}
|
||||
};
|
||||
|
||||
window.chrome.webview.postMessage(message);
|
||||
}
|
||||
|
||||
// Show active tab's URI in the address bar
|
||||
function updateURI() {
|
||||
if (activeTabId == INVALID_TAB_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
let activeTab = tabs.get(activeTabId);
|
||||
document.getElementById('address-field').value = activeTab.uriToShow || activeTab.uri;
|
||||
}
|
||||
|
||||
// Show active tab's favicon in the address bar
|
||||
function updateFavicon() {
|
||||
if (activeTabId == INVALID_TAB_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
let activeTab = tabs.get(activeTabId);
|
||||
|
||||
let faviconElement = document.getElementById('img-favicon');
|
||||
if (!faviconElement) {
|
||||
refreshControls();
|
||||
return;
|
||||
}
|
||||
|
||||
faviconElement.src = activeTab.favicon;
|
||||
|
||||
}
|
||||
|
||||
// Update back and forward buttons for the active tab
|
||||
function updateBackForwardButtons() {
|
||||
if (activeTabId == INVALID_TAB_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
let activeTab = tabs.get(activeTabId);
|
||||
let btnForward = document.getElementById('btn-forward');
|
||||
let btnBack = document.getElementById('btn-back');
|
||||
|
||||
if (!btnBack || !btnForward) {
|
||||
refreshControls();
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeTab.canGoForward)
|
||||
btnForward.className = 'btn';
|
||||
else
|
||||
btnForward.className = 'btn-disabled';
|
||||
|
||||
if (activeTab.canGoBack)
|
||||
btnBack.className = 'btn';
|
||||
else
|
||||
btnBack.className = 'btn-disabled';
|
||||
}
|
||||
|
||||
// Update reload button for the active tab
|
||||
function updateReloadButton() {
|
||||
if (activeTabId == INVALID_TAB_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
let activeTab = tabs.get(activeTabId);
|
||||
|
||||
let btnReload = document.getElementById('btn-reload');
|
||||
if (!btnReload) {
|
||||
refreshControls();
|
||||
return;
|
||||
}
|
||||
|
||||
btnReload.className = activeTab.isLoading ? 'btn-cancel' : 'btn';
|
||||
}
|
||||
|
||||
// Update lock icon for the active tab
|
||||
function updateLockIcon() {
|
||||
if (activeTabId == INVALID_TAB_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
let activeTab = tabs.get(activeTabId);
|
||||
|
||||
let labelElement = document.getElementById('security-label');
|
||||
if (!labelElement) {
|
||||
refreshControls();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (activeTab.securityState) {
|
||||
case 'insecure':
|
||||
labelElement.className = 'label-insecure';
|
||||
break;
|
||||
case 'neutral':
|
||||
labelElement.className = 'label-neutral';
|
||||
break;
|
||||
case 'secure':
|
||||
labelElement.className = 'label-secure';
|
||||
break;
|
||||
default:
|
||||
labelElement.className = 'label-unknown';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Update favorite status for the active tab
|
||||
function updateFavoriteIcon() {
|
||||
if (activeTabId == INVALID_TAB_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
let activeTab = tabs.get(activeTabId);
|
||||
isFavorite(activeTab.uri, (isFavorite) => {
|
||||
let favoriteElement = document.getElementById('btn-fav');
|
||||
if (!favoriteElement) {
|
||||
refreshControls();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isFavorite) {
|
||||
favoriteElement.classList.add('favorited');
|
||||
activeTab.isFavorite = true;
|
||||
} else {
|
||||
favoriteElement.classList.remove('favorited');
|
||||
activeTab.isFavorite = false;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function updateNavigationUI(reason) {
|
||||
switch (reason) {
|
||||
case commands.MG_UPDATE_URI:
|
||||
updateURI();
|
||||
updateFavoriteIcon();
|
||||
updateBackForwardButtons();
|
||||
break;
|
||||
case commands.MG_NAV_COMPLETED:
|
||||
case commands.MG_NAV_STARTING:
|
||||
updateReloadButton();
|
||||
break;
|
||||
case commands.MG_SECURITY_UPDATE:
|
||||
updateLockIcon();
|
||||
break;
|
||||
case commands.MG_UPDATE_FAVICON:
|
||||
updateFavicon();
|
||||
break;
|
||||
// If a reason is not provided (for requests not originating from a
|
||||
// message), default to switch tab behavior.
|
||||
default:
|
||||
case commands.MG_SWITCH_TAB:
|
||||
updateURI();
|
||||
updateLockIcon();
|
||||
updateFavicon();
|
||||
updateFavoriteIcon();
|
||||
updateReloadButton();
|
||||
updateBackForwardButtons();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function loadTabUI(tabId) {
|
||||
if (isValidTabId(tabId)) {
|
||||
let tab = tabs.get(tabId);
|
||||
|
||||
let tabElement = document.createElement('div');
|
||||
tabElement.className = tabId == activeTabId ? 'tab-active' : 'tab';
|
||||
tabElement.id = `tab-${tabId}`;
|
||||
|
||||
let tabLabel = document.createElement('div');
|
||||
tabLabel.className = 'tab-label';
|
||||
|
||||
let labelText = document.createElement('span');
|
||||
labelText.textContent = tab.title;
|
||||
tabLabel.appendChild(labelText);
|
||||
|
||||
let closeButton = document.createElement('div');
|
||||
closeButton.className = 'btn-tab-close';
|
||||
closeButton.addEventListener('click', function(e) {
|
||||
closeTab(tabId);
|
||||
});
|
||||
|
||||
tabElement.appendChild(tabLabel);
|
||||
tabElement.appendChild(closeButton);
|
||||
|
||||
var createTabButton = document.getElementById('btn-new-tab');
|
||||
document.getElementById('tabs-strip').insertBefore(tabElement, createTabButton);
|
||||
|
||||
tabElement.addEventListener('click', function(e) {
|
||||
if (e.srcElement.className != 'btn-tab-close') {
|
||||
switchToTab(tabId, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function toggleOptionsDropdown() {
|
||||
const optionsButtonElement = document.getElementById('btn-options');
|
||||
const elementClass = optionsButtonElement.className;
|
||||
|
||||
var message;
|
||||
if (elementClass === 'btn') {
|
||||
// Update UI
|
||||
optionsButtonElement.className = 'btn-active';
|
||||
|
||||
message = {
|
||||
message: commands.MG_SHOW_OPTIONS,
|
||||
args: {}
|
||||
};
|
||||
} else {
|
||||
// Update UI
|
||||
optionsButtonElement.className = 'btn';
|
||||
|
||||
message = {
|
||||
message:commands.MG_HIDE_OPTIONS,
|
||||
args: {}
|
||||
};
|
||||
}
|
||||
|
||||
window.chrome.webview.postMessage(message);
|
||||
}
|
||||
|
||||
function refreshControls() {
|
||||
let controlsElement = document.getElementById('controls-bar');
|
||||
if (controlsElement) {
|
||||
controlsElement.remove();
|
||||
}
|
||||
|
||||
controlsElement = document.createElement('div');
|
||||
controlsElement.id = 'controls-bar';
|
||||
|
||||
// Navigation controls
|
||||
let navControls = document.createElement('div');
|
||||
navControls.className = 'controls-group';
|
||||
navControls.id = 'nav-controls-container';
|
||||
|
||||
let backButton = document.createElement('div');
|
||||
backButton.className = 'btn-disabled';
|
||||
backButton.id = 'btn-back';
|
||||
navControls.append(backButton);
|
||||
|
||||
let forwardButton = document.createElement('div');
|
||||
forwardButton.className = 'btn-disabled';
|
||||
forwardButton.id = 'btn-forward';
|
||||
navControls.append(forwardButton);
|
||||
|
||||
let reloadButton = document.createElement('div');
|
||||
reloadButton.className = 'btn';
|
||||
reloadButton.id = 'btn-reload';
|
||||
navControls.append(reloadButton);
|
||||
|
||||
controlsElement.append(navControls);
|
||||
|
||||
// Address bar
|
||||
let addressBar = document.createElement('div');
|
||||
addressBar.id = 'address-bar-container';
|
||||
|
||||
let securityLabel = document.createElement('div');
|
||||
securityLabel.className = 'label-unknown';
|
||||
securityLabel.id = 'security-label';
|
||||
|
||||
let labelSpan = document.createElement('span');
|
||||
labelSpan.textContent = 'Not secure';
|
||||
securityLabel.append(labelSpan);
|
||||
|
||||
let lockIcon = document.createElement('div');
|
||||
lockIcon.className = 'icn';
|
||||
lockIcon.id = 'icn-lock';
|
||||
securityLabel.append(lockIcon);
|
||||
addressBar.append(securityLabel);
|
||||
|
||||
let faviconElement = document.createElement('div');
|
||||
faviconElement.className = 'icn';
|
||||
faviconElement.id = 'icn-favicon';
|
||||
|
||||
let faviconImage = document.createElement('img');
|
||||
faviconImage.id = 'img-favicon';
|
||||
faviconImage.src = 'img/favicon.png';
|
||||
faviconElement.append(faviconImage);
|
||||
addressBar.append(faviconElement);
|
||||
|
||||
let addressInput = document.createElement('input');
|
||||
addressInput.id = 'address-field';
|
||||
addressInput.placeholder = 'Search or enter web address';
|
||||
addressInput.type = 'text';
|
||||
addressInput.spellcheck = false;
|
||||
addressBar.append(addressInput);
|
||||
|
||||
let clearButton = document.createElement('button');
|
||||
clearButton.id = 'btn-clear';
|
||||
addressBar.append(clearButton);
|
||||
|
||||
let favoriteButton = document.createElement('div');
|
||||
favoriteButton.className = 'icn';
|
||||
favoriteButton.id = 'btn-fav';
|
||||
addressBar.append(favoriteButton);
|
||||
controlsElement.append(addressBar);
|
||||
|
||||
// Manage controls
|
||||
let manageControls = document.createElement('div');
|
||||
manageControls.className = 'controls-group';
|
||||
manageControls.id = 'manage-controls-container';
|
||||
|
||||
let optionsButton = document.createElement('div');
|
||||
optionsButton.className = 'btn';
|
||||
optionsButton.id = 'btn-options';
|
||||
manageControls.append(optionsButton);
|
||||
controlsElement.append(manageControls);
|
||||
|
||||
// Insert controls bar into document
|
||||
let tabsElement = document.getElementById('tabs-strip');
|
||||
if (tabsElement) {
|
||||
tabsElement.parentElement.insertBefore(controlsElement, tabsElement);
|
||||
} else {
|
||||
let bodyElement = document.getElementsByTagName('body')[0];
|
||||
bodyElement.append(controlsElement);
|
||||
}
|
||||
|
||||
addControlsListeners();
|
||||
updateNavigationUI();
|
||||
}
|
||||
|
||||
function refreshTabs() {
|
||||
let tabsStrip = document.getElementById('tabs-strip');
|
||||
if (tabsStrip) {
|
||||
tabsStrip.remove();
|
||||
}
|
||||
|
||||
tabsStrip = document.createElement('div');
|
||||
tabsStrip.id = 'tabs-strip';
|
||||
|
||||
let newTabButton = document.createElement('div');
|
||||
newTabButton.id = 'btn-new-tab';
|
||||
|
||||
let buttonSpan = document.createElement('span');
|
||||
buttonSpan.textContent = '+';
|
||||
buttonSpan.id = 'plus-label';
|
||||
newTabButton.append(buttonSpan);
|
||||
tabsStrip.append(newTabButton);
|
||||
|
||||
let bodyElement = document.getElementsByTagName('body')[0];
|
||||
bodyElement.append(tabsStrip);
|
||||
|
||||
addTabsListeners();
|
||||
|
||||
Array.from(tabs).map((tabEntry) => {
|
||||
loadTabUI(tabEntry[0]);
|
||||
});
|
||||
}
|
||||
|
||||
function toggleFavorite() {
|
||||
activeTab = tabs.get(activeTabId);
|
||||
if (activeTab.isFavorite) {
|
||||
removeFavorite(activeTab.uri, () => {
|
||||
activeTab.isFavorite = false;
|
||||
updateFavoriteIcon();
|
||||
});
|
||||
} else {
|
||||
addFavorite(favoriteFromTab(activeTabId), () => {
|
||||
activeTab.isFavorite = true;
|
||||
updateFavoriteIcon();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function addControlsListeners() {
|
||||
let inputField = document.querySelector('#address-field');
|
||||
let clearButton = document.querySelector('#btn-clear');
|
||||
|
||||
inputField.addEventListener('keypress', function(e) {
|
||||
var key = e.which || e.keyCode;
|
||||
if (key === 13) { // 13 is enter
|
||||
e.preventDefault();
|
||||
processAddressBarInput();
|
||||
}
|
||||
});
|
||||
|
||||
inputField.addEventListener('focus', function(e) {
|
||||
e.target.select();
|
||||
});
|
||||
|
||||
inputField.addEventListener('blur', function(e) {
|
||||
inputField.setSelectionRange(0, 0);
|
||||
if (!inputField.value) {
|
||||
updateURI();
|
||||
}
|
||||
});
|
||||
|
||||
clearButton.addEventListener('click', function(e) {
|
||||
inputField.value = '';
|
||||
inputField.focus();
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
document.querySelector('#btn-forward').addEventListener('click', function(e) {
|
||||
if (document.getElementById('btn-forward').className === 'btn') {
|
||||
var message = {
|
||||
message: commands.MG_GO_FORWARD,
|
||||
args: {}
|
||||
};
|
||||
window.chrome.webview.postMessage(message);
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('#btn-back').addEventListener('click', function(e) {
|
||||
if (document.getElementById('btn-back').className === 'btn') {
|
||||
var message = {
|
||||
message: commands.MG_GO_BACK,
|
||||
args: {}
|
||||
};
|
||||
window.chrome.webview.postMessage(message);
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('#btn-reload').addEventListener('click', function(e) {
|
||||
var btnReload = document.getElementById('btn-reload');
|
||||
if (btnReload.className === 'btn-cancel') {
|
||||
var message = {
|
||||
message: commands.MG_CANCEL,
|
||||
args: {}
|
||||
};
|
||||
window.chrome.webview.postMessage(message);
|
||||
} else if (btnReload.className === 'btn') {
|
||||
reloadActiveTabContent();
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('#btn-options').addEventListener('click', function(e) {
|
||||
toggleOptionsDropdown();
|
||||
});
|
||||
|
||||
window.onkeydown = function(event) {
|
||||
if (event.ctrlKey) {
|
||||
switch (event.key) {
|
||||
case 'r':
|
||||
case 'R':
|
||||
reloadActiveTabContent();
|
||||
break;
|
||||
case 'd':
|
||||
case 'D':
|
||||
toggleFavorite();
|
||||
break;
|
||||
case 't':
|
||||
case 'T':
|
||||
createNewTab(true);
|
||||
break;
|
||||
case 'p':
|
||||
case 'P':
|
||||
case '+':
|
||||
case '-':
|
||||
case '_':
|
||||
case '=':
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
// Prevent zooming the UI
|
||||
window.addEventListener('wheel', function(event) {
|
||||
if (event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
}}, { passive: false });
|
||||
}
|
||||
|
||||
function addTabsListeners() {
|
||||
document.querySelector('#btn-new-tab').addEventListener('click', function(e) {
|
||||
createNewTab(true);
|
||||
});
|
||||
|
||||
document.querySelector('#btn-fav').addEventListener('click', function(e) {
|
||||
toggleFavorite();
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
window.chrome.webview.addEventListener('message', messageHandler);
|
||||
refreshControls();
|
||||
refreshTabs();
|
||||
|
||||
createNewTab(true);
|
||||
}
|
||||
|
||||
init();
|
||||
72
assets/wvbrowser_ui/controls_ui/favorites.js
Normal file
@ -0,0 +1,72 @@
|
||||
function isFavorite(uri, callback) {
|
||||
queryDB((db) => {
|
||||
let transaction = db.transaction(['favorites']);
|
||||
let favoritesStore = transaction.objectStore('favorites');
|
||||
let favoriteStatusRequest = favoritesStore.get(uri);
|
||||
|
||||
favoriteStatusRequest.onerror = function(event) {
|
||||
console.log(`Could not query for ${uri}: ${event.target.error.message}`);
|
||||
};
|
||||
|
||||
favoriteStatusRequest.onsuccess = function() {
|
||||
callback(favoriteStatusRequest.result);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function addFavorite(favorite, callback) {
|
||||
queryDB((db) => {
|
||||
let transaction = db.transaction(['favorites'], 'readwrite');
|
||||
let favoritesStore = transaction.objectStore('favorites');
|
||||
let addFavoriteRequest = favoritesStore.add(favorite);
|
||||
|
||||
addFavoriteRequest.onerror = function(event) {
|
||||
console.log(`Could not add favorite with key: ${favorite.uri}`);
|
||||
console.log(event.target.error.message);
|
||||
};
|
||||
|
||||
addFavoriteRequest.onsuccess = function(event) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function removeFavorite(key, callback) {
|
||||
queryDB((db) => {
|
||||
let transaction = db.transaction(['favorites'], 'readwrite');
|
||||
let favoritesStore = transaction.objectStore('favorites');
|
||||
let removeFavoriteRequest = favoritesStore.delete(key);
|
||||
|
||||
removeFavoriteRequest.onerror = function(event) {
|
||||
console.log(`Could not remove favorite with key: ${key}`);
|
||||
console.log(event.target.error.message);
|
||||
};
|
||||
|
||||
removeFavoriteRequest.onsuccess = function(event) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function getFavoritesAsJson(callback) {
|
||||
queryDB((db) => {
|
||||
let transaction = db.transaction(['favorites']);
|
||||
let favoritesStore = transaction.objectStore('favorites');
|
||||
let getFavoritesRequest = favoritesStore.getAll();
|
||||
|
||||
getFavoritesRequest.onerror = function(event) {
|
||||
console.log(`Could retrieve favorites`);
|
||||
console.log(event.target.error.message);
|
||||
};
|
||||
|
||||
getFavoritesRequest.onsuccess = function(event) {
|
||||
if (callback) {
|
||||
callback(getFavoritesRequest.result);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
143
assets/wvbrowser_ui/controls_ui/history.js
Normal file
@ -0,0 +1,143 @@
|
||||
const INVALID_HISTORY_ID = -1;
|
||||
|
||||
function addHistoryItem(item, callback) {
|
||||
queryDB((db) => {
|
||||
let transaction = db.transaction(['history'], 'readwrite');
|
||||
let historyStore = transaction.objectStore('history');
|
||||
|
||||
// Check if an item for this URI exists on this day
|
||||
let currentDate = new Date();
|
||||
let year = currentDate.getFullYear();
|
||||
let month = currentDate.getMonth();
|
||||
let date = currentDate.getDate();
|
||||
let todayDate = new Date(year, month, date);
|
||||
|
||||
let existingItemsIndex = historyStore.index('stampedURI');
|
||||
let lowerBound = [item.uri, todayDate];
|
||||
let upperBound = [item.uri, currentDate];
|
||||
let range = IDBKeyRange.bound(lowerBound, upperBound);
|
||||
let request = existingItemsIndex.openCursor(range);
|
||||
|
||||
request.onsuccess = function(event) {
|
||||
let cursor = event.target.result;
|
||||
if (cursor) {
|
||||
// There's an entry for this URI, update the item
|
||||
cursor.value.timestamp = item.timestamp;
|
||||
let updateRequest = cursor.update(cursor.value);
|
||||
|
||||
updateRequest.onsuccess = function(event) {
|
||||
if (callback) {
|
||||
callback(event.target.result.primaryKey);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// No entry for this URI, add item
|
||||
let addItemRequest = historyStore.add(item);
|
||||
|
||||
addItemRequest.onsuccess = function(event) {
|
||||
if (callback) {
|
||||
callback(event.target.result);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function updateHistoryItem(id, item, callback) {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
queryDB((db) => {
|
||||
let transaction = db.transaction(['history'], 'readwrite');
|
||||
let historyStore = transaction.objectStore('history');
|
||||
let storedItemRequest = historyStore.get(id);
|
||||
storedItemRequest.onsuccess = function(event) {
|
||||
let storedItem = event.target.result;
|
||||
item.timestamp = storedItem.timestamp;
|
||||
|
||||
let updateRequest = historyStore.put(item, id);
|
||||
|
||||
updateRequest.onsuccess = function(event) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function getHistoryItems(from, n, callback) {
|
||||
if (n <= 0 || from < 0) {
|
||||
if (callback) {
|
||||
callback([]);
|
||||
}
|
||||
}
|
||||
|
||||
queryDB((db) => {
|
||||
let transaction = db.transaction(['history']);
|
||||
let historyStore = transaction.objectStore('history');
|
||||
let timestampIndex = historyStore.index('timestamp');
|
||||
let cursorRequest = timestampIndex.openCursor(null, 'prev');
|
||||
|
||||
let current = 0;
|
||||
let items = [];
|
||||
cursorRequest.onsuccess = function(event) {
|
||||
let cursor = event.target.result;
|
||||
|
||||
if (!cursor || current >= from + n) {
|
||||
if (callback) {
|
||||
callback(items);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (current >= from) {
|
||||
items.push({
|
||||
id: cursor.primaryKey,
|
||||
item: cursor.value
|
||||
});
|
||||
}
|
||||
|
||||
++current;
|
||||
cursor.continue();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function removeHistoryItem(id, callback) {
|
||||
queryDB((db) => {
|
||||
let transaction = db.transaction(['history'], 'readwrite');
|
||||
let historyStore = transaction.objectStore('history');
|
||||
let removeItemRequest = historyStore.delete(id);
|
||||
|
||||
removeItemRequest.onerror = function(event) {
|
||||
console.log(`Could not remove history item with ID: ${id}`);
|
||||
console.log(event.target.error.message);
|
||||
};
|
||||
|
||||
removeItemRequest.onsuccess = function(event) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function clearHistory(callback) {
|
||||
queryDB((db) => {
|
||||
let transaction = db.transaction(['history'], 'readwrite');
|
||||
let historyStore = transaction.objectStore('history');
|
||||
let clearRequest = historyStore.clear();
|
||||
|
||||
clearRequest.onsuccess = function(event) {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
BIN
assets/wvbrowser_ui/controls_ui/img/cancel.png
Normal file
|
After Width: | Height: | Size: 696 B |
BIN
assets/wvbrowser_ui/controls_ui/img/favicon.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
assets/wvbrowser_ui/controls_ui/img/favorite.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
assets/wvbrowser_ui/controls_ui/img/favorited.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
assets/wvbrowser_ui/controls_ui/img/goBack.png
Normal file
|
After Width: | Height: | Size: 458 B |
BIN
assets/wvbrowser_ui/controls_ui/img/goBack_disabled.png
Normal file
|
After Width: | Height: | Size: 472 B |
BIN
assets/wvbrowser_ui/controls_ui/img/goForward.png
Normal file
|
After Width: | Height: | Size: 479 B |
BIN
assets/wvbrowser_ui/controls_ui/img/goForward_disabled.png
Normal file
|
After Width: | Height: | Size: 479 B |
BIN
assets/wvbrowser_ui/controls_ui/img/insecure.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
assets/wvbrowser_ui/controls_ui/img/neutral.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/wvbrowser_ui/controls_ui/img/options.png
Normal file
|
After Width: | Height: | Size: 417 B |
BIN
assets/wvbrowser_ui/controls_ui/img/reload.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
assets/wvbrowser_ui/controls_ui/img/secure.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/wvbrowser_ui/controls_ui/img/unknown.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
38
assets/wvbrowser_ui/controls_ui/options.css
Normal file
@ -0,0 +1,38 @@
|
||||
html, body {
|
||||
width: 200px;
|
||||
height: 107px;
|
||||
}
|
||||
|
||||
#dropdown-wrapper {
|
||||
width: calc(100% - 2px);
|
||||
height: calc(100% - 2px);
|
||||
border: 1px solid gray;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
background-color: rgb(240, 240, 240);
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background-color: rgb(220, 220, 220);
|
||||
}
|
||||
|
||||
.item-label {
|
||||
display: flex;
|
||||
height: 35px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.item-label span {
|
||||
font-family: Arial;
|
||||
font-size: 0.8em;
|
||||
vertical-align: middle;
|
||||
flex: 1;
|
||||
align-self: center;
|
||||
text-align: left;
|
||||
padding-left: 10px;
|
||||
}
|
||||
28
assets/wvbrowser_ui/controls_ui/options.html
Normal file
@ -0,0 +1,28 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||
<link rel="stylesheet" type="text/css" href="options.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="dropdown-wrapper">
|
||||
<div id="item-settings" class="dropdown-item">
|
||||
<div class="item-label">
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="item-history" class="dropdown-item">
|
||||
<div class="item-label">
|
||||
<span>History</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="item-favorites" class="dropdown-item">
|
||||
<div class="item-label">
|
||||
<span>Favorites</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../commands.js"></script>
|
||||
<script src="options.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
46
assets/wvbrowser_ui/controls_ui/options.js
Normal file
@ -0,0 +1,46 @@
|
||||
function navigateToBrowserPage(path) {
|
||||
const navMessage = {
|
||||
message: commands.MG_NAVIGATE,
|
||||
args: {
|
||||
uri: `browser://${path}`
|
||||
}
|
||||
};
|
||||
|
||||
window.chrome.webview.postMessage(navMessage);
|
||||
}
|
||||
|
||||
// Add listener for the options menu entries
|
||||
function addItemsListeners() {
|
||||
|
||||
// Close dropdown when item is selected
|
||||
(() => {
|
||||
const dropdownItems = Array.from(document.getElementsByClassName('dropdown-item'));
|
||||
dropdownItems.map(item => {
|
||||
item.addEventListener('click', function(e) {
|
||||
const closeMessage = {
|
||||
message: commands.MG_OPTION_SELECTED,
|
||||
args: {}
|
||||
};
|
||||
window.chrome.webview.postMessage(closeMessage);
|
||||
});
|
||||
|
||||
// Navigate to browser page
|
||||
let entry = item.id.split('-')[1];
|
||||
switch (entry) {
|
||||
case 'settings':
|
||||
case 'history':
|
||||
case 'favorites':
|
||||
item.addEventListener('click', function(e) {
|
||||
navigateToBrowserPage(entry);
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
function init() {
|
||||
addItemsListeners();
|
||||
}
|
||||
|
||||
init();
|
||||
44
assets/wvbrowser_ui/controls_ui/storage.js
Normal file
@ -0,0 +1,44 @@
|
||||
function handleUpgradeEvent(event) {
|
||||
console.log('Creating DB');
|
||||
let newDB = event.target.result;
|
||||
|
||||
newDB.onerror = function(event) {
|
||||
console.log('Something went wrong');
|
||||
console.log(event);
|
||||
};
|
||||
|
||||
let newFavoritesStore = newDB.createObjectStore('favorites', {
|
||||
keyPath: 'uri'
|
||||
});
|
||||
|
||||
newFavoritesStore.transaction.oncomplete = function(event) {
|
||||
console.log('Object stores created');
|
||||
};
|
||||
|
||||
let newHistoryStore = newDB.createObjectStore('history', {
|
||||
autoIncrement: true
|
||||
});
|
||||
|
||||
newHistoryStore.createIndex('timestamp', 'timestamp', {
|
||||
unique: false
|
||||
});
|
||||
|
||||
newHistoryStore.createIndex('stampedURI', ['uri', 'timestamp'], {
|
||||
unique: false
|
||||
});
|
||||
}
|
||||
|
||||
function queryDB(query) {
|
||||
let request = window.indexedDB.open('WVBrowser');
|
||||
|
||||
request.onerror = function(event) {
|
||||
console.log('Failed to open database');
|
||||
};
|
||||
|
||||
request.onsuccess = function(event) {
|
||||
let db = event.target.result;
|
||||
query(db);
|
||||
};
|
||||
|
||||
request.onupgradeneeded = handleUpgradeEvent;
|
||||
}
|
||||
72
assets/wvbrowser_ui/controls_ui/strip.css
Normal file
@ -0,0 +1,72 @@
|
||||
#tabs-strip {
|
||||
display: flex;
|
||||
height: 28px;
|
||||
|
||||
border-top: 1px solid rgb(200, 200, 200);
|
||||
border-bottom: 1px solid rgb(200, 200, 200);
|
||||
background-color: rgb(230, 230, 230);
|
||||
}
|
||||
|
||||
.tab, .tab-active {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
border-right: 1px solid rgb(200, 200, 200);
|
||||
overflow: hidden;
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
.tab-active {
|
||||
background-color: rgb(240, 240, 240);
|
||||
}
|
||||
|
||||
#btn-new-tab {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
#btn-new-tab:hover, .btn-tab-close:hover {
|
||||
background-color: rgb(200, 200, 200);
|
||||
}
|
||||
|
||||
#plus-label {
|
||||
display: block;
|
||||
flex: 1;
|
||||
align-self: center;
|
||||
line-height: 30px;
|
||||
width: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-tab-close {
|
||||
display: inline-block;
|
||||
align-self: center;
|
||||
min-width: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0 5px;
|
||||
border-radius: 4px;
|
||||
|
||||
background-size: 100%;
|
||||
background-image: url('img/cancel.png');
|
||||
}
|
||||
|
||||
.tab-label {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding: 5px;
|
||||
padding-left: 10px;
|
||||
height: calc(100% - 10px);
|
||||
vertical-align: middle;
|
||||
|
||||
white-space:nowrap;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
.tab-label span {
|
||||
font-family: Arial;
|
||||
font-size: 0.8em;
|
||||
flex: 1;
|
||||
align-self: center;
|
||||
}
|
||||
13
assets/wvbrowser_ui/controls_ui/styles.css
Normal file
@ -0,0 +1,13 @@
|
||||
html, body {
|
||||
margin: 0;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
* {
|
||||
user-select: none;
|
||||
}
|
||||
216
assets/wvbrowser_ui/controls_ui/tabs.js
Normal file
@ -0,0 +1,216 @@
|
||||
var tabs = new Map();
|
||||
var tabIdCounter = 0;
|
||||
var activeTabId = 0;
|
||||
const INVALID_TAB_ID = 0;
|
||||
|
||||
function getNewTabId() {
|
||||
return ++tabIdCounter;
|
||||
}
|
||||
|
||||
function isValidTabId(tabId) {
|
||||
return tabId != INVALID_TAB_ID && tabs.has(tabId);
|
||||
}
|
||||
|
||||
function createNewTab(shouldBeActive) {
|
||||
const tabId = getNewTabId();
|
||||
|
||||
var message = {
|
||||
message: commands.MG_CREATE_TAB,
|
||||
args: {
|
||||
tabId: parseInt(tabId),
|
||||
active: shouldBeActive || false
|
||||
}
|
||||
};
|
||||
|
||||
window.chrome.webview.postMessage(message);
|
||||
|
||||
tabs.set(parseInt(tabId), {
|
||||
title: 'New Tab',
|
||||
uri: '',
|
||||
uriToShow: '',
|
||||
favicon: 'img/favicon.png',
|
||||
isFavorite: false,
|
||||
isLoading: false,
|
||||
canGoBack: false,
|
||||
canGoForward: false,
|
||||
securityState: 'unknown',
|
||||
historyItemId: INVALID_HISTORY_ID
|
||||
});
|
||||
|
||||
loadTabUI(tabId);
|
||||
|
||||
if (shouldBeActive) {
|
||||
switchToTab(tabId, false);
|
||||
}
|
||||
}
|
||||
|
||||
function switchToTab(id, updateOnHost) {
|
||||
if (!id) {
|
||||
console.log('ID not provided');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the tab to switch to is valid
|
||||
if (!isValidTabId(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No need to switch if the tab is already active
|
||||
if (id == activeTabId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the tab element to switch to
|
||||
var tab = document.getElementById(`tab-${id}`);
|
||||
if (!tab) {
|
||||
console.log(`Can't switch to tab ${id}: element does not exist`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Change the style for the previously active tab
|
||||
if (isValidTabId(activeTabId)) {
|
||||
const activeTabElement = document.getElementById(`tab-${activeTabId}`);
|
||||
|
||||
// Check the previously active tab element does actually exist
|
||||
if (activeTabElement) {
|
||||
activeTabElement.className = 'tab';
|
||||
}
|
||||
}
|
||||
|
||||
// Set tab as active
|
||||
tab.className = 'tab-active';
|
||||
activeTabId = id;
|
||||
|
||||
// Instruct host app to switch tab
|
||||
if (updateOnHost) {
|
||||
var message = {
|
||||
message: commands.MG_SWITCH_TAB,
|
||||
args: {
|
||||
tabId: parseInt(activeTabId)
|
||||
}
|
||||
};
|
||||
|
||||
window.chrome.webview.postMessage(message);
|
||||
}
|
||||
|
||||
updateNavigationUI(commands.MG_SWITCH_TAB);
|
||||
}
|
||||
|
||||
function closeTab(id) {
|
||||
// If closing tab was active, switch tab or close window
|
||||
if (id == activeTabId) {
|
||||
if (tabs.size == 1) {
|
||||
// Last tab is closing, shut window down
|
||||
tabs.delete(id);
|
||||
closeWindow();
|
||||
return;
|
||||
}
|
||||
|
||||
// Other tabs are open, switch to rightmost tab
|
||||
var tabsEntries = Array.from(tabs.entries());
|
||||
var lastEntry = tabsEntries.pop();
|
||||
if (lastEntry[0] == id) {
|
||||
lastEntry = tabsEntries.pop();
|
||||
}
|
||||
switchToTab(lastEntry[0], true);
|
||||
}
|
||||
|
||||
// Remove tab element
|
||||
var tabElement = document.getElementById(`tab-${id}`);
|
||||
if (tabElement) {
|
||||
tabElement.parentNode.removeChild(tabElement);
|
||||
}
|
||||
// Remove tab from map
|
||||
tabs.delete(id);
|
||||
|
||||
var message = {
|
||||
message: commands.MG_CLOSE_TAB,
|
||||
args: {
|
||||
tabId: id
|
||||
}
|
||||
};
|
||||
|
||||
window.chrome.webview.postMessage(message);
|
||||
}
|
||||
|
||||
function updateFaviconURI(tabId, src) {
|
||||
let tab = tabs.get(tabId);
|
||||
if (tab.favicon != src) {
|
||||
let img = new Image();
|
||||
|
||||
// Update favicon element on successful load
|
||||
img.onload = () => {
|
||||
console.log('Favicon loaded');
|
||||
tab.favicon = src;
|
||||
|
||||
if (tabId == activeTabId) {
|
||||
updatedFaviconURIHandler(tabId, tab);
|
||||
}
|
||||
};
|
||||
|
||||
if (src) {
|
||||
// Try load from root on failed load
|
||||
img.onerror = () => {
|
||||
console.log('Cannot load favicon from link, trying with root');
|
||||
updateFaviconURI(tabId, '');
|
||||
};
|
||||
} else {
|
||||
// No link for favicon, try loading from root
|
||||
try {
|
||||
let tabURI = new URL(tab.uri);
|
||||
src = `${tabURI.origin}/favicon.ico`;
|
||||
} catch(e) {
|
||||
console.log(`Could not parse tab ${tabId} URI`);
|
||||
}
|
||||
|
||||
img.onerror = () => {
|
||||
console.log('No favicon in site root. Using default favicon.');
|
||||
tab.favicon = 'img/favicon.png';
|
||||
updatedFaviconURIHandler(tabId, tab);
|
||||
};
|
||||
}
|
||||
|
||||
img.src = src;
|
||||
}
|
||||
}
|
||||
|
||||
function updatedFaviconURIHandler(tabId, tab) {
|
||||
updateNavigationUI(commands.MG_UPDATE_FAVICON);
|
||||
|
||||
// Update favicon in history item
|
||||
if (tab.historyItemId != INVALID_HISTORY_ID) {
|
||||
updateHistoryItem(tab.historyItemId, historyItemFromTab(tabId));
|
||||
}
|
||||
}
|
||||
|
||||
function favoriteFromTab(tabId) {
|
||||
if (!isValidTabId(tabId)) {
|
||||
console.log('Invalid tab ID');
|
||||
return;
|
||||
}
|
||||
|
||||
let tab = tabs.get(tabId);
|
||||
let favicon = tab.favicon == 'img/favicon.png' ? '../controls_ui/' + tab.favicon : tab.favicon;
|
||||
return {
|
||||
uri: tab.uri,
|
||||
uriToShow: tab.uriToShow,
|
||||
title: tab.title,
|
||||
favicon: favicon
|
||||
};
|
||||
}
|
||||
|
||||
function historyItemFromTab(tabId) {
|
||||
if (!isValidTabId(tabId)) {
|
||||
console.log('Invalid tab ID');
|
||||
return;
|
||||
}
|
||||
|
||||
let tab = tabs.get(tabId);
|
||||
let favicon = tab.favicon == 'img/favicon.png' ? '../controls_ui/' + tab.favicon : tab.favicon;
|
||||
return {
|
||||
uri: tab.uri,
|
||||
title: tab.title,
|
||||
favicon: favicon,
|
||||
timestamp: new Date()
|
||||
}
|
||||
}
|
||||