Merge branch 'master' into gh-pages

gh-pages
Lorenz Hübschle-Schneider 5 years ago
commit c2f06c110d
  1. 52
      css/glowingbear.css
  2. 11
      css/themes/base16-default.css
  3. 7
      css/themes/blue.css
  4. 11
      css/themes/dark.css
  5. 12
      css/themes/light.css
  6. 322
      electron-main.js
  7. 41
      electron-start.html
  8. 6
      electron.makefile
  9. 124
      index.html
  10. 2
      js/bufferResume.js
  11. 19
      js/connection.js
  12. 144
      js/glowingbear.js
  13. 17
      js/handlers.js
  14. 27
      js/imgur.js
  15. 12
      js/inputbar.js
  16. 10
      js/irc-utils.js
  17. 27
      js/models.js
  18. 26
      js/plugins.js
  19. 6
      js/weechat.js
  20. 18
      package.json
  21. 9
      test/unit/plugins.js

@ -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 {
@ -264,6 +264,11 @@ input[type=text], input[type=password], #sendMessage {
width: 0;
}
#sidebar[data-state=hidden] {
transform: translate(-200px,0);
-webkit-transform: translate(-200px,0);
}
#nicklist {
position: fixed;
width: 100px;
@ -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;

@ -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 */
/****************************/

@ -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;

@ -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 */
/* */

@ -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 */
/* */

