diff --git a/css/glowingbear.css b/css/glowingbear.css index ca11870..62a10fb 100644 --- a/css/glowingbear.css +++ b/css/glowingbear.css @@ -146,7 +146,7 @@ input[type=text], input[type=password], #sendMessage { .col-sm-9 { padding-right: 5px !important; } -.glyphicon { +#topbar .glyphicon { top: 0; /* Fixes alignment issue in top bar */ } #topbar { @@ -263,6 +263,11 @@ input[type=text], input[type=password], #sendMessage { #sidebar.ng-hide { width: 0; } + +#sidebar[data-state=hidden] { + transform: translate(-200px,0); + -webkit-transform: translate(-200px,0); +} #nicklist { position: fixed; @@ -400,6 +405,7 @@ td.time { margin-left: 0; padding-left: 145px; } + .footer.withnicklist { padding-right: 100px; } @@ -610,7 +616,6 @@ h2 span, h2 small { .panel[data-state=active] .panel-collapse { transition: max-height 0.5s; - max-height: 60em; height: auto; display: block; } @@ -676,6 +681,10 @@ li.buffer.indent.private a { font-size: small; } +.lessleftpad { + padding-left: 5px; +} + .unselectable { -webkit-user-select: none; -moz-user-select: none; @@ -699,6 +708,30 @@ img.emojione { width: auto; } +#toast { + position: fixed; + left: 50%; + bottom: 50px; + width: 250px; + margin-left: -125px; + + text-align: center; + border-radius: 3px; + padding: 10px 15px; + z-index: 100; + animation: fadein 0.5s, fadeout 0.5s 4.5s; +} + +@keyframes fadein { + from { bottom: 0; opacity: 0; } + to { bottom: 50px; opacity: 1; } +} + +@keyframes fadeout { + from { bottom: 50px; opacity: 1; } + to { bottom: 0; opacity: 0; } +} + .glyphicon-spin { -webkit-animation: spin 1000ms infinite linear; animation: spin 1000ms infinite linear; @@ -794,11 +827,6 @@ img.emojione { -webkit-transform: translate(0,0); /* Safari */ } - #sidebar[data-state=hidden] { - transform: translate(-200px,0); - -webkit-transform: translate(-200px,0); - } - .content[sidebar-state=visible] #bufferlines, .content[sidebar-state=visible] .footer { margin-left: 0px; transform: translate(200px,0); @@ -811,17 +839,17 @@ img.emojione { text-align: center; font-size: 18px; width: initial; + z-index: -1; } - #topbar .brand img { - height: 28px; + #topbar .brand a { + padding: 0 2px 0 10px; } - #topbar .badge { - display: none; + #topbar .brand img { + height: 28px; } - #bufferlines, #nicklist { position: relative; min-height: 0; @@ -931,4 +959,4 @@ code { padding: 0px 2px; color: #444; border: 1pt solid #444; -} \ No newline at end of file +} diff --git a/css/themes/base16-default.css b/css/themes/base16-default.css index ec40fcb..2324240 100644 --- a/css/themes/base16-default.css +++ b/css/themes/base16-default.css @@ -65,6 +65,10 @@ a:visited:hover, a:visited:active, a:visited:focus { border: 0px none; } +.form-control[disabled] { + background: var(--base03); +} + .form-control:focus { color: var(--base06); box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.1), 0px 1px 7px 0px rgba(0, 0, 0, 0.2) inset; @@ -301,6 +305,9 @@ tr.bufferline:hover { input[type=text], input[type=password], #sendMessage, .badge { box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.1), 0px 1px 7px 0px rgba(0, 0, 0, 0.2) inset; } +input[type=text].is-invalid{ + box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.1), 0px 1px 7px 0px rgba(255, 0, 0, 0.6) inset; +} input[type=text], input[type=password], #sendMessage, .btn-send, .btn-send-image, .btn-complete-nick { color: var(--base05); @@ -416,6 +423,10 @@ button.close:hover { color: var(--base01); } +#toast { + background-color: var(--base01); +} + /****************************/ /* Weechat colors and style */ /****************************/ diff --git a/css/themes/blue.css b/css/themes/blue.css index c093a43..cb666bc 100644 --- a/css/themes/blue.css +++ b/css/themes/blue.css @@ -134,6 +134,13 @@ input[type=text], input[type=password], #sendMessage, .badge, .btn-send, .btn-se border: 1px solid #363943; } +#toast { + background-color: #283244; + border: 1px solid; + border-color: rgb(29, 94, 152); + border-radius: 0px; +} + .horizontal-line { -webkit-box-shadow: rgba(255, 255, 255, 0.07) 0 1px 0; -moz-box-shadow: rgba(255, 255, 255, 0.07) 0 1px 0; diff --git a/css/themes/dark.css b/css/themes/dark.css index da260c8..4d845fe 100644 --- a/css/themes/dark.css +++ b/css/themes/dark.css @@ -10,6 +10,10 @@ body { border: 0px none; } +.form-control[disabled] { + background: none repeat scroll 0% 0% rgba(63, 63, 63, 0.3); +} + .form-control option { color: #eee; background: #282828; @@ -86,6 +90,9 @@ input[type=text], input[type=password], #sendMessage, .badge, .btn-send, .btn-se color: #ccc; background: none repeat scroll 0% 0% rgba(0, 0, 0, 0.3); } +input[type=text].is-invalid{ + box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.1), 0px 1px 7px 0px rgba(255, 0, 0, 0.8) inset; +} .btn-complete-nick:hover, .btn-complete-nick:focus, .btn-send:hover, .btn-send:focus, @@ -2119,6 +2126,10 @@ code { color: #fff; } +#toast { + background-color: #333; +} + /* */ /* Mobile layout */ /* */ diff --git a/css/themes/light.css b/css/themes/light.css index be8012e..900404d 100644 --- a/css/themes/light.css +++ b/css/themes/light.css @@ -69,6 +69,14 @@ select.form-control, select option, input[type=text], input[type=password], #sen background: none repeat scroll 0% 0% rgba(255, 255, 255, 0.3); } +.form-control[disabled] { + background: none repeat scroll 0% 0% rgba(134, 134, 134, 0.3); +} + +input[type=text].is-invalid{ + box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.1), 0px 1px 7px 0px rgba(255, 0, 0, 0.8) inset; +} + #connection-infos { color: #aaa; } @@ -2084,6 +2092,10 @@ select.form-control, select option, input[type=text], input[type=password], #sen font-weight: bold; } +#toast { + background-color: #ddd; +} + /* */ /* Mobile layout */ /* */ diff --git a/electron-main.js b/electron-main.js old mode 100755 new mode 100644 index 36ef9dc..6b62abf --- a/electron-main.js +++ b/electron-main.js @@ -1,258 +1,122 @@ -(function() { - 'use strict'; - const electron = require('electron'); - const app = electron.app; // Module to control application life. - const BrowserWindow = electron.BrowserWindow; // Module to create native browser window. +// Modules to control application life and create native browser window +const {app, BrowserWindow, shell, ipcMain} = require('electron') +const path = require('path') +const fs = require('fs') - const ipcMain = require('electron').ipcMain; - const nativeImage = require('electron').nativeImage; - const Menu = require('electron').Menu; - // Node fs module - const fs = require("fs"); - var template; +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let mainWindow - template = [ - { - label: 'Edit', - submenu: [ - { - label: 'Undo', - accelerator: 'CmdOrCtrl+Z', - role: 'undo' - }, - { - label: 'Redo', - accelerator: 'Shift+CmdOrCtrl+Z', - role: 'redo' - }, - { - type: 'separator' - }, - { - label: 'Cut', - accelerator: 'CmdOrCtrl+X', - role: 'cut' - }, - { - label: 'Copy', - accelerator: 'CmdOrCtrl+C', - role: 'copy' - }, - { - label: 'Paste', - accelerator: 'CmdOrCtrl+V', - role: 'paste' - }, - { - label: 'Select All', - accelerator: 'CmdOrCtrl+A', - role: 'selectall' - }, - ] - }, - { - label: 'View', - submenu: [ - { - label: 'Reload', - accelerator: 'CmdOrCtrl+R', - click: function(item, focusedWindow) { - if (focusedWindow) - focusedWindow.reload(); - } - }, - { - label: 'Toggle Full Screen', - accelerator: (function() { - if (process.platform == 'darwin') - return 'Ctrl+Command+F'; - else - return 'F11'; - })(), - click: function(item, focusedWindow) { - if (focusedWindow) - focusedWindow.setFullScreen(!focusedWindow.isFullScreen()); - } - }, - { - label: 'Electron Developer Tools', - accelerator: (function() { - if (process.platform == 'darwin') - return 'Alt+Command+E'; - else - return 'Ctrl+Shift+E'; - })(), - click: function(item, focusedWindow) { - if (focusedWindow) - focusedWindow.toggleDevTools(); - } - }, - { - label: 'Web Developer Tools', - accelerator: (function() { - if (process.platform == 'darwin') - return 'Alt+Command+I'; - else - return 'Ctrl+Shift+I'; - })(), - click: function(item, focusedWindow) { - if ( focusedWindow ) { - focusedWindow.webContents.send( 'openDevTools' ); - } - } - } - ] - }, - { - label: 'Window', - role: 'window', - submenu: [ - { - label: 'Minimize', - accelerator: 'CmdOrCtrl+M', - role: 'minimize' - }, - { - label: 'Close', - accelerator: 'CmdOrCtrl+Q', - role: 'close' - }, - ] - }, - { - label: 'Help', - role: 'help', - submenu: [ - { - label: 'Learn More', - click: function() { require('electron').shell.openExternal('https://github.com/glowing-bear/glowing-bear'); } - }, - ] - }, - ]; +// We use this to store some tiny amount of preferences specific to electron +// things like window bounds and location +const initPath = "init.json" - if (process.platform == 'darwin') { - var name = app.getName(); - template.unshift({ - label: name, - submenu: [ - { - label: 'About ' + name, - role: 'about' - }, - { - type: 'separator' - }, - { - label: 'Services', - role: 'services', - submenu: [] - }, - { - type: 'separator' - }, - { - label: 'Hide ' + name, - accelerator: 'Command+H', - role: 'hide' - }, - { - label: 'Hide Others', - accelerator: 'Command+Alt+H', - role: 'hideothers' - }, - { - label: 'Show All', - role: 'unhide' - }, - { - type: 'separator' - }, - { - label: 'Quit', - accelerator: 'Command+Q', - click: function() { app.quit(); } - }, - ] - }); - // Window menu. - template[3].submenu.push( - { - type: 'separator' - }, - { - label: 'Bring All to Front', - role: 'front' - } - ); +function createWindow () { + let data + // read saved state from file (e.g. window bounds) + try { + data = JSON.parse(fs.readFileSync(initPath, 'utf8')) + } + catch(e) { + console.log('Unable to read init.json: ', e) + } + // Create the browser window. + const bounds = (data && data.bounds) ? data.bounds : {width: 1280, height:800 } + mainWindow = new BrowserWindow({ + width: bounds.width, + height: bounds.height, + webPreferences: { + preload: path.join(__dirname, 'electron-globals.js') } + }) - // Keep a global reference of the window object, if you don't, the window will - // be closed automatically when the JavaScript object is garbage collected. - var mainWindow = null; + // Remember window position + if (data && data.bounds.x && data.bounds.y) { + mainWindow.x = data.bounds.x; + mainWindow.y = data.bounds.y; + } - app.on('browser-window-focus', function(e, w) { - w.webContents.send('browser-window-focus'); - }); + mainWindow.setMenu(null) + mainWindow.setMenuBarVisibility(false) + mainWindow.setAutoHideMenuBar(true) - app.on('ready', function() { - var menu = Menu.buildFromTemplate(template); - Menu.setApplicationMenu(menu); - const initPath = __dirname + "/init.json"; - var data; + // and load the index.html of the app. + mainWindow.loadFile('index.html') - // read saved state from file (e.g. window bounds) - try { - data = JSON.parse(fs.readFileSync(initPath, 'utf8')); - } - catch(e) { - console.log('Unable to read init.json: ', e); - } - const bounds = (data && data.bounds) ? data.bounds : {width: 1280, height:800 }; - var bwdata = {width: bounds.width, height: bounds.height, 'min-width': 1024, 'min-height': 600, 'autoHideMenuBar': true, 'web-security': true, 'java': false, 'accept-first-mouse': true, defaultEncoding: 'UTF-8', 'icon':'file://'+__dirname + '/assets/img/favicon.png'}; - // Remembe window position - if (data && data.bounds.x && data.bounds.y) { - bwdata.x = data.bounds.x; - bwdata.y = data.bounds.y; - } + // Open the DevTools. + // mainWindow.webContents.openDevTools() + + var handleLink = (e, url) => { + if(url != mainWindow.webContents.getURL()) { + e.preventDefault() + shell.openExternal(url) + } + } + + mainWindow.webContents.on('will-navigate', handleLink) + mainWindow.webContents.on('new-window', handleLink) + + // Emitted when the window is closing. + mainWindow.on('close', function () { + let data = { + bounds: mainWindow.getBounds() + } + fs.writeFileSync(initPath, JSON.stringify(data)) + }) + + // Emitted when the window is closed. + mainWindow.on('closed', function () { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + mainWindow = null + }) + + app.on('browser-window-focus', function() { + setTimeout(function() { mainWindow.webContents.focus() }, 0) + setTimeout(function() { mainWindow.webContents.executeJavaScript("document.getElementById(\"sendMessage\").focus()") }, 0) + }) +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.on('ready', function() { + createWindow() +}) - mainWindow = new BrowserWindow(bwdata); - mainWindow.loadURL('file://' + __dirname + '/electron-start.html'); - mainWindow.focus(); - // Listen for badge changes - ipcMain.on('badge', function(event, arg) { - if (process.platform === "darwin") { - app.dock.setBadge(String(arg)); - } - else if (process.platform === "win32") { - let n = parseInt(arg, 10); - // Only show notifications with number - if (isNaN(n)) { - return; - } - if (n > 0) { - mainWindow.setOverlayIcon(__dirname + '/assets/img/favicon.ico', String(arg)); - } else { - mainWindow.setOverlayIcon(null, ''); - } - } - }); +// Listen for badge changes +ipcMain.on('badge', function(event, arg) { + if (process.platform === "darwin") { + app.dock.setBadge(String(arg)) + } + else if (process.platform === "win32") { + let n = parseInt(arg, 10) + // Only show notifications with number + if (isNaN(n)) { + return + } + if (n > 0) { + mainWindow.setOverlayIcon(__dirname + '/assets/img/favicon.ico', String(arg)) + } else { + mainWindow.setOverlayIcon(null, '') + } + } +}) - mainWindow.on('devtools-opened', function() { - mainWindow.webContents.executeJavaScript("document.getElementById('glowingbear').openDevTools();"); - }); +// Quit when all windows are closed. +app.on('window-all-closed', function () { + // On macOS it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') app.quit() +}) - mainWindow.on('close', function() { - // Save window bounds to disk - var data = { - bounds: mainWindow.getBounds() - }; - fs.writeFileSync(initPath, JSON.stringify(data)); - }); +app.on('activate', function () { + // On macOS it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (mainWindow === null) createWindow() +}) - mainWindow.on('closed', function() { - app.quit(); - }); - }); -})(); +// In this file you can include the rest of your app's specific main process +// code. You can also put them in separate files and require them here. diff --git a/electron-start.html b/electron-start.html deleted file mode 100644 index 2844bfa..0000000 --- a/electron-start.html +++ /dev/null @@ -1,41 +0,0 @@ - - -
- - - - -WeeChat version 0.4.2 or higher is required—we recommend at least 1.0.
To start using Glowing Bear, follow the instructions below to set up an encrypted relay. All communication goes directly between your browser and your WeeChat relay! This means that your server must be accessible. We never see any of your data or your password, and you don't need to trust a "cloud". All settings, including your password, are saved locally in your own browser between sessions.
Your certificate needs to be renewed every couple of months. Either follow the instructions for automatic renewal at https://certbot.eff.org, or run certbot renew
manually when renewal is due. Important: You'll need to follow the instructions for copying the certificate to the right place again, and re-run /relay sslcertkey
in WeeChat.
Configure WeeChat for TOTP. The secret key has to be a base 32 string.
+/secure set relay_totp_secret xxxxx +/set relay.network.totp_secret "${sec.data.relay_totp_secret}"+
Open an authenticator app and create an entry with the same secret. In Glowing Bear check the checkbox for "use Time-based One-Time Password" and fill in the one time password as you see it in the authenticator app.
Helpful trigger to automatically repin a buffer (in this instance, irc.freenode.#weechat):
/trigger add autopin signal "buffer_opened" "${buffer[${tg_signal_data}].full_name} =~ irc.freenode.#weechat" "" "/command -buffer ${buffer[${tg_signal_data}].full_name} * /buffer set localvar_set_pinned true"
+ + The hostname field can be used to set a custom path. Or a URL parameter can be used see section 'URL Parameters'. +
+
+ To connect to the weechat relay service we connect using a URL. A typical URL consists of 4 parts. {scheme}://{host}:{port}/{path}
. The path can be changed by entering the relay's full URL (except the scheme).
+
+ Examples of correct input for the host field are: +
++ Incorrect input for the host field: +
++ Parameters can be passed in the URL to prefill the fields. This can be useful when you have multiple relays and want to use bookmarks to manage them. + We do not recommend passing the password in this way as it will be visible in plain text and stored in history/bookmarks but it is possible. Special characters should be URL encoded. +
+
+ If we want just the path for example: https://glowing-bear.org/#path=weechat2
+
+ An example: https://glowing-bear.org/#host=my.domain.com&port=8000&password=hunter2&autoconnect=true
+
+ Available parameters: +