Merge branch 'master' into gh-pages

gh-pages
David Cormier 11 years ago
commit bcfeb8fe53
  1. 8
      .jshintrc
  2. 8
      README.md
  3. BIN
      assets/img/favicon.png
  4. BIN
      assets/img/glowing-bear.png
  5. BIN
      assets/img/glowing_bear_128x128.png
  6. BIN
      assets/img/glowing_bear_60x60.png
  7. BIN
      assets/img/glowing_bear_90x90.png
  8. 167
      css/glowingbear.css
  9. 2
      directives/input.html
  10. 2
      directives/plugin.html
  11. 122
      index.html
  12. 247
      js/glowingbear.js
  13. 9
      js/localstorage.js
  14. 9
      js/models.js
  15. 121
      js/plugins.js
  16. 10
      manifest.json
  17. 5
      manifest.webapp

@ -0,0 +1,8 @@
{
"globals": {
"angular": false,
"$": false,
"window": false,
"console": false
}
}

@ -1,4 +1,4 @@
A web client for WeeChat [![Build Status](https://api.travis-ci.org/glowing-bear/glowing-bear.png)](https://travis-ci.org/glowing-bear/glowing-bear) A web client for WeeChat [![Build Status](https://api.travis-ci.org/glowing-bear/glowing-bear.png)](https://travis-ci.org/glowing-bear/glowing-bear?branch=master)
======================== ========================
Glowing Bear is an HTML5 web frontend for [WeeChat](http://weechat.org) that strives to be a modern and slick interface. It relies on WeeChat to do all the heavy lifting (connections, servers, history, etc) and then provides some nice features on top of that, like content embedding (images, video) and desktop notifications. The main advantage, though, is that you can access it from any modern internet device without having to worry about ssh connections or terminal emulators. Glowing Bear is an HTML5 web frontend for [WeeChat](http://weechat.org) that strives to be a modern and slick interface. It relies on WeeChat to do all the heavy lifting (connections, servers, history, etc) and then provides some nice features on top of that, like content embedding (images, video) and desktop notifications. The main advantage, though, is that you can access it from any modern internet device without having to worry about ssh connections or terminal emulators.
@ -15,7 +15,7 @@ To use the web interface you first need to set a relay and a password:
/relay add weechat 9001 /relay add weechat 9001
/set relay.network.password YOURPASSWORD /set relay.network.password YOURPASSWORD
Then go to the GitHub hosted version of [Glowing Bear](http://glowing-bear.github.io/glowing-bear)! Then go to our hosted version of [Glowing Bear](http://www.glowing-bear.org)!
You can run Glowing Bear in multiple ways: use it like any other webpage, as a Firefox or Chrome app, or a full-screen Chrome app on Android ("Add to homescreen"). We also provide an [Android app](https://play.google.com/store/apps/details?id=com.glowing_bear) that you can install from the Google Play Store. You can run Glowing Bear in multiple ways: use it like any other webpage, as a Firefox or Chrome app, or a full-screen Chrome app on Android ("Add to homescreen"). We also provide an [Android app](https://play.google.com/store/apps/details?id=com.glowing_bear) that you can install from the Google Play Store.
@ -29,7 +29,7 @@ FAQ
--- ---
- *Can I use Glowing Bear to access a machine or port not exposed to the internet by passing the connection through my server?* No, that's not what Glowing Bear does. You can use a websocket proxy module for your webserver to forward `/weechat` to your WeeChat instance though. Here are some pointers you might find helpful for setting this up with [nginx](http://nginx.com/blog/websocket-nginx/) or [apache](https://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html). - *Can I use Glowing Bear to access a machine or port not exposed to the internet by passing the connection through my server?* No, that's not what Glowing Bear does. You can use a websocket proxy module for your webserver to forward `/weechat` to your WeeChat instance though. Here are some pointers you might find helpful for setting this up with [nginx](http://nginx.com/blog/websocket-nginx/) or [apache](https://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html).
- *How does the encryption work?* TLS is used for securing the connection if you enable encryption. You can find more detailed instructions on how to communicate securely in the "encryption instructions" tab on the [landing page](http://glowing-bear.github.io/glowing-bear). Note that your browser will perform the certificate validation, so it is strongly recommended to use a certificate that your browser trusts. - *How does the encryption work?* TLS is used for securing the connection if you enable encryption. You can find more detailed instructions on how to communicate securely in the "encryption instructions" tab on the [landing page](http://www.glowing-bear.org). Note that your browser will perform the certificate validation, so it is strongly recommended to use a certificate that your browser trusts.
Development Development
----------- -----------
@ -45,7 +45,7 @@ python -m SimpleHTTPServer
Now you can point your browser to [http://localhost:8000](http://localhost:8000)! Now you can point your browser to [http://localhost:8000](http://localhost:8000)!
Remember that **you don't need to host Glowing Bear yourself to use it**, you can just use the [GitHub hosted version](http://glowing-bear.github.io/glowing-bear), and we'll take care of updates for you. Your browser connects to WeeChat directly, so it does not matter where Glowing Bear is hosted. Remember that **you don't need to host Glowing Bear yourself to use it**, you can just use [our hosted version](http://www.glowing-bear.org) powered by GitHub pages, and we'll take care of updates for you. Your browser connects to WeeChat directly, so it does not matter where Glowing Bear is hosted.
If you'd prefer a version hosted with HTTPS, GitHub serves that as well with an undocumented, not officially supported (by GitHub) link. Be careful though, it might break any minute. Anyway, here's the link: [secret GitHub HTTPS link](https://glowing-bear.github.io/glowing-bear/) (the trailing forward slash in the URL seems to make all the difference). If you'd prefer a version hosted with HTTPS, GitHub serves that as well with an undocumented, not officially supported (by GitHub) link. Be careful though, it might break any minute. Anyway, here's the link: [secret GitHub HTTPS link](https://glowing-bear.github.io/glowing-bear/) (the trailing forward slash in the URL seems to make all the difference).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

@ -104,6 +104,20 @@ input[type=text], input[type=password], #sendMessage, .badge {
color: #ccc; color: #ccc;
box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.1), 0px 1px 7px 0px rgba(0, 0, 0, 0.8) inset; box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.1), 0px 1px 7px 0px rgba(0, 0, 0, 0.8) inset;
background: none repeat scroll 0% 0% rgba(0, 0, 0, 0.3); background: none repeat scroll 0% 0% rgba(0, 0, 0, 0.3);
margin-bottom: 5px !important;
}
.input-group {
width: 100%;
}
.row {
margin: 0px;
max-width: 300px;
}
.no-gutter [class*="col"] {
padding: 0px
}
.col-sm-9 {
padding-right: 5px !important;
} }
.glyphicon { .glyphicon {
top: 0; /* Fixes alignment issue in top bar */ top: 0; /* Fixes alignment issue in top bar */
@ -171,10 +185,14 @@ input[type=text], input[type=password], #sendMessage, .badge {
padding-top: 35px; /* topbar */ padding-top: 35px; /* topbar */
padding-bottom: 1px; /* need to force a padding here */ padding-bottom: 1px; /* need to force a padding here */
font-size: smaller; font-size: smaller;
-webkit-transition:0.35s ease all; transition:0.2s ease-in-out;
transition:0.35s ease all;
z-index: 2; z-index: 2;
} }
#sidebar[data-state=visible] {
left: 0px;
}
#sidebar form { #sidebar form {
} }
#sidebar.ng-hide-add, #sidebar.ng-hide-remove { #sidebar.ng-hide-add, #sidebar.ng-hide-remove {
@ -253,10 +271,6 @@ input[type=text], input[type=password], #sendMessage, .badge {
min-height: 100%; min-height: 100%;
} }
.monospace {
font-family: 'Terminus', 'Consolas', 'Monaco', 'Inconsolata', 'Ubuntu Mono', monospace;
}
#bufferlines { #bufferlines {
position: relative; position: relative;
height: 100%; height: 100%;
@ -274,7 +288,7 @@ input[type=text], input[type=password], #sendMessage, .badge {
.withnicklist { .withnicklist {
margin-right: 100px !important; /* nicklist */ margin-right: 100px !important; /* nicklist */
} }
.withsidebar { .content[sidebar-state=visible] #bufferlines {
margin-left: 145px; /* sidebar */ margin-left: 145px; /* sidebar */
} }
#bufferlines .btn { #bufferlines .btn {
@ -290,7 +304,7 @@ input[type=text], input[type=password], #sendMessage, .badge {
transition:0.35s ease all; transition:0.35s ease all;
z-index: 1; z-index: 1;
} }
.footer.withsidebar { .content[sidebar-state=visible] .footer {
margin-left: 0; margin-left: 0;
padding-left: 145px; padding-left: 145px;
} }
@ -336,8 +350,23 @@ li.notification {
background: rgba(255,255,255,0.5); background: rgba(255,255,255,0.5);
} }
img.embed { div.embed * {
max-width: 100%;
}
/* not for all img embeds so as not to affect the yr plugin (302px) */
div.embed img.embed {
max-height: 300px; max-height: 300px;
max-width: 100%;
}
div.colourbox {
display: inline-block;
border-radius: 3px;
border: 1px solid #bbb;
width: 14px;
height: 14px;
margin-bottom: -2px;
} }
@ -349,21 +378,80 @@ table.notimestampseconds td.time span.seconds {
display: none !important; display: none !important;
} }
.modal ul { .gb-modal {
z-index: 1000;
height: 100%;
overflow-y: scroll;
position: absolute;
top: 0;
left: 0;
width: 100%;
}
.gb-modal[data-state=hidden] {
transition: .2s ease-in-out;
visibility: hidden;
opacity: 0;
}
.gb-modal[data-state=visible] {
transition: .2s ease-in-out;
visibility: visible;
opacity: 1;
}
.gb-modal[data-state=hidden] .modal-dialog {
transition: top .3s ease-in;
top: -150px;
}
.gb-modal[data-state=visible] .modal-dialog {
transition: top .3s ease-out;
top: 0px;
}
.gb-modal .backdrop {
z-index: 999;
position: fixed;
top: 0;
height: 100%;
width: 100%;
overflow: none;
background-color:rgba(0, 0, 0, 0.5)
}
.gb-modal .modal-dialog {
z-index: 1001;
position: absolute;
margin-left: auto;
margin-right: auto;
left: 0;
right: 0;
top: 35px;
}
.gb-modal[ng-click], .gb-modal div[ng-click] {
cursor: default;
}
.gb-modal ul {
list-style: none; list-style: none;
padding-left: 15px; padding-left: 15px;
} }
.modal li { .gb-modal li {
font-size: larger; font-size: larger;
margin-bottom: 10px; margin-bottom: 10px;
} }
.modal li li { .gb-modal li li {
font-size: medium; font-size: medium;
} }
.modal-header { .modal-header {
padding-top: 23px;
border-bottom: 0; border-bottom: 0;
} }
#fontchoice label {
font-weight: normal;
text-align: left;
}
h2 { h2 {
padding-bottom: 5px; padding-bottom: 5px;
height: 72px; height: 72px;
@ -380,14 +468,35 @@ h2 span, h2 small {
display: block; display: block;
} }
.panel[data-state=active] .panel-collapse {
transition: max-height 0.5s;
max-height: 60em;
height: auto;
display: block;
}
.panel[data-state=collapsed] .panel-collapse {
transition: max-height 0.5s;
max-height: 0;
}
.panel[data-state=collapsed] {
border: 0px solid transparent;
}
/* fix for firefox being stupid */
@-moz-document url-prefix() {
.panel[data-state=collapsed] .panel-collapse * {
display: none;
}
}
.make-thinner {
padding-right: -15px;
}
/* */ /* */
/* Mobile layout */ /* Mobile layout */
/* */ /* */
@media (max-width: 968px) { @media (max-width: 968px) {
.monospace {
/* readability on mobile +9001% */
font-family: sans-serif;
}
#bufferlines table { #bufferlines table {
border-collapse: separate; border-collapse: separate;
@ -397,8 +506,6 @@ h2 span, h2 small {
#sidebar { #sidebar {
font-size: normal; font-size: normal;
bottom: 0px; bottom: 0px;
width: auto;
max-width: 60%;
top: 0px; top: 0px;
padding-bottom: 35px; padding-bottom: 35px;
} }
@ -409,6 +516,18 @@ h2 span, h2 small {
bottom: 0px; bottom: 0px;
} }
#sidebar[data-state=visible] {
width: 200px;
}
#sidebar[data-state=hidden] {
left: -200px;
}
.content[sidebar-state=visible] #bufferlines, .content[sidebar-state=visible] .footer {
margin-left: 0px;
}
#topbar .title { #topbar .title {
left: 40px; left: 40px;
} }
@ -466,15 +585,20 @@ h2 span, h2 small {
#bufferlines td.prefix { #bufferlines td.prefix {
display: inline; display: inline;
padding-right: 0; padding-right: 5px;
border: 0; border: 0;
font-weight: bold; font-weight: bold;
font-size: 15px; font-size: 15px;
} }
#bufferlines td.message { #bufferlines td.message {
padding-left: 0;
display: inline; display: inline;
padding: 0px !important;
}
.gb-modal .modal-dialog {
margin: 20px 2%;
width: 96%;
} }
/* a different colour is too irregular on mobile */ /* a different colour is too irregular on mobile */
@ -498,4 +622,7 @@ h2 span, h2 small {
width: 5px; width: 5px;
height: 5px; height: 5px;
} }
.col-sm-9 {
padding-right: 0px !important;
}
} }

