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. 243
      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.
@ -15,7 +15,7 @@ To use the web interface you first need to set a relay and a password:
/relay add weechat 9001
/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.
@ -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).
- *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
-----------
@ -45,7 +45,7 @@ python -m SimpleHTTPServer
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).

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;
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);
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 {
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-bottom: 1px; /* need to force a padding here */
font-size: smaller;
-webkit-transition:0.35s ease all;
transition:0.35s ease all;
transition:0.2s ease-in-out;
z-index: 2;
}
#sidebar[data-state=visible] {
left: 0px;
}
#sidebar form {
}
#sidebar.ng-hide-add, #sidebar.ng-hide-remove {
@ -253,10 +271,6 @@ input[type=text], input[type=password], #sendMessage, .badge {
min-height: 100%;
}
.monospace {
font-family: 'Terminus', 'Consolas', 'Monaco', 'Inconsolata', 'Ubuntu Mono', monospace;
}
#bufferlines {
position: relative;
height: 100%;
@ -274,7 +288,7 @@ input[type=text], input[type=password], #sendMessage, .badge {
.withnicklist {
margin-right: 100px !important; /* nicklist */
}
.withsidebar {
.content[sidebar-state=visible] #bufferlines {
margin-left: 145px; /* sidebar */
}
#bufferlines .btn {
@ -290,7 +304,7 @@ input[type=text], input[type=password], #sendMessage, .badge {
transition:0.35s ease all;
z-index: 1;
}
.footer.withsidebar {
.content[sidebar-state=visible] .footer {
margin-left: 0;
padding-left: 145px;
}
@ -336,8 +350,23 @@ li.notification {
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-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;
}
.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;
padding-left: 15px;
}
.modal li {
.gb-modal li {
font-size: larger;
margin-bottom: 10px;
}
.modal li li {
.gb-modal li li {
font-size: medium;
}
.modal-header {
padding-top: 23px;
border-bottom: 0;
}
#fontchoice label {
font-weight: normal;
text-align: left;
}
h2 {
padding-bottom: 5px;
height: 72px;
@ -380,14 +468,35 @@ h2 span, h2 small {
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 */
/* */
@media (max-width: 968px) {
.monospace {
/* readability on mobile +9001% */
font-family: sans-serif;
}
#bufferlines table {
border-collapse: separate;
@ -397,8 +506,6 @@ h2 span, h2 small {
#sidebar {
font-size: normal;
bottom: 0px;
width: auto;
max-width: 60%;
top: 0px;
padding-bottom: 35px;
}
@ -409,6 +516,18 @@ h2 span, h2 small {
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 {
left: 40px;
}
@ -466,15 +585,20 @@ h2 span, h2 small {
#bufferlines td.prefix {
display: inline;
padding-right: 0;
padding-right: 5px;
border: 0;
font-weight: bold;
font-size: 15px;
}
#bufferlines td.message {
padding-left: 0;
display: inline;
padding: 0px !important;
}
.gb-modal .modal-dialog {
margin: 20px 2%;
width: 96%;
}
/* a different colour is too irregular on mobile */
@ -498,4 +622,7 @@ h2 span, h2 small {
width: 5px;
height: 5px;
}
.col-sm-9 {
padding-right: 0px !important;
}
}

@ -1,6 +1,6 @@
<form class="form form-horizontal" id="inputform" ng-submit="sendMessage()">
<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>
<span class="input-group-btn">
<button class="btn btn-default btn-primary">Send</button>

@ -4,7 +4,7 @@
Hide {{ plugin.name }}
</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 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="apple-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>
<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">
@ -13,12 +14,10 @@
<link rel="shortcut icon" type="image/png" href="assets/img/favicon.png" >
<link href="css/style.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-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-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 type="text/javascript" src="3rdparty/inflate.min.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="3rdparty/bindonce.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>
<body ng-controller="WeechatCtrl" ng-keydown="handleKeyPress($event)" ng-class="{'no-overflow': connected}">
<div ng-hide="connected" class="container">
@ -48,26 +46,32 @@
<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.
</div>
<div class="panel-group" id="accordion">
<div class="panel">
<div class="panel-group accordion">
<div class="panel" data-state="active">
<div class="panel-heading">
<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
</a>
</h4>
</div>
<div id="collapseOne" class="panel-collapse collapse in">
<div class="panel-body">
<form class="form-signin" role="form">
<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">
<input type="text" class="form-control monospace" id="host" ng-model="host" placeholder="Address">
<input type="text" class="form-control monospace" id="port" ng-model="port">
<div class="row no-gutter">
<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>
<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">
Error: wrong password
</div>
@ -78,6 +82,12 @@
Save password in your browser
</label>
</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">
<label class="control-label" for="ssl">
<input type="checkbox" id="ssl" ng-model="ssl">
@ -90,15 +100,15 @@
</div>
</div>
</div>
<div class="panel">
<div class="panel" data-state="collapsed">
<div class="panel-heading">
<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
</a>
</h4>
</div>
<div id="collapseTwo" class="panel-collapse collapse">
<div id="collapseTwo" class="panel-collapse collapse in">
<div class="panel-body">
<h3>Configuring the relay</h3>
<div>To start using glowing bear, please enable the relay plugin in your WeeChat client:
@ -115,26 +125,28 @@
<ul>
<li>ALT-n: Toggle nicklist</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-&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>Esc-Esc: disconnect (double-tap)</li>
<li>arrow keys: history navigation</li>
<li>Esc-Esc: Disconnect (double-tap)</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>
</div>
</div>
</div>
</div>
<div class="panel ">
<div class="panel" data-state="collapsed">
<div class="panel-heading">
<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
</a>
</h4>
</div>
<div id="collapseThree" class="panel-collapse collapse">
<div id="collapseThree" class="panel-collapse collapse in">
<div class="panel-body">
<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>
@ -152,15 +164,15 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
</div>
</div>
</div>
<div class="panel" ng-hide="isinstalled">
<div class="panel" data-state="collapsed" ng-hide="isinstalled">
<div class="panel-heading">
<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
</a>
</h4>
</div>
<div id="collapseFour" class="panel-collapse collapse">
<div id="collapseFour" class="panel-collapse collapse in">
<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>
<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 class="panel">
<div class="panel" data-state="collapsed">
<div class="panel-heading">
<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
</a>
</h4>
</div>
<div id="collapseFive" class="panel-collapse collapse">
<div id="collapseFive" class="panel-collapse collapse in">
<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>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 class="content" ng-show="connected">
<div class="content" id="content" sidebar-state="visible" ng-show="connected">
<div id="topbar">
<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}}">
</a>
<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="actions pull-right vertical-line-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>
</a>
</div>
@ -208,14 +220,14 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
</a>
</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')}">
<li class="bufferfilter">
<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>
</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 }}">
<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>
@ -223,7 +235,7 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
</li>
</ul>
</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">
<ul class="nicklistgroup list-unstyled" ng-repeat="group in nicklist">
<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="message">
<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>
</tr>
<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>
</table>
</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>
</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-content">
<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>
<p>Settings will be stored in your browser.</p>
</div>
<div class="modal-body">
<p>Settings will be stored in your browser.</p>
<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>
<form class="form-inline" role="form">
<div class="checkbox">
@ -323,7 +351,7 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
<div class="checkbox">
<label>
<input type="checkbox" ng-model="hotlistsync">
Sync hotlist with WeeChat
Mark messages as read in WeeChat
</label>
</div>
</form>
@ -343,7 +371,7 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
<div class="checkbox">
<label>
<input type="checkbox" ng-model="orderbyserver">
Order channels by server
Hierarchical buffer view (order by server)
</label>
</div>
</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">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="useFavico">
Display unread count in favicon
<input type="checkbox" ng-model="readlineBindings">
Enable common readline keybindings in input bar
</label>
</div>
</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">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="soundnotification">
Play sound on notification
<input type="checkbox" ng-model="useFavico">
Display unread count in favicon
</label>
</div>
</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">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="debugMode">
Debug Mode
<input type="checkbox" ng-model="soundnotification">
Play sound on notification
</label>
</div>
</form>
@ -381,7 +409,7 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
</ul>
</div>
<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><!-- /.modal-content -->
</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 () {
'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) {
'use strict';
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
// 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.
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.
// 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>');
@ -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) {
var handleBufferClosing = function(message) {
@ -50,7 +79,7 @@ weechat.factory('handlers', ['$rootScope', '$log', 'models', 'plugins', function
buffer.requestedLines++;
// Only react to line if its displayed
if (message.displayed) {
message = plugins.PluginManager.contentForMessage(message, $rootScope.visible);
message = plugins.PluginManager.contentForMessage(message);
buffer.addLine(message);
if (manually) {
@ -483,6 +512,9 @@ function($rootScope,
// 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);
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;
// Count number of lines recieved
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) {
$scope.port = 9001;
// From: http://stackoverflow.com/a/18539624 by StackOverflow user "plantian"
$rootScope.countWatchers = function () {
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() {
if (typeof $scope.documentHidden === "undefined") {
@ -699,9 +739,14 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
$rootScope.$on('activeBufferChanged', function(event, unreadSum) {
var ab = models.getActiveBuffer();
// trim lines to 2 screenfuls + 10 lines
ab.lines.splice(0, ab.lines.length - (2 * $scope.lines_per_screen + 10));
ab.requestedLines = ab.lines.length;
// Discard surplus lines. This is done *before* lines are fetched because that saves us the effort of special handling for the
// case where a buffer is opened for the first time ;)
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.nicklist = ab.nicklist;
@ -721,8 +766,7 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
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
// try to determine how many lines to fetch
var numLines = $scope.lines_per_screen; // that's a screenful plus 10 lines
unreadSum += 10; // let's just add a 10 line safety margin here again
var numLines = $scope.lines_per_screen + 10; // that's (a screenful plus 10 lines) plus 10 lines, just to be safe
if (unreadSum > numLines) {
// request up to 4*(screenful + 10 lines)
numLines = Math.min(4*numLines, unreadSum);
@ -774,23 +818,20 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
});
$rootScope.$on('relayDisconnect', function() {
// this reinitialze just breaks the bufferlist upon reconnection.
// Disabled it until it's fully investigated and fixed
//models.reinitialize();
models.reinitialize();
$rootScope.$emit('notificationChanged');
$scope.connectbutton = 'Connect';
});
$scope.connectbutton = 'Connect';
$scope.showSidebar = true;
$scope.buffers = models.model.buffers;
$scope.getBuffers = models.getBuffers.bind(models);
$scope.bufferlines = {};
$scope.nicklist = {};
$scope.activeBuffer = models.getActiveBuffer;
$rootScope.connected = false;
$rootScope.waseverconnected = false;
$rootScope.models = models;
@ -805,6 +846,7 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
if ($scope.savepassword) {
$store.bind($scope, "password", "");
}
$store.bind($scope, "autoconnect", false);
// If we are on mobile change some defaults
// 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
$store.bind($scope, "noembed", noembed);
// Save setting for channel ordering
$store.bind($scope, "orderbyserver", false);
$store.bind($scope, "orderbyserver", true);
// Save setting for updating favicon
$store.bind($scope, "useFavico", true);
// Save setting for showtimestamp
@ -840,17 +882,58 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
$store.bind($scope, "showtimestampSeconds", false);
// Save setting for playing sound on notification
$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
$rootScope.visible = $scope.noembed === false;
$rootScope.auto_display_embedded_content = $scope.noembed === false;
// Open and close panels while on mobile devices through swiping
$scope.swipeSidebar = function() {
$scope.isSidebarVisible = 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()) {
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()) {
$scope.showSidebar = !$scope.showSidebar;
if ($scope.isSidebarVisible()) {
$scope.hideSidebar();
} else {
$scope.showSidebar();
}
}
};
// Open and close panels while on mobile devices through swiping
$scope.openNick = function() {
if ($rootScope.isMobileUi()) {
if ($scope.nonicklist) {
@ -869,7 +952,7 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
// Watch model and update show setting when it changes
$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
$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) {
// 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
if ($rootScope.isMobileUi()) {
$scope.showSidebar = false;
$scope.hideSidebar();
}
return models.setActiveBuffer(bufferId, key);
};
@ -933,10 +1029,8 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
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 !== $scope.isMobileUi() &&
$scope.showSidebar === $scope.isMobileUi()) {
$scope.showSidebar = !$scope.showSidebar;
$scope.$apply();
if ($scope.wasMobileUi && !$scope.isMobileUi()) {
$scope.showSidebar();
}
$scope.wasMobileUi = $scope.isMobileUi();
$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 */
$rootScope.createHighlight = function(buffer, message) {
@ -1125,7 +1249,7 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
$rootScope.switchToActivityBuffer = function() {
// Find next buffer with activity and switch to it
var sortedBuffers = _.sortBy($scope.buffers, 'number');
var sortedBuffers = _.sortBy($scope.getBuffers(), 'number');
var i, buffer;
// Try to find buffer with notification
for (i in sortedBuffers) {
@ -1190,7 +1314,7 @@ weechat.config(['$routeProvider',
]);
weechat.directive('plugin', function() {
weechat.directive('plugin', function($rootScope) {
/*
* Plugin directive
* Shows additional plugin content
@ -1206,6 +1330,8 @@ weechat.directive('plugin', function() {
$scope.displayedContent = "";
$scope.plugin.visible = $rootScope.auto_display_embedded_content;
$scope.hideContent = function() {
$scope.plugin.visible = false;
};
@ -1229,6 +1355,10 @@ weechat.directive('plugin', function() {
};
setTimeout(scroll, 100);
};
if ($scope.plugin.visible) {
$scope.showContent();
}
}
};
});
@ -1312,6 +1442,8 @@ weechat.directive('inputBar', function() {
// Empty the input after it's sent
$scope.command = '';
}
$scope.getInputNode().focus();
};
$rootScope.addMention = function(prefix) {
@ -1428,6 +1560,16 @@ weechat.directive('inputBar', function() {
// Double-tap Escape -> disconnect
if (code === 27) {
$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) {
// Double-tap
connection.disconnect();
@ -1449,7 +1591,9 @@ weechat.directive('inputBar', function() {
// Set cursor to last position. Need 0ms timeout because browser sets cursor
// position to the beginning after this key handler returns.
setTimeout(function() {
inputNode.setSelectionRange($scope.command.length, $scope.command.length);
if ($scope.command) {
inputNode.setSelectionRange($scope.command.length, $scope.command.length);
}
}, 0);
return true;
}
@ -1462,12 +1606,47 @@ weechat.directive('inputBar', function() {
}
// Enter to submit, shift-enter for newline
//
if (code == 13 && !$event.shiftKey && document.activeElement === inputNode) {
$event.preventDefault();
$scope.sendMessage();
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',[]);
@ -97,8 +99,10 @@ ls.factory("$store",function($parse){
* @returns {*} - returns whatever the stored value is
*/
bind: function ($scope, key, def) {
def = def || '';
if (publicMethods.get(key) === undefined) {
if (def === undefined) {
def = '';
}
if (publicMethods.get(key) === undefined || publicMethods.get(key) === null) {
publicMethods.set(key, def);
}
$parse(key).assign($scope, publicMethods.get(key));
@ -110,3 +114,4 @@ ls.factory("$store",function($parse){
};
return publicMethods;
});
})();

@ -167,7 +167,14 @@ models.service('models', ['$rootScope', '$filter', function($rootScope, $filter)
};
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
return currentLine;
} else {

@ -53,26 +53,39 @@ plugins.service('plugins', ['userPlugins', '$sce', function(userPlugins, $sce) {
* Iterates through all the registered plugins
* and run their contentForMessage function.
*/
var contentForMessage = function(message, visible) {
var contentForMessage = function(message) {
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++) {
var nsfw = false;
if (message.text.match(nsfwRegexp)) {
nsfw = true;
visible = false;
}
var pluginContent = plugins[i].contentForMessage(message.text);
if (pluginContent) {
pluginContent = {'visible': visible,
'content': $sce.trustAsHtml(pluginContent),
'nsfw': nsfw,
'name': plugins[i].name };
message.metadata.push(pluginContent);
if (pluginContent && pluginContent !== []) {
if (pluginContent instanceof Array) {
for (var j = pluginContent.length - 1; j >= 0; j--) {
// only give a number if there are multiple embeds
var num = (pluginContent.length == 1) ? undefined : (j + 1);
addPluginContent(pluginContent[j], plugins[i].name, num);
}
} else {
addPluginContent(pluginContent, plugins[i].name);
}
if (plugins[i].exclusive) {
break;
@ -111,7 +124,7 @@ plugins.service('plugins', ['userPlugins', '$sce', function(userPlugins, $sce) {
*/
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
@ -121,18 +134,16 @@ plugins.factory('userPlugins', function() {
*/
var spotifyPlugin = new Plugin(function(message) {
content = [];
var addMatch = function(match) {
var ret = '';
for(var i in match) {
var id = match[i].substr(match[i].length-22, match[i].length);
ret += '<iframe src="//embed.spotify.com/?uri=spotify:track:'+id+'" width="300" height="80" frameborder="0" allowtransparency="true"></iframe>';
for (var i = 0; match && i < match.length; i++) {
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>');
}
return ret;
};
var match = message.match(/spotify:track:([a-zA-Z-0-9]{22})/g);
var ret = addMatch(match);
ret += addMatch(message.match(/open.spotify.com\/track\/([a-zA-Z-0-9]{22})/g));
return ret;
addMatch(message.match(/spotify:track:([a-zA-Z-0-9]{22})/g));
addMatch(message.match(/open.spotify.com\/track\/([a-zA-Z-0-9]{22})/g));
return content;
});
spotifyPlugin.name = 'Spotify track';
@ -143,20 +154,20 @@ plugins.factory('userPlugins', function() {
*/
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 retval = '';
var content = [];
// iterate over all matches
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";
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
match = regExp.exec(message);
}
return retval;
return content;
});
youtubePlugin.name = 'YouTube video';
@ -206,11 +217,11 @@ plugins.factory('userPlugins', function() {
*/
var imagePlugin = new Plugin(function(message) {
var url = message.match(urlRegexp);
var content = null;
var urls = message.match(urlRegexp);
var content = [];
if (url) {
url = url[0]; /* Actually parse one url per message */
for (var i = 0; urls && i < urls.length; i++) {
var url = urls[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. */
@ -221,7 +232,7 @@ plugins.factory('userPlugins', function() {
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 match = message.match(urlRegexp);
var urls = message.match(urlRegexp);
var content = [];
if (match) {
var url = match[0];
for (var i = 0; urls && i < urls.length; i++) {
var url = urls[i];
/* SoundCloud http://help.soundcloud.com/customer/portal/articles/247785-what-widgets-can-i-use-from-soundcloud- */
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 */
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';
@ -259,17 +271,18 @@ plugins.factory('userPlugins', function() {
*/
var googlemapPlugin = new Plugin(function(message) {
var match = message.match(urlRegexp);
var urls = message.match(urlRegexp);
var content = [];
if (match) {
var url = match[0];
for (var i = 0; urls && i < urls.length; i++) {
var url = urls[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>';
if (url.match(/^https?:\/\/maps\.google\./i) || url.match(/^https?:\/\/(?:[\w]+\.)?google\.[\w]+\/maps/i)) {
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';
@ -287,8 +300,28 @@ plugins.factory('userPlugins', function() {
});
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 {
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",
"version": "0.0.0.1",
"version": "0.4.0",
"manifest_version": 2,
"icons": {
"32": "img/favicon.png",
"128": "img/weechat_logo_128x128.png"
"32": "assets/img/favicon.png",
"128": "assets/img/glowing_bear_128x128.png"
},
"app": {
"urls": [
@ -20,6 +20,6 @@
"notifications"
],
"web_accessible_resources": [
"img/favicon.png"
"assets/img/favicon.png"
]
}

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

Loading…
Cancel
Save