Merge pull request #72 from torhve/nicklist

Nicklist
This commit is contained in:
David Cormier 2013-10-27 07:25:44 -07:00
commit 5e70391bcf
7 changed files with 632 additions and 118 deletions

View file

@ -44,7 +44,7 @@ body {
} }
table { table {
width: 100%; width: 100%;
} }
tr { tr {
line-height: 100%; line-height: 100%;
@ -58,7 +58,14 @@ td.time {
} }
.repeated-time { .repeated-time {
display: none; }
.repeated-time .cof-chat_time,
.repeated-time .cof-chat_time_delimiters {
color: #333;
}
.repeated-time .cob-chat_time,
.repeated-time .cob-chat_time_delimiters {
background-color: transparent;
} }
td.prefix { td.prefix {
text-align: right; text-align: right;
@ -74,8 +81,8 @@ td.message {
padding-left: 5px; padding-left: 5px;
} }
hr { hr {
margin: 0; margin: 0;
border-color: darkgreen; border-color: darkgreen;
} }
.text { .text {
white-space: pre-wrap; white-space: pre-wrap;
@ -100,7 +107,6 @@ body {
input#sendMessage { input#sendMessage {
width: 100%; width: 100%;
font-size: large;
} }
#footer button { #footer button {
border-radius: 0; border-radius: 0;
@ -111,7 +117,7 @@ input#sendMessage {
input[type=text], input[type=password], .badge { input[type=text], input[type=password], .badge {
border: 0; border: 0;
border-radius: 0; border-radius: 0;
color: #6e6e6e; color: #ccc;
box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.1), 0px 1px 7px 0px rgba(0, 0, 0, 0.8) inset; box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.1), 0px 1px 7px 0px rgba(0, 0, 0, 0.8) inset;
background: none repeat scroll 0% 0% rgba(0, 0, 0, 0.3); background: none repeat scroll 0% 0% rgba(0, 0, 0, 0.3);
} }
@ -124,30 +130,30 @@ input[type=text], input[type=password], .badge {
line-height: 25px; line-height: 25px;
white-space: nowrap; white-space: nowrap;
} }
#topbar .brand { #topbar .brand {
float: left; float: left;
height: 25px;
padding-left: 5px;
}
#topbar .brand img {
height: 25px; height: 25px;
padding-left: 5px;
}
#topbar .brand img {
height: 25px;
}
#topbar .title {
position: fixed;
left: 14%;
overflow: hidden;
}
#topbar .actions {
margin-left: 5px;
padding-left: 5px;
position: fixed;
right: 0;
background: #282828;
}
#topbar .dropdown-menu form {
padding-left: 6px;
padding-right: 6px;
} }
#topbar .title {
position: fixed;
left: 14%;
overflow: hidden;
}
#topbar .actions {
margin-left: 5px;
padding-left: 5px;
position: fixed;
right: 0;
background: #282828;
}
#topbar .dropdown-menu form {
padding-left: 6px;
padding-right: 6px;
}
#topbar, #sidebar, .panel, .dropdown-menu { #topbar, #sidebar, .panel, .dropdown-menu {
background: #282828; background: #282828;
@ -162,13 +168,50 @@ input[type=text], input[type=password], .badge {
padding-top: 35px; /* topbar */ padding-top: 35px; /* topbar */
font-size: smaller; font-size: smaller;
} }
#sidebar form { #sidebar form {
} }
#sidebar .badge {
border-radius: 0;
margin-right: -15px;
}
#nicklist {
position: fixed;
width: 100px;
min-height: 100%;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
right: 0;
top: 0;
padding-top: 25px;
padding-left: 5px;
padding-bottom: 35px;
}
#nicklist ul {
padding: 0;
margin: 0;
}
#nicklist li,
#nicklist a {
display: block;
}
#nicklist a {
text-decoration: none;
}
#nicklist a:hover {
background: #3b3b3b;
}
#connection-infos {
float: left;
max-width: 10%;
padding-left: 5px;
font-size: 12px;
color: #aaa;
overflow: hidden;
}
#sidebar .badge {
border-radius: 0;
margin-right: -15px;
}
.nav-pills > li > a { .nav-pills > li > a {
border-radius: 0; border-radius: 0;
color: #ddd; color: #ddd;
@ -181,16 +224,22 @@ input[type=text], input[type=password], .badge {
min-height: 100%; min-height: 100%;
} }
#bufferlines { .monospace {
font-family: 'Terminus', 'Consolas', 'Monaco', 'Inconsolata', 'Ubuntu Mono', monospace; font-family: 'Terminus', 'Consolas', 'Monaco', 'Inconsolata', 'Ubuntu Mono', monospace;
}
#bufferlines {
position: relative; position: relative;
height: 99%; height: 99%;
overflow-y: auto; overflow-y: auto;
margin-left: 14%; margin-left: 14%; /* sidebar */
width: auto; width: auto;
top: 25px; /* topbar */ top: 25px; /* topbar */
padding-bottom: 10px; padding-bottom: 10px;
} }
.withnicklist {
margin-right: 100px !important; /* nicklist */
}
#bufferlines .btn { #bufferlines .btn {
font-family: sans-serif; font-family: sans-serif;
} }
@ -202,7 +251,7 @@ input[type=text], input[type=password], .badge {
background-color: #181818; background-color: #181818;
} }
@media (max-width: 968px) { @media (max-width: 968px) {
#sidebar, #bufferlines { #sidebar, #bufferlines, #nicklist {
position: relative; position: relative;
min-height: 0; min-height: 0;
margin-left: 0; margin-left: 0;
@ -210,7 +259,7 @@ input[type=text], input[type=password], .badge {
max-width: 100%; max-width: 100%;
border: 0; border: 0;
} }
#sidebar { #sidebar, #nicklist {
width: 100%; width: 100%;
text-align: center; text-align: center;
} }
@ -235,50 +284,6 @@ input[type=text], input[type=password], .badge {
color: black; color: black;
} }
.color-28 {
color: greenyellow;
}
.color-00 {
color: coral;
}
.color-yellow {
color: yellow;
}
.color-dark-red {
color: darkred;
}
.color-dark-green {
color: green;
}
.color-dark-blue {
color: darkblue;
}
.color-dark-gray {
color: gray;
}
.color-gray {
color: lightgray;u
}
.color-brown {
color: brown;
}
.color-cyan {
color: cyan;
}
.color-dark-cyan {
color: darkcyan;
}
li.notification { li.notification {
color: green; color: green;
} }