@ -1,6 +1,6 @@
<form class="form form-horizontal" id="inputform" ng-submit="sendMessage()"> <form class="form form-horizontal" id="inputform" ng-submit="sendMessage()">
<div class="input-group"> <div class="input-group">
<textarea id="{{inputId}}" class="form-control monospace" ng-trim="false" rows="1" autocomplete="off" ng-model="command"> <textarea id="{{inputId}}" class="form-control favorite-font" ng-trim="false" rows="1" autocomplete="off" ng-model="command">
</textarea> </textarea>
<span class="input-group-btn"> <span class="input-group-btn">
<button class="btn btn-default btn-primary">Send</button> <button class="btn btn-default btn-primary">Send</button>

@ -4,7 +4,7 @@
Hide {{ plugin.name }} Hide {{ plugin.name }}
</button> </button>
<div ng-bind-html="displayedContent" bo-class="'embed_' + plugin.$$hashKey"></div> <div ng-bind-html="displayedContent" class="embed" bo-class="'embed_' + plugin.$$hashKey"></div>
</div> </div>
<div ng-hide="plugin.visible"> <div ng-hide="plugin.visible">

@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<title ng-bind-template="{{ notificationStatus }}Glowing Bear {{ pageTitle}}"></title> <title ng-bind-template="{{ notificationStatus }}Glowing Bear {{ pageTitle}}"></title>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet" media="screen"> <link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet" media="screen">
<link rel="shortcut icon" sizes="128x128" href="assets/img/glowing_bear_128x128.png"> <link rel="shortcut icon" sizes="128x128" href="assets/img/glowing_bear_128x128.png">
@ -13,12 +14,10 @@
<link rel="shortcut icon" type="image/png" href="assets/img/favicon.png" > <link rel="shortcut icon" type="image/png" href="assets/img/favicon.png" >
<link href="css/style.css" rel="stylesheet" media="screen"> <link href="css/style.css" rel="stylesheet" media="screen">
<link href="css/glowingbear.css" rel="stylesheet" media="screen"> <link href="css/glowingbear.css" rel="stylesheet" media="screen">
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.8/angular.min.js"></script> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.8/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.8/angular-route.min.js"></script> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.8/angular-route.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.8/angular-sanitize.min.js"></script> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.8/angular-sanitize.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.8/angular-touch.min.js"></script> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.8/angular-touch.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.8/angular-animate.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js"></script>
<script type="text/javascript" src="3rdparty/inflate.min.js"></script> <script type="text/javascript" src="3rdparty/inflate.min.js"></script>
<script type="text/javascript" src="js/localstorage.js"></script> <script type="text/javascript" src="js/localstorage.js"></script>
@ -30,7 +29,6 @@
<script type="text/javascript" src="js/plugins.js"></script> <script type="text/javascript" src="js/plugins.js"></script>
<script type="text/javascript" src="3rdparty/bindonce.min.js"></script> <script type="text/javascript" src="3rdparty/bindonce.min.js"></script>
<script type="text/javascript" src="3rdparty/favico-0.3.4-mod.min.js"></script> <script type="text/javascript" src="3rdparty/favico-0.3.4-mod.min.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
</head> </head>
<body ng-controller="WeechatCtrl" ng-keydown="handleKeyPress($event)" ng-class="{'no-overflow': connected}"> <body ng-controller="WeechatCtrl" ng-keydown="handleKeyPress($event)" ng-class="{'no-overflow': connected}">
<div ng-hide="connected" class="container"> <div ng-hide="connected" class="container">
@ -48,26 +46,32 @@
<div class="alert alert-danger" ng-show="securityError"> <div class="alert alert-danger" ng-show="securityError">
<strong>Secure connection error</strong> Unable to connect to unencrypted relay when your are connecting to Glowing Bear over HTTPS. Please use an encrypted relay or load the page without using HTTPS. <strong>Secure connection error</strong> Unable to connect to unencrypted relay when your are connecting to Glowing Bear over HTTPS. Please use an encrypted relay or load the page without using HTTPS.
</div> </div>
<div class="panel-group" id="accordion"> <div class="panel-group accordion">
<div class="panel"> <div class="panel" data-state="active">
<div class="panel-heading"> <div class="panel-heading">
<h4 class="panel-title"> <h4 class="panel-title">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion" href="#collapseOne"> <a class="accordion-toggle" ng-click="toggleAccordion($event)">
Connection settings Connection settings
</a> </a>
</h4> </h4>
</div> </div>
<div id="collapseOne" class="panel-collapse collapse in"> <div id="collapseOne" class="panel-collapse collapse in">
<div class="panel-body"> <div class="panel-body">
<form class="form-signin" role="form"> <form class="form-signin" role="form">
<div class="form-group"> <div class="form-group">
<label class="control-label" for="host">WeeChat relay hostname and port number</label> <label class="control-label" for="host">WeeChat relay hostname and port number</label>
<div class="input-group"> <div class="input-group">
<input type="text" class="form-control monospace" id="host" ng-model="host" placeholder="Address"> <div class="row no-gutter">
<input type="text" class="form-control monospace" id="port" ng-model="port"> <div class="col-sm-9">
<input type="text" class="form-control favorite-font" id="host" ng-model="host" placeholder="Address" >
</div>
<div class="col-sm-3">
<input type="text" class="form-control favorite-font" id="port" ng-model="port" placeholder="Port">
</div>
</div>
</div> </div>
<label class="control-label" for="password">WeeChat relay password</label> <label class="control-label" for="password">WeeChat relay password</label>
<input type="password" class="form-control monospace" id="password" ng-model="password" placeholder="Password"> <input type="password" class="form-control favorite-font" id="password" ng-model="password" placeholder="Password">
<div class="alert alert-danger" ng-show="passwordError"> <div class="alert alert-danger" ng-show="passwordError">
Error: wrong password Error: wrong password
</div> </div>
@ -78,6 +82,12 @@
Save password in your browser Save password in your browser
</label> </label>
</div> </div>
<div class="checkbox" ng-show="savepassword">
<label class="control-label" for="autoconnect">
<input type="checkbox" id="autoconnect" ng-model="autoconnect">
Automatically connect
</label>
</div>
<div class="checkbox"> <div class="checkbox">
<label class="control-label" for="ssl"> <label class="control-label" for="ssl">
<input type="checkbox" id="ssl" ng-model="ssl"> <input type="checkbox" id="ssl" ng-model="ssl">
@ -90,15 +100,15 @@
</div> </div>
</div> </div>
</div> </div>
<div class="panel"> <div class="panel" data-state="collapsed">
<div class="panel-heading"> <div class="panel-heading">
<h4 class="panel-title"> <h4 class="panel-title">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion" href="#collapseTwo"> <a class="accordion-toggle" ng-click="toggleAccordion($event)">
Usage instructions Usage instructions
</a> </a>
</h4> </h4>
</div> </div>
<div id="collapseTwo" class="panel-collapse collapse"> <div id="collapseTwo" class="panel-collapse collapse in">
<div class="panel-body"> <div class="panel-body">
<h3>Configuring the relay</h3> <h3>Configuring the relay</h3>
<div>To start using glowing bear, please enable the relay plugin in your WeeChat client: <div>To start using glowing bear, please enable the relay plugin in your WeeChat client:
@ -115,26 +125,28 @@
<ul> <ul>
<li>ALT-n: Toggle nicklist</li> <li>ALT-n: Toggle nicklist</li>
<li>ALT-l: Focus on input bar</li> <li>ALT-l: Focus on input bar</li>
<li>ALT-[0-9]: Focus on buffer</li> <li>ALT-[0-9]: Switch to buffer number N</li>
<li>ALT-a: Focus on next buffer with activity</li> <li>ALT-a: Focus on next buffer with activity</li>
<li>ALT-&lt;: Switch to previous buffer</li> <li>ALT-&lt;: Switch to previous active buffer</li>
<li>ALT-g: Focus on buffer list filter</li> <li>ALT-g: Focus on buffer list filter</li>
<li>Esc-Esc: disconnect (double-tap)</li> <li>Esc-Esc: Disconnect (double-tap)</li>
<li>arrow keys: history navigation</li> <li>Arrow keys: Navigate history</li>
<li>Tab key: Complete nick</li>
<li>The following readline/emacs style keybindings can be enabled with a setting: <span title="Move cursor to beginning of line">Ctrl-a</span>, <span title="Move cursor to te end of the line">Ctrl-e</span>, <span title="Delete from cursor to beginning of the line">Ctrl-u</span>, <span title="Delete from cursor to the end of the line">Ctrl-k</span>, <span title="Delete from cursor to previous space">Ctrl-w</span></li>
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="panel "> <div class="panel" data-state="collapsed">
<div class="panel-heading"> <div class="panel-heading">
<h4 class="panel-title"> <h4 class="panel-title">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion" href="#collapseThree"> <a class="accordion-toggle" ng-click="toggleAccordion($event)">
Encryption instructions Encryption instructions
</a> </a>
</h4> </h4>
</div> </div>
<div id="collapseThree" class="panel-collapse collapse"> <div id="collapseThree" class="panel-collapse collapse in">
<div class="panel-body"> <div class="panel-body">
<p>If you check the encryption box, the communication between browser and WeeChat will be encrypted with SSL.</p> <p>If you check the encryption box, the communication between browser and WeeChat will be encrypted with SSL.</p>
<p><strong>Note</strong>: If you are using a self-signed certificate, you have to visit <a href="https://{{ host }}:{{ port }}/">https://{{ host || 'weechathost' }}:{{ port || 'relayport' }}/</a> in your browser first to add a security exception. You can close that tab once you confirmed the certificate, no content will appear. The necessity of this process is a bug in <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=594502">Firefox</a> and other browsers.</p> <p><strong>Note</strong>: If you are using a self-signed certificate, you have to visit <a href="https://{{ host }}:{{ port }}/">https://{{ host || 'weechathost' }}:{{ port || 'relayport' }}/</a> in your browser first to add a security exception. You can close that tab once you confirmed the certificate, no content will appear. The necessity of this process is a bug in <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=594502">Firefox</a> and other browsers.</p>
@ -152,15 +164,15 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
</div> </div>
</div> </div>
</div> </div>
<div class="panel" ng-hide="isinstalled"> <div class="panel" data-state="collapsed" ng-hide="isinstalled">
<div class="panel-heading"> <div class="panel-heading">
<h4 class="panel-title"> <h4 class="panel-title">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion" href="#collapseFour"> <a class="accordion-toggle" ng-click="toggleAccordion($event)">
Install app Install app
</a> </a>
</h4> </h4>
</div> </div>
<div id="collapseFour" class="panel-collapse collapse"> <div id="collapseFour" class="panel-collapse collapse in">
<div class="panel-body"> <div class="panel-body">
<p>You don't need to install anything to use this app, it should work with any modern browser. Start using it <a data-toggle="collapse" data-parent="#accordion" href="#collapseOne">right now</a>! However, there are a few ways to improve integration with your operating system.</p> <p>You don't need to install anything to use this app, it should work with any modern browser. Start using it <a data-toggle="collapse" data-parent="#accordion" href="#collapseOne">right now</a>! However, there are a few ways to improve integration with your operating system.</p>
<h3>Firefox</h3> <h3>Firefox</h3>
@ -171,15 +183,15 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
</div> </div>
</div> </div>
</div> </div>
<div class="panel"> <div class="panel" data-state="collapsed">
<div class="panel-heading"> <div class="panel-heading">
<h4 class="panel-title"> <h4 class="panel-title">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion" href="#collapseFive"> <a class="accordion-toggle" ng-click="toggleAccordion($event)">
Get involved Get involved
</a> </a>
</h4> </h4>
</div> </div>
<div id="collapseFive" class="panel-collapse collapse"> <div id="collapseFive" class="panel-collapse collapse in">
<div class="panel-body"> <div class="panel-body">
<p>Glowing bear is built by a small group of developers in their free time. As we're always trying to improve it, we would love getting your feedback and help. If that sounds like something you might enjoy, check out our <a href="https://github.com/glowing-bear/glowing-bear">project page</a> on GitHub!</p> <p>Glowing bear is built by a small group of developers in their free time. As we're always trying to improve it, we would love getting your feedback and help. If that sounds like something you might enjoy, check out our <a href="https://github.com/glowing-bear/glowing-bear">project page</a> on GitHub!</p>
<p>If you're interested in contributing or simply want to say hello, head over to <strong>#glowing-bear</strong> on <strong>freenode!</strong> We won't bite, promise (-ish).</p> <p>If you're interested in contributing or simply want to say hello, head over to <strong>#glowing-bear</strong> on <strong>freenode!</strong> We won't bite, promise (-ish).</p>
@ -188,10 +200,10 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
</div> </div>
</div> </div>
</div> </div>
<div class="content" ng-show="connected"> <div class="content" id="content" sidebar-state="visible" ng-show="connected">
<div id="topbar"> <div id="topbar">
<div class="brand"> <div class="brand">
<a href="#" ng-click="swipeSidebar()"> <a href="#" ng-click="toggleSidebar()">
<img alt="brand" src="assets/img/favicon.png" title="Connected to {{ host }}:{{ port}}"> <img alt="brand" src="assets/img/favicon.png" title="Connected to {{ host }}:{{ port}}">
</a> </a>
<button ng-if="debugMode" ng-click="countWatchers()">Count<br />Watchers</button> <button ng-if="debugMode" ng-click="countWatchers()">Count<br />Watchers</button>
@ -199,7 +211,7 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
<div class="title" ng-bind-html="activeBuffer().title | irclinky:'_blank'" title="{{activeBuffer().title}}"></div> <div class="title" ng-bind-html="activeBuffer().title | irclinky:'_blank'" title="{{activeBuffer().title}}"></div>
<div class="actions pull-right vertical-line-left"> <div class="actions pull-right vertical-line-left">
<div class="pull-left"> <div class="pull-left">
<a class="" data-toggle="modal" data-target="#settingsModal" title="Options menu"> <a class="settings-toggle" ng-click="showModal('settingsModal')" title="Options menu">
<i class="glyphicon glyphicon-cog"></i> <i class="glyphicon glyphicon-cog"></i>
</a> </a>
</div> </div>
@ -208,14 +220,14 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
</a> </a>
</div> </div>
</div> </div>
<div bindonce id="sidebar" ng-show="showSidebar" ng-swipe-left="swipeSidebar()" class="vertical-line"> <div bindonce id="sidebar" data-state="visible" ng-swipe-left="hideSidebar()" class="vertical-line">
<ul class="nav nav-pills nav-stacked" ng-class="{'indented': (predicate === 'serverSortKey')}"> <ul class="nav nav-pills nav-stacked" ng-class="{'indented': (predicate === 'serverSortKey')}">
<li class="bufferfilter"> <li class="bufferfilter">
<form role="form"> <form role="form">
<input class="form-control monospace" type="text" id="bufferFilter" ng-model="search" ng-keydown="handleSearchBoxKey($event)" placeholder="Search"> <input class="form-control favorite-font" type="text" id="bufferFilter" ng-model="search" ng-keydown="handleSearchBoxKey($event)" placeholder="Search">
</form> </form>
</li> </li>
<li class="buffer" ng-class="{'active': buffer.active, 'indent': buffer.indent }" ng-repeat="(key, buffer) in (filteredBuffers = (buffers | toArray | filter:{fullName:search} | filter:hasUnread | orderBy:predicate))"> <li class="buffer" ng-class="{'active': buffer.active, 'indent': buffer.indent }" ng-repeat="(key, buffer) in (filteredBuffers = (getBuffers() | toArray | filter:{fullName:search} | filter:hasUnread | orderBy:predicate))">
<a href="#" ng-click="setActiveBuffer(buffer.id)" title="{{ buffer.fullName }}"> <a href="#" ng-click="setActiveBuffer(buffer.id)" title="{{ buffer.fullName }}">
<span class="badge pull-right" ng-class="{'danger': buffer.notification}" ng-if="buffer.notification || buffer.unread" ng-bind="buffer.notification || buffer.unread"></span> <span class="badge pull-right" ng-class="{'danger': buffer.notification}" ng-if="buffer.notification || buffer.unread" ng-bind="buffer.notification || buffer.unread"></span>
<span class="buffername">{{ buffer.shortName || buffer.fullName }}</span> <span class="buffername">{{ buffer.shortName || buffer.fullName }}</span>
@ -223,7 +235,7 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
</li> </li>
</ul> </ul>
</div> </div>
<div bindonce id="bufferlines" class="monospace" ng-swipe-right="swipeSidebar()" ng-swipe-left="openNick()" ng-class="{'withnicklist': showNicklist, 'withsidebar': showSidebar}"> <div bindonce id="bufferlines" class="favorite-font" ng-swipe-right="showSidebar()" ng-swipe-left="hideSidebar()" ng-class="{'withnicklist': showNicklist}">
<div id="nicklist" ng-if="showNicklist" ng-swipe-right="closeNick()" class="vertical-line-left"> <div id="nicklist" ng-if="showNicklist" ng-swipe-right="closeNick()" class="vertical-line-left">
<ul class="nicklistgroup list-unstyled" ng-repeat="group in nicklist"> <ul class="nicklistgroup list-unstyled" ng-repeat="group in nicklist">
<li ng-repeat="nick in group.nicks|orderBy:'name'"> <li ng-repeat="nick in group.nicks|orderBy:'name'">
@ -250,7 +262,7 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
<td class="prefix"><a ng-click="addMention(bufferline.prefix)"><span ng-repeat="part in bufferline.prefix" bo-class="part.classes" bo-html="part.text"></span></a></td> <td class="prefix"><a ng-click="addMention(bufferline.prefix)"><span ng-repeat="part in bufferline.prefix" bo-class="part.classes" bo-html="part.text"></span></a></td>
<td class="message"> <td class="message">
<div ng-repeat="metadata in bufferline.metadata" plugin data="metadata"></div> <div ng-repeat="metadata in bufferline.metadata" plugin data="metadata"></div>
<span ng-repeat="part in bufferline.content" class="text" bo-class="part.classes" bo-html="part.text|irclinky:'_blank'"></span> <span ng-repeat="part in bufferline.content" class="text" bo-class="part.classes" bo-html="part.text | irclinky:'_blank' | inlinecolour"></span>
</td> </td>
</tr> </tr>
<tr class="readmarker" ng-if="activeBuffer().lastSeen==$index"> <tr class="readmarker" ng-if="activeBuffer().lastSeen==$index">
@ -261,21 +273,37 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="footer" ng-class="{'withnicklist': showNicklist, 'withsidebar': showSidebar}"> <div class="footer" ng-class="{'withnicklist': showNicklist}">
<div input-bar input-id="sendMessage"></div> <div input-bar input-id="sendMessage"></div>
</div> </div>
</div> </div>
<div id="soundNotification"></div> <div id="soundNotification"></div>
<div id="settingsModal" class="modal fade"> <div id="settingsModal" class="gb-modal" data-state="hidden">
<div class="backdrop" ng-click="closeModal($event)"></div>
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button> <button type="button" class="close" ng-click="closeModal($event)" aria-hidden="true">&times;</button>
<h4 class="modal-title">Settings</h4> <h4 class="modal-title">Settings</h4>
<p>Settings will be stored in your browser.</p>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Settings will be stored in your browser.</p>
<ul class=""> <ul class="">
<li id="fontchoice">
<form class="form-horizontal" role="form">
<div class="form-group">
<label for="font" class="col-sm-3 control-label make-thinner">Preferred font</label>
<div class="col-sm-4">
<input type="text" ng-model="fontfamily" class="form-control" id="font">
</div>
<label for="size" class="col-sm-1 control-label">Size</label>
<div class="col-sm-2">
<input type="text" ng-model="fontsize" class="form-control" id="size" placeholder="14px">
</div>
</div>
</form>
</li>
<li> <li>
<form class="form-inline" role="form"> <form class="form-inline" role="form">
<div class="checkbox"> <div class="checkbox">
@ -323,7 +351,7 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" ng-model="hotlistsync"> <input type="checkbox" ng-model="hotlistsync">
Sync hotlist with WeeChat Mark messages as read in WeeChat
</label> </label>
</div> </div>
</form> </form>
@ -343,7 +371,7 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" ng-model="orderbyserver"> <input type="checkbox" ng-model="orderbyserver">
Order channels by server Hierarchical buffer view (order by server)
</label> </label>
</div> </div>
</form> </form>
@ -352,8 +380,8 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
<form class="form-inline" role="form"> <form class="form-inline" role="form">
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" ng-model="useFavico"> <input type="checkbox" ng-model="readlineBindings">
Display unread count in favicon Enable common readline keybindings in input bar
</label> </label>
</div> </div>
</form> </form>
@ -362,8 +390,8 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
<form class="form-inline" role="form"> <form class="form-inline" role="form">
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" ng-model="soundnotification"> <input type="checkbox" ng-model="useFavico">
Play sound on notification Display unread count in favicon
</label> </label>
</div> </div>
</form> </form>
@ -372,8 +400,8 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
<form class="form-inline" role="form"> <form class="form-inline" role="form">
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" ng-model="debugMode"> <input type="checkbox" ng-model="soundnotification">
Debug Mode Play sound on notification
</label> </label>
</div> </div>
</form> </form>
@ -381,7 +409,7 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
</ul> </ul>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-primary" ng-click="closeModal($event)">Close</button>
</div> </div>
</div><!-- /.modal-content --> </div><!-- /.modal-content -->
</div><!-- /.modal-dialog --> </div><!-- /.modal-dialog -->