@ -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' );
// We use this to store some tiny amount of preferences specific to electron
// things like window bounds and location
const initPath = "init.json"
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')
}
]
},
{
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'); }
},
]
},
];
})
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'
}
);
// Remember window position
if (data && data.bounds.x && data.bounds.y) {
mainWindow.x = data.bounds.x;
mainWindow.y = data.bounds.y;
}
// 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;
mainWindow.setMenu(null)
mainWindow.setMenuBarVisibility(false)
mainWindow.setAutoHideMenuBar(true)
app.on('browser-window-focus', function(e, w) {
w.webContents.send('browser-window-focus');
});
// and load the index.html of the app.
mainWindow.loadFile('index.html')
app.on('ready', function() {
var menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
const initPath = __dirname + "/init.json";
var data;
// Open the DevTools.
// mainWindow.webContents.openDevTools()
// read saved state from file (e.g. window bounds)
try {
data = JSON.parse(fs.readFileSync(initPath, 'utf8'));
var handleLink = (e, url) => {
if(url != mainWindow.webContents.getURL()) {
e.preventDefault()
shell.openExternal(url)
}
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;
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)
})
}
mainWindow = new BrowserWindow(bwdata);
mainWindow.loadURL('file://' + __dirname + '/electron-start.html');
mainWindow.focus();
// 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()
})
// Listen for badge changes
ipcMain.on('badge', function(event, arg) {
// Listen for badge changes
ipcMain.on('badge', function(event, arg) {
if (process.platform === "darwin") {
app.dock.setBadge(String(arg));
app.dock.setBadge(String(arg))
}
else if (process.platform === "win32") {
let n = parseInt(arg, 10);
let n = parseInt(arg, 10)
// Only show notifications with number
if (isNaN(n)) {
return;
return
}
if (n > 0) {
mainWindow.setOverlayIcon(__dirname + '/assets/img/favicon.ico', String(arg));
mainWindow.setOverlayIcon(__dirname + '/assets/img/favicon.ico', String(arg))
} else {
mainWindow.setOverlayIcon(null, '');
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.

@ -1,41 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script>
onload = function() {
const ipc= require('electron').ipcRenderer;
const remote = require('electron').remote;
const nativeImage = require('electron').nativeImage;
const shell = require('electron').shell;
var webview = document.getElementById("glowingbear");
var handleconsole = function(e) {
console.log("webview: " + e.message);
}
var handlenewwindow = function(e) {
shell.openExternal(e.url);
}
var handletitleset = function(e) {
document.title = e.title;
}
webview.addEventListener("console-message", handleconsole);
webview.addEventListener("new-window", handlenewwindow);
webview.addEventListener("page-title-set", handletitleset);
ipc.on('openDevTools', function() {
setTimeout(function() { webview.openDevTools(); }, 0 );
});
ipc.on('browser-window-focus', function() {
setTimeout(function() { webview.focus(); }, 0);
setTimeout(function() { webview.executeJavaScript("document.getElementById(\"sendMessage\").focus();") }, 0);
});
}
</script>
</head>
<body>
<webview preload="electron-globals.js" id="glowingbear" src="index.html" style="position:fixed; top:0; left:0; bottom:0; right:0;"></webview>
</body>
</html>

@ -18,10 +18,10 @@ uselocal: copylocal
# build the electron app for various platforms
build-electron-windows: uselocal
electron-packager ${ELECTRON_COMMON} --platform=win32 --arch=ia32 --electron-version=3.0.6 --icon=assets/img/favicon.ico --asar=true
electron-packager ${ELECTRON_COMMON} --platform=win32 --arch=ia32 --electron-version=8.0.1 --icon=assets/img/favicon.ico --asar=true
build-electron-darwin: uselocal
electron-packager ${ELECTRON_COMMON} --platform=darwin --arch=x64 --electron-version=3.0.6 --icon=assets/img/glowing-bear.icns
electron-packager ${ELECTRON_COMMON} --platform=darwin --arch=x64 --electron-version=8.0.1 --icon=assets/img/glowing-bear.icns
build-electron-linux: uselocal
electron-packager ${ELECTRON_COMMON} --platform=linux --arch=x64 --electron-version=3.0.6 --icon=assets/img/favicon.ico
electron-packager ${ELECTRON_COMMON} --platform=linux --arch=x64 --electron-version=8.0.1 --icon=assets/img/favicon.ico

@ -81,33 +81,41 @@
<div class="panel-body">
<form class="form-signin" role="form">
<div class="form-group">
<label class="control-label" for="host">WeeChat relay hostname and port number</label>
<div class="input-group">
<div class="row no-gutter">
<div class="col-sm-9">
<input type="text" class="form-control favorite-font" id="host" ng-model="settings.host" placeholder="Address" autocapitalize="off">
<label class="control-label" for="host">WeeChat relay hostname</label>
<input type="text" class="form-control favorite-font" id="host" ng-model="settings.hostField" ng-change="parseHost()" ng-class="{'is-invalid': hostInvalid}" placeholder="Address" autocapitalize="off">
</div>
<div class="col-sm-3">
<input type="text" class="form-control favorite-font" id="port" ng-model="settings.port" placeholder="Port">
<label class="control-label" for="port">Port</label>
<input type="text" class="form-control favorite-font" id="port" ng-model="settings.port" ng-disabled="portDisabled" placeholder="Port">
</div>
</div>
</div>
<div class="row no-gutter">
<div ng-class="settings.useTotp ? 'col-sm-9' : 'col-sm-12'" ng>
<label class="control-label" for="password">WeeChat relay password</label>
<input type="password" class="form-control favorite-font" id="password" ng-model="password" placeholder="Password">
</div>
<div class="col-sm-3" ng-Show="settings.useTotp">
<label class="control-label" for="totp">Token</label>
<input type="text" class="form-control favorite-font" id="totp" ng-model="totp" ng-change="parseTotp()" ng-class="{'is-invalid': totpInvalid}" autocomplete="off">
</div>
</div>
<div class="alert alert-danger" ng-show="passwordError" ng-cloak>
Error: wrong password
Error: wrong password or token
</div>
<div class="checkbox">
<label class="control-label" for="savepassword">
<input type="checkbox" id="savepassword" ng-model="settings.savepassword">
Save password in your browser
</label>
</div>
<div class="checkbox" ng-show="settings.savepassword">
<label class="control-label" for="autoconnect">
<input type="checkbox" id="autoconnect" ng-model="settings.autoconnect">
Automatically connect
<div class="checkbox">
<label class="control-label" for="useTotp">
<input type="checkbox" id="useTotp" ng-model="settings.useTotp">
Use Time-based One-Time Password <a href="https://blog.weechat.org/post/2019/01/14/Support-of-TOTP" target="_blank"><i class="glyphicon glyphicon-info-sign"></i></a>
</label>
</div>
<div class="checkbox">
@ -116,8 +124,14 @@
Encryption. <strong>Strongly recommended!</strong> Need help? Check below.
</label>
</div>
<div class="checkbox" ng-show="settings.savepassword || settings.autoconnect">
<label class="control-label" for="autoconnect">
<input type="checkbox" id="autoconnect" ng-model="settings.autoconnect" ng-disabled="settings.useTotp">
Automatically connect
</label>
</div>
</div>
<button class="btn btn-lg btn-primary" ng-click="connect()" ng-cloak>{{ connectbutton }} <i ng-class="connectbuttonicon" class="glyphicon"></i></button>
<button class="btn btn-lg btn-primary" ng-disabled="hostInvalid || (totpInvalid && settings.useTotp)" ng-click="connect()" ng-cloak>{{ connectbutton }} <i ng-class="connectbuttonicon" class="glyphicon"></i></button>
</form>
</div>
</div>
@ -132,6 +146,7 @@
</div>
<div id="collapseTwo" class="panel-collapse collapse">
<div class="panel-body">
<h3>Use TLS encryption</h3>
<p><span class="label label-danger">WeeChat version 0.4.2 or higher is required—we recommend at least 1.0.</p>
<p>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.</p>
<div class="alert alert-warning" ng-show="show_tls_warning"><strong>You're using Glowing Bear over an unencrypted connection (http://). This is not recommended!</strong> We recommend using our secure hosted version at <a href="https://www.glowing-bear.org/">https://www.glowing-bear.org/</a>, or <a href="https://latest.glowing-bear.org/">https://latest.glowing-bear.org</a> for the latest and greatest development version. You can still follow the instructions below to set up an encrypted relay, though.</div>
@ -149,6 +164,11 @@ chown -R <strong>username</strong>:<strong>username</strong> ~<strong>username</
/relay add ssl.weechat {{ settings.port || 9001 }}
</pre>
<p>Your certificate needs to be renewed every couple of months. Either follow the instructions for automatic renewal at <a href="https://certbot.eff.org/">https://certbot.eff.org</a>, or run <code>certbot renew</code> manually when renewal is due. <strong>Important:</strong> You'll need to follow the instructions for copying the certificate to the right place again, and re-run <code>/relay sslcertkey</code> in WeeChat.</p>
<h3>Use TOTP (Time-based One-Time Password)</h3>
<p><a href="https://blog.weechat.org/post/2019/01/14/Support-of-TOTP">Configure</a> WeeChat for TOTP. The secret key has to be a base 32 string.</p>
<pre>/secure set relay_totp_secret xxxxx
/set relay.network.totp_secret "${sec.data.relay_totp_secret}"</pre>
<p>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.</p>
</div>
</div>
</div>
@ -188,6 +208,65 @@ chown -R <strong>username</strong>:<strong>username</strong> ~<strong>username</
<p>
Helpful trigger to automatically repin a buffer (in this instance, <var>irc.freenode.#weechat</var>): <pre><code>/trigger add autopin signal "buffer_opened" "${buffer[${tg_signal_data}].full_name} =~ <var>irc.freenode.#weechat</var>" "" "/command -buffer ${buffer[${tg_signal_data}].full_name} * /buffer set localvar_set_pinned true"</code></pre>
</p>
<h3>Setting a custom path</h3>
<p>
The hostname field can be used to set a custom path. Or a URL parameter can be used see section 'URL Parameters'.
</p>
<p>
To connect to the weechat relay service we connect using a URL. A typical URL consists of 4 parts. <code>{scheme}://{host}:{port}/{path}</code>. The path can be changed by entering the relay's full URL (except the scheme).
</p>
<ul>
<li><b>scheme</b>: The scheme must never be input. The scheme is "ws" if TLS isn't used and it is "wss" if TLS is used.</li>
<li><b>host</b>: Can be an IPv4, IPv6 or a FQDN. IPv6 addresses must be wrapped in square brackets.</li>
<li><b>port</b>: can be specified in the host field or the seperate port field. However if the path is specified in the host field the port must also be specified.</li>
<li><b>path</b>: by defautl this is "weechat". In case <a href="https://github.com/glowing-bear/glowing-bear/wiki/Proxying-WeeChat-relay-with-a-web-server">a proxy</a> is used the path can be changed by entering it in the host field.</li>
</ul>
<p>
Examples of correct input for the host field are:
</p>
<ul>
<li>192.168.0.1</li>
<li>192.168.0.1:8000</li>
<li>192.168.0.1:8000/weechat2</li>
<li>[2001:db8:85a3::8a2e:370:7334]</li>
<li>[2001:db8:85a3::8a2e:370:7334]:8000</li>
<li>[2001:db8:85a3::8a2e:370:7334]:8000/weechat2</li>
<li>yourhost.yourdomain.com</li>
<li>yourhost.yourdomain.com:8000</li>
<li>yourhost.yourdomain.com:8000/weechat2</li>
</ul>
<p>
Incorrect input for the host field:
</p>
<ul>
<li><span class="text-danger">ws://192.168.0.1</span> (do not specify the scheme)</li>
<li><span class="text-danger">192.168.0.1/weechat2</span> (must specify port when specifying path)</li>
<li><span class="text-danger">[2001:db8:85a3::8a2e:370:7334]/weechat2</span> (must specify port when specifying path)</li>
<li><span class="text-danger">yourhost.yourdomain.com/weechat2</span> (must specify port when specifying path)</li>
<li><span class="text-danger">2001:db8:85a3::8a2e:370:7334</span> (must wrap IPv6 address in square brackets)</li>
<li><span class="text-danger">2001:db8:85a3::8a2e:370:7334:8000</span> (must wrap IPv6 address in square brackets)</li>
</ul>
<h3>URL Parameters</h3>
<p>
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 <a href="https://www.w3schools.com/tags/ref_urlencode.asp">URL encoded</a>.
</p>
<p>
If we want just the path for example: <code>https://glowing-bear.org/#path=weechat2</code>
</p>
<p>
An example: <code>https://glowing-bear.org/#host=my.domain.com&port=8000&password=hunter2&autoconnect=true</code>
</p>
<p>
Available parameters:
<ul>
<li>host</li>
<li>port</li>
<li>path</li>
<li>password</li>
<li>autoconnect</li>
</ul>
</p>
</div>
</div>
</div>
@ -236,8 +315,8 @@ npm run build-electron-{windows, darwin, linux}</pre>
</div>
<div class="content" id="content" sidebar-state="visible" ng-show="connected" ng-cloak>
<div id="topbar">
<div class="brand">
<a href="#" ng-click="toggleSidebar()">
<div class="brand" ng-click="toggleSidebar()">
<a href="#">
<img alt="brand" src="assets/img/favicon.png" title="Connected to {{ settings.host }}:{{ settings.port}}">
</a>
<span class="badge" ng-show="unread > 0">{{unread}}</span>
@ -359,7 +438,7 @@ npm run build-electron-{windows, darwin, linux}</pre>
</div>
<label for="size" class="col-sm-1 control-label">Size</label>
<div class="col-sm-2">
<div class="col-sm-3">
<input type="text" ng-model="settings.fontsize" class="form-control" id="size">
</div>
</div>
@ -369,7 +448,7 @@ npm run build-electron-{windows, darwin, linux}</pre>
<form class="form-horizontal" role="form">
<div class="form-group">
<label for="theme" class="col-sm-3 control-label make-thinner">Theme</label>
<div class="col-sm-7">
<div class="col-sm-8">
<select id="theme" class="form-control" ng-model="settings.theme" ng-options="theme for theme in themes"></select>
</div>
</div>
@ -380,13 +459,28 @@ npm run build-electron-{windows, darwin, linux}</pre>
<form class="form-horizontal" role="form">
<div class="form-group">
<label for="custom-css" class="col-sm-3 control-label make-thinner">Custom CSS</label>
<div class="col-sm-7">
<div class="col-sm-8">
<textarea id="custom-css" class="form-control" ng-model="settings.customCSS"></textarea>
</div>
</div>
</form>
</li>
<li class="standard-labels">
<form class="form-horizontal" role="form">
<div class="form-group">
<label class="control-label col-sm-3" for="iToken">Imgur Token <a target="_blank" href="https://github.com/glowing-bear/glowing-bear/wiki/Getting-an-imgur-token-&-album-hash"><i class="glyphicon glyphicon-info-sign"></i></a></label>
<div class="col-sm-4">
<input type="text" class="form-control" ng-model="settings.iToken" placeholder="Access Token" autocapitalize="off">
</div>
<label class="control-label col-sm-1 lessleftpad" for="iAlb">Album</label>
<div class="col-sm-3">
<input type="text" class="form-control" ng-model="settings.iAlb" placeholder="Album Hash" autocapitalize="off">
</div>
</div>
</form>
</li>
<li>
<form class="form-inline" role="form">
<div class="checkbox">

@ -11,7 +11,7 @@ var bufferResume = angular.module('bufferResume', []);
bufferResume.service('bufferResume', ['settings', function(settings) {
var resumer = {};
var key = settings.host + ":" + settings.port;
var key = settings.host + ":" + settings.port + "/" + settings.path;
// Hold the status that we were able to find the previously accessed buffer
// and reload it. If we cannot, we'll need to know so we can load the default

@ -20,15 +20,15 @@ weechat.factory('connection',
var locked = false;
// Takes care of the connection and websocket hooks
var connect = function (host, port, passwd, ssl, noCompression, successCallback, failCallback) {
var connect = function (host, port, path, passwd, ssl, useTotp, totp, noCompression, successCallback, failCallback) {
$rootScope.passwordError = false;
connectionData = [host, port, passwd, ssl, noCompression];
connectionData = [host, port, path, passwd, ssl, noCompression];
var proto = ssl ? 'wss' : 'ws';
// If host is an IPv6 literal wrap it in brackets
if (host.indexOf(":") !== -1 && host[0] !== "[" && host[host.length-1] !== "]") {
host = "[" + host + "]";
}
var url = proto + "://" + host + ":" + port + "/weechat";
var url = proto + "://" + host + ":" + port + "/" + path;
$log.debug('Connecting to URL: ', url);
var onopen = function () {
@ -45,7 +45,9 @@ weechat.factory('connection',
ngWebsockets.send(
weeChat.Protocol.formatInit({
password: passwd,
compression: noCompression ? 'off' : 'zlib'
compression: noCompression ? 'off' : 'zlib',
useTotp: useTotp,
totp: totp
})
);
@ -326,9 +328,16 @@ weechat.factory('connection',
};
var attemptReconnect = function (bufferId, timeout) {
// won't work if totp is mandatory
if (settings.useTotp)
{
$log.info('Not reconnecting because totp will be expired.');
return;
}
$log.info('Attempting to reconnect...');
var d = connectionData;
connect(d[0], d[1], d[2], d[3], d[4], function() {
connect(d[0], d[1], d[2], d[3], d[4], false, "", d[5], function() {
$rootScope.reconnecting = false;
// on success, update active buffer
models.setActiveBuffer(bufferId);

@ -19,8 +19,8 @@ weechat.config(['$compileProvider', function ($compileProvider) {
}
}]);
weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout', '$log', 'models', 'bufferResume', 'connection', 'notifications', 'utils', 'settings',
function ($rootScope, $scope, $store, $timeout, $log, models, bufferResume, connection, notifications, utils, settings)
weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout','$location', '$log', 'models', 'bufferResume', 'connection', 'notifications', 'utils', 'settings',
function ($rootScope, $scope, $store, $timeout, $location, $log, models, bufferResume, connection, notifications, utils, settings)
{
window.openBuffer = function(channel) {
@ -41,9 +41,11 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
// or else they won't be saved to the localStorage.
settings.setDefaults({
'theme': 'dark',
'host': 'localhost',
'hostField': 'localhost',
'port': 9001,
'path': 'weechat',
'ssl': (window.location.protocol === "https:"),
'useTotp': false,
'savepassword': false,
'autoconnect': false,
'nonicklist': utils.isMobileUi(),
@ -62,9 +64,17 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
'enableQuickKeys': true,
'customCSS': '',
"currentlyViewedBuffers":{},
'iToken': '',
'iAlb': '',
});
$scope.settings = settings;
//For upgrade reasons because we changed the name of host to hostField
//check if the value might still be in the host key instead of the hostField key
if (!settings.hostField && settings.host) {
settings.hostField = settings.host;
}
$rootScope.countWatchers = function () {
$log.debug($rootScope.$$watchersCount);
};
@ -104,8 +114,8 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
})();
// Show a TLS warning if GB was loaded over an unencrypted connection,
// except for local instances (testing, cordova, or electron)
$scope.show_tls_warning = (window.location.protocol !== "https:") &&
// except for local instances (local files, testing, cordova, or electron)
$scope.show_tls_warning = (["https:", "file:"].indexOf(window.location.protocol) === -1) &&
(["localhost", "127.0.0.1", "::1"].indexOf(window.location.hostname) === -1) &&
!window.is_electron && !utils.isCordova();
@ -149,6 +159,9 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
}, false);
}
$rootScope.$on('nickListChanged', function() {
$scope.updateShowNicklist();
});
$rootScope.$on('activeBufferChanged', function(event, unreadSum) {
var ab = models.getActiveBuffer();
@ -316,11 +329,6 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
}
});
$rootScope.wasMobileUi = false;
if (utils.isMobileUi()) {
$rootScope.wasMobileUi = true;
}
if (!settings.fontfamily) {
if (utils.isMobileUi()) {
settings.fontfamily = 'sans-serif';
@ -573,12 +581,10 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
// Recalculation fails when not connected
if ($rootScope.connected) {
// Show the sidebar if switching away from mobile view, hide it when switching to mobile
// Wrap in a condition so we save ourselves the $apply if nothing changes (50ms or more)
if ($scope.wasMobileUi && !utils.isMobileUi()) {
if (!utils.isMobileUi()) {
$scope.showSidebar();
$scope.updateShowNicklist();
}
$scope.wasMobileUi = utils.isMobileUi();
$scope.calculateNumLines();
// if we're scrolled to the bottom, scroll down to the same position after the resize
@ -620,6 +626,7 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
}
$rootScope.bufferBottom = eob.offsetTop <= bl.scrollTop + bl.clientHeight;
};
$rootScope.scrollWithBuffer = function(scrollToReadmarker, moreLines) {
// First, get scrolling status *before* modification
// This is required to determine where we were in the buffer pre-change
@ -653,8 +660,76 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
window.requestAnimationFrame(scroll);
};
$scope.parseHost = function() {
//The host field is multi purpose for advanced users
//There can be a combination of host, port and path
//If host is specified here the dedicated port field is disabled
$rootScope.hostInvalid = false;
var parts;
var regexHost = /^([^:\/]*|\[.*\])$/;
var regexHostPort = /^([^:]*|\[.*\]):(\d+)$/;
var regexHostPortPath = /^([^:]*|\[.*\]):(\d*)\/(.+)$/;
if ((parts = regexHost.exec(settings.hostField)) !== null) { //host only
settings.host = parts[1];
settings.path = "weechat";
$rootScope.portDisabled = false;
} else if ((parts = regexHostPort.exec(settings.hostField)) !== null) { //host:port
settings.host = parts[1];
settings.port = parts[2];
settings.path = "weechat";
$rootScope.portDisabled = true;
} else if ((parts = regexHostPortPath.exec(settings.hostField)) !== null) { //host:port/path
settings.host = parts[1];
settings.port = parts[2];
settings.path = parts[3];
$rootScope.portDisabled = true;
} else {
$rootScope.hostInvalid = true;
}
};
settings.addCallback('useTotp', function() {
if (settings.useTotp) {
settings.autoconnect = false;
}
});
$scope.parseTotp = function() {
$scope.totpInvalid = !/^\d{4,10}$/.test($scope.totp);
};
$scope.parseHash = function() {
//Fill in url parameters, they take precedence over the stored settings, but store them
var params = {};
$location.$$hash.split('&').map(function(val) {
var segs = val.split('=');
params[segs[0]] = segs[1];
});
if (params.host) {
$scope.settings.host = params.host;
$scope.settings.hostField = params.host;
}
if (params.port) {
$scope.settings.port = parseInt(params.port);
}
if (params.path) {
$scope.settings.path = params.path;
$scope.settings.hostField = $scope.settings.host + ":" + $scope.settings.port + "/" + $scope.settings.path;
}
if (params.password) {
$scope.password = params.password;
}
if (params.autoconnect) {
$scope.settings.autoconnect = params.autoconnect === 'true';
}
};
$scope.connect = function() {
notifications.requestNotificationPermission();
$rootScope.sslError = false;
$rootScope.securityError = false;
@ -662,14 +737,17 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
$rootScope.bufferBottom = true;
$scope.connectbutton = 'Connecting';
$scope.connectbuttonicon = 'glyphicon-refresh glyphicon-spin';
connection.connect(settings.host, settings.port, $scope.password, settings.ssl);
connection.connect(settings.host, settings.port, settings.path, $scope.password, settings.ssl, settings.useTotp, $scope.totp);
$scope.totp = "";//clear for next time
};
$scope.disconnect = function() {
$scope.connectbutton = 'Connect';
$scope.connectbuttonicon = 'glyphicon-chevron-right';
bufferResume.reset();
connection.disconnect();
};
$scope.reconnect = function() {
var bufferId = models.getActiveBuffer().id;
connection.attemptReconnect(bufferId, 3000);
@ -678,6 +756,7 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
$scope.showModal = function(elementId) {
document.getElementById(elementId).setAttribute('data-state', 'visible');
};
$scope.closeModal = function($event) {
function closest(elem, selector) {
var matchesSelector = elem.matches || elem.webkitMatchesSelector || elem.mozMatchesSelector || elem.msMatchesSelector;
@ -776,7 +855,7 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
$scope.updateShowNicklist = function() {
var ab = models.getActiveBuffer();
// Check whether buffer exists and nicklist is non-empty
if (!ab || ab.isNicklistEmpty()) {
if (!ab || !ab.nicklistRequested() || ab.isNicklistEmpty()) {
$scope.showNicklist = false;
return false;
}
@ -923,34 +1002,29 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
}
};
window.onhashchange = function() {
$scope.parseHash();
};
$scope.init = function() {
if (window.location.hash) {
var rawStr = atob(window.location.hash.substring(1));
window.location.hash = "";
var spl = rawStr.split(":");
var host = spl[0];
var port = parseInt(spl[1]);
var password = spl[2];
var ssl = spl.length > 3;
notifications.requestNotificationPermission();
$rootScope.sslError = false;
$rootScope.securityError = false;
$rootScope.errorMessage = false;
$rootScope.bufferBottom = true;
$scope.connectbutton = 'Connecting';
$scope.connectbuttonicon = 'glyphicon-chevron-right';
connection.connect(host, port, password, ssl);
}
$scope.parseHost();
$scope.parseHash();
};
}]);
weechat.config(['$routeProvider',
function($routeProvider) {
$routeProvider.when('/', {
weechat.config(['$routeProvider', '$locationProvider',
function($routeProvider, $locationProvider) {
$routeProvider.when('', {
templateUrl: 'index.html',
controller: 'WeechatCtrl'
});
//remove hashbang from url
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
}
]);

@ -168,7 +168,7 @@ weechat.factory('handlers', ['$rootScope', '$log', 'models', 'plugins', 'notific
$rootScope.$emit('notificationChanged');
}
if ((buffer.notify !== 0 && message.highlight) || _.contains(message.tags, 'notify_private')) {
if ((buffer.notify !== 0) && (message.highlight || _.contains(message.tags, 'notify_private'))) {
buffer.notification++;
server.unread++;
notifications.createHighlight(buffer, message);
@ -431,12 +431,24 @@ weechat.factory('handlers', ['$rootScope', '$log', 'models', 'plugins', 'notific
/*
* Handle nicklist event
*
* This event can either fill or clear a nicklist. It is always a complete nicklist.
*/
var handleNicklist = function(message) {
var nicklist = message.objects[0].content;
var group = 'root';
//clear the nicklists in case we are clearing
if (nicklist.length==1)
{
models.getBuffer(nicklist[0].pointers[0]).clearNicklist();
}
//fill the nicklist
nicklist.forEach(function(n) {
var buffer = models.getBuffer(n.pointers[0]);
//buffer nicklist
if (n.group === 1) {
var g = new models.NickGroup(n);
group = g.name;
@ -446,6 +458,9 @@ weechat.factory('handlers', ['$rootScope', '$log', 'models', 'plugins', 'notific
buffer.addNick(group, nick);
}
});
//check if nicklist should be hidden or not
$rootScope.$emit('nickListChanged');
};
/*
* Handle nicklist diff event

@ -3,7 +3,7 @@
var weechat = angular.module('weechat');
weechat.factory('imgur', ['$rootScope', function($rootScope) {
weechat.factory('imgur', ['$rootScope', 'settings', function($rootScope, settings) {
var process = function(image, callback) {
@ -26,8 +26,18 @@ weechat.factory('imgur', ['$rootScope', function($rootScope) {
// Upload image to imgur from base64
var upload = function( base64img, callback ) {
// Set client ID (Glowing Bear)
var clientId = "164efef8979cd4b";
// API authorization, either via Client ID (anonymous) or access token
// (add to user's imgur account), see also:
// https://github.com/glowing-bear/glowing-bear/wiki/Getting-an-imgur-token-&-album-hash
var accessToken = "164efef8979cd4b";
var isClientID = true;
// Check whether the user has provided an access token
if (settings.iToken.length > 37){
accessToken = settings.iToken;
isClientID = false;
}
// Progress bars container
var progressBars = document.getElementById("imgur-upload-progress"),
@ -45,6 +55,11 @@ weechat.factory('imgur', ['$rootScope', function($rootScope) {
fd.append("image", base64img); // Append the file
fd.append("type", "base64"); // Set image type to base64
// Add the image to the provided album if configured to do so
if (!isClientID && settings.iAlb.length >= 6) {
fd.append("album", settings.iAlb);
}
// Create new XMLHttpRequest
var xhttp = new XMLHttpRequest();
@ -52,7 +67,11 @@ weechat.factory('imgur', ['$rootScope', function($rootScope) {
xhttp.open("POST", "https://api.imgur.com/3/image", true);
// Set headers
xhttp.setRequestHeader("Authorization", "Client-ID " + clientId);
if (isClientID) {
xhttp.setRequestHeader("Authorization", "Client-ID " + accessToken);
} else {
xhttp.setRequestHeader("Authorization", "Bearer " + accessToken);
}
xhttp.setRequestHeader("Accept", "application/json");
// Handler for response

@ -217,6 +217,18 @@ weechat.directive('inputBar', function() {
// Extract nick from bufferline prefix
var nick = prefix[prefix.length - 1].text;
// Check whether the user is still online
var buffer = models.getBuffer(bufferline.buffer);
var is_online = buffer.queryNicklist(nick);
if (!is_online) {
// show a toast that the user left
var toast = document.createElement('div');
toast.id = "toast";
toast.innerHTML = nick + " has left the room";
document.body.appendChild(toast);
setTimeout(function() { document.body.removeChild(toast); }, 5000);
}
var newValue = $scope.command || ''; // can be undefined, in that case, use the empty string
var addColon = newValue.length === 0;
if (newValue.length > 0) {

@ -119,7 +119,7 @@ IrcUtils.service('IrcUtils', [function() {
suf = ':';
}
// addSpace defaults to true
var addSpaceChar = (addSpace === undefined || addSpace === true) ? ' ' : '';
var addSpaceChar = (addSpace === undefined || addSpace === 'on') ? ' ' : '';
// new nick list to search in
var searchNickList = _ciNickList(nickList);
@ -144,7 +144,11 @@ IrcUtils.service('IrcUtils', [function() {
if (doIterate) {
// try iterating
newNick = _nextNick(iterCandidate, m[1], searchNickList);
if (suf.endsWith(' ')) {
beforeCaret = newNick + suf;
} else {
beforeCaret = newNick + suf + ' ';
}
return {
text: beforeCaret + afterCaret,
caretPos: beforeCaret.length,
@ -166,7 +170,11 @@ IrcUtils.service('IrcUtils', [function() {
// no match
return ret;
}
if (suf.endsWith(' ')) {
beforeCaret = newNick + suf;
} else {
beforeCaret = newNick + suf + ' ';
}
if (afterCaret[0] === ' ') {
// swallow first space after caret if any
afterCaret = afterCaret.substring(1);

@ -154,6 +154,18 @@ models.service('models', ['$rootScope', '$filter', 'bufferResume', function($roo
}
*/
};
/*
* Clear the nicklist
*/
var clearNicklist = function() {
//only keep the root node
for (var obj in nicklist) {
if (obj !== 'root') {
delete nicklist[obj];
}
}
};
/*
* Updates a nick in nicklist
*/
@ -296,6 +308,19 @@ models.service('models', ['$rootScope', '$filter', 'bufferResume', function($roo
return nicklist.hasOwnProperty('root');
};
// Check whether a particular nick is in the nicklist
var queryNicklist = function(nick) {
for (var groupIdx in nicklist) {
var nicks = nicklist[groupIdx].nicks;
for (var nickIdx in nicks) {
if (nicks[nickIdx].name === nick) {
return true;
}
}
}
return false;
};
/* Clear all our buffer lines */
var clear = function() {
while(lines.length > 0) {
@ -325,6 +350,7 @@ models.service('models', ['$rootScope', '$filter', 'bufferResume', function($roo
nicklist: nicklist,
addNick: addNick,
delNick: delNick,
clearNicklist: clearNicklist,
updateNick: updateNick,
getNicklistByTime: getNicklistByTime,
serverSortKey: serverSortKey,
@ -340,6 +366,7 @@ models.service('models', ['$rootScope', '$filter', 'bufferResume', function($roo
isNicklistEmpty: isNicklistEmpty,
nicklistRequested: nicklistRequested,
pinned: pinned,
queryNicklist: queryNicklist,
};
};

@ -463,8 +463,10 @@ plugins.factory('userPlugins', function() {
jsonp(url, function(data) {
// Add the gist stylesheet only once
if (document.querySelectorAll('link[rel=stylesheet][href="' + data.stylesheet + '"]').length < 1) {
var stylesheet = '<link rel="stylesheet" href="' + data.stylesheet + '"></link>';
document.getElementsByTagName('head')[0].innerHTML += stylesheet;
var stylesheet = document.createElement("link");
stylesheet.href = data.stylesheet;
stylesheet.setAttribute('rel', 'stylesheet');
document.head.appendChild(stylesheet);
}
element.innerHTML = '<div style="clear:both">' + data.div + '</div>';
});
@ -534,24 +536,6 @@ plugins.factory('userPlugins', function() {
}
});
/*
* Vine plugin
*/
var vinePlugin = new UrlPlugin('Vine', function (url) {
var regexp = /^https?:\/\/(www\.)?vine\.co\/v\/([a-zA-Z0-9]+)(\/.*)?/i,
match = url.match(regexp);
if (match) {
var id = match[2], embedurl = "https://vine.co/v/" + id + "/embed/simple?audio=1";
var element = angular.element('<iframe></iframe>')
.addClass('vine-embed')
.attr('src', embedurl)
.attr('width', '600')
.attr('height', '600')
.attr('frameborder', '0');
return element.prop('outerHTML') + '<script async src="https://platform.vine.co/static/scripts/embed.js" charset="utf-8"></script>';
}
});
/*
* Streamable Embedded Player
*/
@ -570,7 +554,7 @@ plugins.factory('userPlugins', function() {
});
return {
plugins: [youtubePlugin, dailymotionPlugin, allocinePlugin, imagePlugin, videoPlugin, audioPlugin, spotifyPlugin, cloudmusicPlugin, googlemapPlugin, asciinemaPlugin, yrPlugin, gistPlugin, pastebinPlugin, giphyPlugin, tweetPlugin, vinePlugin, streamablePlugin]
plugins: [youtubePlugin, dailymotionPlugin, allocinePlugin, imagePlugin, videoPlugin, audioPlugin, spotifyPlugin, cloudmusicPlugin, googlemapPlugin, asciinemaPlugin, yrPlugin, gistPlugin, pastebinPlugin, giphyPlugin, tweetPlugin, streamablePlugin]
};

@ -363,7 +363,8 @@
// "*" + (A)STD + "," + EXT
// "*" + (A)EXT + "," + STD
// "*" + (A)EXT + "," + EXT
regex: /^\*(?:([\x01\x02\x03\x04*!\/_|]*)(\d{2})|@([\x01\x02\x03\x04*!\/_|]*)(\d{5})),(\d{2}|@\d{5})/,
// WeeChat 2.6+ use a tilde (~) instead of a comma (,) so recognise both
regex: /^\*(?:([\x01\x02\x03\x04*!\/_|]*)(\d{2})|@([\x01\x02\x03\x04*!\/_|]*)(\d{5}))[,~](\d{2}|@\d{5})/,
fn: function(m) {
var ret = {};
@ -647,6 +648,9 @@
if (params.password !== null) {
keys.push('password=' + params.password);
}
if (params.useTotp) {
keys.push('totp=' + params.totp);
}
parts.push(keys.join(','));
return WeeChatProtocol._formatCmd(null, 'init', parts);

@ -8,17 +8,17 @@
"license": "GPLv3",
"devDependencies": {
"bower": "^1.8.8",
"electron-packager": "^12.2.0",
"http-server": "^0.11",
"jasmine-core": "^3.1",
"jshint": "^2.9.6",
"karma": "^4.2.0",
"karma-jasmine": "~1.1",
"karma-junit-reporter": "^1.2",
"electron-packager": "~14.2",
"http-server": "^0.12.0",
"jasmine-core": "^3.5.0",
"jshint": "^2.11.0",
"karma": "~4.4",
"karma-jasmine": "~3.1",
"karma-junit-reporter": "~2.0",
"karma-phantomjs-launcher": "^1.0.0",
"protractor": "^5.4.1",
"protractor": "^5.4.2",
"shelljs": "^0.8.0",
"uglify-js": "^3.4.9"
"uglify-js": "^3.6.9"
},
"scripts": {
"postinstall": "bower install -p",

@ -169,14 +169,5 @@ describe('filter', function() {
plugins);
}));
it('should recognize vines', inject(function(plugins) {
expectTheseMessagesToContain([
'https://vine.co/v/hWh262H9HM5',
'https://vine.co/v/hWh262H9HM5/embed',
],
'Vine',
plugins);
}));
});
});

Loading…
Cancel
Save