Merge branch 'master' into gh-pages

gh-pages
David Cormier 11 years ago
commit 93ae5e620a
  1. 2
      .gitignore
  2. 4
      .travis.yml
  3. 1
      3rdparty/bindonce.min.js
  4. 75
      README.md
  5. 15
      bower.json
  6. 59
      css/glowingbear.css
  7. 2
      directives/input.html
  8. 8
      directives/plugin.html
  9. 29
      index.html
  10. 51
      js/glowingbear.js
  11. 4
      js/localstorage.js
  12. 174
      js/plugins.js
  13. 2
      manifest.json
  14. 38
      package.json
  15. 2
      run_tests.sh
  16. 26
      test/e2e/scenarios.js
  17. 34
      test/karma.conf.js
  18. 19
      test/protractor-conf.js
  19. 140
      test/unit/plugins.js

2
.gitignore vendored

@ -0,0 +1,2 @@
bower_components/
node_modules/

@ -1,8 +1,8 @@
language: node_js language: node_js
node_js: node_js:
- "0.10" - "0.10"
install: "npm -g install jshint" install: "npm install"
script: "jshint js/*" script: "sh -e run_tests.sh"
notifications: notifications:
email: false email: false
irc: irc:

File diff suppressed because one or more lines are too long

@ -1,39 +1,43 @@
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) #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 a web frontend for the [WeeChat](http://weechat.org) IRC client and strives to be a modern interface. It relies on WeeChat to do all the heavy lifting and then provides some nice features on top of that, like embedding images, videos, and other content. The best part, however, is that you can use it from any modern internet device -- whether it's a computer, tablet, or smart phone -- and all your stuff is there, whereever you are. You don't have to deal with the messy technical details, and all you need to have installed is a browser or our app.
Glowing Bear uses WeeChat directly as the backend through its relay plugin, which allows it to directly connect to WeeChat from the browser using Websockets. That means that the client does not need a special "backend service", and you don't have to install anything. A connection is made from your browser to your WeeChat, with no services in between. Thus, Glowing Bear is written purely in client-side JavaScript with a bit of HTML and CSS. ##Getting Started
Getting started
---------------
Required WeeChat version: 0.4.2 Glowing Bear connects to the WeeChat instance you're already running (version 0.4.2 or later is required), and you need to be able to establish a connection to the WeeChat host from your device. It makes use of the relay plugin, and therefore you need to set up a relay. If you want to get started as quickly as possible, use these commands in WeeChat:
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 our hosted version of [Glowing Bear](http://www.glowing-bear.org)! Now point your browser to the [Glowing Bear](http://www.glowing-bear.org)! If you're having trouble connecting, check that the host and port of your WeeChat host are entered correctly, and that your server's firewall permits incoming connections on the relay port.
Please note that the above instructions set up an *unencrypted* relay, and all your data will be transmitted in clear. Therefore, we strongly recommend that you set up encryption if you want to keep using Glowing Bear. We've written [a detailed guide on how to set up a trusted secure relay](https://4z2.de/2014/07/06/weechat-trusted-relay) for you.
You can run Glowing Bear in many ways: use it like any other webpage, as an app in Firefox (choose "Install app" on the landing page) or Chrome ("Tools", then "Create application shortcuts"), 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, and a [Firefox OS app](https://marketplace.firefox.com/app/glowing-bear/) in the Firefox Marketplace.
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. <a href="https://play.google.com/store/apps/details?id=com.glowing_bear"><img alt="Android app on Google Play" src="https://developer.android.com/images/brand/en_app_rgb_wo_45.png" /></a><a href="https://marketplace.firefox.com/app/glowing-bear/"><img alt="Firefox OS app in the Firefox Marketplace" src="https://marketplace.cdn.mozilla.net/media/img/mkt/badges/firefox-marketplace_badge-orange_129_45.png" /></a>
##Screenshots
Screenshots
-----------
Running as Chrome application in a separate window on Windows and as Android app: Running as Chrome application in a separate window on Windows and as Android app:
![Glowing bear screenshot](https://4z2.de/glowingbear.png) ![Glowing bear screenshot](https://4z2.de/glowingbear.png)
FAQ ##How it Works
---
What follows is a more technical explanation of how Glowing Bear works, and you don't need to understand it to use it.
Glowing Bear uses WeeChat directly as its backend through the relay plugin. This means that we can connect to WeeChat directly from the browser using WebSockets. Therefore, the client does not need a special "backend service", and you don't have to install anything. A connection is made from your browser to your WeeChat, with no services in between. Thus, Glowing Bear is written purely in client-side JavaScript with a bit of HTML and CSS.
##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://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. - *How does the encryption work?* TLS is used for securing the connection if you enable encryption. This is handled by your browser, and we have no influence on certificate handling, etc. 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). A detailed guide on setting up a trusted secure relay is available [here](https://4z2.de/2014/07/06/weechat-trusted-relay).
Development ##Development
-----------
###Setup
Getting started with the development of Glowing Bear is really simple, partly because we don't have a build process (pure client-side JS, remember). All you have to do is clone the repository, fire up a webserver to host the files, and start fiddling around. You can try out your changes by reloading the page. Getting started with the development of Glowing Bear is really simple, partly because we don't have a build process (pure client-side JS, remember). All you have to do is clone the repository, fire up a webserver to host the files, and start fiddling around. You can try out your changes by reloading the page.
Here's a simple example using the python simple web server: Here's a simple example using the python simple web server:
@ -47,13 +51,36 @@ 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 [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. 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/).
You can also use the latest and greatest development version of Glowing Bear at [https://latest.glowing-bear.org/](https://latest.glowing-bear.org/).
###Running the tests
Glowing Bear uses Karma and Jasmine to run its unit tests. To run the tests locally, you will first need to install `npm` on your machine. Check out the wonderful [nvm](https://github.com/creationix/nvm) if you don't know it already, it's highly recommended.
Once this is done, you will need to retrieve the necessary packages for testing Glowing-Bear (first, you might want to use `npm link` on any packages you have already installed globally):
`$ npm install`
Finally, you can run the unit tests:
`$ npm test`
Or the end to end tests:
`$ npm run protractor`
**Note**: the end to end tests assume that a web server is hosting Glowing Bear on `localhost:8000` and that a WeeChat relay is configured on port 9001.
##Contributing
Whether you are interested in contributing or simply want to talk about the project, join us at **#glowing-bear** on **freenode**!
We appreciate all forms of contributions -- whether you're a coder, designer, or user, we are always curious what you have to say. Whether you have suggestions or already implemented a solution, let us know and we'll try to help. We're also very keen to hear which devices and platforms Glowing Bear works on (or doesn't), as we're a small team and don't have access to the resources we would need to test it everywhere.
Contributing If you wish to submit code, we try to make the contribution process as simple as possible. Any pull request that is submitted has to go through automatic and manual testing. Please make sure that your changes pass the [Travis](https://travis-ci.org/glowing-bear/glowing-bear) tests before submitting a pull request. Here is how you can run the tests:
------------
Interested in contributing or simply want to talk about the project? Join us at **#glowing-bear** on **freenode**! `$ ./run_tests.sh`
If you're curious about the projects we're using, here's a list: [AngularJS](https://angularjs.org/), [Bootstrap](http://getbootstrap.com/), [bindonce](https://github.com/Pasvaz/bindonce), [Underscore](http://underscorejs.org/), [favico.js](http://lab.ejci.net/favico.js/), and [zlib.js](https://github.com/imaya/zlib.js). Technology-wise, [WebSockets](http://en.wikipedia.org/wiki/WebSocket) are the most important part, but we also use [local storage](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Storage#localStorage), the [Notification Web API](https://developer.mozilla.org/en/docs/Web/API/notification), and last (but not least) [Apache Cordova](https://cordova.apache.org/) for our slick app. We'd also like to ask you to join our IRC channel, #glowing-bear on freenode, so we can discuss your ideas and changes.
We always appreciate pull requests, and if you don't know where to get started, why not browse our [issue tracker](https://github.com/glowing-bear/glowing-bear/issues)? We're also always happy to hear which devices and platforms Glowing Bear works on (or doesn't) — for example, none of our current developers own an Apple device, so if you're interested in helping us test, we'd be most grateful! If you're curious about the projects we're using, here's a list: [AngularJS](https://angularjs.org/), [Bootstrap](http://getbootstrap.com/), [Underscore](http://underscorejs.org/), [favico.js](http://lab.ejci.net/favico.js/), and [zlib.js](https://github.com/imaya/zlib.js). Technology-wise, [WebSockets](http://en.wikipedia.org/wiki/WebSocket) are the most important part, but we also use [local storage](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Storage#localStorage), the [Notification Web API](https://developer.mozilla.org/en/docs/Web/API/notification), and last (but not least) [Apache Cordova](https://cordova.apache.org/) for our mobile app.

@ -0,0 +1,15 @@
{
"name": "glowing-bear",
"description": "A webclient for WeeChat",
"version": "0.4.0",
"homepage": "https://github.com/glowing-bear/glowing-bear",
"license": "GPLv3",
"private": true,
"dependencies": {
"angular": "1.3.x",
"angular-route": "1.3.x",
"angular-loader": "1.3.x",
"angular-mocks": "~1.3.x",
"html5-boilerplate": "~4.3.0"
}
}

@ -38,20 +38,6 @@ a {
cursor: pointer; cursor: pointer;
} }
table {
width: 100%;
}
tr {
line-height: 100%;
}
tr:hover {
background-color: #222222;
}
td.time {
padding: 1px 5px 1px 1px;
vertical-align: top;
}
.repeated-time { .repeated-time {
} }
.repeated-time .cof-chat_time, .repeated-time .cof-chat_time,
@ -70,11 +56,21 @@ td.prefix {
border-right: 1px solid #444; border-right: 1px solid #444;
} }
td.message { td.message {
word-wrap: break-word;
word-break: break-word;
vertical-align: top; vertical-align: top;
width: 100%; width: 100%;
padding: 1px 1px 1px 5px; padding: 1px 1px 1px 5px;
-ms-word-break: break-all;
word-break: break-all;
/* Non standard for webkit */
word-break: break-word;
-webkit-hyphens: auto;
-moz-hyphens: auto;
-ms-hyphens: auto;
hyphens: auto;
} }
#readmarker { #readmarker {
margin-top: 5px; margin-top: 5px;
@ -122,6 +118,10 @@ input[type=text], input[type=password], #sendMessage, .badge {
.glyphicon { .glyphicon {
top: 0; /* Fixes alignment issue in top bar */ top: 0; /* Fixes alignment issue in top bar */
} }
.glyphicon-off {
top: 1px; /* Fixes for relative glyphicon size */
font-size: 28px;
}
#topbar { #topbar {
position: fixed; position: fixed;
width: 100%; width: 100%;
@ -224,7 +224,7 @@ input[type=text], input[type=password], #sendMessage, .badge {
overflow-x: hidden; overflow-x: hidden;
right: 0; right: 0;
top: 0; top: 0;
padding-top: 35px; padding-top: 35px;
padding-left: 5px; padding-left: 5px;
padding-bottom: 35px; padding-bottom: 35px;
z-index: 2; z-index: 2;
@ -281,8 +281,19 @@ input[type=text], input[type=password], #sendMessage, .badge {
-webkit-transition:0.35s ease all; -webkit-transition:0.35s ease all;
transition:0.35s ease all; transition:0.35s ease all;
} }
#bufferlines table { #bufferlines > table {
margin-top: 35px; margin-top: 35px;
width: 100%;
}
tr.bufferline {
line-height: 100%;
}
tr.bufferline:hover {
background-color: #222222;
}
td.time {
padding: 1px 5px 1px 1px;
vertical-align: top;
} }
.withnicklist { .withnicklist {
@ -481,6 +492,18 @@ h2 span, h2 small {
.panel[data-state=collapsed] { .panel[data-state=collapsed] {
border: 0px solid transparent; border: 0px solid transparent;
} }
.panel .panel-title:before {
display: inline-block;
font-size: 22px;
line-height: 20px;
margin: -3px 5px -3px 0;
}
.panel[data-state=active] .panel-title:before {
content: "–";
}
.panel[data-state=collapsed] .panel-title:before {
content: "+";
}
/* fix for firefox being stupid */ /* fix for firefox being stupid */
@-moz-document url-prefix() { @-moz-document url-prefix() {

@ -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 favorite-font" 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" ng-focus="hideSidebar()">
</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>

@ -1,18 +1,18 @@
<div ng-show="plugin.visible"> <div ng-show="plugin.visible">
<button class="btn btn-primary btn-sm pull-right" <button class="btn btn-primary btn-sm pull-right"
ng-click="hideContent()"> ng-click="hideContent()">
Hide {{ plugin.name }} Hide {{ ::plugin.name }}
</button> </button>
<div ng-bind-html="displayedContent" class="embed" bo-class="'embed_' + plugin.$$hashKey"></div> <div ng-bind-html="displayedContent" class="embed" ng-class="::('embed_' + plugin.$$hashKey)"></div>
</div> </div>
<div ng-hide="plugin.visible"> <div ng-hide="plugin.visible">
<button class="btn btn-sm pull-right" <button class="btn btn-sm pull-right"
bo-class="{ ng-class="::{
'btn-warning': plugin.nsfw, 'btn-warning': plugin.nsfw,
'btn-primary': !plugin.nsfw}" 'btn-primary': !plugin.nsfw}"
ng-click="showContent()"> ng-click="showContent()">
Show {{ plugin.name }} Show {{ ::plugin.name }}
</button> </button>
</div> </div>

@ -14,10 +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/angularjs/1.3.0-beta.8/angular.min.js"></script> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.19/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.19/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.19/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.19/angular-touch.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>
@ -27,10 +27,9 @@
<script type="text/javascript" src="js/websockets.js"></script> <script type="text/javascript" src="js/websockets.js"></script>
<script type="text/javascript" src="js/models.js"></script> <script type="text/javascript" src="js/models.js"></script>
<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/favico-0.3.4-mod.min.js"></script> <script type="text/javascript" src="3rdparty/favico-0.3.4-mod.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-keypress="handleKeyPress($event)" ng-class="{'no-overflow': connected}" lang="en-US">
<div ng-hide="connected" class="container"> <div ng-hide="connected" class="container">
<h2> <h2>
<img alt="logo" src="assets/img/glowing-bear.svg"> <img alt="logo" src="assets/img/glowing-bear.svg">
@ -216,11 +215,11 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
</a> </a>
</div> </div>
<a ng-click="disconnect()" title="Disconnect from WeeChat"> <a ng-click="disconnect()" title="Disconnect from WeeChat">
<i class="glyphicon glyphicon-remove"></i> <i class="glyphicon glyphicon-off"></i>
</a> </a>
</div> </div>
</div> </div>
<div bindonce id="sidebar" data-state="visible" ng-swipe-left="hideSidebar()" class="vertical-line"> <div 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">
@ -235,11 +234,11 @@ $ 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="favorite-font" ng-swipe-right="showSidebar()" ng-swipe-left="hideSidebar()" ng-class="{'withnicklist': showNicklist}"> <div 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'">
<a ng-click="openBuffer(nick.name)"><span bo-class="nick.prefixClasses" bo-text="nick.prefix"></span><span bo-class="nick.nameClasses" bo-text="nick.name"></span></a> <a ng-click="openBuffer(nick.name)"><span ng-class="::nick.prefixClasses" ng-bind="::nick.prefix"></span><span ng-class="::nick.nameClasses" ng-bind="::nick.name"></span></a>
</li> </li>
</ul> </ul>
</div> </div>
@ -255,14 +254,14 @@ $ openssl req -nodes -newkey rsa:4096 -keyout relay.pem -x509 -days 365 -out rel
<tbody ng-repeat="bufferline in bufferlines"> <tbody ng-repeat="bufferline in bufferlines">
<tr class="bufferline"> <tr class="bufferline">
<td class="time"> <td class="time">
<span class="date" bo-class="{'repeated-time': bufferline.shortTime==bufferlines[$index-1].shortTime}"> <span class="date" ng-class="::{'repeated-time': bufferline.shortTime==bufferlines[$index-1].shortTime}">
<span class="cof-chat_time cob-chat_time coa-chat_time" bo-text="bufferline.date|date:'HH'"></span><span class="cof-chat_time_delimiters cob-chat_time_delimiters coa-chat_time_delimiters">:</span><span class="cof-chat_time cob-chat_time coa-chat_time" bo-text="bufferline.date|date:'mm'"></span><span class="seconds"><span class="cof-chat_time_delimiters cob-chat_time_delimiters coa-chat_time_delimiters">:</span><span class="cof-chat_time cob-chat_time coa-chat_time" bo-text="bufferline.date|date:'ss'"></span></span> <span class="cof-chat_time cob-chat_time coa-chat_time" ng-bind="::(bufferline.date|date:'HH')"></span><span class="cof-chat_time_delimiters cob-chat_time_delimiters coa-chat_time_delimiters">:</span><span class="cof-chat_time cob-chat_time coa-chat_time" ng-bind="::(bufferline.date|date:'mm')"></span><span class="seconds"><span class="cof-chat_time_delimiters cob-chat_time_delimiters coa-chat_time_delimiters">:</span><span class="cof-chat_time cob-chat_time coa-chat_time" ng-bind="::(bufferline.date|date:'ss')"></span></span>
</span> </span>
</td> </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="prefix"><a ng-click="addMention(bufferline.prefix)"><span ng-repeat="part in ::bufferline.prefix" ng-class="::part.classes" ng-bind="::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' | inlinecolour"></span> <span ng-repeat="part in ::bufferline.content" class="text" ng-class="::part.classes" ng-bind-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">

@ -1,4 +1,4 @@
var weechat = angular.module('weechat', ['ngRoute', 'localStorage', 'weechatModels', 'plugins', 'ngSanitize', 'ngWebsockets', 'pasvaz.bindonce', 'ngTouch']); var weechat = angular.module('weechat', ['ngRoute', 'localStorage', 'weechatModels', 'plugins', 'ngSanitize', 'ngWebsockets', 'ngTouch']);
weechat.filter('toArray', function () { weechat.filter('toArray', function () {
'use strict'; 'use strict';
@ -49,7 +49,7 @@ weechat.filter('irclinky', ['$filter', function($filter) {
}; };
}]); }]);
weechat.filter('inlinecolour', function() { weechat.filter('inlinecolour', ['$sce', function($sce) {
'use strict'; 'use strict';
return function(text) { return function(text) {
@ -61,9 +61,9 @@ weechat.filter('inlinecolour', function() {
var hexColourRegex = /(^|[^&])\#([0-9a-f]{6})($|[^\w'"])/gmi; var hexColourRegex = /(^|[^&])\#([0-9a-f]{6})($|[^\w'"])/gmi;
var substitute = '$1#$2 <div class="colourbox" style="background-color:#$2"></div> $3'; var substitute = '$1#$2 <div class="colourbox" style="background-color:#$2"></div> $3';
return text.replace(hexColourRegex, substitute); return $sce.trustAsHtml(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) {
@ -841,7 +841,7 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
$store.bind($scope, "host", "localhost"); $store.bind($scope, "host", "localhost");
$store.bind($scope, "port", "9001"); $store.bind($scope, "port", "9001");
$store.bind($scope, "proto", "weechat"); $store.bind($scope, "proto", "weechat");
$store.bind($scope, "ssl", false); $store.bind($scope, "ssl", (window.location.protocol === "https:"));
$store.bind($scope, "savepassword", false); $store.bind($scope, "savepassword", false);
if ($scope.savepassword) { if ($scope.savepassword) {
$store.bind($scope, "password", ""); $store.bind($scope, "password", "");
@ -907,9 +907,15 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
$scope.showSidebar = function() { $scope.showSidebar = function() {
document.getElementById('sidebar').setAttribute('data-state', 'visible'); document.getElementById('sidebar').setAttribute('data-state', 'visible');
document.getElementById('content').setAttribute('sidebar-state', 'visible'); document.getElementById('content').setAttribute('sidebar-state', 'visible');
if ($rootScope.isMobileUi()) {
// de-focus the input bar when opening the sidebar on mobile, so that the keyboard goes down
_.each(document.getElementsByTagName('textarea'), function(elem) {
elem.blur();
});
}
}; };
$scope.hideSidebar = function() { $rootScope.hideSidebar = function() {
if ($rootScope.isMobileUi()) { if ($rootScope.isMobileUi()) {
document.getElementById('sidebar').setAttribute('data-state', 'hidden'); document.getElementById('sidebar').setAttribute('data-state', 'hidden');
document.getElementById('content').setAttribute('sidebar-state', 'hidden'); document.getElementById('content').setAttribute('sidebar-state', 'hidden');
@ -1314,7 +1320,7 @@ weechat.config(['$routeProvider',
]); ]);
weechat.directive('plugin', function($rootScope) { weechat.directive('plugin', ['$rootScope', function($rootScope) {
/* /*
* Plugin directive * Plugin directive
* Shows additional plugin content * Shows additional plugin content
@ -1326,7 +1332,7 @@ weechat.directive('plugin', function($rootScope) {
plugin: '=data' plugin: '=data'
}, },
controller: function($scope) { controller: ["$scope", function($scope) {
$scope.displayedContent = ""; $scope.displayedContent = "";
@ -1343,6 +1349,12 @@ weechat.directive('plugin', function($rootScope) {
* Actual plugin content is only fetched when * Actual plugin content is only fetched when
* content is shown. * content is shown.
*/ */
// If the plugin is asynchronous / lazy, execute it now and store
// the result. This ensures that the callback is executed only once
if ($scope.plugin.content instanceof Function) {
$scope.plugin.content = $scope.plugin.content();
}
$scope.displayedContent = $scope.plugin.content; $scope.displayedContent = $scope.plugin.content;
$scope.plugin.visible = true; $scope.plugin.visible = true;
@ -1359,9 +1371,9 @@ weechat.directive('plugin', function($rootScope) {
if ($scope.plugin.visible) { if ($scope.plugin.visible) {
$scope.showContent(); $scope.showContent();
} }
} }]
}; };
}); }]);
weechat.directive('inputBar', function() { weechat.directive('inputBar', function() {
@ -1374,7 +1386,8 @@ weechat.directive('inputBar', function() {
inputId: '@inputId' inputId: '@inputId'
}, },
controller: function($rootScope, controller: ['$rootScope', '$scope', '$element', '$log', 'connection', 'models',
function($rootScope,
$scope, $scope,
$element, $element,
$log, $log,
@ -1388,6 +1401,10 @@ weechat.directive('inputBar', function() {
return document.querySelector('textarea#' + $scope.inputId); return document.querySelector('textarea#' + $scope.inputId);
}; };
$scope.hideSidebar = function() {
$rootScope.hideSidebar();
};
$scope.completeNick = function() { $scope.completeNick = function() {
// input DOM node // input DOM node
var inputNode = $scope.getInputNode(); var inputNode = $scope.getInputNode();
@ -1412,8 +1429,10 @@ weechat.directive('inputBar', function() {
$scope.command = nickComp.text; $scope.command = nickComp.text;
// update current caret position // update current caret position
inputNode.focus(); setTimeout(function() {
inputNode.setSelectionRange(nickComp.caretPos, nickComp.caretPos); inputNode.focus();
inputNode.setSelectionRange(nickComp.caretPos, nickComp.caretPos);
}, 0);
}; };
@ -1586,7 +1605,7 @@ weechat.directive('inputBar', function() {
} }
// Arrow up -> go up in history // Arrow up -> go up in history
if (code === 38) { if ($event.type === "keydown" && code === 38) {
$scope.command = models.getActiveBuffer().getHistoryUp($scope.command); $scope.command = models.getActiveBuffer().getHistoryUp($scope.command);
// 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.
@ -1599,7 +1618,7 @@ weechat.directive('inputBar', function() {
} }
// Arrow down -> go down in history // Arrow down -> go down in history
if (code === 40) { if ($event.type === "keydown" && code === 40) {
$scope.command = models.getActiveBuffer().getHistoryDown($scope.command); $scope.command = models.getActiveBuffer().getHistoryDown($scope.command);
// We don't need to set the cursor to the rightmost position here, the browser does that for us // We don't need to set the cursor to the rightmost position here, the browser does that for us
return true; return true;
@ -1648,6 +1667,6 @@ weechat.directive('inputBar', function() {
return true; return true;
} }
}; };
} }]
}; };
}); });

@ -3,7 +3,7 @@
var ls = angular.module('localStorage',[]); var ls = angular.module('localStorage',[]);
ls.factory("$store",function($parse){ ls.factory("$store", ["$parse", function($parse){
/** /**
* Global Vars * Global Vars
*/ */
@ -113,5 +113,5 @@ ls.factory("$store",function($parse){
} }
}; };
return publicMethods; return publicMethods;
}); }]);
})(); })();

@ -60,8 +60,14 @@ plugins.service('plugins', ['userPlugins', '$sce', function(userPlugins, $sce) {
if (num) { if (num) {
pluginName += " " + num; pluginName += " " + num;
} }
// If content isn't a callback, it's HTML
if (!(content instanceof Function)) {
content = $sce.trustAsHtml(content);
}
message.metadata.push({ message.metadata.push({
'content': $sce.trustAsHtml(content), 'content': content,
'nsfw': nsfw, 'nsfw': nsfw,
'name': pluginName 'name': pluginName
}); });
@ -123,9 +129,37 @@ plugins.service('plugins', ['userPlugins', '$sce', function(userPlugins, $sce) {
* *
*/ */
plugins.factory('userPlugins', function() { plugins.factory('userPlugins', function() {
// standard JSONp origin policy trick
var jsonp = function (url, callback) {
var callbackName = 'jsonp_callback_' + Math.round(100000 * Math.random());
window[callbackName] = function(data) {
delete window[callbackName];
document.body.removeChild(script);
callback(data);
};
var script = document.createElement('script');
script.src = url + (url.indexOf('?') >= 0 ? '&' : '?') + 'callback=' + callbackName;
document.body.appendChild(script);
};
var urlRegexp = RegExp(/(?:ftp|https?):\/\/\S*[^\s.;,(){}<>]/g); var urlRegexp = RegExp(/(?:ftp|https?):\/\/\S*[^\s.;,(){}<>]/g);
var urlPlugin = function(callback) {
return function(message) {
var urls = message.match(urlRegexp);
var content = [];
for (var i = 0; urls && i < urls.length; i++) {
var result = callback(urls[i]);
if (result) {
content.push(result);
}
}
return content;
};
};
/* /*
* Spotify Embedded Player * Spotify Embedded Player
* *
@ -215,75 +249,61 @@ plugins.factory('userPlugins', function() {
/* /*
* Image Preview * Image Preview
*/ */
var imagePlugin = new Plugin(function(message) { var imagePlugin = new Plugin(
urlPlugin(function(url) {
var urls = message.match(urlRegexp); var embed = false;
var content = []; // Check the get parameters as well, they might contain an image to load
var segments = url.split(/[?&]/).forEach(function(param) {
for (var i = 0; urls && i < urls.length; i++) { if (param.match(/\.(png|gif|jpg|jpeg)$/i)) {
var url = urls[i]; embed = true;
if (url.match(/\.(png|gif|jpg|jpeg)$/i)) { }
});
if (embed) {
/* 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. */
if (url.indexOf("^https?://fukung.net/v/") != -1) { if (url.indexOf("^https?://fukung.net/v/") != -1) {
url = url.replace(/.*\//, "http://media.fukung.net/imgs/"); url = url.replace(/.*\//, "http://media.fukung.net/imgs/");
} else if (url.match(/^http:\/\/(i\.)?imgur\.com\//i)) { } else if (url.match(/^http:\/\/(i\.)?imgur\.com\//i)) {
// remove protocol specification to load over https if used by g-b // remove protocol specification to load over https if used by g-b
url = url.replace(/http:/, ""); url = url.replace(/http:/, "");
} else if (url.match(/^https:\/\/www\.dropbox\.com\/s\/[a-z0-9]+\/[^?]+$/i)) {
// Dropbox requires a get parameter, dl=1
url = url + "?dl=1";
} }
content.push('<a target="_blank" href="'+url+'"><img class="embed" src="' + url + '"></a>'); return '<a target="_blank" href="'+url+'"><img class="embed" src="' + url + '"></a>';
} }
} })
);
return content;
});
imagePlugin.name = 'image'; imagePlugin.name = 'image';
/* /*
* Cloud Music Embedded Players * Cloud Music Embedded Players
*/ */
var cloudmusicPlugin = new Plugin(function(message) { var cloudmusicPlugin = new Plugin(
urlPlugin(function(url) {
var urls = message.match(urlRegexp);
var content = [];
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- */ /* 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\//)) {
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>'); 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>';
} }
/* MixCloud */ /* MixCloud */
if (url.match(/^https?:\/\/([a-z]+\.)?mixcloud.com\//)) { if (url.match(/^https?:\/\/([a-z]+\.)?mixcloud.com\//)) {
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 '<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 content;
});
cloudmusicPlugin.name = 'cloud music'; cloudmusicPlugin.name = 'cloud music';
/* /*
* Google Maps * Google Maps
*/ */
var googlemapPlugin = new Plugin(function(message) { var googlemapPlugin = new Plugin(
urlPlugin(function(url) {
var urls = message.match(urlRegexp);
var content = [];
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)) { 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 '<iframe width="450" height="350" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="' + url + '&output=embed"></iframe>';
} }
} })
);
return content;
});
googlemapPlugin.name = 'Google Map'; googlemapPlugin.name = 'Google Map';
/* /*
@ -300,12 +320,8 @@ plugins.factory('userPlugins', function() {
}); });
asciinemaPlugin.name = "ascii cast"; asciinemaPlugin.name = "ascii cast";
var yrPlugin = new Plugin(function(message) { var yrPlugin = new Plugin(
var urls = message.match(urlRegexp); urlPlugin(function(url) {
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 regexp = /^https?:\/\/(?:www\.)?yr\.no\/(place|stad|sted|sadji|paikka)\/(([^\s.;,(){}<>\/]+\/){3,})/;
var match = url.match(regexp); var match = url.match(regexp);
if (match) { if (match) {
@ -313,15 +329,67 @@ plugins.factory('userPlugins', function() {
var location = match[2]; var location = match[2];
var city = match[match.length - 1].slice(0, -1); var city = match[match.length - 1].slice(0, -1);
url = "http://www.yr.no/" + language + "/" + location + "avansert_meteogram.png"; url = "http://www.yr.no/" + language + "/" + location + "avansert_meteogram.png";
content.push("<img src='" + url + "' alt='Meteogram for " + city + "' />"); return "<img src='" + url + "' alt='Meteogram for " + city + "' />";
} }
} })
return content; );
});
yrPlugin.name = "meteogram"; yrPlugin.name = "meteogram";
// Embed GitHub gists
var gistPlugin = new Plugin(
urlPlugin(function(url) {
var regexp = /^https:\/\/gist\.github.com\/[^.?]+/i;
var match = url.match(regexp);
if (match) {
// get the URL from the match to trim away pseudo file endings and request parameters
url = match[0] + '.json';
// load gist asynchronously -- return a function here
return function() {
var element = document.querySelector('.embed_' + this.$$hashKey);
jsonp(url, function(data) {
// Add the gist stylesheet only once
if (document.querySelectorAll('link[rel=stylesheet][href="' + data.stylesheet + '"]').length < 1) {
var stylesheet = '<link rel="stylesheet" href="' + data.stylesheet + '"></link>';
document.getElementsByTagName('head')[0].innerHTML += stylesheet;
}
element.innerHTML = '<div style="clear:both">' + data.div + '</div>';
});
};
}
})
);
gistPlugin.name = 'Gist';
var tweetPlugin = new Plugin(
urlPlugin(function(url) {
var regexp = /^https?:\/\/twitter\.com\/(?:#!\/)?(\w+)\/status(?:es)?\/(\d+)/i;
var match = url.match(regexp);
if (match) {
url = 'https://api.twitter.com/1/statuses/oembed.json?id=' + match[2];
return function() {
var element = document.querySelector('.embed_' + this.$$hashKey);
jsonp(url, function(data) {
// sepearate the HTML into content and script tag
var scriptIndex = data.html.indexOf("<script ");
var content = data.html.substr(0, scriptIndex);
// Set DNT (Do Not Track)
content = content.replace("<blockquote class=\"twitter-tweet\">", "<blockquote class=\"twitter-tweet\" data-dnt=\"true\">");
element.innerHTML = content;
// The script tag needs to be generated manually or the browser won't load it
var scriptElem = document.createElement('script');
// Hardcoding the URL here, I don't suppose it's going to change anytime soon
scriptElem.src = "//platform.twitter.com/widgets.js";
element.appendChild(scriptElem);
});
};
}
})
);
tweetPlugin.name = 'Tweet';
return { return {
plugins: [youtubePlugin, dailymotionPlugin, allocinePlugin, imagePlugin, spotifyPlugin, cloudmusicPlugin, googlemapPlugin, asciinemaPlugin, yrPlugin] plugins: [youtubePlugin, dailymotionPlugin, allocinePlugin, imagePlugin, spotifyPlugin, cloudmusicPlugin, googlemapPlugin, asciinemaPlugin, yrPlugin, gistPlugin, tweetPlugin]
}; };

@ -1,7 +1,7 @@
{ {
"name": "Glowing Bear", "name": "Glowing Bear",
"description": "WeeChat Web frontend", "description": "WeeChat Web frontend",
"version": "0.4.0", "version": "0.4.1",
"manifest_version": 2, "manifest_version": 2,
"icons": { "icons": {
"32": "assets/img/favicon.png", "32": "assets/img/favicon.png",

@ -0,0 +1,38 @@
{
"name": "glowing-bear",
"private": true,
"version": "0.4.1",
"description": "A web client for Weechat",
"repository": "https://github.com/glowing-bear/glowing-bear",
"license": "GPLv3",
"devDependencies": {
"karma": "~0.10",
"protractor": "~0.20.1",
"http-server": "^0.6.1",
"bower": "^1.3.1",
"shelljs": "^0.2.6",
"jshint": "^2.5.2",
"karma-junit-reporter": "^0.2.2",
"uglify-js": "^2.4"
},
"scripts": {
"postinstall": "bower install",
"minify": " uglifyjs js/localstorage.js js/weechat.js js/irc-utils.js js/glowingbear.js js/websockets.js js/models.js js/plugins.js -c -m --screw-ie8 -o min.js --source-map min.map",
"prestart": "npm install",
"start": "http-server -a localhost -p 8000",
"pretest": "npm install",
"test": "karma start test/karma.conf.js",
"test-single-run": "karma start test/karma.conf.js --single-run",
"preupdate-webdriver": "npm install",
"update-webdriver": "webdriver-manager update",
"preprotractor": "npm run update-webdriver",
"protractor": "protractor test/protractor-conf.js",
"update-index-async": "node -e \"require('shelljs/global'); sed('-i', /\\/\\/@@NG_LOADER_START@@[\\s\\S]*\\/\\/@@NG_LOADER_END@@/, '//@@NG_LOADER_START@@\\n' + cat('app/bower_components/angular-loader/angular-loader.min.js') + '\\n//@@NG_LOADER_END@@', 'app/index-async.html');\""
}
}

@ -0,0 +1,2 @@
./node_modules/.bin/jshint js/*.js test/unit/*.js
npm test

@ -0,0 +1,26 @@
'use strict';
/* https://github.com/angular/protractor/blob/master/docs/getting-started.md */
describe('Auth', function() {
browser.get('index.html');
var ptor = protractor.getInstance();
it('auth should fail when trying to connect to an unused port', function() {
var host = ptor.findElement(protractor.By.model('host'));
var password = ptor.findElement(protractor.By.model('password'));
var port = ptor.findElement(protractor.By.model('port'));
var submit = ptor.findElement(protractor.By.tagName('button'));
// Fill out the form?
host.sendKeys('localhost');
password.sendKeys('password');
port.sendKeys(2462);
submit.click();
var error = ptor.findElement(
protractor.By.css('.alert.alert-danger > strong')
)
expect(error.getText()).toBeDefined();
});
});

@ -0,0 +1,34 @@
module.exports = function(config){
config.set({
basePath : '../',
files : [
'bower_components/angular/angular.js',
'bower_components/angular-route/angular-route.js',
'bower_components/angular-mocks/angular-mocks.js',
'js/**/*.js',
'test/unit/**/*.js'
],
autoWatch : true,
frameworks: ['jasmine'],
browsers : ['PhantomJS'],
singleRun: true,
plugins : [
'karma-phantomjs-launcher',
'karma-jasmine',
'karma-junit-reporter'
],
junitReporter : {
outputFile: 'test_out/unit.xml',
suite: 'unit'
}
});
};

@ -0,0 +1,19 @@
exports.config = {
allScriptsTimeout: 11000,
specs: [
'e2e/*.js'
],
capabilities: {
'browserName': 'firefox'
},
baseUrl: 'http://localhost:8000/',
framework: 'jasmine',
jasmineNodeOpts: {
defaultTimeoutInterval: 30000
}
};

@ -0,0 +1,140 @@
/* plugins go here */
var msg = function(msg) {
return {'text': msg };
};
var metadata_name = function(message) {
if (message.metadata && message.metadata[0] && message.metadata[0].name) {
return message.metadata[0].name;
}
return null;
};
var expectTheseMessagesToContain = function(urls, pluginType, plugins) {
for (var i = 0; i < urls.length; i++) {
expect(
metadata_name(
plugins.PluginManager.contentForMessage(msg(urls[i]))
)
).toEqual(pluginType);
}
};
describe('filter', function() {
beforeEach(module('plugins'));
describe('Plugins', function() {
beforeEach(module(function($provide) {
$provide.value('version', 'TEST_VER');
}));
it('should recognize spotify tracks', inject(function(plugins) {
expectTheseMessagesToContain([
'spotify:track:6JEK0CvvjDjjMUBFoXShNZ',
'https://open.spotify.com/track/6JEK0CvvjDjjMUBFoXShNZ'
],
'Spotify track',
plugins);
}));
it('should recognize youtube videos', inject(function(plugins) {
expectTheseMessagesToContain([
'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
'http://www.youtube.com/watch?v=dQw4w9WgXcQ',
'http://youtu.be/J6vIS8jb6Fs',
'https://youtu.be/J6vIS8jb6Fs',
'http://www.youtube.com/embed/dQw4w9WgXcQ',
'https://www.youtube.com/embed/dQw4w9WgXcQ',
'youtu.be/dQw4w9WgXcQ'
],
'YouTube video',
plugins);
}));
it('should recognize dailymotion videos', inject(function(plugins) {
expectTheseMessagesToContain([
'dailymotion.com/video/test',
'dailymotion.com/video/#video=asdf',
'dai.ly/sfg'
],
'Dailymotion video',
plugins);
}));
it('should recognize allocine videos', inject(function(plugins) {
expectTheseMessagesToContain([
'allocine.fr/videokast/video-12',
'allocine.fr/cmedia=234'
],
'AlloCine video',
plugins);
}));
it('should recognize images', inject(function(plugins) {
expectTheseMessagesToContain([
'http://i.imgur.com/BTNIDBR.gif',
'https://i.imgur.com/1LmDmct.jpg',
'http://i.imgur.com/r4FKrnu.jpeg',
'https://4z2.de/gb-mobile-new.png',
'http://static.weechat.org/images/screenshots/relay/medium/glowing-bear.png',
'http://foo.bar/baz.php?img=trololo.png&dummy=yes',
'https://tro.lo.lo/images/rick.png?size=123x45'
],
'image',
plugins);
}));
it('should recognize cloud music', inject(function(plugins) {
expectTheseMessagesToContain([
'http://soundcloud.com/',
'https://sadf.mixcloud.com/',
],
'cloud music',
plugins);
}));
it('should recognize google map', inject(function(plugins) {
expectTheseMessagesToContain([
'https://www.google.com/maps/@48.0034139,-74.9129088,6z',
],
'Google Map',
plugins);
}));
it('should recognize google map', inject(function(plugins) {
expectTheseMessagesToContain([
'https://asciinema.org/a/10625',
],
'ascii cast',
plugins);
}));
it('should recognize meteograms', inject(function(plugins) {
expectTheseMessagesToContain([
'http://www.yr.no/sted/Canada/Quebec/Montreal/',
],
'meteogram',
plugins);
}));
it('should recognize gists', inject(function(plugins) {
expectTheseMessagesToContain([
'https://gist.github.com/lorenzhs/e8c1a7d56fa170320eb8',
'https://gist.github.com/e8c1a7d56fa170320eb8',
],
'Gist',
plugins);
}));
it('should recognize tweets', inject(function(plugins) {
expectTheseMessagesToContain([
'https://twitter.com/DFB_Team_EN/statuses/488436782959448065',
],
'Tweet',
plugins);
}));
});
});
Loading…
Cancel
Save