@ -1,4 +1,4 @@
var weechat = angular.module('weechat', ['ngRoute', 'localStorage', 'weechatModels', 'plugins', 'ngSanitize', 'ngWebsockets', 'pasvaz.bindonce', 'ngTouch', 'ngAnimate']); var weechat = angular.module('weechat', ['ngRoute', 'localStorage', 'weechatModels', 'plugins', 'ngSanitize', 'ngWebsockets', 'pasvaz.bindonce', 'ngTouch']);
weechat.filter('toArray', function () { weechat.filter('toArray', function () {
'use strict'; 'use strict';
@ -14,6 +14,19 @@ weechat.filter('toArray', function () {
}; };
}); });
// Helper to change style of a class
var changeClassStyle = function(classSelector, attr, value) {
_.each(document.getElementsByClassName(classSelector), function(e) {
e.style[attr] = value;
});
};
// Helper to get style from a class
var getClassStyle = function(classSelector, attr) {
_.each(document.getElementsByClassName(classSelector), function(e) {
return e.style[attr];
});
};
weechat.filter('irclinky', ['$filter', function($filter) { weechat.filter('irclinky', ['$filter', function($filter) {
'use strict'; 'use strict';
return function(text, target) { return function(text, target) {
@ -28,7 +41,7 @@ weechat.filter('irclinky', ['$filter', function($filter) {
// However, it matches all *common* IRC channels while trying to minimise false positives. "#1" is much // However, it matches all *common* IRC channels while trying to minimise false positives. "#1" is much
// more likely to be "number 1" than "IRC channel #1". // more likely to be "number 1" than "IRC channel #1".
// Thus, we only match channels beginning with a # and having at least one letter in them. // Thus, we only match channels beginning with a # and having at least one letter in them.
var channelRegex = /(^|[\s,.:;?!"'()+@])(#+[a-z0-9-_]*[a-z][a-z0-9-_]*)/gmi; var channelRegex = /(^|[\s,.:;?!"'()+@-])(#+[a-z0-9-_]*[a-z][a-z0-9-_]*)/gmi;
// This is SUPER nasty, but ng-click does not work inside a filter, as the markup has to be $compiled first, which is not possible in filter afaik. // This is SUPER nasty, but ng-click does not work inside a filter, as the markup has to be $compiled first, which is not possible in filter afaik.
// Therefore, get the scope, fire the method, and $apply. Yuck. I sincerely hope someone finds a better way of doing this. // Therefore, get the scope, fire the method, and $apply. Yuck. I sincerely hope someone finds a better way of doing this.
linkiedText = linkiedText.replace(channelRegex, '$1<a href="#" onclick="var $scope = angular.element(event.target).scope(); $scope.openBuffer(\'$2\'); $scope.$apply();">$2</a>'); linkiedText = linkiedText.replace(channelRegex, '$1<a href="#" onclick="var $scope = angular.element(event.target).scope(); $scope.openBuffer(\'$2\'); $scope.$apply();">$2</a>');
@ -36,6 +49,22 @@ weechat.filter('irclinky', ['$filter', function($filter) {
}; };
}]); }]);
weechat.filter('inlinecolour', function() {
'use strict';
return function(text) {
if (!text) {
return text;
}
// only match 6-digit colour codes, 3-digit ones have too many false positives (issue numbers, etc)
var hexColourRegex = /(^|[^&])\#([0-9a-f]{6})($|[^\w'"])/gmi;
var substitute = '$1#$2 <div class="colourbox" style="background-color:#$2"></div> $3';
return text.replace(hexColourRegex, substitute);
};
});
weechat.factory('handlers', ['$rootScope', '$log', 'models', 'plugins', function($rootScope, $log, models, plugins) { weechat.factory('handlers', ['$rootScope', '$log', 'models', 'plugins', function($rootScope, $log, models, plugins) {
var handleBufferClosing = function(message) { var handleBufferClosing = function(message) {
@ -50,7 +79,7 @@ weechat.factory('handlers', ['$rootScope', '$log', 'models', 'plugins', function
buffer.requestedLines++; buffer.requestedLines++;
// Only react to line if its displayed // Only react to line if its displayed
if (message.displayed) { if (message.displayed) {
message = plugins.PluginManager.contentForMessage(message, $rootScope.visible); message = plugins.PluginManager.contentForMessage(message);
buffer.addLine(message); buffer.addLine(message);
if (manually) { if (manually) {
@ -87,7 +116,7 @@ weechat.factory('handlers', ['$rootScope', '$log', 'models', 'plugins', function
var buffer = new models.Buffer(bufferMessage); var buffer = new models.Buffer(bufferMessage);
models.addBuffer(buffer); models.addBuffer(buffer);
/* Until we can decide if user asked for this buffer to be opened /* Until we can decide if user asked for this buffer to be opened
* or not we will let user click opened buffers. * or not we will let user click opened buffers.
models.setActiveBuffer(buffer.id); models.setActiveBuffer(buffer.id);
*/ */
}; };
@ -483,6 +512,9 @@ function($rootScope,
// Don't do that if we didn't get any more lines than we already had // Don't do that if we didn't get any more lines than we already had
var setReadmarker = (buffer.lastSeen >= 0) && (oldLength !== buffer.lines.length); var setReadmarker = (buffer.lastSeen >= 0) && (oldLength !== buffer.lines.length);
buffer.lines.length = 0; buffer.lines.length = 0;
// We need to set the number of requested lines to 0 here, because parsing a line
// increments it. This is needed to also count newly arriving lines while we're
// already connected.
buffer.requestedLines = 0; buffer.requestedLines = 0;
// Count number of lines recieved // Count number of lines recieved
var linesReceivedCount = lineinfo.objects[0].content.length; var linesReceivedCount = lineinfo.objects[0].content.length;
@ -523,9 +555,6 @@ function($rootScope,
weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout', '$log', 'models', 'connection', function ($rootScope, $scope, $store, $timeout, $log, models, connection) { weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout', '$log', 'models', 'connection', function ($rootScope, $scope, $store, $timeout, $log, models, connection) {
$scope.port = 9001;
// From: http://stackoverflow.com/a/18539624 by StackOverflow user "plantian" // From: http://stackoverflow.com/a/18539624 by StackOverflow user "plantian"
$rootScope.countWatchers = function () { $rootScope.countWatchers = function () {
var q = [$rootScope], watchers = 0, scope; var q = [$rootScope], watchers = 0, scope;
@ -611,6 +640,17 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
} }
})(); })();
// Enable debug mode if "?debug=1" or "?debug=true" is set
(function() {
window.location.search.substring(1).split('&').forEach(function(f) {
var segs = f.split('=');
if (segs[0] === "debug" && ["true", "1"].indexOf(segs[1]) != -1) {
$rootScope.debugMode = true;
return;
}
});
})();
$rootScope.isWindowFocused = function() { $rootScope.isWindowFocused = function() {
if (typeof $scope.documentHidden === "undefined") { if (typeof $scope.documentHidden === "undefined") {
@ -699,9 +739,14 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
$rootScope.$on('activeBufferChanged', function(event, unreadSum) { $rootScope.$on('activeBufferChanged', function(event, unreadSum) {
var ab = models.getActiveBuffer(); var ab = models.getActiveBuffer();
// trim lines to 2 screenfuls + 10 lines // Discard surplus lines. This is done *before* lines are fetched because that saves us the effort of special handling for the
ab.lines.splice(0, ab.lines.length - (2 * $scope.lines_per_screen + 10)); // case where a buffer is opened for the first time ;)
ab.requestedLines = ab.lines.length; var minRetainUnread = ab.lines.length - unreadSum + 5; // do not discard unread lines and keep 5 additional lines for context
var surplusLines = ab.lines.length - (2 * $scope.lines_per_screen + 10); // retain up to 2*(screenful + 10) + 10 lines because magic numbers
var linesToRemove = Math.max(0, Math.min(minRetainUnread, surplusLines));
ab.lines.splice(0, linesToRemove); // remove the lines from the buffer
ab.requestedLines -= linesToRemove; // to ensure that the correct amount of lines is fetched should more be requested
ab.lastSeen -= linesToRemove; // adjust readmarker
$scope.bufferlines = ab.lines; $scope.bufferlines = ab.lines;
$scope.nicklist = ab.nicklist; $scope.nicklist = ab.nicklist;
@ -721,8 +766,7 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
if (ab.requestedLines < $scope.lines_per_screen) { if (ab.requestedLines < $scope.lines_per_screen) {
// buffer has not been loaded, but some lines may already be present if they arrived after we connected // buffer has not been loaded, but some lines may already be present if they arrived after we connected
// try to determine how many lines to fetch // try to determine how many lines to fetch
var numLines = $scope.lines_per_screen; // that's a screenful plus 10 lines var numLines = $scope.lines_per_screen + 10; // that's (a screenful plus 10 lines) plus 10 lines, just to be safe
unreadSum += 10; // let's just add a 10 line safety margin here again
if (unreadSum > numLines) { if (unreadSum > numLines) {
// request up to 4*(screenful + 10 lines) // request up to 4*(screenful + 10 lines)
numLines = Math.min(4*numLines, unreadSum); numLines = Math.min(4*numLines, unreadSum);
@ -774,23 +818,20 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
}); });
$rootScope.$on('relayDisconnect', function() { $rootScope.$on('relayDisconnect', function() {
// this reinitialze just breaks the bufferlist upon reconnection. models.reinitialize();
// Disabled it until it's fully investigated and fixed
//models.reinitialize();
$rootScope.$emit('notificationChanged'); $rootScope.$emit('notificationChanged');
$scope.connectbutton = 'Connect'; $scope.connectbutton = 'Connect';
}); });
$scope.connectbutton = 'Connect'; $scope.connectbutton = 'Connect';
$scope.showSidebar = true; $scope.getBuffers = models.getBuffers.bind(models);
$scope.buffers = models.model.buffers;
$scope.bufferlines = {}; $scope.bufferlines = {};
$scope.nicklist = {}; $scope.nicklist = {};
$scope.activeBuffer = models.getActiveBuffer; $scope.activeBuffer = models.getActiveBuffer;
$rootScope.connected = false;
$rootScope.waseverconnected = false; $rootScope.waseverconnected = false;
$rootScope.models = models; $rootScope.models = models;
@ -805,6 +846,7 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
if ($scope.savepassword) { if ($scope.savepassword) {
$store.bind($scope, "password", ""); $store.bind($scope, "password", "");
} }
$store.bind($scope, "autoconnect", false);
// If we are on mobile change some defaults // If we are on mobile change some defaults
// We use 968 px as the cutoff, which should match the value in glowingbear.css // We use 968 px as the cutoff, which should match the value in glowingbear.css
@ -831,7 +873,7 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
// Save setting for displaying embeds // Save setting for displaying embeds
$store.bind($scope, "noembed", noembed); $store.bind($scope, "noembed", noembed);
// Save setting for channel ordering // Save setting for channel ordering
$store.bind($scope, "orderbyserver", false); $store.bind($scope, "orderbyserver", true);
// Save setting for updating favicon // Save setting for updating favicon
$store.bind($scope, "useFavico", true); $store.bind($scope, "useFavico", true);
// Save setting for showtimestamp // Save setting for showtimestamp
@ -840,17 +882,58 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
$store.bind($scope, "showtimestampSeconds", false); $store.bind($scope, "showtimestampSeconds", false);
// Save setting for playing sound on notification // Save setting for playing sound on notification
$store.bind($scope, "soundnotification", false); $store.bind($scope, "soundnotification", false);
// Save setting for font family
$store.bind($scope, "fontfamily", getClassStyle('favorite-font', 'fontFamily'));
// Save setting for font size
$store.bind($scope, "fontsize", getClassStyle('favorite-font', 'fontSize'));
// Save setting for readline keybindings
$store.bind($scope, "readlineBindings", false);
if (!$scope.fontfamily) {
if ($rootScope.isMobileUi()) {
$scope.fontfamily = 'sans-serif';
} else {
$scope.fontfamily = "Inconsolata, Consolas, Monaco, Ubuntu Mono, monospace";
}
}
// Save setting for displaying embeds in rootScope so it can be used from service // Save setting for displaying embeds in rootScope so it can be used from service
$rootScope.visible = $scope.noembed === false; $rootScope.auto_display_embedded_content = $scope.noembed === false;
// Open and close panels while on mobile devices through swiping $scope.isSidebarVisible = function() {
$scope.swipeSidebar = function() { return document.getElementById('sidebar').getAttribute('sidebar-state') === 'visible';
};
$scope.showSidebar = function() {
document.getElementById('sidebar').setAttribute('data-state', 'visible');
document.getElementById('content').setAttribute('sidebar-state', 'visible');
};
$scope.hideSidebar = function() {
if ($rootScope.isMobileUi()) { if ($rootScope.isMobileUi()) {
$scope.showSidebar = !$scope.showSidebar; document.getElementById('sidebar').setAttribute('data-state', 'hidden');
document.getElementById('content').setAttribute('sidebar-state', 'hidden');
} }
}; };
// This also fires on page load
$scope.$watch('autoconnect', function() {
if ($scope.autoconnect && !$rootScope.connected && !$rootScope.sslError && !$rootScope.securityError && !$rootScope.errorMessage) {
$scope.connect();
}
});
// toggle sidebar (if on mobile)
$scope.toggleSidebar = function() {
if ($rootScope.isMobileUi()) {
if ($scope.isSidebarVisible()) {
$scope.hideSidebar();
} else {
$scope.showSidebar();
}
}
};
// Open and close panels while on mobile devices through swiping
$scope.openNick = function() { $scope.openNick = function() {
if ($rootScope.isMobileUi()) { if ($rootScope.isMobileUi()) {
if ($scope.nonicklist) { if ($scope.nonicklist) {
@ -869,7 +952,7 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
// Watch model and update show setting when it changes // Watch model and update show setting when it changes
$scope.$watch('noembed', function() { $scope.$watch('noembed', function() {
$rootScope.visible = $scope.noembed === false; $rootScope.auto_display_embedded_content = $scope.noembed === false;
}); });
// Watch model and update channel sorting when it changes // Watch model and update channel sorting when it changes
$scope.$watch('orderbyserver', function() { $scope.$watch('orderbyserver', function() {
@ -888,12 +971,25 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
} }
}); });
// Update font family when changed
$scope.$watch('fontfamily', function() {
changeClassStyle('favorite-font', 'fontFamily', $scope.fontfamily);
});
// Update font size when changed
$scope.$watch('fontsize', function() {
changeClassStyle('favorite-font', 'fontSize', $scope.fontsize);
});
// Crude scoping hack. The keypress listener does not live in the same scope as
// the checkbox, so we need to transfer this between scopes here.
$scope.$watch('readlineBindings', function() {
$rootScope.readlineBindings = $scope.readlineBindings;
});
$scope.setActiveBuffer = function(bufferId, key) { $scope.setActiveBuffer = function(bufferId, key) {
// If we are on mobile we need to collapse the menu on sidebar clicks // If we are on mobile we need to collapse the menu on sidebar clicks
// We use 968 px as the cutoff, which should match the value in glowingbear.css // We use 968 px as the cutoff, which should match the value in glowingbear.css
if ($rootScope.isMobileUi()) { if ($rootScope.isMobileUi()) {
$scope.showSidebar = false; $scope.hideSidebar();
} }
return models.setActiveBuffer(bufferId, key); return models.setActiveBuffer(bufferId, key);
}; };
@ -933,10 +1029,8 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
if ($rootScope.connected) { if ($rootScope.connected) {
// Show the sidebar if switching away from mobile view, hide it when switching to mobile // 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) // Wrap in a condition so we save ourselves the $apply if nothing changes (50ms or more)
if ($scope.wasMobileUi !== $scope.isMobileUi() && if ($scope.wasMobileUi && !$scope.isMobileUi()) {
$scope.showSidebar === $scope.isMobileUi()) { $scope.showSidebar();
$scope.showSidebar = !$scope.showSidebar;
$scope.$apply();
} }
$scope.wasMobileUi = $scope.isMobileUi(); $scope.wasMobileUi = $scope.isMobileUi();
$scope.calculateNumLines(); $scope.calculateNumLines();
@ -1027,6 +1121,36 @@ 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;
while (elem) {
if (matchesSelector.call(elem, selector)) return elem;
else elem = elem.parentElement;
}
}
closest($event.target, '.gb-modal').setAttribute('data-state', 'hidden');
};
$scope.toggleAccordion = function(event) {
event.stopPropagation();
event.preventDefault();
var target = event.target.parentNode.parentNode.parentNode;
target.setAttribute('data-state', target.getAttribute('data-state') === 'active' ? 'collapsed' : 'active');
// Hide all other siblings
var siblings = target.parentNode.children;
for (var childId in siblings) {
var child = siblings[childId];
if (child.nodeType === 1 && child !== target) {
child.setAttribute('data-state', 'collapsed');
}
}
};
/* Function gets called from bufferLineAdded code if user should be notified */ /* Function gets called from bufferLineAdded code if user should be notified */
$rootScope.createHighlight = function(buffer, message) { $rootScope.createHighlight = function(buffer, message) {
@ -1125,7 +1249,7 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
$rootScope.switchToActivityBuffer = function() { $rootScope.switchToActivityBuffer = function() {
// Find next buffer with activity and switch to it // Find next buffer with activity and switch to it
var sortedBuffers = _.sortBy($scope.buffers, 'number'); var sortedBuffers = _.sortBy($scope.getBuffers(), 'number');
var i, buffer; var i, buffer;
// Try to find buffer with notification // Try to find buffer with notification
for (i in sortedBuffers) { for (i in sortedBuffers) {
@ -1190,7 +1314,7 @@ weechat.config(['$routeProvider',
]); ]);
weechat.directive('plugin', function() { weechat.directive('plugin', function($rootScope) {
/* /*
* Plugin directive * Plugin directive
* Shows additional plugin content * Shows additional plugin content
@ -1206,6 +1330,8 @@ weechat.directive('plugin', function() {
$scope.displayedContent = ""; $scope.displayedContent = "";
$scope.plugin.visible = $rootScope.auto_display_embedded_content;
$scope.hideContent = function() { $scope.hideContent = function() {
$scope.plugin.visible = false; $scope.plugin.visible = false;
}; };
@ -1229,6 +1355,10 @@ weechat.directive('plugin', function() {
}; };
setTimeout(scroll, 100); setTimeout(scroll, 100);
}; };
if ($scope.plugin.visible) {
$scope.showContent();
}
} }
}; };
}); });
@ -1239,7 +1369,7 @@ weechat.directive('inputBar', function() {
return { return {
templateUrl: 'directives/input.html', templateUrl: 'directives/input.html',
scope: { scope: {
inputId: '@inputId' inputId: '@inputId'
}, },
@ -1312,6 +1442,8 @@ weechat.directive('inputBar', function() {
// Empty the input after it's sent // Empty the input after it's sent
$scope.command = ''; $scope.command = '';
} }
$scope.getInputNode().focus();
}; };
$rootScope.addMention = function(prefix) { $rootScope.addMention = function(prefix) {
@ -1428,6 +1560,16 @@ weechat.directive('inputBar', function() {
// Double-tap Escape -> disconnect // Double-tap Escape -> disconnect
if (code === 27) { if (code === 27) {
$event.preventDefault(); $event.preventDefault();
// Check if a modal is visible. If so, close it instead of disconnecting
var modals = document.querySelectorAll('.gb-modal');
for (var modalId = 0; modalId < modals.length; modalId++) {
if (modals[modalId].getAttribute('data-state') === 'visible') {
modals[modalId].setAttribute('data-state', 'hidden');
return true;
}
}
if (typeof $scope.lastEscape !== "undefined" && (Date.now() - $scope.lastEscape) <= 500) { if (typeof $scope.lastEscape !== "undefined" && (Date.now() - $scope.lastEscape) <= 500) {
// Double-tap // Double-tap
connection.disconnect(); connection.disconnect();
@ -1449,7 +1591,9 @@ weechat.directive('inputBar', function() {
// Set cursor to last position. Need 0ms timeout because browser sets cursor // Set cursor to last position. Need 0ms timeout because browser sets cursor
// position to the beginning after this key handler returns. // position to the beginning after this key handler returns.
setTimeout(function() { setTimeout(function() {
inputNode.setSelectionRange($scope.command.length, $scope.command.length); if ($scope.command) {
inputNode.setSelectionRange($scope.command.length, $scope.command.length);
}
}, 0); }, 0);
return true; return true;
} }
@ -1462,12 +1606,47 @@ weechat.directive('inputBar', function() {
} }
// Enter to submit, shift-enter for newline // Enter to submit, shift-enter for newline
//
if (code == 13 && !$event.shiftKey && document.activeElement === inputNode) { if (code == 13 && !$event.shiftKey && document.activeElement === inputNode) {
$event.preventDefault(); $event.preventDefault();
$scope.sendMessage(); $scope.sendMessage();
return true; return true;
} }
// Some readline keybindings
if ($rootScope.readlineBindings && $event.ctrlKey && !$event.altKey && !$event.shiftKey && document.activeElement === inputNode) {
// get current caret position
var caretPos = inputNode.selectionStart;
// Ctrl-a
if (code == 65) {
inputNode.setSelectionRange(0, 0);
// Ctrl-e
} else if (code == 69) {
inputNode.setSelectionRange($scope.command.length, $scope.command.length);
// Ctrl-u
} else if (code == 85) {
$scope.command = $scope.command.slice(caretPos);
setTimeout(function() {
inputNode.setSelectionRange(0, 0);
});
// Ctrl-k
} else if (code == 75) {
$scope.command = $scope.command.slice(0, caretPos);
setTimeout(function() {
inputNode.setSelectionRange($scope.command.length, $scope.command.length);
});
// Ctrl-w
} else if (code == 87) {
var trimmedValue = $scope.command.slice(0, caretPos);
var lastSpace = trimmedValue.lastIndexOf(' ') + 1;
$scope.command = $scope.command.slice(0, lastSpace) + $scope.command.slice(caretPos, $scope.command.length);
setTimeout(function() {
inputNode.setSelectionRange(lastSpace, lastSpace);
});
} else {
return false;
}
$event.preventDefault();
return true;
}
}; };
} }
}; };

@ -1,3 +1,5 @@
(function() {
'use strict';
var ls = angular.module('localStorage',[]); var ls = angular.module('localStorage',[]);
@ -97,8 +99,10 @@ ls.factory("$store",function($parse){
* @returns {*} - returns whatever the stored value is * @returns {*} - returns whatever the stored value is
*/ */
bind: function ($scope, key, def) { bind: function ($scope, key, def) {
def = def || ''; if (def === undefined) {
if (publicMethods.get(key) === undefined) { def = '';
}
if (publicMethods.get(key) === undefined || publicMethods.get(key) === null) {
publicMethods.set(key, def); publicMethods.set(key, def);
} }
$parse(key).assign($scope, publicMethods.get(key)); $parse(key).assign($scope, publicMethods.get(key));
@ -110,3 +114,4 @@ ls.factory("$store",function($parse){
}; };
return publicMethods; return publicMethods;
}); });
})();

@ -167,7 +167,14 @@ models.service('models', ['$rootScope', '$filter', function($rootScope, $filter)
}; };
var getHistoryDown = function(currentLine) { var getHistoryDown = function(currentLine) {
if (historyPos < 0 || historyPos >= history.length) { if (historyPos === history.length) {
// stash on history like weechat does
if (currentLine !== undefined && currentLine !== '') {
history.push(currentLine);
historyPos++;
}
return '';
} else if (historyPos < 0 || historyPos > history.length) {
// Can't go down from out of bounds or last message // Can't go down from out of bounds or last message
return currentLine; return currentLine;
} else { } else {

@ -53,26 +53,39 @@ plugins.service('plugins', ['userPlugins', '$sce', function(userPlugins, $sce) {
* Iterates through all the registered plugins * Iterates through all the registered plugins
* and run their contentForMessage function. * and run their contentForMessage function.
*/ */
var contentForMessage = function(message, visible) { var contentForMessage = function(message) {
message.metadata = []; message.metadata = [];
var addPluginContent = function(content, pluginName, num) {
if (num) {
pluginName += " " + num;
}
message.metadata.push({
'content': $sce.trustAsHtml(content),
'nsfw': nsfw,
'name': pluginName
});
};
for (var i = 0; i < plugins.length; i++) { for (var i = 0; i < plugins.length; i++) {
var nsfw = false; var nsfw = false;
if (message.text.match(nsfwRegexp)) { if (message.text.match(nsfwRegexp)) {
nsfw = true; nsfw = true;
visible = false;
} }
var pluginContent = plugins[i].contentForMessage(message.text); var pluginContent = plugins[i].contentForMessage(message.text);
if (pluginContent) { if (pluginContent && pluginContent !== []) {
pluginContent = {'visible': visible,
'content': $sce.trustAsHtml(pluginContent), if (pluginContent instanceof Array) {
'nsfw': nsfw, for (var j = pluginContent.length - 1; j >= 0; j--) {
'name': plugins[i].name }; // only give a number if there are multiple embeds
var num = (pluginContent.length == 1) ? undefined : (j + 1);
message.metadata.push(pluginContent); addPluginContent(pluginContent[j], plugins[i].name, num);
}
} else {
addPluginContent(pluginContent, plugins[i].name);
}
if (plugins[i].exclusive) { if (plugins[i].exclusive) {
break; break;
@ -111,7 +124,7 @@ plugins.service('plugins', ['userPlugins', '$sce', function(userPlugins, $sce) {
*/ */
plugins.factory('userPlugins', function() { plugins.factory('userPlugins', function() {
var urlRegexp = RegExp(/(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&amp;:\/~+#-]*[\w@?^=%&amp;\/~+#-])?/); var urlRegexp = RegExp(/(?:ftp|https?):\/\/\S*[^\s.;,(){}<>]/g);
/* /*
* Spotify Embedded Player * Spotify Embedded Player
@ -121,18 +134,16 @@ plugins.factory('userPlugins', function() {
*/ */
var spotifyPlugin = new Plugin(function(message) { var spotifyPlugin = new Plugin(function(message) {
content = [];
var addMatch = function(match) { var addMatch = function(match) {
var ret = ''; for (var i = 0; match && i < match.length; i++) {
for(var i in match) { var id = match[i].substr(match[i].length - 22, match[i].length);
var id = match[i].substr(match[i].length-22, match[i].length); content.push('<iframe src="//embed.spotify.com/?uri=spotify:track:' + id + '" width="300" height="80" frameborder="0" allowtransparency="true"></iframe>');
ret += '<iframe src="//embed.spotify.com/?uri=spotify:track:'+id+'" width="300" height="80" frameborder="0" allowtransparency="true"></iframe>';
} }
return ret;
}; };
var match = message.match(/spotify:track:([a-zA-Z-0-9]{22})/g); addMatch(message.match(/spotify:track:([a-zA-Z-0-9]{22})/g));
var ret = addMatch(match); addMatch(message.match(/open.spotify.com\/track\/([a-zA-Z-0-9]{22})/g));
ret += addMatch(message.match(/open.spotify.com\/track\/([a-zA-Z-0-9]{22})/g)); return content;
return ret;
}); });
spotifyPlugin.name = 'Spotify track'; spotifyPlugin.name = 'Spotify track';
@ -143,20 +154,20 @@ plugins.factory('userPlugins', function() {
*/ */
var youtubePlugin = new Plugin(function(message) { var youtubePlugin = new Plugin(function(message) {
var regExp = /((?:https?:\/\/)?(?:www\.)?(?:youtube.com|youtu.be)\/(?:v\/|embed\/|watch(?:\?v=|\/))?([a-zA-Z0-9-]+))/gm; var regExp = /(?:https?:\/\/)?(?:www\.)?(?:youtube.com|youtu.be)\/(?:v\/|embed\/|watch(?:\?v=|\/))?([a-zA-Z0-9-]+)/gm;
var match = regExp.exec(message); var match = regExp.exec(message);
var retval = ''; var content = [];
// iterate over all matches // iterate over all matches
while (match !== null){ while (match !== null){
var token = match[2]; var token = match[1];
var embedurl = "https://www.youtube.com/embed/" + token + "?html5=1&iv_load_policy=3&modestbranding=1&rel=0&showinfo=0"; var embedurl = "https://www.youtube.com/embed/" + token + "?html5=1&iv_load_policy=3&modestbranding=1&rel=0&showinfo=0";
retval += '<iframe width="560" height="315" src="'+ embedurl + '" frameborder="0" allowfullscreen frameborder="0"></iframe>'; content.push('<iframe width="560" height="315" src="'+ embedurl + '" frameborder="0" allowfullscreen frameborder="0"></iframe>');
// next match // next match
match = regExp.exec(message); match = regExp.exec(message);
} }
return retval; return content;
}); });
youtubePlugin.name = 'YouTube video'; youtubePlugin.name = 'YouTube video';
@ -206,11 +217,11 @@ plugins.factory('userPlugins', function() {
*/ */
var imagePlugin = new Plugin(function(message) { var imagePlugin = new Plugin(function(message) {
var url = message.match(urlRegexp); var urls = message.match(urlRegexp);
var content = null; var content = [];
if (url) { for (var i = 0; urls && i < urls.length; i++) {
url = url[0]; /* Actually parse one url per message */ var url = urls[i];
if (url.match(/\.(png|gif|jpg|jpeg)$/i)) { if (url.match(/\.(png|gif|jpg|jpeg)$/i)) {
/* A fukung.net URL may end by an image extension but is not a direct link. */ /* A fukung.net URL may end by an image extension but is not a direct link. */
@ -221,7 +232,7 @@ plugins.factory('userPlugins', function() {
url = url.replace(/http:/, ""); url = url.replace(/http:/, "");
} }
content = '<a target="_blank" href="'+url+'"><img class="embed" src="' + url + '"></a>'; content.push('<a target="_blank" href="'+url+'"><img class="embed" src="' + url + '"></a>');
} }
} }
@ -234,23 +245,24 @@ plugins.factory('userPlugins', function() {
*/ */
var cloudmusicPlugin = new Plugin(function(message) { var cloudmusicPlugin = new Plugin(function(message) {
var match = message.match(urlRegexp); var urls = message.match(urlRegexp);
var content = [];
if (match) { for (var i = 0; urls && i < urls.length; i++) {
var url = match[0]; var url = urls[i];
/* SoundCloud http://help.soundcloud.com/customer/portal/articles/247785-what-widgets-can-i-use-from-soundcloud- */ /* SoundCloud http://help.soundcloud.com/customer/portal/articles/247785-what-widgets-can-i-use-from-soundcloud- */
if (url.match(/^https?:\/\/soundcloud.com\//)) { if (url.match(/^https?:\/\/soundcloud.com\//)) {
return '<iframe width="100%" height="120" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url=' + url + '&amp;color=ff6600&amp;auto_play=false&amp;show_artwork=true"></iframe>'; content.push('<iframe width="100%" height="120" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url=' + url + '&amp;color=ff6600&amp;auto_play=false&amp;show_artwork=true"></iframe>');
} }
/* MixCloud */ /* MixCloud */
if (url.match(/^https?:\/\/([a-z]+\.)?mixcloud.com\//)) { if (url.match(/^https?:\/\/([a-z]+\.)?mixcloud.com\//)) {
return '<iframe width="480" height="60" src="//www.mixcloud.com/widget/iframe/?feed=' + url + '&mini=1&stylecolor=&hide_artwork=&embed_type=widget_standard&hide_tracklist=1&hide_cover=" frameborder="0"></iframe>'; content.push('<iframe width="480" height="60" src="//www.mixcloud.com/widget/iframe/?feed=' + url + '&mini=1&stylecolor=&hide_artwork=&embed_type=widget_standard&hide_tracklist=1&hide_cover=" frameborder="0"></iframe>');
} }
} }
return null; return content;
}); });
cloudmusicPlugin.name = 'cloud music'; cloudmusicPlugin.name = 'cloud music';
@ -259,17 +271,18 @@ plugins.factory('userPlugins', function() {
*/ */
var googlemapPlugin = new Plugin(function(message) { var googlemapPlugin = new Plugin(function(message) {
var match = message.match(urlRegexp); var urls = message.match(urlRegexp);
var content = [];
if (match) { for (var i = 0; urls && i < urls.length; i++) {
var url = match[0]; var url = urls[i];
if (url.match(/^https?:\/\/maps\.google\./i) || url.match(/^https?:\/\/([^\w]+\.)?google\.[^\w]+\/maps/i)) { if (url.match(/^https?:\/\/maps\.google\./i) || url.match(/^https?:\/\/(?:[\w]+\.)?google\.[\w]+\/maps/i)) {
return '<iframe width="450" height="350" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="' + url + '&output=embed"></iframe>'; content.push('<iframe width="450" height="350" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="' + url + '&output=embed"></iframe>');
} }
} }
return null; return content;
}); });
googlemapPlugin.name = 'Google Map'; googlemapPlugin.name = 'Google Map';
@ -287,8 +300,28 @@ plugins.factory('userPlugins', function() {
}); });
asciinemaPlugin.name = "ascii cast"; asciinemaPlugin.name = "ascii cast";
var yrPlugin = new Plugin(function(message) {
var urls = message.match(urlRegexp);
var content = [];
for (var i = 0; urls && i < urls.length; i++) {
var url = urls[i];
var regexp = /^https?:\/\/(?:www\.)?yr\.no\/(place|stad|sted|sadji|paikka)\/(([^\s.;,(){}<>\/]+\/){3,})/;
var match = url.match(regexp);
if (match) {
var language = match[1];
var location = match[2];
var city = match[match.length - 1].slice(0, -1);
url = "http://www.yr.no/" + language + "/" + location + "avansert_meteogram.png";
content.push("<img src='" + url + "' alt='Meteogram for " + city + "' />");
}
}
return content;
});
yrPlugin.name = "meteogram";
return { return {
plugins: [youtubePlugin, dailymotionPlugin, allocinePlugin, imagePlugin, spotifyPlugin, cloudmusicPlugin, googlemapPlugin, asciinemaPlugin] plugins: [youtubePlugin, dailymotionPlugin, allocinePlugin, imagePlugin, spotifyPlugin, cloudmusicPlugin, googlemapPlugin, asciinemaPlugin, yrPlugin]
}; };

@ -1,11 +1,11 @@
{ {
"name": "Weechat", "name": "Glowing Bear",
"description": "WeeChat Web frontend", "description": "WeeChat Web frontend",
"version": "0.0.0.1", "version": "0.4.0",
"manifest_version": 2, "manifest_version": 2,
"icons": { "icons": {
"32": "img/favicon.png", "32": "assets/img/favicon.png",
"128": "img/weechat_logo_128x128.png" "128": "assets/img/glowing_bear_128x128.png"
}, },
"app": { "app": {
"urls": [ "urls": [
@ -20,6 +20,6 @@
"notifications" "notifications"
], ],
"web_accessible_resources": [ "web_accessible_resources": [
"img/favicon.png" "assets/img/favicon.png"
] ]
} }

@ -1,5 +1,5 @@
{ {
"name": "WeeChat", "name": "Glowing Bear",
"description": "WeeChat Web frontend", "description": "WeeChat Web frontend",
"launch_path": "/glowing-bear/index.html", "launch_path": "/glowing-bear/index.html",
"icons": { "icons": {
@ -16,6 +16,5 @@
"url": "https://github.com/glowing-bear" "url": "https://github.com/glowing-bear"
}, },
"default_locale": "en", "default_locale": "en",
"version": "2" "version": "0.4.0"
} }

Loading…
Cancel
Save