View file

@ -45,7 +45,7 @@
color: #77dfd8; color: #77dfd8;
} }
.cof-chat_nick_self { .cof-chat_nick_self {
color: #d9d9d9; color: #fff1bd;
} }
.cof-chat_nick_other { .cof-chat_nick_other {
color: #77dfd8; color: #77dfd8;
@ -143,6 +143,7 @@
.cob-chat_nick { .cob-chat_nick {
} }
.cob-chat_nick_self { .cob-chat_nick_self {
background: #080808;
} }
.cob-chat_nick_other { .cob-chat_nick_other {
} }
@ -268,13 +269,13 @@
.cwf-darkgray { .cwf-darkgray {
color: #5d5d5d; color: #5d5d5d;
} }
.cwf-darkred { .cwf-red {
color: #c75646; color: #c75646;
} }
.cwf-lightred { .cwf-lightred {
color: #e09690; color: #e09690;
} }
.cwf-darkgreen { .cwf-green {
color: #8eb33b; color: #8eb33b;
} }
.cwf-lightgreen { .cwf-lightgreen {
@ -286,19 +287,19 @@
.cwf-yellow { .cwf-yellow {
color: #ffe377; color: #ffe377;
} }
.cwf-darkblue { .cwf-blue {
color: #72b3cc; color: #72b3cc;
} }
.cwf-lightblue { .cwf-lightblue {
color: #9cd9f0; color: #9cd9f0;
} }
.cwf-darkmagenta { .cwf-magenta {
color: #c8a0d1; color: #c8a0d1;
} }
.cwf-lightmagenta { .cwf-lightmagenta {
color: #fbb1f9; color: #fbb1f9;
} }
.cwf-darkcyan { .cwf-cyan {
color: #218693; color: #218693;
} }
.cwf-lightcyan { .cwf-lightcyan {
@ -321,13 +322,13 @@
.cwb-darkgray { .cwb-darkgray {
background-color: #5d5d5d; background-color: #5d5d5d;
} }
.cwb-darkred { .cwb-red {
background-color: #c75646; background-color: #c75646;
} }
.cwb-lightred { .cwb-lightred {
background-color: #e09690; background-color: #e09690;
} }
.cwb-darkgreen { .cwb-green {
background-color: #8eb33b; background-color: #8eb33b;
} }
.cwb-lightgreen { .cwb-lightgreen {
@ -339,19 +340,19 @@
.cwb-yellow { .cwb-yellow {
background-color: #ffe377; background-color: #ffe377;
} }
.cwb-darkblue { .cwb-blue {
background-color: #72b3cc; background-color: #72b3cc;
} }
.cwb-lightblue { .cwb-lightblue {
background-color: #9cd9f0; background-color: #9cd9f0;
} }
.cwb-darkmagenta { .cwb-magenta {
background-color: #c8a0d1; background-color: #c8a0d1;
} }
.cwb-lightmagenta { .cwb-lightmagenta {
background-color: #fbb1f9; background-color: #fbb1f9;
} }
.cwb-darkcyan { .cwb-cyan {
background-color: #218693; background-color: #218693;
} }
.cwb-lightcyan { .cwb-lightcyan {

View file

@ -15,6 +15,7 @@
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.2/underscore-min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.2/underscore-min.js"></script>
<script type="text/javascript" src="js/localstorage.js"></script> <script type="text/javascript" src="js/localstorage.js"></script>
<script type="text/javascript" src="js/weechat.js"></script> <script type="text/javascript" src="js/weechat.js"></script>
<script type="text/javascript" src="js/irc-utils.js"></script>
<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>
@ -48,14 +49,14 @@
<div class="form-group"> <div class="form-group">
<label class="control-label" for="host">WeeChat relay hostname and port number</label> <label class="control-label" for="host">WeeChat relay hostname and port number</label>
<div class="input-group"> <div class="input-group">
<input type="text" class="form-control" id="host" ng-model="host" placeholder="Address"> <input type="text" class="form-control monospace" id="host" ng-model="host" placeholder="Address">
<input type="text" class="form-control" id="port" ng-model="port" placeholder="9001"> <input type="text" class="form-control monospace" id="port" ng-model="port" placeholder="9001">
</div> </div>
<div class="alert alert-danger" ng-show="passwordError"> <div class="alert alert-danger" ng-show="passwordError">
Error wrong password Error wrong password
</div> </div>
<label class="control-label" for="password">WeeChat relay password</label> <label class="control-label" for="password">WeeChat relay password</label>
<input type="password" class="form-control" id="password" ng-model="password" placeholder="Password"> <input type="password" class="form-control monospace" id="password" ng-model="password" placeholder="Password">
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input type="checkbox" class="checkbox" id="savepassword" ng-model="savepassword"> <input type="checkbox" class="checkbox" id="savepassword" ng-model="savepassword">
@ -71,7 +72,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="control-label" for="port">Lines</label> <label class="control-label" for="port">Lines</label>
<input type="text" class="form-control" id="lines" ng-model="lines" placeholder="40"> <input type="text" class="form-control monospace" id="lines" ng-model="lines" placeholder="40">
<p class="help-block">Enter number of lines to sync from WeeChat on connect</p> <p class="help-block">Enter number of lines to sync from WeeChat on connect</p>
</div> </div>
<button class="btn btn-lg btn-primary" ng-click="connect()">Connect <i class="glyphicon glyphicon-chevron-right"></i></button> <button class="btn btn-lg btn-primary" ng-click="connect()">Connect <i class="glyphicon glyphicon-chevron-right"></i></button>
@ -150,6 +151,7 @@ $ openssl req -nodes -newkey rsa:2048 -keyout relay.pem -x509 -days 365 -out rel
<div class="brand"> <div class="brand">
<img alt="brand" src="img/favicon.png"> <img alt="brand" src="img/favicon.png">
</div> </div>
<div id="connection-infos" class="monospace">{{ host }}:{{ port }}</div>
<div class="title"> <div class="title">
{{ activeBuffer().title}} {{ activeBuffer().title}}
</div> </div>
@ -190,6 +192,16 @@ $ openssl req -nodes -newkey rsa:2048 -keyout relay.pem -x509 -days 365 -out rel
</div> </div>
</form> </form>
</li> </li>
<li class="">
<form class="form-inline" role="form">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="nonicklist">
Hide nicklist
</label>
</div>
</form>
</li>
</ul> </ul>
</div> </div>
<a ng-click="disconnect()"> <a ng-click="disconnect()">
@ -201,7 +213,7 @@ $ openssl req -nodes -newkey rsa:2048 -keyout relay.pem -x509 -days 365 -out rel
<ul class="nav nav-pills nav-stacked"> <ul class="nav nav-pills nav-stacked">
<li class="bufferfilter"> <li class="bufferfilter">
<form role="form"> <form role="form">
<input class="form-control" type="text" id="bufferFilter" ng-model="search" ng-keydown="handleSearchBoxKey($event)" placeholder="Search"> <input class="form-control monospace" type="text" id="bufferFilter" ng-model="search" ng-keydown="handleSearchBoxKey($event)" placeholder="Search">
</form> </form>
</li> </li>
<li class="buffer" ng-class="{'active': content.active }" ng-repeat="(key, content) in buffers | toArray | filter:{fullName:search} | filter:hasUnread | orderBy:'content.number':true"> <li class="buffer" ng-class="{'active': content.active }" ng-repeat="(key, content) in buffers | toArray | filter:{fullName:search} | filter:hasUnread | orderBy:'content.number':true">
@ -213,7 +225,14 @@ $ openssl req -nodes -newkey rsa:2048 -keyout relay.pem -x509 -days 365 -out rel
</li> </li>
</ul> </ul>
</div> </div>
<div id="bufferlines"> <div id="bufferlines" class="monospace" ng-class="{'withnicklist': showNicklist}">
<div id="nicklist" ng-show="showNicklist" class="vertical-line-left">
<ul class="nicklistgroup list-unstyled" ng-repeat="group in activeBuffer().nicklist">
<li class="" ng-repeat="nick in group.nicks|orderBy:'nick.name'">
<a ng-click="nickAction(nick)"><span ng-class="nick.prefixClasses">{{nick.prefix}}</span><span ng-class="nick.nameClasses">{{nick.name}}</span></a>
</li>
</ul>
</div>
<table> <table>
<tbody> <tbody>
<tr class="bufferline" ng-repeat-start="bufferline in (bufferlines = activeBuffer().lines)"> <tr class="bufferline" ng-repeat-start="bufferline in (bufferlines = activeBuffer().lines)">
@ -249,10 +268,10 @@ $ openssl req -nodes -newkey rsa:2048 -keyout relay.pem -x509 -days 365 -out rel
<div class="push"></div> <div class="push"></div>
</div> </div>
<div id="footer" ng-show="connected"> <div id="footer" ng-show="connected">
<div class="navbar navbar-inverse navbar-fixed-bottom"> <div class="navbar navbar-inverse navbar-fixed-bottom" ng-class="{'withnicklist': showNicklist}">
<form class="form form-horizontal" ng-submit="sendMessage()"> <form class="form form-horizontal" ng-submit="sendMessage()">
<div class="input-group"> <div class="input-group">
<input id="sendMessage" type="text" class="form-control" autocomplete="off" ng-model="command" autofocus> <input id="sendMessage" type="text" class="form-control monospace" autocomplete="off" ng-model="command" autofocus>
<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>
</span> </span>

208
js/irc-utils.js Normal file
View file

@ -0,0 +1,208 @@
/**
* Portable utilities for IRC.
*/
var IrcUtils = {
/**
* Get a new version of a nick list, sorted alphabetically by lowercase nick.
*
* @param nickList Original nick list
* @return Nick list sorted alphabetically by lowercase nick
*/
_ciSearchNickList: function(nickList) {
var newList = [];
nickList.forEach(function(nick) {
newList.push(nick);
});
newList.sort(function(a, b) {
return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
});
return newList;
},
/**
* Completes a single nick.
*
* @param candidate What to search for
* @param nickList Array of current nicks sorted for case insensitive searching
* @return Completed nick (null if not found)
*/
_completeSingleNick: function(candidate, nickList) {
var foundNick = null;
nickList.some(function(nick) {
if (nick.toLowerCase().search(candidate.toLowerCase()) == 0) {
// found!
foundNick = nick;
return true;
}
return false;
});
return foundNick;
},
/**
* Get the next nick when iterating nicks.
*
* @param iterCandidate First characters to look at
* @param currentNick Current selected nick
* @param nickList Array of current nicks sorted for case insensitive searching
* @return Next nick (may be the same)
*/
_nextNick: function(iterCandidate, currentNick, nickList) {
var firstInGroup = null;
var matchingNicks = [];
var at = null;
var lcIterCandidate = iterCandidate.toLowerCase();
var lcCurrentNick = currentNick.toLowerCase();
// collect matching nicks
for (var i = 0; i < nickList.length; ++i) {
var lcNick = nickList[i].toLowerCase();
if (lcNick.search(lcIterCandidate) == 0) {
matchingNicks.push(nickList[i]);
if (lcCurrentNick == lcNick) {
at = matchingNicks.length - 1;
}
} else if (matchingNicks.length > 0) {
// end of group, no need to check after this
break;
}
}
if (at == null || matchingNicks.length == 0) {
return currentNick;
} else {
++at;
if (at == matchingNicks.length) {
// cycle
at = 0;
}
return matchingNicks[at];
}
},
/**
* Nicks tab completion.
*
* @param text Plain text (no colors)
* @param caretPos Current caret position (0 means before the first character)
* @param iterCandidate Current iteration candidate (null if not iterating)
* @param nickList Array of current nicks
* @param suf Custom suffix (at least one character, escaped for regex)
* @return Object with following properties:
* text: new complete replacement text
* caretPos: new caret position within new text
* foundNick: completed nick (or null if not possible)
* iterCandidate: current iterating candidate
*/
completeNick: function(text, caretPos, iterCandidate, nickList, suf) {
var doIterate = (iterCandidate !== null);
if (suf === null) {
suf = ':';
}
// new nick list to search in
var searchNickList = IrcUtils._ciSearchNickList(nickList);
// text before and after caret
var beforeCaret = text.substring(0, caretPos);
var afterCaret = text.substring(caretPos);
// default: don't change anything
var ret = {
text: text,
caretPos: caretPos,
foundNick: null,
iterCandidate: null
};
// iterating nicks at the beginning?
var m = beforeCaret.match(new RegExp('^([a-zA-Z0-9_\\\\\\[\\]{}^`|-]+)' + suf + ' $'));
if (m) {
if (doIterate) {
// try iterating
var newNick = IrcUtils._nextNick(iterCandidate, m[1], searchNickList);
beforeCaret = newNick + suf + ' ';
return {
text: beforeCaret + afterCaret,
caretPos: beforeCaret.length,
foundNick: newNick,
iterCandidate: iterCandidate
};
} else {
// if not iterating, don't do anything
return ret;
}
}
// nick completion in the beginning?
m = beforeCaret.match(/^([a-zA-Z0-9_\\\[\]{}^`|-]+)$/);
if (m) {
// try completing
var newNick = IrcUtils._completeSingleNick(m[1], searchNickList);
if (newNick === null) {
// no match
return ret;
}
beforeCaret = newNick + suf + ' ';
if (afterCaret[0] == ' ') {
// swallow first space after caret if any
afterCaret = afterCaret.substring(1);
}
return {
text: beforeCaret + afterCaret,
caretPos: beforeCaret.length,
foundNick: newNick,
iterCandidate: m[1]
};
}
// iterating nicks in the middle?
m = beforeCaret.match(/^(.* )([a-zA-Z0-9_\\\[\]{}^`|-]+) $/);
if (m) {
if (doIterate) {
// try iterating
var newNick = IrcUtils._nextNick(iterCandidate, m[2], searchNickList);
beforeCaret = m[1] + newNick + ' ';
return {
text: beforeCaret + afterCaret,
caretPos: beforeCaret.length,
foundNick: newNick,
iterCandidate: iterCandidate
};
} else {
// if not iterating, don't do anything
return ret;
}
}
// nick completion elsewhere in the middle?
m = beforeCaret.match(/^(.* )([a-zA-Z0-9_\\\[\]{}^`|-]+)$/);
if (m) {
// try completing
var newNick = IrcUtils._completeSingleNick(m[2], searchNickList);
if (newNick === null) {
// no match
return ret;
}
beforeCaret = m[1] + newNick + ' ';
if (afterCaret[0] == ' ') {
// swallow first space after caret if any
afterCaret = afterCaret.substring(1);
}
return {
text: beforeCaret + afterCaret,
caretPos: beforeCaret.length,
foundNick: newNick,
iterCandidate: m[2]
};
}
// completion not possible
return ret;
}
};

View file

@ -18,6 +18,8 @@ models.service('models', ['$rootScope', '$filter', function($rootScope, $filter)
var local_variables = message['local_vars']; var local_variables = message['local_vars'];
var notify = 3 // Default 3 == message var notify = 3 // Default 3 == message
var lines = [] var lines = []
var nicklist = {}
var flatnicklist = []
var active = false var active = false
var notification = 0 var notification = 0
var unread = 0 var unread = 0
@ -38,6 +40,64 @@ models.service('models', ['$rootScope', '$filter', function($rootScope, $filter)
lines.push(line); lines.push(line);
} }
/*
* Adds a nick to nicklist
*/
var addNick = function(group, nick) {
nicklist[group].nicks.push(nick);
flatnicklist = getFlatNicklist();
}
/*
* Deletes a nick from nicklist
*/
var delNick = function(group, nick) {
var group = nicklist[group];
group.nicks = _.filter(group.nicks, function(n) { return n.name != nick.name});
flatnicklist = getFlatNicklist();
/*
for(i in group.nicks) {
if(group.nicks[i].name == nick.name) {
delete group.nicks[i];
break;
}
}
*/
}
/*
* Updates a nick in nicklist
*/
var updateNick = function(group, nick) {
var group = nicklist[group];
for(i in group.nicks) {
if(group.nicks[i].name == nick.name) {
group.nicks[i] = nick;
break;
}
}
flatnicklist = getFlatNicklist();
}
/*
* Maintain a cached version of a flat sorted nicklist
*
*/
var getFlatNicklist = function() {
var newlist = [];
_.each(nicklist, function(nickGroup) {
_.each(nickGroup.nicks, function(nickObj) {
newlist.push(nickObj.name);
});
});
newlist.sort(function(a, b) {
return a.toLowerCase() < b.toLowerCase() ? -1 : 1;
});
return newlist;
}
var flatNicklist = function() {
return flatnicklist;
}
return { return {
id: pointer, id: pointer,
fullName: fullName, fullName: fullName,
@ -51,6 +111,11 @@ models.service('models', ['$rootScope', '$filter', function($rootScope, $filter)
notification: notification, notification: notification,
localvars: local_variables, localvars: local_variables,
notify: notify, notify: notify,
nicklist: nicklist,
addNick: addNick,
delNick: delNick,
updateNick: updateNick,
flatNicklist: flatNicklist
} }
} }
@ -130,6 +195,75 @@ models.service('models', ['$rootScope', '$filter', function($rootScope, $filter)
} }
function nickGetColorClasses(nickMsg, propName) {
if (propName in nickMsg && nickMsg[propName] && nickMsg[propName].length > 0) {
var color = nickMsg[propName];
if (color.match(/^weechat/)) {
// color option
var colorName = color.match(/[a-zA-Z0-9_]+$/)[0];
return [
'cof-' + colorName,
'cob-' + colorName,
'coa-' + colorName
];
} else if (color.match(/^[a-zA-Z]+$/)) {
// WeeChat color name
return [
'cwf-' + color
];
} else if (color.match(/^[0-9]+$/)) {
// extended color
return [
'cef-' + color
];
}
}
return [
'cwf-default'
];
}
function nickGetClasses(nickMsg) {
return {
'name': nickGetColorClasses(nickMsg, 'color'),
'prefix': nickGetColorClasses(nickMsg, 'prefix_color')
};
}
/*
* Nick class
*/
this.Nick = function(message) {
var prefix = message['prefix'];
var visible = message['visible'];
var name = message['name'];
var colorClasses = nickGetClasses(message);
return {
prefix: prefix,
visible: visible,
name: name,
prefixClasses: colorClasses.prefix,
nameClasses: colorClasses.name
}
}
/*
* Nicklist Group class
*/
this.NickGroup = function(message) {
var name = message['name'];
var visible = message['visible'];
var nicks = [];
return {
name: name,
visible: visible,
nicks: nicks
}
}
var BufferList = [] var BufferList = []
activeBuffer = null; activeBuffer = null;

View file

@ -114,6 +114,54 @@ weechat.factory('handlers', ['$rootScope', 'models', 'plugins', function($rootSc
}); });
} }
/*
* Handle nicklist event
*/
var handleNicklist = function(message) {
var nicklist = message['objects'][0]['content'];
var group = 'root';
nicklist.forEach(function(n) {
var buffer = models.getBuffer(n.pointers[0]);
if(n.group == 1) {
var g = new models.NickGroup(n);
group = g.name;
buffer.nicklist[group] = g;
}else{
var nick = new models.Nick(n);
buffer.addNick(group, nick);
}
});
}
/*
* Handle nicklist diff event
*/
var handleNicklistDiff = function(message) {
var nicklist = message['objects'][0]['content'];
var group;
nicklist.forEach(function(n) {
var buffer = models.getBuffer(n.pointers[0]);
var d = n['_diff'];
if(n.group == 1) {
group = n.name;
if(group==undefined) {
var g = new models.NickGroup(n);
buffer.nicklist[group] = g;
group = g.name;
}
}
else {
var nick = new models.Nick(n);
if(d == 43) { // +
buffer.addNick(group, nick);
}else if (d == 45) { // -
buffer.delNick(group, nick);
}else if (d == 42) { // *
buffer.updateNick(group, nick);
}
}
});
}
var handleEvent = function(event) { var handleEvent = function(event) {
if (_.has(eventHandlers, event['id'])) { if (_.has(eventHandlers, event['id'])) {
@ -127,13 +175,16 @@ weechat.factory('handlers', ['$rootScope', 'models', 'plugins', function($rootSc
_buffer_line_added: handleBufferLineAdded, _buffer_line_added: handleBufferLineAdded,
_buffer_opened: handleBufferOpened, _buffer_opened: handleBufferOpened,
_buffer_title_changed: handleBufferTitleChanged, _buffer_title_changed: handleBufferTitleChanged,
_buffer_renamed: handleBufferRenamed _buffer_renamed: handleBufferRenamed,
_nicklist: handleNicklist,
_nicklist_diff: handleNicklistDiff
} }
return { return {
handleEvent: handleEvent, handleEvent: handleEvent,
handleLineInfo: handleLineInfo, handleLineInfo: handleLineInfo,
handleHotlistInfo: handleHotlistInfo handleHotlistInfo: handleHotlistInfo,
handleNicklist: handleNicklist
} }
}]); }]);
@ -237,6 +288,12 @@ weechat.factory('connection', ['$q', '$rootScope', '$log', '$store', 'handlers',
})).then(function(hdata) { })).then(function(hdata) {
handlers.handleHotlistInfo(hdata) handlers.handleHotlistInfo(hdata)
}); });
}).then(function() {
$log.info("Requesting nicklist");
doSendWithCallback(weeChat.Protocol.formatNicklist({
})).then(function(nicklistdata) {
handlers.handleNicklist(nicklistdata)
});
}).then(function() { }).then(function() {
doSend(weeChat.Protocol.formatSync({})); doSend(weeChat.Protocol.formatSync({}));
$log.info("Synced"); $log.info("Synced");
@ -313,8 +370,6 @@ weechat.factory('connection', ['$q', '$rootScope', '$log', '$store', 'handlers',
}]); }]);
weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout', '$log', 'models', 'connection', function ($rootScope, $scope, $store, $timeout, $log, models, connection, testService) { weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout', '$log', 'models', 'connection', function ($rootScope, $scope, $store, $timeout, $log, models, connection, testService) {
if(window.Notification) { if(window.Notification) {
// Request notification permission // Request notification permission
Notification.requestPermission(function (status) { Notification.requestPermission(function (status) {
@ -361,6 +416,9 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
// Clear search term on buffer change // Clear search term on buffer change
$scope.search = ''; $scope.search = '';
// Check if we should show nicklist or not
$scope.showNicklist = $scope.updateShowNicklist();
}); });
$rootScope.$on('notificationChanged', function() { $rootScope.$on('notificationChanged', function() {
var notifications = _.reduce(models.model.buffers, function(memo, num) { return (memo||0) + num.notification;}); var notifications = _.reduce(models.model.buffers, function(memo, num) { return (memo||0) + num.notification;});
@ -389,6 +447,8 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
$rootScope.buffer = [] $rootScope.buffer = []
$rootScope.iterCandidate = null;
$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");
@ -405,7 +465,8 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
$store.bind($scope, "notimestamp", false); $store.bind($scope, "notimestamp", false);
// Save setting for syncing hotlist // Save setting for syncing hotlist
$store.bind($scope, "hotlistsync", true); $store.bind($scope, "hotlistsync", true);
// Save setting for displaying nicklist
$store.bind($scope, "nonicklist", false);
$scope.setActiveBuffer = function(key) { $scope.setActiveBuffer = function(key) {
models.setActiveBuffer(key); models.setActiveBuffer(key);
@ -500,6 +561,30 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
return true; return true;
}; };
// Watch model and update show setting when it changes
$scope.$watch('nonicklist', function() {
$scope.showNicklist = $scope.updateShowNicklist();
});
$scope.showNicklist = false;
// Utility function that template can use to check if nicklist should
// be displayed for current buffer or not
// is called on buffer switch
$scope.updateShowNicklist = function() {
var ab = models.getActiveBuffer();
if(!ab) {
return false;
}
// Check if option no nicklist is set
if($scope.nonicklist) {
return false;
}
// Use flat nicklist to check if empty
if(ab.flatNicklist().length === 0) {
return false;
}
return true;
}
$rootScope.switchToActivityBuffer = function() { $rootScope.switchToActivityBuffer = function() {
// Find next buffer with activity and switch to it // Find next buffer with activity and switch to it
for(i in $scope.buffers) { for(i in $scope.buffers) {
@ -514,11 +599,49 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
} }
} }
$rootScope.completeNick = function() {
// input DOM node
var inputNode = document.getElementById('sendMessage');
// get current input
var inputText = inputNode.value;
// get current caret position
var caretPos = inputNode.selectionStart;
// create flat array of nicks
var activeBuffer = models.getActiveBuffer();
// complete nick
var nickComp = IrcUtils.completeNick(inputText, caretPos,
$rootScope.iterCandidate, activeBuffer.flatNicklist(), ':');
// remember iteration candidate
$rootScope.iterCandidate = nickComp.iterCandidate;
// update current input
inputNode.value = nickComp.text;
// update current caret position
inputNode.focus();
inputNode.setSelectionRange(nickComp.caretPos, nickComp.caretPos);
}
$scope.handleKeyPress = function($event) { $scope.handleKeyPress = function($event) {
// don't do anything if not connected
if (!$rootScope.connected) {
return true;
}
// Support different browser quirks // Support different browser quirks
var code = $event.keyCode ? $event.keyCode : $event.charCode; var code = $event.keyCode ? $event.keyCode : $event.charCode;
if ($event.altKey && (code > 47 && code < 58)) { // any other key than Tab resets nick completion iteration
var tmpIterCandidate = $rootScope.iterCandidate;
$rootScope.iterCandidate = null;
// Left Alt+[0-9] -> jump to buffer
if ($event.altKey && !$event.ctrlKey && (code > 47 && code < 58)) {
if (code == 48) { if (code == 48) {
code = 58; code = 58;
} }
@ -531,16 +654,40 @@ weechat.controller('WeechatCtrl', ['$rootScope', '$scope', '$store', '$timeout',
} }
} }
//log('keypress', $event.charCode, $event.altKey); // Tab -> nick completion
if (code == 9 && !$event.altKey && !$event.ctrlKey) {
$event.preventDefault();
$rootScope.iterCandidate = tmpIterCandidate;
$rootScope.completeNick();
return true;
}
// Handle alt-a // Alt+A -> switch to buffer with activity
if($event.altKey && (code == 97 || code == 65)) { if ($event.altKey && (code == 97 || code == 65)) {
$event.preventDefault(); $event.preventDefault();
$rootScope.switchToActivityBuffer(); $rootScope.switchToActivityBuffer();
return true; return true;
} }
// Handle ctrl-g
if($event.ctrlKey && (code == 103 || code == 71)) { // Alt+L -> focus on input bar
if ($event.altKey && (code == 76 || code == 108)) {
$event.preventDefault();
var inputNode = document.getElementById('sendMessage');
inputNode.focus();
inputNode.setSelectionRange(inputNode.value.length, inputNode.value.length);
return true;
}
// Escape -> disconnect
if (code == 27) {
$event.preventDefault();
connection.disconnect();
return true;
}
// Ctrl+G -> focus on buffer filter input
if ($event.ctrlKey && (code == 103 || code == 71)) {
$event.preventDefault();
document.getElementById('bufferFilter').focus(); document.getElementById('bufferFilter').focus();
return true; return true;
} }

View file

@ -44,17 +44,17 @@
'default', 'default',
'black', 'black',
'darkgray', 'darkgray',
'darkred', 'red',
'lightred', 'lightred',
'darkgreen', 'green',
'lightgreen', 'lightgreen',
'brown', 'brown',
'yellow', 'yellow',
'darkblue', 'blue',
'lightblue', 'lightblue',
'darkmagenta', 'magenta',
'lightmagenta', 'lightmagenta',
'darkcyan', 'cyan',
'lightcyan', 'lightcyan',
'gray', 'gray',
'white' 'white'