Browse Source

test

test
master
Hedgehog 10 years ago
commit
87094753c7
  1. 88
      README.md
  2. 40
      abort.html
  3. BIN
      css/OpenSans-Bold.ttf
  4. BIN
      css/OpenSans-Regular.ttf
  5. BIN
      css/OpenSansCondensed300.ttf
  6. BIN
      css/images/ajax-loader.gif
  7. BIN
      css/images/form-arrow-down-black.png
  8. BIN
      css/images/icons-18-black.png
  9. BIN
      css/images/icons-18-white.png
  10. BIN
      css/images/icons-36-black.png
  11. BIN
      css/images/icons-36-white.png
  12. 12
      css/jquery.mobile-1.3.2.min.css
  13. 319
      css/profile.css
  14. 1641
      css/style.css
  15. 430
      following.html
  16. 448
      home.html
  17. BIN
      img/config.png
  18. BIN
      img/connections.png
  19. BIN
      img/edit.png
  20. BIN
      img/following.png
  21. BIN
      img/genericPerson.png
  22. BIN
      img/grayed_avatar_placeholder_24.png
  23. BIN
      img/home.png
  24. BIN
      img/loader.gif
  25. BIN
      img/messages.png
  26. BIN
      img/network.png
  27. BIN
      img/profile.png
  28. BIN
      img/reply.png
  29. BIN
      img/repost.png
  30. BIN
      img/spinner-medium.gif
  31. BIN
      img/spinner-small.gif
  32. BIN
      img/spinner.gif
  33. BIN
      img/tornado_avatar.png
  34. BIN
      img/twister_mini.png
  35. BIN
      img/wash-white-30.png
  36. 42
      index.html
  37. 463
      interface_common.js
  38. 113
      interface_home.js
  39. 1091
      interface_localization.js
  40. 45
      interface_login.js
  41. 120
      interface_profile-edit.js
  42. 14
      jQueryPlugins.js
  43. 770
      jpeg_encoder_basic.js
  44. 5
      jquery-1.9.1.min.js
  45. 12
      jquery.animate-colors-min.js
  46. 200
      jquery.json-2.4.js
  47. 440
      jquery.jsonrpcclient.js
  48. 6
      jquery.min.js
  49. 9
      jquery.mobile-1.3.2.min.js
  50. 10
      jquery.mobile.router.min.js
  51. 382
      jquery.storageapi.js
  52. 170
      login.html
  53. 356
      mobile_abstract.js
  54. 185
      network.html
  55. 17
      polyglot.min.js
  56. 105
      profile-edit.html
  57. 807
      tmobile.html
  58. 567
      tmobile.js
  59. 253
      twister_actions.js
  60. 175
      twister_directmsg.js
  61. 553
      twister_following.js
  62. 231
      twister_formatpost.js
  63. 368
      twister_io.js
  64. 277
      twister_network.js
  65. 182
      twister_newmsgs.js
  66. 291
      twister_timeline.js
  67. 268
      twister_user.js

88
README.md

@ -0,0 +1,88 @@ @@ -0,0 +1,88 @@
twister-html
============
HTML interface for [Twister](http://twister.net.co).
To use it, clone this repo under ~/.twister/html like this:
git clone https://github.com/miguelfreitas/twister-html.git ~/.twister/html
Localisation
------------
If you want a localised interface (currently available only for NL, IT, FR, DE and RU),
use the experimental `i18n` branch:
git checkout i18n
If you want to translate it in your own language, check [these instructions](#translations)
Contribute
----------
Feel free to fork and send pull requests!
To make it easier for us to accept your patches, please follow the conventional
GitHub workflow:
# after forking, clone your repo
rm -rf ~/.twister/html
git clone git@github.com:yournickname/twister-html.git ~/.twister/html
cd ~/.twister/html
# CREATE A NEW BRANCH, specific to the fix you're implementing
git checkout -b my-fix
# ... make your changes ...
# commit and push
git commit -m "Fixing #1234 - bad foobarizer" && git push
# Now open a pull request from branch my-fix to miguelfreitas:master on github.
# Once the request is accepted, switch back to master and track upstream
git remote add upstream https://github.com/miguelfreitas/twister-html.git # one-off setup
git fetch upstream
git checkout master
git merge upstream/master # you should get a fast-forward message here
git push
Translations
------------
If you want to add your own translation, edit `interface_localization.js` like this:
1. fork the repo, checkout `i18n` and create a new branch
git clone git@github.com:yournickname/twister-html.git ~/.twister/html
cd ~/.twister/html
git checkout i18n
git checkout -b Klingon
2. add your language to the list of available choices. You should use your ISO code here,
it should match what the browser reports. The Klingon ISO is "tlh", so:
var knownLanguages = ["en","nl","it","fr","ru","tlh"];`
For multi-region languages, if you want to catch them all, use only the first half
(e.g. to match it and it-ch, specify "it").
3. add a new wordset block after existing ones
if(preferredLanguage == "tlh"){
polyglot.locale("tlh");
wordset = {
"Insults": "mu'qaD,
....
}
}
4. stage all changes in file `interface_localization.js`
git add interface_localization.js
5. commit & push
git commit -m "Klingon translation"
git push
3. When opening the pull request on github, make sure you're pointing to `miguelfreitas:i18n`
as the base, so we can merge it straight away in the right place. For any help, ping @toyg.

40
abort.html

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Aborting...</title>
<link rel="stylesheet" href="css/style.css" type="text/css"/>
<script src="jquery.min.js"></script>
<script src="jQueryPlugins.js"></script>
<script src="jquery.json-2.4.js"></script>
<script src="jquery.jsonrpcclient.js"></script>
<script src="jquery.storageapi.js"></script>
<script src="mobile_abstract.js"></script>
<script src="twister_io.js"></script>
<script src="twister_user.js"></script>
<script src="twister_formatpost.js"></script>
<script src="twister_following.js"></script>
<script src="twister_newmsgs.js"></script>
<script src="twister_network.js"></script>
<script src="interface_common.js"></script>
<link rel="icon" type="image/png" href="img/twister_mini.png" />
</head>
<body>
<!-- MENU SUPERIOR INIT -->
<nav class="userMenu">
</nav>
<!-- MENU SUPERIOR END -->
<div class="wrapper">
<div class="network singleBlock">
<h2 style="text-align: center"> Daemon exited... </h2>
</div>
</div>
</body>
</html>

BIN
css/OpenSans-Bold.ttf

Binary file not shown.

BIN
css/OpenSans-Regular.ttf

Binary file not shown.

BIN
css/OpenSansCondensed300.ttf

Binary file not shown.

BIN
css/images/ajax-loader.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
css/images/form-arrow-down-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

BIN
css/images/icons-18-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
css/images/icons-18-white.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
css/images/icons-36-black.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
css/images/icons-36-white.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

12
css/jquery.mobile-1.3.2.min.css vendored

File diff suppressed because one or more lines are too long

319
css/profile.css

@ -0,0 +1,319 @@ @@ -0,0 +1,319 @@
/**************************************
********************* PROFILE PHOTO ***
***************************************/
.profile-card
{
padding: 7px;
background: rgba( 255, 255, 255, .5 );
border: solid 1px rgba( 69, 71, 77, .05 );
position: relative;
background: #43464d;
}
.profile-card-main
{
width: 520px;
padding: 10px;
text-align: center;
position: relative;
transition: all .2s linear;
background: rgba(255,255,255, .1);
}
/*.profile-card-main:before
{
content: "";
border: solid 0px #fff;
transition: all .2s linear;
position: absolute;
left: 1px;
top: 1px;
right: 1px;
bottom: 1px;
z-index: 0;
}*/
.profile-card-main *
{
position: relative;
z-index: 1;
}
.profile-card-photo
{
width: 74px;
height: 74px;
border: solid 3px #45474d;
border-radius: 10%;
/* border-top-right-radius: 0;
border-bottom-left-radius: 0;*/
box-sizing: content-box;
float:left;
top: 50%;
background-color: #3b3c41;
}
.profile-card-main h1
{
font-size: 24px;
font-weight: bold;
color: #fff;
}
.profile-card-main h2 {
display: block;
position: absolute;
left: 17%;
top: 16%;
color: #7691ce;
}
.profile-card .direct-messages,
.profile-card .direct-messages-with-user,
.profile-card .follow
{
display: block;
position: absolute;
bottom: 20px;
right: 10px;
padding: 10px;
font-size: 12px;
width: 120px;
text-align: center;
color: rgba( 0, 0, 0, .7 );
background: rgba( 0, 0, 0, .1 );
border: none;
transition: all .2s linear;
}
.profile-card .follow
{
right: 135px;
}
.profile-card .direct-messages:hover,
.profile-card .direct-messages-with-user:hover,
.profile-card .follow:hover
{
background: rgba( 0, 0, 0, .3 );
}
.profile-card.forEdition
{
margin: 0 auto;
width: 540px;
}
.forEdition .profile-card-photo
{
border: solid 2px #fff;
position: relative;
cursor: pointer;
transition: all .2s linear;
z-index: 10;
}
.forEdition .profile-card-main:hover:after,
.forEdition .profile-card-photo:after
{
content: "";
width: 36px;
height: 36px;
position: absolute;
top: -5px;
right: 0px;
background: url(../img/edit.png) no-repeat right top;
}
.forEdition .profile-card-main:hover:before
{
border: solid 5px #fff;
}
.forEdition .profile-card-main h2
{
margin-bottom: 8px;
}
.forEdition .profile-card-main input
{
display: block;
margin: 0 auto;
background: rgba( 255, 255, 255, .8 );
border: none;
padding: 6px 4px;
margin-bottom: 4px;
text-align: center;
transition: all .2s linear;
}
.forEdition .profile-card-main input:hover
{
background: rgba( 255, 255, 255, .7 );
}
.forEdition .profile-card-photo:hover{
background: #9096a5;
}
.forEdition .profile-card-main input:focus{
background: #fff;
color: #4d4d4d;
}
/* inputs placeholders color */
.forEdition .profile-card-main input::-webkit-input-placeholder {
color: #4d4d4d;
}
.forEdition .profile-card-main input:-moz-placeholder {
color: #4d4d4d;
}
.forEdition .profile-card-main input::-moz-placeholder {
color: #4d4d4d;
}
.forEdition .profile-card-main input:-ms-input-placeholder {
color: #4d4d4d;
}
.forEdition .profile-card-main input:focus::-webkit-input-placeholder {
color: #fff;
}
.forEdition .profile-card-main input:focus:-moz-placeholder {
color: #fff;
}
.forEdition .profile-card-main input:focus::-moz-placeholder {
color: #fff;
}
.forEdition .profile-card-main input:focus::-ms-input-placeholder {
color: #fff;
}
.profile-card-main input.input-name{
position: absolute;
top: 30px;
right: 20px;
}
.profile-card-main input.input-description
{
width: 500px;
margin-top: 100px;
}
.input-name
{
font-size: 20px;
}
.forEdition .profile-card-main .input-website,
.forEdition .profile-card-main .input-city
{
display: inline-block;
margin-top: 10px;
}
.profile-edition-buttons
{
padding: 10px;
text-align: right;
}
.profile-edition-buttons button {
background: #f1f1f1;
color: #333;
}
.profile-edition-buttons button:hover {
background: #fff;
transition: background-color 300ms ease-in;
}
.secret-key-container .label {
color: #9096a5;
}
.secret-key-container .secret-key {
color: #d2dbf1;
}
/*************************************
****************** PROFILE MODAL
**************************************/
.profile-modal .modal-wrapper
{
width: 580px;
border-radius: 5px;
overflow: hidden;
position: absolute;
top:10%;
height: 80%;
margin-left: -300px;
}
.profile-modal .modal-content
{
padding: 3px;
height: 90%;
}
.profile-modal h1.profile-name {
color: #43464d;
}
.profile-modal .profile-location {
color: #48577d;
}
.profile-modal .profile-data
{
display: inline-block;
margin-left: -4px;
border-bottom: 0;
}
button.follow:hover {
background: #b2d67b;
color: #fff;
}
.profile-modal button.follow:hover {
background: #b2d67b;
color: #fff;
}
.profile-modal button.direct-messages-with-user:hover {
background: #b2d67b;
color: #fff;
}
.profile-modal .postboard
{
margin-left: 0;
padding: 5px 0 5px 0;
height: 75%;
}
.profile-modal .postboard h2 {
width: auto;
}
.profile-modal .postboard h2 span {
font: 18px/40px 'Open Sans Condensed', sans-serif;
padding-left: 10px;
}
.profile-modal .postboard-posts
{
display: block;
height: 90%;
overflow: auto;
}
.profile-modal .profile-card-main
{
background: #f3f5fb;
color: #8d8d8d;
width:100%;
}
.profile-modal .profile-card-main a {
color: #8bb9e0;
}
.profile-modal .postboard-posts .post
{
padding: 0;
}
.profile-modal .post-interactions
{
margin: 2px 10px 3px 60px;
}
.profile-modal .profile-card
{
margin: 0;
padding: 0;
background: #f3f5fb;
}
/*
.profile-card-main
{
height: 200px;
}
*/
.profile-modal .direct-messages,
.profile-modal .direct-messages-with-user,
.profile-modal .follow
{
bottom: 10px;
}
h1.profile-name {
display: inline;
}
h2.profile-screen-name {
display: inline;
}
.profile-modal .modal-buttons {
display: none;
}

1641
css/style.css

File diff suppressed because it is too large Load Diff

430
following.html

@ -0,0 +1,430 @@ @@ -0,0 +1,430 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Following</title>
<link rel="stylesheet" href="css/style.css" type="text/css"/>
<link rel="stylesheet" href="css/profile.css" type="text/css"/>
<script src="jquery.min.js"></script>
<script src="jQueryPlugins.js"></script>
<script src="jquery.json-2.4.js"></script>
<script src="jquery.jsonrpcclient.js"></script>
<script src="jquery.storageapi.js"></script>
<script src="mobile_abstract.js"></script>
<script src="twister_io.js"></script>
<script src="polyglot.min.js"></script>
<script src="interface_localization.js"></script>
<script src="twister_network.js"></script>
<script src="twister_user.js"></script>
<script src="twister_formatpost.js"></script>
<script src="twister_newmsgs.js"></script>
<script src="twister_following.js"></script>
<script src="twister_directmsg.js"></script>
<script src="twister_actions.js"></script>
<script src="interface_common.js"></script>
<script>
$(document).ready(function()
{
initInterfaceFollowing();
});
</script>
<link rel="icon" type="image/png" href="img/twister_mini.png" />
</head>
<body>
<!-- MENU SUPERIOR INIT -->
<nav class="userMenu">
<ul>
<li class="userMenu-home"><a href="home.html"></a></li>
<li class="userMenu-following current"><a href="following.html">Following</a></li>
<!-- <li class="userMenu-network"><a href="network.html">Network</a></li>
<li class="userMenu-profile"><a href="profile-edit.html">Profile</a></li> -->
<li class="userMenu-config current">
<a class="userMenu-config-dropdown" href="#">
<div class="config-menu dialog-modal">
<!-- <div class="mini-profile-info">
<div class="mini-profile-photo"><img src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/></div>
<a href="#" class="mini-profile-name">Fulano da Silva</a>
<span class="mini-profile-view">View</span>
</div> -->
<a class="dropdown-menu-item" href="profile-edit.html">Setup account</a>
<a class="dropdown-menu-item" href="following.html">Following users</a>
<a class="dropdown-menu-item" href="network.html">Network config</a>
<a class="dropdown-menu-item" href="login.html">Change user</a>
<!-- <a class="direct-messages" href="#">Direct Messages</a> -->
</div>
</a>
</li>
<!-- <li class="userMenu-connections">
<a href="#">
<span class="messages-qtd" style="display:none;">12</span>
</a>
</li>
<li class="userMenu-messages">
<a href="#">
<span class="messages-qtd" style="display:none;">12</span>
</a>
</li> -->
<!-- BUSCA -->
<li class="userMenu-search">
<input type="text" class="userMenu-search-field" placeholder="search"/>
<div class="search-results dialog-modal">
<ul class="userMenu-search-sugestions" style="display: none;">
<li><a href="#">Fulano</a></li>
<li><a href="#">Outro Fulano</a></li>
<li><a href="#">Mais outro</a></li>
</ul>
<ul class="userMenu-search-profiles">
<li id="search-profile-template" style="display: none;">
<div class="mini-profile-info" data-screen-name="">
<a href="#" class="open-profile-modal">
<img class="mini-profile-photo" src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/>
<span class="mini-screen-name">@<b></b></span>
<span class="mini-profile-name"></span>
</a>
<button class="follow">Follow</button>
</div>
</li>
</ul>
</div>
</li>
</ul>
</nav>
<!-- MENU SUPERIOR END -->
<div class="wrapper">
<!-- LADO ESQUERDO DE MÓDULOS INIT -->
<div class="following">
<!-- <h2> Following </h2> -->
<div class="postboard-loading" style="display: none;">
<div></div>
</div>
<ol class="following-list">
<li id="following-user-template" style="display: none;">
<div class="mini-profile-info" data-screen-name="">
<a href="#" class="open-profile-modal">
<img class="mini-profile-photo" src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/>
<span class="mini-profile-name">Fulano da Silva</span>
<span class="mini-screen-name">@
<b class="following-screen-name"></b>
</span>
</a>
<div>
<div class="mini-profile-actions">
<span>Actions ▼</span>
<ul>
<li class="direct-messages-with-user">Sent Direct Message</li>
<li>Sent Post to @
<b class="following-screen-name"></b>
</li>
<li class="mentions-from-user">Display mentions to @
<b class="following-screen-name"></b>
</li>
<li>Display retransmissions</li>
</ul>
</div>
<button class="unfollow">Unfollow</button>
<button class="public-following">Public</button>
</div>
<div>
<span class="swarm-status" style="display: none;"></span>
</div>
</div>
</li>
</ol>
<div class="not-following-any" style="display:none;">
<div> Not following any users! </div>
<div> Search some users in the right-upper search box and follow them. </div>
</div>
</div>
<!-- LADO ESQUERDO DE MÓDULOS END -->
</div>
<!-- TEMPLATES INIT -->
<div id="templates" style="display:none;">
<!-- TEMPLATE DE WHO-TO-FOLLOW SUGGESTION -->
<li id="follow-suggestion-template" class="twister-user">
<div class="">
<a href="#" class="twister-user-name open-profile-modal">
<img class="twister-user-photo" src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/>
</a>
</div>
<div class="twister-user-info" data-screen-name="">
<a href="#" class="twister-user-name open-profile-modal">
<span class="twister-user-tag"></span>
</a>
<div class="followers">
<span class="label">Followed by</span>
<span class="followed-by"></span>
</div>
<a class="twister-user-remove">&times;</a>
<button class="follow">Follow</button>
</div>
</li>
<!-- TEMPLATE DA OL INVÓLUCRO DAS LIST ELEMENTS DO POST EXPANDIDO INIT -->
<ol class="expanded-post">
</ol>
<!-- TEMPLATE DA OL INVÓLUCRO DAS LIST ELEMENTS DO POST EXPANDIDO END -->
<!-- TEMPLATE DOS 3 TIPOS DE LI DO POST EXPANDIDO INIT -->
<li class="ancestor module post">
</li>
<li id="post-template" class="module post" data-time="">
<div class="post-data" data-userpost="" data-content_to_rt="" data-content_to_sigrt=""
data-screen-name="" data-id="" data-text="" data-text-mentions="">
<div class="post-photo"><img class="avatar" src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/></div>
<div class="post-info">
<a href="#" class="post-info-name open-profile-modal"></a>
<span class="post-info-tag"></span>
<a class="post-info-time"></a>
</div>
<p class="post-text"></p>
<div class="post-context" style="display: none;">
<i class="post-retransmited-icon"></i>
<span>Retransmitted by
<a class="post-retransmited-by open-profile-modal" href=""></a>
</span>
</div>
<span class="post-expand">Expand</span>
<!-- elementos de interação com o post que são exibidos no hover -->
<div class="post-interactions">
<span class="post-reply">Reply</span>
<span class="post-propagate">Retransmit</span>
<!--span class="post-favorite">Favorite</span-->
</div>
<div class="expanded-content" style="display: none;">
<ul class="post-stats" style="display: none;">
<li class="stat-count">
<span class="stat-count-value"></span>
<span>Retransmits</span>
</li>
<li class="avatar-row">
<!-- use "avatar-row-template" here -->
</li>
</ul>
<div class="post-reply-content" style="display: block;">
<form class="post-area-new">
<textarea placeholder="Reply..."></textarea>
<div class="post-area-extras">
<span class="post-area-remaining">140</span>
<button class="post-submit disabled" disabled="true">post</button>
</div>
</form>
</div>
</div> <!-- expanded-content -->
</div> <!-- post-data -->
</li> <!-- post-template -->
<!-- template for user links in message (open profile modal) -->
<a id="msg-user-link-template" class="open-profile-modal"></a>
<!-- template for user links in message (open profile modal) -->
<a id="external-page-link-template" rel="nofollow" target="_blank"></a>
<!-- template for user links in message (open profile modal) -->
<a id="hashtag-link-template" class="open-hashtag-modal"></a>
<!-- template para ir dentro de avatar-row -->
<a id="avatar-row-template" class="open-profile-modal" href="">
<img class="size24" src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/>
<span class="user-name-tooltip"></span>
</a>
<li class="descendant module post">
</li>
<!-- TEMPLATE INVÓLUCRO DO POST EXPANDIDO END -->
<!-- TEMPLATE DO MODAL GENÉRICO INIT -->
<div class="modal-blackout">
<div class="modal-wrapper">
<div class="modal-header">
<h3></h3>
<span id="closeModal" class="modal-close cancel">&times;</span>
</div>
<div class="modal-content"></div>
<div class="modal-buttons">
<button class="modal-cancel cancel">Cancel</button>
<button class="modal-propagate">Retransmit</button>
</div>
</div>
</div>
<!-- TEMPLATE DO MODAL GENÉRICO END -->
<!-- MODAL DE RETWEET INIT -->
<!-- MODAL DE RETWEET END -->
<!-- MODAL DE REPLY INIT -->
<div id="reply-modal-template">
<div class="post-area">
<form class="post-area-new open">
<textarea placeholder="New Post..."></textarea>
<div class="post-area-extras">
<span class="post-area-remaining">140</span>
<button class="post-submit disabled" disabled="true">post</button>
</div>
</form>
</div>
<!-- use "module post" here -->
</div>
<!-- MODAL DE REPLY END -->
<!-- MODAL DE DIRECT MESSAGES INIT -->
<!-- Este modal possui dois templates, o primeiro da lista de pessoas que enviaram mensagens e o segundo
com a thread de mensagens individual -->
<div class="direct-messages-template">
<ol class="direct-messages-list">
<!-- use "dm-snippet-template here -->
</ol>
</div>
<li id="dm-snippet-template" class="module post message">
<div class="post-photo"><img src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/></div>
<div class="post-info">
<a href="#" class="post-info-name open-profile-modal"></a>
<span class="post-info-tag">@</span>
<a href="#" class="post-info-time" title="">10min</a>
</div>
<!-- o texto do post (restrição de caracteres?) -->
<p class="post-text"></p>
</li>
<div class="messages-thread-template">
<ol class="direct-messages-thread">
<!-- use "dm-chat-template" here -->
</ol>
</div>
<!-- cada li é uma mensagem -->
<li id="dm-chat-template" class="module post message">
<div class="post-photo"><img src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/></div>
<a href="#" class="post-info-time" title="08/10/13 às 18:00">10min</a>
<p class="post-text"></p>
</li>
<div class="dm-form-template">
<form class="post-area-new open">
<textarea placeholder="New direct message..."></textarea>
<div class="post-area-extras">
<span class="post-area-remaining">140</span>
<button class="dm-submit disabled" disabled="true">send</button>
</div>
</form>
</div>
<!-- MODAL DE DIRECT MESSAGES INIT -->
<!-- MODAL DE PROFILE -->
<div id="profile-modal-template">
<!-- ÁREA DE PROFILE PHOTO INIT -->
<div class="profile-card" data-screen-name="">
<!-- Coloquei a imagem de fundo do card do usuário como background da div
abaixo inline na tag para poder ser alterada dinamicamente
style="background: url(img/imgBack.png) no-repeat center center;"-->
<div class="profile-card-main">
<img class="profile-card-photo" src="img/grayed_avatar_placeholder_24.png"/>
<h1 class="profile-name"></h1>
<h2 class="profile-screen-name">@<b></b></h2>
<div>
<span class="profile-location"></span>
<a class="profile-url" rel="nofollow" target="_blank"></a>
</div>
<div class="profile-bio">
</div>
</div>
<ul class="module profile-data">
<li><a href="#"><span class="posts-count">&nbsp;</span><span class="label">Posts</span></a></li>
<li><a href="#" class="open-following-modal"><span class="following-count">&nbsp;</span><span class="label">Following</span></a></li>
<li><a href="#"><span class="followers-count">&nbsp;</span><span class="label">Followers</span></a></li>
</ul>
<button class="follow" href="#">Follow</button>
<button class="direct-messages-with-user" href="#">Direct Messages</button>
</div>
<!-- ÁREA DE POSTS END -->
<!-- ÁREA DE POSTS INIT -->
<div class="postboard">
<h2>
Posts
<!-- o botão de novas postagens deve ser ocultado quando o usuário clicá-lo via javascript -->
<button class="postboard-news" style="display:none;"></button>
</h2>
<ol id="profile-posts" class="postboard-posts">
</ol>
</div>
<!-- ÁREA DE POSTS END -->
</div>
<!-- MODAL DE PROFILE END -->
<!-- MODAL DE HASHTAGS -->
<div id="hashtag-modal-template">
<!-- ÁREA DE POSTS INIT -->
<div class="postboard">
<h2>
Posts
<!-- o botão de novas postagens deve ser ocultado quando o usuário clicá-lo via javascript -->
<button class="postboard-news" style="display:none;"></button>
</h2>
<ol id="profile-posts" class="postboard-posts">
</ol>
</div>
<!-- ÁREA DE POSTS END -->
</div>
<!-- MODAL DE HASHTAGS END -->
<!-- MODAL DE FOLLOWING -->
<div id="following-modal-template">
<!-- ÁREA DE PROFILE PHOTO INIT -->
<h2>All users publicly followed by <span class="following-screen-name">@<b></b></span></h2>
<!-- ÁREA DE PROFILE END -->
<!-- ÁREA DE FOLLOWING INIT -->
<ol class="">
<li id="following-by-user-template" style="display: none;">
<div class="mini-following-info" data-screen-name="">
<a href="#" class="open-profile-modal">
<img class="mini-profile-photo" src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/>
<span class="mini-following-name">Fulano da Silva</span>
<span class="mini-screen-name">@
<b class="following-screen-name"></b>
</span>
</a>
</div>
</li>
</ol>
<!-- ÁREA DE FOLLOWERS END -->
</div>
<!-- MODAL DE FOLLOWING END -->
</div>
<!-- TEMPLATES END -->
</body>
</html>

448
home.html

@ -0,0 +1,448 @@ @@ -0,0 +1,448 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>twister</title>
<link rel="stylesheet" href="css/style.css" type="text/css"/>
<link rel="stylesheet" href="css/profile.css" type="text/css"/>
<script src="jquery.min.js"></script>
<script src="jQueryPlugins.js"></script>
<script src="jquery.json-2.4.js"></script>
<script src="jquery.jsonrpcclient.js"></script>
<script src="jquery.storageapi.js"></script>
<script src="mobile_abstract.js"></script>
<script src="twister_io.js"></script>
<script src="polyglot.min.js"></script>
<script src="interface_localization.js"></script>
<script src="twister_network.js"></script>
<script src="twister_user.js"></script>
<script src="twister_formatpost.js"></script>
<script src="twister_following.js"></script>
<script src="twister_timeline.js"></script>
<script src="twister_newmsgs.js"></script>
<script src="twister_actions.js"></script>
<script src="twister_directmsg.js"></script>
<script src="interface_common.js"></script>
<script src="interface_home.js"></script>
<script src="jquery.animate-colors-min.js"></script>
<link rel="icon" type="image/png" href="img/twister_mini.png" />
</head>
<body>
<!-- MENU SUPERIOR INIT -->
<nav class="userMenu">
<ul>
<li class="userMenu-home"><a href="#"></a></li>
<!-- <li class="userMenu-network"><a href="network.html">Network</a></li>
<li class="userMenu-profile"><a href="profile-edit.html">Profile</a></li> -->
<li class="userMenu-config">
<a class="userMenu-config-dropdown" href="#">
<div class="config-menu dialog-modal">
<!-- <div class="mini-profile-info">
<div class="mini-profile-photo"><img src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/></div>
<a href="#" class="mini-profile-name">Fulano da Silva</a>
<span class="mini-profile-view">View</span>
</div> -->
<a class="dropdown-menu-item" href="profile-edit.html">Setup account</a>
<a class="dropdown-menu-item" href="following.html">Following users</a>
<a class="dropdown-menu-item" href="network.html">Network config</a>
<a class="dropdown-menu-item" href="login.html">Change user</a>
<!-- <a class="dropdown-menu-item promoted-posts-only" href="#">Switch to Promoted posts</a> -->
<!-- <a class="direct-messages dropdown-menu-item" href="#">Direct Messages</a> -->
</div>
</a>
</li>
<li class="userMenu-connections">
<a href="#">
<span class="messages-qtd" style="display:none;">12</span>
</a>
</li>
<li class="userMenu-messages">
<a href="#">
<span class="messages-qtd" style="display:none;">12</span>
</a>
</li>
<!-- BUSCA -->
<li class="userMenu-search">
<input type="text" class="userMenu-search-field" placeholder="search"/>
<div class="search-results dialog-modal">
<ul class="userMenu-search-sugestions" style="display: none;">
<li><a href="#">Fulano</a></li>
<li><a href="#">Outro Fulano</a></li>
<li><a href="#">Mais outro</a></li>
</ul>
<ul class="userMenu-search-profiles">
<li id="search-profile-template" style="display: none;">
<div class="mini-profile-info" data-screen-name="">
<a href="#" class="open-profile-modal">
<img class="mini-profile-photo" src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/>
<span class="mini-screen-name">@<b></b></span>
<span class="mini-profile-name"></span>
</a>
<button class="follow">Follow</button>
</div>
</li>
</ul>
</div>
</li>
</ul>
</nav>
<!-- MENU SUPERIOR END -->
<div class="wrapper">
<!-- LADO ESQUERDO DE MÓDULOS INIT -->
<div class="dashboard">
<!-- PROFILE MODULE INIT -->
<div class="module mini-profile">
<div class="mini-profile-info">
<div class="mini-profile-photo"><img src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/></div>
<a href="#" class="mini-profile-name open-profile-modal"></a>
<span class="mini-profile-view">View</span>
</div>
<ul class="module profile-data">
<li><a href="#" class="open-profile-modal"><span class="posts-count">&nbsp;</span><span class="label">Posts</span></a></li>
<li><a href="following.html"><span class="following-count">&nbsp;</span><span class="label">Following</span></a></li>
<li><a href="#"><span class="followers-count">&nbsp;</span><span class="label">Followers</span> *</a></li>
</ul>
<div class="post-area">
<form class="post-area-new">
<textarea placeholder="New Post..."></textarea>
<div class="post-area-extras">
<span class="post-area-remaining">140</span>
<button class="post-submit disabled" disabled="true">post</button>
</div>
</form>
</div>
</div>
<!-- PROFILE MODULE INIT -->
<!-- WHO TO FOLLOW MODULE INIT -->
<div class="module who-to-follow">
<h3>Who to Follow</h3>
<a class="refresh-users">Refresh</a>
<a class="view-all-users">View All</a>
<ol class="follow-suggestions">
<!-- use "follow-suggestion-template" here -->
</ol>
</div>
<!-- WHO TO FOLLOW MODULE END -->
<!-- WHO TO FOLLOW MODULE INIT -->
<div class="module toptrends">
<h3>Top Trends</h3>
<ol class="toptrends-list">
<!-- use "follow-suggestion-template" here -->
</ol>
</div>
<!-- WHO TO FOLLOW MODULE END -->
</div>
<!-- LADO ESQUERDO DE MÓDULOS END -->
<!-- ÁREA DE POSTS INIT -->
<div class="postboard">
<h2>
<!-- Postboard > -->
<!-- o botão de novas postagens deve ser ocultado quando o usuário clicá-lo via javascript -->
<a class="promoted-posts-only" href="#">Normal posts</a>
<button class="postboard-news" style="display: none;"></button>
</h2>
<ol id="posts" class="postboard-posts">
<!-- use "post-template" here -->
</ol>
<div class="postboard-loading" style="display: none;">
<div></div>
</div>
</div>
<!-- ÁREA DE POSTS END -->
</div>
<!-- TEMPLATES INIT -->
<div id="templates" style="display:none;">
<!-- TEMPLATE DE WHO-TO-FOLLOW SUGGESTION -->
<li id="follow-suggestion-template" class="twister-user">
<div class="">
<a href="#" class="twister-user-name open-profile-modal">
<img class="twister-user-photo" src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/>
</a>
</div>
<div class="twister-user-info" data-screen-name="">
<a href="#" class="twister-user-name open-profile-modal">
<span class="twister-user-tag"></span>
</a>
<div class="followers">
<span class="label">Followed by</span>
<a href="#" class="twister-by-user-name open-profile-modal">
<span class="followed-by"></span>
</a>
</div>
<a class="twister-user-remove">&times;</a>
<button class="follow">Follow</button>
</div>
</li>
<!-- TEMPLATE DA OL INVÓLUCRO DAS LIST ELEMENTS DO POST EXPANDIDO INIT -->
<ol class="expanded-post">
</ol>
<!-- TEMPLATE DA OL INVÓLUCRO DAS LIST ELEMENTS DO POST EXPANDIDO END -->
<!-- TEMPLATE DOS 3 TIPOS DE LI DO POST EXPANDIDO INIT -->
<li class="ancestor module post">
</li>
<li id="post-template" class="module post" data-time="">
<div class="post-data" data-userpost="" data-content_to_rt="" data-content_to_sigrt=""
data-screen-name="" data-id="" data-text="" data-text-mentions="">
<div class="post-photo"><img class="avatar" src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/></div>
<div class="post-info">
<a href="#" class="post-info-name open-profile-modal"></a>
<span class="post-info-tag"></span>
<a class="post-info-time"></a>
</div>
<p class="post-text"></p>
<div class="post-context" style="display: none;">
<i class="post-retransmited-icon"></i>
<span>Retransmitted by
<a class="post-retransmited-by open-profile-modal" href=""></a>
</span>
</div>
<span class="post-expand">Expand</span>
<!-- elementos de interação com o post que são exibidos no hover -->
<div class="post-interactions">
<span class="post-reply">Reply</span>
<span class="post-propagate">Retransmit</span>
<!--span class="post-favorite">Favorite</span-->
</div>
<div class="expanded-content" style="display: none;">
<ul class="post-stats" style="display: none;">
<li class="stat-count">
<span class="stat-count-value"></span>
<span>Retransmits</span>
</li>
<li class="avatar-row">
<!-- use "avatar-row-template" here -->
</li>
</ul>
<div class="post-reply-content" style="display: block;">
<form class="post-area-new">
<textarea placeholder="Reply..."></textarea>
<div class="post-area-extras">
<span class="post-area-remaining">140</span>
<button class="post-submit disabled" disabled="true">post</button>
</div>
</form>
</div>
</div> <!-- expanded-content -->
</div> <!-- post-data -->
</li> <!-- post-template -->
<!-- template for user links in message (open profile modal) -->
<a id="msg-user-link-template" class="open-profile-modal"></a>
<!-- template for user links in message (open profile modal) -->
<a id="external-page-link-template" rel="nofollow" target="_blank"></a>
<!-- template for user links in message (open profile modal) -->
<a id="hashtag-link-template" class="open-hashtag-modal"></a>
<!-- template para ir dentro de avatar-row -->
<a id="avatar-row-template" class="open-profile-modal" href="">
<img class="size24" src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/>
<span class="user-name-tooltip"></span>
</a>
<li class="descendant module post">
</li>
<!-- TEMPLATE INVÓLUCRO DO POST EXPANDIDO END -->
<!-- TEMPLATE DO MODAL GENÉRICO INIT -->
<div class="modal-blackout">
<div class="modal-wrapper">
<div class="modal-header">
<h3></h3>
<span id="closeModal" class="modal-close cancel">&times;</span>
</div>
<div class="modal-content"></div>
<div class="modal-buttons">
<button class="modal-cancel cancel">Cancel</button>
<button class="modal-propagate">Retransmit</button>
</div>
</div>
</div>
<!-- TEMPLATE DO MODAL GENÉRICO END -->
<!-- MODAL DE RETWEET INIT -->
<!-- MODAL DE RETWEET END -->
<!-- MODAL DE REPLY INIT -->
<div id="reply-modal-template">
<div class="post-area">
<form class="post-area-new open">
<textarea placeholder="New Post..."></textarea>
<div class="post-area-extras">
<span class="post-area-remaining">140</span>
<button class="post-submit disabled" disabled="true">post</button>
</div>
</form>
</div>
<!-- use "module post" here -->
</div>
<!-- MODAL DE REPLY END -->
<!-- MODAL DE DIRECT MESSAGES INIT -->
<!-- Este modal possui dois templates, o primeiro da lista de pessoas que enviaram mensagens e o segundo
com a thread de mensagens individual -->
<div class="direct-messages-template">
<ol class="direct-messages-list">
<!-- use "dm-snippet-template here -->
</ol>
</div>
<li id="dm-snippet-template" class="module post message">
<div class="post-photo"><img src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/></div>
<div class="post-info">
<a href="#" class="post-info-name open-profile-modal"></a>
<span class="post-info-tag">@</span>
<a href="#" class="post-info-time" title="">10min</a>
</div>
<!-- o texto do post (restrição de caracteres?) -->
<p class="post-text"></p>
</li>
<div class="messages-thread-template">
<ol class="direct-messages-thread">
<!-- use "dm-chat-template" here -->
</ol>
</div>
<!-- cada li é uma mensagem -->
<li id="dm-chat-template" class="module post message">
<div class="post-photo"><img src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/></div>
<a href="#" class="post-info-time" title="08/10/13 às 18:00">10min</a>
<p class="post-text"></p>
</li>
<div class="dm-form-template">
<form class="post-area-new open">
<textarea placeholder="New direct message..."></textarea>
<div class="post-area-extras">
<span class="post-area-remaining">140</span>
<button title="Direct messages are encrypted, only you and receiver can read them" style="margin-right:7.5%;" class="dm-submit disabled" disabled="true">send</button>
</div>
</form>
</div>
<!-- MODAL DE DIRECT MESSAGES INIT -->
<!-- MODAL DE PROFILE -->
<div id="profile-modal-template">
<!-- ÁREA DE PROFILE PHOTO INIT -->
<div class="profile-card" data-screen-name="">
<!-- Coloquei a imagem de fundo do card do usuário como background da div
abaixo inline na tag para poder ser alterada dinamicamente
style="background: url(img/imgBack.png) no-repeat center center;"-->
<div class="profile-card-main">
<img class="profile-card-photo" src="img/grayed_avatar_placeholder_24.png"/>
<h1 class="profile-name"></h1>
<h2 class="profile-screen-name">@<b></b></h2>
<div>
<span class="profile-location"></span>
<a class="profile-url" rel="nofollow" target="_blank"></a>
</div>
<div class="profile-bio">
</div>
</div>
<ul class="module profile-data">
<li><a href="#"><span class="posts-count">&nbsp;</span><span class="label">Posts</span></a></li>
<li><a href="#" class="open-following-modal"><span class="following-count">&nbsp;</span><span class="label">Following</span></a></li>
<li><a href="#"><span class="followers-count">&nbsp;</span><span class="label">Followers</span></a></li>
</ul>
<button class="follow" href="#">Follow</button>
<button class="direct-messages-with-user" href="#">Direct Messages</button>
</div>
<!-- ÁREA DE POSTS END -->
<!-- ÁREA DE POSTS INIT -->
<div class="postboard">
<h2>
<span>Posts</span>
<!-- o botão de novas postagens deve ser ocultado quando o usuário clicá-lo via javascript -->
<button class="postboard-news" style="display:none;"></button>
</h2>
<ol id="profile-posts" class="postboard-posts">
</ol>
</div>
<!-- ÁREA DE POSTS END -->
</div>
<!-- MODAL DE PROFILE END -->
<!-- MODAL DE HASHTAGS -->
<div id="hashtag-modal-template">
<!-- ÁREA DE POSTS INIT -->
<div class="postboard">
<h2>
Posts
<!-- o botão de novas postagens deve ser ocultado quando o usuário clicá-lo via javascript -->
<button class="postboard-news" style="display:none;"></button>
</h2>
<ol id="profile-posts" class="postboard-posts">
</ol>
</div>
<!-- ÁREA DE POSTS END -->
</div>
<!-- MODAL DE HASHTAGS END -->
<!-- MODAL DE FOLLOWING -->
<div id="following-modal-template">
<!-- ÁREA DE PROFILE PHOTO INIT -->
<h2>All users publicly followed by <span class="following-screen-name">@<b></b></span></h2>
<!-- ÁREA DE PROFILE END -->
<!-- ÁREA DE FOLLOWING INIT -->
<ol class="">
<li id="following-by-user-template" style="display: none;">
<div class="mini-following-info" data-screen-name="">
<a href="#" class="open-profile-modal">
<img class="mini-profile-photo" src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/>
<span class="mini-following-name">Fulano da Silva</span>
<span class="mini-screen-name">@
<b class="following-screen-name"></b>
</span>
</a>
</div>
</li>
</ol>
<!-- ÁREA DE FOLLOWERS END -->
</div>
<!-- MODAL DE FOLLOWING END -->
</div>
<!-- TEMPLATES END -->
</body>
</html>

BIN
img/config.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

BIN
img/connections.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 B

BIN
img/edit.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

BIN
img/following.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 B

BIN
img/genericPerson.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

BIN
img/grayed_avatar_placeholder_24.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

BIN
img/home.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

BIN
img/loader.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
img/messages.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

BIN
img/network.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

BIN
img/profile.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 B

BIN
img/reply.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

BIN
img/repost.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

BIN
img/spinner-medium.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
img/spinner-small.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

BIN
img/spinner.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

BIN
img/tornado_avatar.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
img/twister_mini.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
img/wash-white-30.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

42
index.html

@ -0,0 +1,42 @@ @@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>twister</title>
<meta name="viewport" content="width=device-width, user-scalable=no"/>
<link rel="stylesheet" href="css/jquery.mobile-1.3.2.min.css" />
<script src="jquery-1.9.1.min.js"></script>
<script src="jquery.mobile.router.min.js"></script>
<script>
$(document).bind('mobileinit', function () {
$.mobile.allowCrossDomainPages = true;
$.mobile.zoom.enabled = false;
$.mobile.buttonMarkup.hoverDelay = 0; //defaults 200
$.mobile.defaultDialogTransition = 'none';
$.mobile.defaultPageTransition = 'none';
});
</script>
<script src="jquery.mobile-1.3.2.min.js"></script>
<link rel="icon" type="image/png" href="img/twister_mini.png" />
</head>
<body>
<div id="index" data-role="page">
<div data-role="header" data-position="fixed" data-nobackbtn="true">
<h1 class="rtitle">twister</h1>
</div>
<div class="content" data-role="content" style="">
<a href="home.html" data-role="button" rel="external">Desktop</a>
<a href="tmobile.html#home" data-role="button" rel="external">Mobile (test version)</a>
</div>
</div>
</body>
</html>

463
interface_common.js

@ -0,0 +1,463 @@ @@ -0,0 +1,463 @@
// interface_common.js
// 2013 Lucas Leal, Miguel Freitas
//
// Common interface functions to all pages, modal manipulation, button manipulation etc
// Profile, mentions and hashtag modal
// Post actions: submit, count characters
//dispara o modal genérico
//o modalClass me permite fazer tratamentos específicos de CSS para cada modal
function openModal( modalClass )
{
var $oldModal = $("body").children(".modal-blackout");
var $template = $( "#templates" );
var $templateModal = $template.find( ".modal-blackout" ).clone(true);
$templateModal.addClass( modalClass );
if( $oldModal.length ) {
$templateModal.show();
$oldModal.replaceWith($templateModal);
} else {
$templateModal.prependTo( "body" ).fadeIn( "fast" );
}
//escondo o overflow da tela
var $body = $( "body" );
$body.css({
"overflow": "hidden",
})
}
//fecha o modal removendo o conteúdo por detach
function closeModal($this)
{
var $body = $( "body" );
var $modalWindows = $( "body" ).children( ".modal-blackout" );
$modalWindows.fadeOut( "fast", function()
{
$modalWindows.detach();
});
$body.css({
"overflow": "auto",
"margin-right": "0"
});
}
function checkNetworkStatusAndAskRedirect(cbFunc, cbArg) {
networkUpdate(function(args) {
if( !twisterdConnectedAndUptodate ) {
var redirect =
window.confirm(polyglot.t("switch_to_network"));
if( redirect )
$.MAL.goNetwork();
} else {
if( args.cbFunc )
args.cbFunc(args.cbArg);
}
}, {cbFunc:cbFunc,cbArg:cbArg});
}
function timeGmtToText(t) {
var d = new Date(0);
d.setUTCSeconds(t);
return d.toString().replace(/GMT.*/g,"");
}
function timeSincePost(t) {
var d = new Date(0);
d.setUTCSeconds(t);
var now = new Date();
var t_delta = Math.ceil((now - d) / 1000);
var expression = "";
if(t_delta < 60) {
expression = polyglot.t("seconds", t_delta);
}
else if(t_delta < 60 * 60) {
expression = polyglot.t("minutes", Math.floor(t_delta/60));
}
else if(t_delta < 24 * 60 * 60) {
expression = polyglot.t("hours", Math.floor(t_delta/60/60));
}
else {
expression = polyglot.t("days", Math.floor(t_delta/24/60/60));
}
return polyglot.t("time_ago", { time: expression });
}
//
// Profile, mentions, hashtag, and following modal
// -----------------------------------
function newProfileModal(username) {
var profileModalContent = $( "#profile-modal-template" ).children().clone(true);
updateProfileData(profileModalContent, username);
return profileModalContent;
}
function openProfileModal(e)
{
e.stopPropagation();
e.preventDefault();
var $this = $( this );
var username = $.MAL.urlToUser( $this.attr("href") );
var profileModalClass = "profile-modal";
openModal( profileModalClass );
var profileModalContent = newProfileModal( username );
profileModalContent.appendTo("." +profileModalClass + " .modal-content");
//título do modal
$( "."+profileModalClass + " h3" ).text( polyglot.t("users_profile", { username: username }) );
}
function newHashtagModal(hashtag) {
var hashtagModalContent = $( "#hashtag-modal-template" ).children().clone(true);
hashtagModalContent.find( ".postboard-news").click(function (){
$(this).hide();
displayHashtagPending($(".hashtag-modal .postboard-posts"));
});
clearHashtagProcessed();
updateHashtagModal( hashtagModalContent.find(".postboard-posts"), hashtag );
return hashtagModalContent;
}
function openHashtagModal(e)
{
e.stopPropagation();
e.preventDefault();
var $this = $( this );
var hashtag = $this.text().substring(1);
var hashtagModalClass = "hashtag-modal";
openModal( hashtagModalClass );
$( "."+hashtagModalClass ).attr("data-resource","hashtag");
var hashtagModalContent = newHashtagModal( hashtag );
hashtagModalContent.appendTo("." +hashtagModalClass + " .modal-content");
//título do modal
$( "."+hashtagModalClass + " h3" ).text( "#" + hashtag );
}
function updateHashtagModal(postboard,hashtag) {
var $hashtagModalClass = $(".hashtag-modal");
if( !$hashtagModalClass.length || $hashtagModalClass.css("display") == 'none' )
return;
var resource = $hashtagModalClass.attr("data-resource");
requestHashtag(postboard,hashtag,resource);
setTimeout( function() {updateHashtagModal(postboard,hashtag);}, 5000);
}
function openMentionsModal(e)
{
e.stopPropagation();
e.preventDefault();
// reuse the same hashtag modal to show mentions
var hashtagModalClass = "hashtag-modal";
openModal( hashtagModalClass );
$( "."+hashtagModalClass ).attr("data-resource","mention");
var username;
var $userInfo = $(this).closest("[data-screen-name]");
if( $userInfo.length )
username = $userInfo.attr("data-screen-name");
else
username = defaultScreenName;
var hashtagModalContent = newHashtagModal( username );
hashtagModalContent.appendTo("." +hashtagModalClass + " .modal-content");
//título do modal
$( "."+hashtagModalClass + " h3" ).text( polyglot.t("users_mentions", { username: username }) );
// obtain already cached mention posts from twister_newmsgs.js
processHashtag(hashtagModalContent.find(".postboard-posts"), defaultScreenName, getMentionsData() );
resetMentionsCount();
}
function newFollowingModal(username) {
var followingModalContent = $( "#following-modal-template" ).children().clone(true);
updateFollowingData(followingModalContent, username);
return followingModalContent;
}
function openFollowingModal(e)
{
e.stopPropagation();
e.preventDefault();
var $this = $( this );
var username = $.MAL.followingUrlToUser( $this.attr("href") );
var followingModalClass = "following-modal";
openModal( followingModalClass );
var followingModalContent = newFollowingModal( username );
followingModalContent.appendTo("." +followingModalClass + " .modal-content");
//título do modal
$( "."+followingModalClass + " h3" ).text( polyglot.t("followed_by", { username: username }) );
}
//
// Post actions, submit, count characters
// --------------------------------------
//dispara o modal de retweet
var reTwistPopup = function( e )
{
var reTwistClass = "reTwist";
openModal( reTwistClass );
//título do modal
$( ".reTwist h3" ).text( polyglot.t("retransmit_this") );
var postdata = $(this).parents(".post-data").attr("data-userpost");
var postElem = postToElem($.evalJSON(postdata),"");
postElem.appendTo( ".reTwist .modal-content" );
e.stopPropagation();
}
//Expande Área do Novo post
var replyInitPopup = function(e, post)
{
var replyClass = "reply";
openModal( replyClass );
//título do modal
var fullname = post.find(".post-info-name").text();
$( ".reply h3" ).text( polyglot.t("reply_to", { fullname: fullname }) );
//para poder exibir a thread selecionada...
var replyModalContent = $(".reply .modal-content").hide();
var retweetContent = $( "#reply-modal-template" ).children().clone(true);
retweetContent.appendTo(replyModalContent);
var postdata = post.find(".post-data").attr("data-userpost");
var postElem = postToElem($.evalJSON(postdata),"");
postElem.appendTo(replyModalContent);
var replyArea = $(".reply .post-area .post-area-new");
replyArea.addClass("open");
var replyText = replyArea.find("textarea");
var postInlineReplyText = $(".reply .post .post-area-new textarea");
var attrToCopy = ["placeholder", "data-reply-to"];
$.each(attrToCopy, function( i, attribute ) {
replyText.attr( attribute, postInlineReplyText.attr(attribute) );
});
composeNewPost(e, replyArea);
replyModalContent.fadeIn( "fast" );
}
//abre o menu dropdown de configurações
var dropDownMenu = function( e )
{
var $configMenu = $( ".config-menu" );
$configMenu.slideToggle( "fast" );
e.stopPropagation();
}
//fecha o config menu ao clicar em qualquer lugar da tela
var closeThis = function()
{
$( this ).slideUp( "fast" );
};
var postExpandFunction = function( e, postLi )
{
if( !postLi.hasClass( "original" ) ) {
return;
}
var originalPost = postLi.find(".post-data.original");
var $postInteractionText = originalPost.find( ".post-expand" );
var $postExpandedContent = originalPost.find( ".expanded-content" );
var $postsRelated = postLi.find(".related");
var openClass = "open";
if( !postLi.hasClass( openClass ) ) {
originalPost.detach();
postLi.empty();
postLi.addClass( openClass );
$postInteractionText.text( polyglot.t("Collapse") );
var itemOl = $("<ol/>", {class:"expanded-post"}).appendTo(postLi);
var originalLi = $("<li/>", {class: "module post original"}).appendTo(itemOl);
originalLi.append(originalPost);
$postExpandedContent.slideDown( "fast" );
// insert "reply_to" before
requestRepliedBefore(originalLi);
// insert replies to this post after
requestRepliesAfter(originalLi);
// RTs faces and counter
requestRTs(originalLi);
}
else
{
postLi.removeClass( openClass );
$postInteractionText.text( polyglot.t("Expand") );
if( $postsRelated ) $postsRelated.slideUp( "fast" );
$postExpandedContent.slideUp( "fast", function()
{
originalPost.detach();
postLi.empty();
postLi.append(originalPost);
});
}
e.stopPropagation();
}
var postReplyClick = function( e )
{
var post = $(this).closest(".post");
if( !post.hasClass( "original" ) ) {
replyInitPopup(e, post);
} else {
var postLiOpen = post.parents(".post.open");
if( !postLiOpen.length ) {
postExpandFunction(e, post);
}
var postAreaNew = post.find(".post-area-new")
composeNewPost(e, postAreaNew);
}
e.stopPropagation();
}
//Expande Área do Novo post
var composeNewPost = function( e, postAreaNew )
{
e.stopPropagation();
if( !postAreaNew.hasClass("open") ) {
postAreaNew.addClass( "open" );
//se o usuário clicar fora é pra fechar
postAreaNew.clickoutside( unfocusThis )
}
var textArea = postAreaNew.find("textarea");
textArea.focus();
if( textArea.attr("data-reply-to") && !textArea.val().length ) {
textArea.val(textArea.attr("data-reply-to"));
}
}
//Reduz Área do Novo post
var unfocusThis = function()
{
var $this = $( this );
$this.removeClass( "open" );
}
function replyTextKeypress(e) {
e = e || event;
var $this = $( this );
var tweetForm = $this.parents("form");
if( tweetForm != undefined ) {
var c = 140 - $this.val().length;
var remainingCount = tweetForm.find(".post-area-remaining");
remainingCount.text(c);
if( c < 0 ) remainingCount.addClass("warn");
else remainingCount.removeClass("warn");
var tweetAction = tweetForm.find(".post-submit");
if( !tweetAction.length ) tweetAction = tweetForm.find(".dm-submit");
if( c >= 0 && c < 140 &&
$this.val() != $this.attr("data-reply-to") ) {
$.MAL.enableButton(tweetAction);
} else {
$.MAL.disableButton(tweetAction);
}
if (e.keyCode === 13) {
if (!e.ctrlKey) {
$this.val($this.val().trim());
if( !tweetAction.hasClass("disabled") ) {
tweetAction.click();
}
} else {
$this.val($this.val() + "\r");
}
}
}
}
var postSubmit = function(e)
{
e.stopPropagation();
e.preventDefault();
var $this = $( this );
var $replyText = $this.closest(".post-area-new").find("textarea");
var $postOrig = $this.closest(".post-data");
if (!$postOrig.length) {
$postOrig = $this.closest(".modal-content").find(".post-data");
}
newPostMsg($replyText.val(), $postOrig);
$replyText.val("");
$replyText.attr("placeholder", polyglot.t("Your message was sent!"));
var tweetForm = $this.parents("form");
var remainingCount = tweetForm.find(".post-area-remaining");
remainingCount.text(140);
$replyText.attr("placeholder", "Your message was sent!");
closeModal($this);
}
var retweetSubmit = function(e)
{
e.stopPropagation();
e.preventDefault();
var $this = $( this );
var $postOrig = $this.closest(".modal-wrapper").find(".post-data");
newRtMsg($postOrig);
closeModal($this);
}
function initInterfaceCommon() {
$( "body" ).on( "click", ".cancel" , function() { closeModal($(this)); } );
$( ".post-reply" ).bind( "click", postReplyClick );
$( ".post-propagate" ).bind( "click", reTwistPopup );
$( ".userMenu-config-dropdown" ).bind( "click", dropDownMenu );
$( ".config-menu" ).clickoutside( closeThis );
$( ".module.post" ).bind( "click", function(e) {
postExpandFunction(e,$(this)); });
$( ".post-area-new" ).bind( "click", function(e) {
composeNewPost(e,$(this));} );
$( ".post-area-new" ).clickoutside( unfocusThis );
$( ".post-submit").click( postSubmit );
$( ".modal-propagate").click( retweetSubmit );
var $replyText = $( ".post-area-new textarea" );
$replyText.keyup( replyTextKeypress );
$( ".open-profile-modal").bind( "click", openProfileModal );
$( ".open-hashtag-modal").bind( "click", openHashtagModal );
$( ".open-following-modal").bind( "click", openFollowingModal );
$( ".userMenu-connections a").bind( "click", openMentionsModal );
}

113
interface_home.js

@ -0,0 +1,113 @@ @@ -0,0 +1,113 @@
// interface_home.js
// 2013 Lucas Leal, Miguel Freitas
//
// Specific interface functions for home.html
var promotedPostsOnly = false;
//***********************************************
//******************* DECLARATIONS **************
//***********************************************
var InterfaceFunctions = function()
{
//faço os binds no init
this.init = function()
{
$( ".wrapper .postboard-news").click(function() {
requestTimelineUpdate("latest",postsPerRefresh,followingUsers,promotedPostsOnly);});
$( ".promoted-posts-only").click(function() {
promotedPostsOnly = !promotedPostsOnly;
$(this).text( promotedPostsOnly ? "Promoted posts" : "Normal posts" );
$(".postboard h2").animate({backgroundColor: promotedPostsOnly ? '#48577d' : '#768fce'}, 200);
timelineChangedUser();
$.MAL.getStreamPostsParent().empty();
requestTimelineUpdate("latestFirstTime",postsPerRefresh,followingUsers,promotedPostsOnly);
});
initInterfaceCommon();
initUserSearch();
initInterfaceDirectMsg();
initUser(initHome);
}
function initHome(cbFunc, cbArg) {
if( !defaultScreenName ) {
alert(polyglot.t("username_undefined"));
$.MAL.goLogin();
return;
}
checkNetworkStatusAndAskRedirect();
//$("span.screen-name").text('@' + user);
var $miniProfile = $(".mini-profile");
$miniProfile.find("a.mini-profile-name").attr("href",$.MAL.userUrl(defaultScreenName));
$miniProfile.find("a.open-profile-modal").attr("href",$.MAL.userUrl(defaultScreenName));
$miniProfile.find(".mini-profile-name").text(defaultScreenName);
getFullname( defaultScreenName, $miniProfile.find(".mini-profile-name") );
getAvatar( defaultScreenName, $miniProfile.find(".mini-profile-photo").find("img") );
getPostsCount( defaultScreenName, $miniProfile.find(".posts-count") );
getFollowers( defaultScreenName, $miniProfile.find(".followers-count") );
loadFollowing( function(args) {
$(".mini-profile .following-count").text(followingUsers.length-1);
requestLastHave();
setInterval("requestLastHave()", 1000);
initMentionsCount();
initDMsCount();
requestTimelineUpdate("latestFirstTime",postsPerRefresh,followingUsers,promotedPostsOnly);
// install scrollbottom handler to load more posts as needed
$(window).scroll(function(){
if ($(window).scrollTop() >= $(document).height() - $(window).height() - 20){
if( timelineLoaded ) {
requestTimelineUpdate("older", postsPerRefresh, followingUsers, promotedPostsOnly);
}
}
});
setTimeout("getRandomFollowSuggestion(processSuggestion)", 1000);
setTimeout("getRandomFollowSuggestion(processSuggestion)", 1000);
setTimeout("getRandomFollowSuggestion(processSuggestion)", 1000);
twisterRpc("gettrendinghashtags", [10],
function(args, ret) {
for( var i = 0; i < ret.length; i++ ) {
var $li = $("<li>");
var hashtagLinkTemplate = $("#hashtag-link-template").clone(true);
hashtagLinkTemplate.removeAttr("id");
hashtagLinkTemplate.attr("href",$.MAL.hashtagUrl(ret[i]));
hashtagLinkTemplate.text("#"+ret[i]);
$li.append(hashtagLinkTemplate);
$(".toptrends-list").append($li);
}
}, {},
function(args, ret) {
console.log("Error with gettrendinghashtags. Older twister daemon?");
}, {});
if( args.cbFunc )
args.cbFunc(args.cbArg);
}, {cbFunc:cbFunc, cbArg:cbArg});
}
}
//***********************************************
//******************* INIT **************
//***********************************************
var interfaceFunctions = new InterfaceFunctions;
$( document ).ready( interfaceFunctions.init );
//função no window que fixa o header das postagens
function fixDiv()
{
var $cache = $('.postboard h2');
if ($(window).scrollTop() > 26)
$cache.addClass( "fixed" );
else
$cache.removeClass( "fixed" );
}
$(window).scroll(fixDiv);

1091
interface_localization.js

File diff suppressed because it is too large Load Diff

45
interface_login.js

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
// interface_login.js
// 2013 Miguel Freitas
function processCreateUser(username, secretKey) {
defaultScreenName = username;
if(defaultScreenName) {
saveScreenName();
}
var newUserClass = "new-user";
openModal( newUserClass );
//título do modal
$( "." + newUserClass + " h3" ).text( polyglot.t("propagating_nickname", { username: username }) );
var modalContent = $("." + newUserClass + " .modal-content");
var templateContent = $( "#new-user-modal-template" ).children().clone(true);
templateContent.appendTo(modalContent);
$( "." + newUserClass + " .secret-key" ).text(secretKey);
sendNewUserTransaction( username, processSendnewusertransaction );
}
function processSendnewusertransaction() {
$( ".login-created-user").show();
}
function loginCreatedUser() {
$.MAL.goProfileEdit();
}
function initInterfaceLogin() {
initUser();
initInterfaceCommon();
checkNetworkStatusAndAskRedirect();
interfaceCommonLoginHandlers();
$( ".create-user").bind( "click", function() { createUserClick( processCreateUser ); } );
$( ".login-created-user").bind( "click", loginCreatedUser );
}

120
interface_profile-edit.js

@ -0,0 +1,120 @@ @@ -0,0 +1,120 @@
// interface_profile-edit.js
// 2013 Miguel Freitas
//
// Profile editing interface (profile-edit.html)
var newUserWarnDisplayed = false;
function initProfileEdit() {
// Check for the various File API support.
if (window.File && window.FileReader && window.FileList && window.Blob) {
// Great success! All the File APIs are supported.
} else {
alert('The File APIs are not fully supported in this browser.');
}
initInterfaceCommon();
$(".profile-card-photo.forEdition").click( function() { $('#avatar-file').click(); } );
$("#avatar-file").bind( "change", handleAvatarFileSelect);
$(".submit-changes").click( saveProfile );
$(".cancel-changes").click( $.MAL.goHome );
initUser( function() {
if( !defaultScreenName ) {
alert("Username undefined, login required.");
$.MAL.goLogin();
return;
}
checkNetworkStatusAndAskRedirect(verifyUserAlreadyInBlockchain);
if( defaultScreenName ) {
loadFollowing( function() {
initMentionsCount();
initDMsCount();
});
}
$(".profile-card-main h2").text("@" + defaultScreenName);
loadAvatarForEdit();
loadProfileForEdit();
$(".secret-key-container").hide();
$(".toggle-priv-key").click(function () {
if ($(".secret-key-container").is(":visible")) {
$(".secret-key-container").fadeOut(function () {
$(".secret-key").text('');
});
} else {
dumpPrivkey(defaultScreenName, function(args, key) {
$(".secret-key").text(key);
$(".secret-key-container").fadeIn();
}, {});
}
});
});
}
function handleAvatarFileSelect(evt) {
var files = evt.target.files; // FileList object
var f = files[0];
// Only process image files.
if (f.type.match('image.*')) {
var reader = new FileReader();
reader.onload=function(e){
var img=new Image();
img.onload=function(){
var MAXWidthHeight=64;
var r=MAXWidthHeight/Math.max(this.width,this.height),
w=Math.round(this.width*r),
h=Math.round(this.height*r),
c=document.createElement("canvas");
c.width=w;c.height=h;
c.getContext("2d").drawImage(this,0,0,w,h);
var imgURL = undefined;
for(var q = 0.9; (!imgURL || imgURL.length > 4096) && q > 0.1; q -= 0.01) {
imgURL = c.toDataURL("image/jpeg", q);
}
$(".profile-card-photo.forEdition").attr("src", imgURL );
}
img.src=e.target.result;
}
// Read in the image file as a data URL.
reader.readAsDataURL(f);
}
}
function verifyUserAlreadyInBlockchain()
{
$.MAL.disableButton($(".submit-changes"));
dumpPubkey(defaultScreenName, function(args, pubkey) {
//pubkey = "";
if( pubkey.length > 0 ) {
follow('twister', true, function() {
$.MAL.enableButton($(".submit-changes"));
});
} else {
if( !newUserWarnDisplayed ) {
alert("Other peers have not yet accepted this new user.\n" +
"Unfortunately it is not possible to save profile\n" +
"or send any posts in this state.\n\n" +
"Please wait a few minutes to continue.\n\n" +
"The 'Save Changes' will be automatically enabled\n" +
"when the process completes. (I promise this is\n"+
"the last time you will have to wait before using\n" +
"twister).\n\n" +
"Tip: choose your avatar in the meantime!");
newUserWarnDisplayed = true;
}
setTimeout("verifyUserAlreadyInBlockchain()", 5000);
}
}, {} );
}

14
jQueryPlugins.js

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
(function(jQuery) {
jQuery.fn.clickoutside = function(callback) {
var outside = 1, self = $(this);
self.cb = callback;
this.click(function() {
outside = 0;
});
$(document).click(function() {
outside && self.cb();
outside = 1;
});
return $(this);
}
})(jQuery);

770
jpeg_encoder_basic.js

@ -0,0 +1,770 @@ @@ -0,0 +1,770 @@
/*
Copyright (c) 2008, Adobe Systems Incorporated
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Adobe Systems Incorporated nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009
Basic GUI blocking jpeg encoder
*/
function JPEGEncoder(quality) {
var self = this;
var fround = Math.round;
var ffloor = Math.floor;
var YTable = new Array(64);
var UVTable = new Array(64);
var fdtbl_Y = new Array(64);
var fdtbl_UV = new Array(64);
var YDC_HT;
var UVDC_HT;
var YAC_HT;
var UVAC_HT;
var bitcode = new Array(65535);
var category = new Array(65535);
var outputfDCTQuant = new Array(64);
var DU = new Array(64);
var byteout = [];
var bytenew = 0;
var bytepos = 7;
var YDU = new Array(64);
var UDU = new Array(64);
var VDU = new Array(64);
var clt = new Array(256);
var RGB_YUV_TABLE = new Array(2048);
var currentQuality;
var ZigZag = [
0, 1, 5, 6,14,15,27,28,
2, 4, 7,13,16,26,29,42,
3, 8,12,17,25,30,41,43,
9,11,18,24,31,40,44,53,
10,19,23,32,39,45,52,54,
20,22,33,38,46,51,55,60,
21,34,37,47,50,56,59,61,
35,36,48,49,57,58,62,63
];
var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0];
var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11];
var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d];
var std_ac_luminance_values = [
0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,
0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,
0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,
0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,
0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,
0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,
0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,
0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,
0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,
0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,
0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,
0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,
0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,
0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,
0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,
0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,
0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,
0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,
0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,
0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
0xf9,0xfa
];
var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0];
var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11];
var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77];
var std_ac_chrominance_values = [
0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,
0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,
0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,
0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,
0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,
0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,
0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,
0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,
0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,
0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,
0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,
0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,
0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,
0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,
0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,
0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,
0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,
0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,
0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,
0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
0xf9,0xfa
];
function initQuantTables(sf){
var YQT = [
16, 11, 10, 16, 24, 40, 51, 61,
12, 12, 14, 19, 26, 58, 60, 55,
14, 13, 16, 24, 40, 57, 69, 56,
14, 17, 22, 29, 51, 87, 80, 62,
18, 22, 37, 56, 68,109,103, 77,
24, 35, 55, 64, 81,104,113, 92,
49, 64, 78, 87,103,121,120,101,
72, 92, 95, 98,112,100,103, 99
];
for (var i = 0; i < 64; i++) {
var t = ffloor((YQT[i]*sf+50)/100);
if (t < 1) {
t = 1;
} else if (t > 255) {
t = 255;
}
YTable[ZigZag[i]] = t;
}
var UVQT = [
17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99
];
for (var j = 0; j < 64; j++) {
var u = ffloor((UVQT[j]*sf+50)/100);
if (u < 1) {
u = 1;
} else if (u > 255) {
u = 255;
}
UVTable[ZigZag[j]] = u;
}
var aasf = [
1.0, 1.387039845, 1.306562965, 1.175875602,
1.0, 0.785694958, 0.541196100, 0.275899379
];
var k = 0;
for (var row = 0; row < 8; row++)
{
for (var col = 0; col < 8; col++)
{
fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
k++;
}
}
}
function computeHuffmanTbl(nrcodes, std_table){
var codevalue = 0;
var pos_in_table = 0;
var HT = new Array();
for (var k = 1; k <= 16; k++) {
for (var j = 1; j <= nrcodes[k]; j++) {
HT[std_table[pos_in_table]] = [];
HT[std_table[pos_in_table]][0] = codevalue;
HT[std_table[pos_in_table]][1] = k;
pos_in_table++;
codevalue++;
}
codevalue*=2;
}
return HT;
}
function initHuffmanTbl()
{
YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values);
UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values);
YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values);
UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values);
}
function initCategoryNumber()
{
var nrlower = 1;
var nrupper = 2;
for (var cat = 1; cat <= 15; cat++) {
//Positive numbers
for (var nr = nrlower; nr<nrupper; nr++) {
category[32767+nr] = cat;
bitcode[32767+nr] = [];
bitcode[32767+nr][1] = cat;
bitcode[32767+nr][0] = nr;
}
//Negative numbers
for (var nrneg =-(nrupper-1); nrneg<=-nrlower; nrneg++) {
category[32767+nrneg] = cat;
bitcode[32767+nrneg] = [];
bitcode[32767+nrneg][1] = cat;
bitcode[32767+nrneg][0] = nrupper-1+nrneg;
}
nrlower <<= 1;
nrupper <<= 1;
}
}
function initRGBYUVTable() {
for(var i = 0; i < 256;i++) {
RGB_YUV_TABLE[i] = 19595 * i;
RGB_YUV_TABLE[(i+ 256)>>0] = 38470 * i;
RGB_YUV_TABLE[(i+ 512)>>0] = 7471 * i + 0x8000;
RGB_YUV_TABLE[(i+ 768)>>0] = -11059 * i;
RGB_YUV_TABLE[(i+1024)>>0] = -21709 * i;
RGB_YUV_TABLE[(i+1280)>>0] = 32768 * i + 0x807FFF;
RGB_YUV_TABLE[(i+1536)>>0] = -27439 * i;
RGB_YUV_TABLE[(i+1792)>>0] = - 5329 * i;
}
}
// IO functions
function writeBits(bs)
{
var value = bs[0];
var posval = bs[1]-1;
while ( posval >= 0 ) {
if (value & (1 << posval) ) {
bytenew |= (1 << bytepos);
}
posval--;
bytepos--;
if (bytepos < 0) {
if (bytenew == 0xFF) {
writeByte(0xFF);
writeByte(0);
}
else {
writeByte(bytenew);
}
bytepos=7;
bytenew=0;
}
}
}
function writeByte(value)
{
byteout.push(clt[value]); // write char directly instead of converting later
}
function writeWord(value)
{
writeByte((value>>8)&0xFF);
writeByte((value )&0xFF);
}
// DCT & quantization core
function fDCTQuant(data, fdtbl)
{
var d0, d1, d2, d3, d4, d5, d6, d7;
/* Pass 1: process rows. */
var dataOff=0;
var i;
const I8 = 8;
const I64 = 64;
for (i=0; i<I8; ++i)
{
d0 = data[dataOff];
d1 = data[dataOff+1];
d2 = data[dataOff+2];
d3 = data[dataOff+3];
d4 = data[dataOff+4];
d5 = data[dataOff+5];
d6 = data[dataOff+6];
d7 = data[dataOff+7];
var tmp0 = d0 + d7;
var tmp7 = d0 - d7;
var tmp1 = d1 + d6;
var tmp6 = d1 - d6;
var tmp2 = d2 + d5;
var tmp5 = d2 - d5;
var tmp3 = d3 + d4;
var tmp4 = d3 - d4;
/* Even part */
var tmp10 = tmp0 + tmp3; /* phase 2 */
var tmp13 = tmp0 - tmp3;
var tmp11 = tmp1 + tmp2;
var tmp12 = tmp1 - tmp2;
data[dataOff] = tmp10 + tmp11; /* phase 3 */
data[dataOff+4] = tmp10 - tmp11;
var z1 = (tmp12 + tmp13) * 0.707106781; /* c4 */
data[dataOff+2] = tmp13 + z1; /* phase 5 */
data[dataOff+6] = tmp13 - z1;
/* Odd part */
tmp10 = tmp4 + tmp5; /* phase 2 */
tmp11 = tmp5 + tmp6;
tmp12 = tmp6 + tmp7;
/* The rotator is modified from fig 4-8 to avoid extra negations. */
var z5 = (tmp10 - tmp12) * 0.382683433; /* c6 */
var z2 = 0.541196100 * tmp10 + z5; /* c2-c6 */
var z4 = 1.306562965 * tmp12 + z5; /* c2+c6 */
var z3 = tmp11 * 0.707106781; /* c4 */
var z11 = tmp7 + z3; /* phase 5 */
var z13 = tmp7 - z3;
data[dataOff+5] = z13 + z2; /* phase 6 */
data[dataOff+3] = z13 - z2;
data[dataOff+1] = z11 + z4;
data[dataOff+7] = z11 - z4;
dataOff += 8; /* advance pointer to next row */
}
/* Pass 2: process columns. */
dataOff = 0;
for (i=0; i<I8; ++i)
{
d0 = data[dataOff];
d1 = data[dataOff + 8];
d2 = data[dataOff + 16];
d3 = data[dataOff + 24];
d4 = data[dataOff + 32];
d5 = data[dataOff + 40];
d6 = data[dataOff + 48];
d7 = data[dataOff + 56];
var tmp0p2 = d0 + d7;
var tmp7p2 = d0 - d7;
var tmp1p2 = d1 + d6;
var tmp6p2 = d1 - d6;
var tmp2p2 = d2 + d5;
var tmp5p2 = d2 - d5;
var tmp3p2 = d3 + d4;
var tmp4p2 = d3 - d4;
/* Even part */
var tmp10p2 = tmp0p2 + tmp3p2; /* phase 2 */
var tmp13p2 = tmp0p2 - tmp3p2;
var tmp11p2 = tmp1p2 + tmp2p2;
var tmp12p2 = tmp1p2 - tmp2p2;
data[dataOff] = tmp10p2 + tmp11p2; /* phase 3 */
data[dataOff+32] = tmp10p2 - tmp11p2;
var z1p2 = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */
data[dataOff+16] = tmp13p2 + z1p2; /* phase 5 */
data[dataOff+48] = tmp13p2 - z1p2;
/* Odd part */
tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */
tmp11p2 = tmp5p2 + tmp6p2;
tmp12p2 = tmp6p2 + tmp7p2;
/* The rotator is modified from fig 4-8 to avoid extra negations. */
var z5p2 = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */
var z2p2 = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */
var z4p2 = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */
var z3p2 = tmp11p2 * 0.707106781; /* c4 */
var z11p2 = tmp7p2 + z3p2; /* phase 5 */
var z13p2 = tmp7p2 - z3p2;
data[dataOff+40] = z13p2 + z2p2; /* phase 6 */
data[dataOff+24] = z13p2 - z2p2;
data[dataOff+ 8] = z11p2 + z4p2;
data[dataOff+56] = z11p2 - z4p2;
dataOff++; /* advance pointer to next column */
}
// Quantize/descale the coefficients
var fDCTQuant;
for (i=0; i<I64; ++i)
{
// Apply the quantization and scaling factor & Round to nearest integer
fDCTQuant = data[i]*fdtbl[i];
outputfDCTQuant[i] = (fDCTQuant > 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0);
//outputfDCTQuant[i] = fround(fDCTQuant);
}
return outputfDCTQuant;
}
function writeAPP0()
{
writeWord(0xFFE0); // marker
writeWord(16); // length
writeByte(0x4A); // J
writeByte(0x46); // F
writeByte(0x49); // I
writeByte(0x46); // F
writeByte(0); // = "JFIF",'\0'
writeByte(1); // versionhi
writeByte(1); // versionlo
writeByte(0); // xyunits
writeWord(1); // xdensity
writeWord(1); // ydensity
writeByte(0); // thumbnwidth
writeByte(0); // thumbnheight
}
function writeSOF0(width, height)
{
writeWord(0xFFC0); // marker
writeWord(17); // length, truecolor YUV JPG
writeByte(8); // precision
writeWord(height);
writeWord(width);
writeByte(3); // nrofcomponents
writeByte(1); // IdY
writeByte(0x11); // HVY
writeByte(0); // QTY
writeByte(2); // IdU
writeByte(0x11); // HVU
writeByte(1); // QTU
writeByte(3); // IdV
writeByte(0x11); // HVV
writeByte(1); // QTV
}
function writeDQT()
{
writeWord(0xFFDB); // marker
writeWord(132); // length
writeByte(0);
for (var i=0; i<64; i++) {
writeByte(YTable[i]);
}
writeByte(1);
for (var j=0; j<64; j++) {
writeByte(UVTable[j]);
}
}
function writeDHT()
{
writeWord(0xFFC4); // marker
writeWord(0x01A2); // length
writeByte(0); // HTYDCinfo
for (var i=0; i<16; i++) {
writeByte(std_dc_luminance_nrcodes[i+1]);
}
for (var j=0; j<=11; j++) {
writeByte(std_dc_luminance_values[j]);
}
writeByte(0x10); // HTYACinfo
for (var k=0; k<16; k++) {
writeByte(std_ac_luminance_nrcodes[k+1]);
}
for (var l=0; l<=161; l++) {
writeByte(std_ac_luminance_values[l]);
}
writeByte(1); // HTUDCinfo
for (var m=0; m<16; m++) {
writeByte(std_dc_chrominance_nrcodes[m+1]);
}
for (var n=0; n<=11; n++) {
writeByte(std_dc_chrominance_values[n]);
}
writeByte(0x11); // HTUACinfo
for (var o=0; o<16; o++) {
writeByte(std_ac_chrominance_nrcodes[o+1]);
}
for (var p=0; p<=161; p++) {
writeByte(std_ac_chrominance_values[p]);
}
}
function writeSOS()
{
writeWord(0xFFDA); // marker
writeWord(12); // length
writeByte(3); // nrofcomponents
writeByte(1); // IdY
writeByte(0); // HTY
writeByte(2); // IdU
writeByte(0x11); // HTU
writeByte(3); // IdV
writeByte(0x11); // HTV
writeByte(0); // Ss
writeByte(0x3f); // Se
writeByte(0); // Bf
}
function processDU(CDU, fdtbl, DC, HTDC, HTAC){
var EOB = HTAC[0x00];
var M16zeroes = HTAC[0xF0];
var pos;
const I16 = 16;
const I63 = 63;
const I64 = 64;
var DU_DCT = fDCTQuant(CDU, fdtbl);
//ZigZag reorder
for (var j=0;j<I64;++j) {
DU[ZigZag[j]]=DU_DCT[j];
}
var Diff = DU[0] - DC; DC = DU[0];
//Encode DC
if (Diff==0) {
writeBits(HTDC[0]); // Diff might be 0
} else {
pos = 32767+Diff;
writeBits(HTDC[category[pos]]);
writeBits(bitcode[pos]);
}
//Encode ACs
var end0pos = 63; // was const... which is crazy
for (; (end0pos>0)&&(DU[end0pos]==0); end0pos--) {};
//end0pos = first element in reverse order !=0
if ( end0pos == 0) {
writeBits(EOB);
return DC;
}
var i = 1;
var lng;
while ( i <= end0pos ) {
var startpos = i;
for (; (DU[i]==0) && (i<=end0pos); ++i) {}
var nrzeroes = i-startpos;
if ( nrzeroes >= I16 ) {
lng = nrzeroes>>4;
for (var nrmarker=1; nrmarker <= lng; ++nrmarker)
writeBits(M16zeroes);
nrzeroes = nrzeroes&0xF;
}
pos = 32767+DU[i];
writeBits(HTAC[(nrzeroes<<4)+category[pos]]);
writeBits(bitcode[pos]);
i++;
}
if ( end0pos != I63 ) {
writeBits(EOB);
}
return DC;
}
function initCharLookupTable(){
var sfcc = String.fromCharCode;
for(var i=0; i < 256; i++){ ///// ACHTUNG // 255
clt[i] = sfcc(i);
}
}
this.encode = function(image,quality) // image data object
{
var time_start = new Date().getTime();
if(quality) setQuality(quality);
// Initialize bit writer
byteout = new Array();
bytenew=0;
bytepos=7;
// Add JPEG headers
writeWord(0xFFD8); // SOI
writeAPP0();
writeDQT();
writeSOF0(image.width,image.height);
writeDHT();
writeSOS();
// Encode 8x8 macroblocks
var DCY=0;
var DCU=0;
var DCV=0;
bytenew=0;
bytepos=7;
this.encode.displayName = "_encode_";
var imageData = image.data;
var width = image.width;
var height = image.height;
var quadWidth = width*4;
var tripleWidth = width*3;
var x, y = 0;
var r, g, b;
var start,p, col,row,pos;
while(y < height){
x = 0;
while(x < quadWidth){
start = quadWidth * y + x;
p = start;
col = -1;
row = 0;
for(pos=0; pos < 64; pos++){
row = pos >> 3;// /8
col = ( pos & 7 ) * 4; // %8
p = start + ( row * quadWidth ) + col;
if(y+row >= height){ // padding bottom
p-= (quadWidth*(y+1+row-height));
}
if(x+col >= quadWidth){ // padding right
p-= ((x+col) - quadWidth +4)
}
r = imageData[ p++ ];
g = imageData[ p++ ];
b = imageData[ p++ ];
/* // calculate YUV values dynamically
YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80
UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b));
VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b));
*/
// use lookup table (slightly faster)
YDU[pos] = ((RGB_YUV_TABLE[r] + RGB_YUV_TABLE[(g + 256)>>0] + RGB_YUV_TABLE[(b + 512)>>0]) >> 16)-128;
UDU[pos] = ((RGB_YUV_TABLE[(r + 768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128;
VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128;
}
DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
x+=32;
}
y+=8;
}
////////////////////////////////////////////////////////////////
// Do the bit alignment of the EOI marker
if ( bytepos >= 0 ) {
var fillbits = [];
fillbits[1] = bytepos+1;
fillbits[0] = (1<<(bytepos+1))-1;
writeBits(fillbits);
}
writeWord(0xFFD9); //EOI
var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join(''));
byteout = [];
// benchmarking
var duration = new Date().getTime() - time_start;
console.log('Encoding time: '+ duration + 'ms');
//
return jpegDataUri
}
function setQuality(quality){
if (quality <= 0) {
quality = 1;
}
if (quality > 100) {
quality = 100;
}
if(currentQuality == quality) return // don't recalc if unchanged
var sf = 0;
if (quality < 50) {
sf = Math.floor(5000 / quality);
} else {
sf = Math.floor(200 - quality*2);
}
initQuantTables(sf);
currentQuality = quality;
console.log('Quality set to: '+quality +'%');
}
function init(){
var time_start = new Date().getTime();
if(!quality) quality = 50;
// Create tables
initCharLookupTable()
initHuffmanTbl();
initCategoryNumber();
initRGBYUVTable();
setQuality(quality);
var duration = new Date().getTime() - time_start;
console.log('Initialization '+ duration + 'ms');
}
init();
};
// helper function to get the imageData of an existing image on the current page.
function getImageDataFromImage(idOrElement){
var theImg = (typeof(idOrElement)=='string')? document.getElementById(idOrElement):idOrElement;
var cvs = document.createElement('canvas');
cvs.width = theImg.width;
cvs.height = theImg.height;
var ctx = cvs.getContext("2d");
ctx.drawImage(theImg,0,0);
return (ctx.getImageData(0, 0, cvs.width, cvs.height));
}
/*
function init(qu){
var theImg = document.getElementById('testimage');
var cvs = document.createElement('canvas');
cvs.width = theImg.width;
cvs.height = theImg.height;
//document.body.appendChild(cvs);
var ctx = cvs.getContext("2d");
ctx.drawImage(theImg,0,0);
var theImgData = (ctx.getImageData(0, 0, cvs.width, cvs.height));
var jpegURI = encoder.encode(theImgData,qu);
var img = document.createElement('img');
img.src = jpegURI;
document.body.appendChild(img);
}
*/

5
jquery-1.9.1.min.js vendored

File diff suppressed because one or more lines are too long

12
jquery.animate-colors-min.js vendored

@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
/*
Color animation 1.6.0
http://www.bitstorm.org/jquery/color-animation/
Copyright 2011, 2013 Edwin Martin <edwin@bitstorm.org>
Released under the MIT and GPL licenses.
*/
'use strict';(function(d){function h(a,b,e){var c="rgb"+(d.support.rgba?"a":"")+"("+parseInt(a[0]+e*(b[0]-a[0]),10)+","+parseInt(a[1]+e*(b[1]-a[1]),10)+","+parseInt(a[2]+e*(b[2]-a[2]),10);d.support.rgba&&(c+=","+(a&&b?parseFloat(a[3]+e*(b[3]-a[3])):1));return c+")"}function f(a){var b;return(b=/#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})/.exec(a))?[parseInt(b[1],16),parseInt(b[2],16),parseInt(b[3],16),1]:(b=/#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/.exec(a))?[17*parseInt(b[1],16),17*parseInt(b[2],
16),17*parseInt(b[3],16),1]:(b=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(a))?[parseInt(b[1]),parseInt(b[2]),parseInt(b[3]),1]:(b=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9\.]*)\s*\)/.exec(a))?[parseInt(b[1],10),parseInt(b[2],10),parseInt(b[3],10),parseFloat(b[4])]:l[a]}d.extend(!0,d,{support:{rgba:function(){var a=d("script:first"),b=a.css("color"),e=!1;if(/^rgba/.test(b))e=!0;else try{e=b!=a.css("color","rgba(0, 0, 0, 0.5)").css("color"),
a.css("color",b)}catch(c){}return e}()}});var k="color backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor outlineColor".split(" ");d.each(k,function(a,b){d.Tween.propHooks[b]={get:function(a){return d(a.elem).css(b)},set:function(a){var c=a.elem.style,g=f(d(a.elem).css(b)),m=f(a.end);a.run=function(a){c[b]=h(g,m,a)}}}});d.Tween.propHooks.borderColor={set:function(a){var b=a.elem.style,e=[],c=k.slice(2,6);d.each(c,function(b,c){e[c]=f(d(a.elem).css(c))});var g=f(a.end);
a.run=function(a){d.each(c,function(d,c){b[c]=h(e[c],g,a)})}}};var l={aqua:[0,255,255,1],azure:[240,255,255,1],beige:[245,245,220,1],black:[0,0,0,1],blue:[0,0,255,1],brown:[165,42,42,1],cyan:[0,255,255,1],darkblue:[0,0,139,1],darkcyan:[0,139,139,1],darkgrey:[169,169,169,1],darkgreen:[0,100,0,1],darkkhaki:[189,183,107,1],darkmagenta:[139,0,139,1],darkolivegreen:[85,107,47,1],darkorange:[255,140,0,1],darkorchid:[153,50,204,1],darkred:[139,0,0,1],darksalmon:[233,150,122,1],darkviolet:[148,0,211,1],fuchsia:[255,
0,255,1],gold:[255,215,0,1],green:[0,128,0,1],indigo:[75,0,130,1],khaki:[240,230,140,1],lightblue:[173,216,230,1],lightcyan:[224,255,255,1],lightgreen:[144,238,144,1],lightgrey:[211,211,211,1],lightpink:[255,182,193,1],lightyellow:[255,255,224,1],lime:[0,255,0,1],magenta:[255,0,255,1],maroon:[128,0,0,1],navy:[0,0,128,1],olive:[128,128,0,1],orange:[255,165,0,1],pink:[255,192,203,1],purple:[128,0,128,1],violet:[128,0,128,1],red:[255,0,0,1],silver:[192,192,192,1],white:[255,255,255,1],yellow:[255,255,
0,1],transparent:[255,255,255,0]}})(jQuery);

200
jquery.json-2.4.js

@ -0,0 +1,200 @@ @@ -0,0 +1,200 @@
/**
* jQuery JSON plugin 2.4.0
*
* @author Brantley Harris, 2009-2011
* @author Timo Tijhof, 2011-2012
* @source This plugin is heavily influenced by MochiKit's serializeJSON, which is
* copyrighted 2005 by Bob Ippolito.
* @source Brantley Harris wrote this plugin. It is based somewhat on the JSON.org
* website's http://www.json.org/json2.js, which proclaims:
* "NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.", a sentiment that
* I uphold.
* @license MIT License <http://www.opensource.org/licenses/mit-license.php>
*/
(function ($) {
'use strict';
var escape = /["\\\x00-\x1f]/g,
meta = {
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
hasOwn = Object.prototype.hasOwnProperty;
/**
* jQuery.toJSON
* Converts the given argument into a JSON representation.
*
* @param o {Mixed} The json-serializable *thing* to be converted
*
* If an object has a toJSON prototype, that will be used to get the representation.
* Non-integer/string keys are skipped in the object, as are keys that point to a
* function.
*
*/
$.toJSON = typeof JSON === 'object' && JSON.stringify ? JSON.stringify : function (o) {
//$.toJSON = function (o) {
if (o === null) {
return 'null';
}
var pairs, k, name, val,
type = $.type(o);
if (type === 'undefined') {
return undefined;
}
// Also covers instantiated Number and Boolean objects,
// which are typeof 'object' but thanks to $.type, we
// catch them here. I don't know whether it is right
// or wrong that instantiated primitives are not
// exported to JSON as an {"object":..}.
// We choose this path because that's what the browsers did.
if (type === 'number' || type === 'boolean') {
return String(o);
}
if (type === 'string') {
return $.quoteString(o);
}
if (typeof o.toJSON === 'function') {
return $.toJSON(o.toJSON());
}
if (type === 'date') {
var month = o.getUTCMonth() + 1,
day = o.getUTCDate(),
year = o.getUTCFullYear(),
hours = o.getUTCHours(),
minutes = o.getUTCMinutes(),
seconds = o.getUTCSeconds(),
milli = o.getUTCMilliseconds();
if (month < 10) {
month = '0' + month;
}
if (day < 10) {
day = '0' + day;
}
if (hours < 10) {
hours = '0' + hours;
}
if (minutes < 10) {
minutes = '0' + minutes;
}
if (seconds < 10) {
seconds = '0' + seconds;
}
if (milli < 100) {
milli = '0' + milli;
}
if (milli < 10) {
milli = '0' + milli;
}
return '"' + year + '-' + month + '-' + day + 'T' +
hours + ':' + minutes + ':' + seconds +
'.' + milli + 'Z"';
}
pairs = [];
if ($.isArray(o)) {
for (k = 0; k < o.length; k++) {
pairs.push($.toJSON(o[k]) || 'null');
}
return '[' + pairs.join(',') + ']';
}
// Any other object (plain object, RegExp, ..)
// Need to do typeof instead of $.type, because we also
// want to catch non-plain objects.
if (typeof o === 'object') {
for (k in o) {
// Only include own properties,
// Filter out inherited prototypes
if (hasOwn.call(o, k)) {
// Keys must be numerical or string. Skip others
type = typeof k;
if (type === 'number') {
name = '"' + k + '"';
} else if (type === 'string') {
name = $.quoteString(k);
} else {
continue;
}
type = typeof o[k];
// Invalid values like these return undefined
// from toJSON, however those object members
// shouldn't be included in the JSON string at all.
if (type !== 'function' && type !== 'undefined') {
val = $.toJSON(o[k]);
pairs.push(name + ':' + val);
}
}
}
return '{' + pairs.join(',') + '}';
}
};
/**
* jQuery.evalJSON
* Evaluates a given json string.
*
* @param str {String}
*/
$.evalJSON = typeof JSON === 'object' && JSON.parse ? JSON.parse : function (str) {
/*jshint evil: true */
return eval('(' + str + ')');
};
/**
* jQuery.secureEvalJSON
* Evals JSON in a way that is *more* secure.
*
* @param str {String}
*/
$.secureEvalJSON = typeof JSON === 'object' && JSON.parse ? JSON.parse : function (str) {
var filtered =
str
.replace(/\\["\\\/bfnrtu]/g, '@')
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
.replace(/(?:^|:|,)(?:\s*\[)+/g, '');
if (/^[\],:{}\s]*$/.test(filtered)) {
/*jshint evil: true */
return eval('(' + str + ')');
}
throw new SyntaxError('Error parsing JSON, source is not valid.');
};
/**
* jQuery.quoteString
* Returns a string-repr of a string, escaping quotes intelligently.
* Mostly a support function for toJSON.
* Examples:
* >>> jQuery.quoteString('apple')
* "apple"
*
* >>> jQuery.quoteString('"Where are we going?", she asked.')
* "\"Where are we going?\", she asked."
*/
$.quoteString = function (str) {
if (str.match(escape)) {
return '"' + str.replace(escape, function (a) {
var c = meta[a];
if (typeof c === 'string') {
return c;
}
c = a.charCodeAt();
return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
}) + '"';
}
return '"' + str + '"';
};
}(jQuery));

440
jquery.jsonrpcclient.js

@ -0,0 +1,440 @@ @@ -0,0 +1,440 @@
/**
* This plugin requires jquery.json.js to be available, or at least the methods $.toJSON and
* $.parseJSON.
*
* The plan is to make use of websockets if they are available, but work just as well with only
* http if not.
*
* Usage example:
*
* var foo = new $.JsonRpcClient({ ajaxUrl: '/backend/jsonrpc' });
* foo.call(
* 'bar', [ 'A parameter', 'B parameter' ],
* function(result) { alert('Foo bar answered: ' + result.my_answer); },
* function(error) { console.log('There was an error', error); }
* );
*
* More examples are available in README.md
*/
(function($) {
/**
* @fn new
* @memberof $.JsonRpcClient
*
* @param options An object stating the backends:
* ajaxUrl A url (relative or absolute) to a http(s) backend.
* socketUrl A url (relative of absolute) to a ws(s) backend.
* onmessage A socket message handler for other messages (non-responses).
* getSocket A function returning a WebSocket or null.
* It must take an onmessage_cb and bind it to the onmessage event
* (or chain it before/after some other onmessage handler).
* Or, it could return null if no socket is available.
* The returned instance must have readyState <= 1, and if less than 1,
* react to onopen binding.
*/
$.JsonRpcClient = function(options) {
var self = this;
this.options = $.extend({
ajaxUrl : null,
socketUrl : null, ///< The ws-url for default getSocket.
onmessage : null, ///< Other onmessage-handler.
getSocket : function(onmessage_cb) { return self._getSocket(onmessage_cb); }
}, options);
// Declare an instance version of the onmessage callback to wrap 'this'.
this.wsOnMessage = function(event) { self._wsOnMessage(event); };
};
/// Holding the WebSocket on default getsocket.
$.JsonRpcClient.prototype._ws_socket = null;
/// Object <id>: { success_cb: cb, error_cb: cb }
$.JsonRpcClient.prototype._ws_callbacks = {};
/// The next JSON-RPC request id.
$.JsonRpcClient.prototype._current_id = 1;
/**
* @fn call
* @memberof $.JsonRpcClient
*
* @param method The method to run on JSON-RPC server.
* @param params The params; an array or object.
* @param success_cb A callback for successful request.
* @param error_cb A callback for error.
*/
$.JsonRpcClient.prototype.call = function(method, params, success_cb, error_cb) {
// Construct the JSON-RPC 2.0 request.
var request = {
jsonrpc : '2.0',
method : method,
params : params,
id : this._current_id++ // Increase the id counter to match request/response
};
// Try making a WebSocket call.
var socket = this.options.getSocket(this.wsOnMessage);
if (socket !== null) {
this._wsCall(socket, request, success_cb, error_cb);
return;
}
// No WebSocket, and no HTTP backend? This won't work.
if (this.options.ajaxUrl === null) {
throw "$.JsonRpcClient.call used with no websocket and no http endpoint.";
}
var options = this.options;
$.ajax({
type : 'POST',
url : this.options.ajaxUrl,
data : $.toJSON(request),
dataType : 'json',
contentType: "application/json; charset=utf-8",
cache : false,
beforeSend: function (xhr) {
if( options.username != null && options.username != undefined ) {
xhr.setRequestHeader('Authorization', "Basic " + btoa(options.username + ":" + options.password));
}
},
success : function(data) {
if ('error' in data) error_cb(data.error);
success_cb(data.result);
},
// JSON-RPC Server could return non-200 on error
error : function(jqXHR, textStatus, errorThrown) {
try {
var response = $.parseJSON(jqXHR.responseText);
if ('console' in window) console.log(response);
error_cb(response.error);
}
catch (err) {
// Perhaps the responseText wasn't really a jsonrpc-error.
error_cb({ error: jqXHR.responseText });
}
}
});
};
/**
* Notify sends a command to the server that won't need a response. In http, there is probably
* an empty response - that will be dropped, but in ws there should be no response at all.
*
* This is very similar to call, but has no id and no handling of callbacks.
*
* @fn notify
* @memberof $.JsonRpcClient
*
* @param method The method to run on JSON-RPC server.
* @param params The params; an array or object.
*/
$.JsonRpcClient.prototype.notify = function(method, params) {
// Construct the JSON-RPC 2.0 request.
var request = {
jsonrpc: '2.0',
method: method,
params: params
};
// Try making a WebSocket call.
var socket = this.options.getSocket(this.wsOnMessage);
if (socket !== null) {
this._wsCall(socket, request);
return;
}
// No WebSocket, and no HTTP backend? This won't work.
if (this.options.ajaxUrl === null) {
throw "$.JsonRpcClient.notify used with no websocket and no http endpoint.";
}
$.ajax({
type : 'POST',
url : this.options.ajaxUrl,
data : $.toJSON(request),
dataType : 'json',
contentType: "application/json; charset=utf-8",
cache : false,
beforeSend: function (xhr){
if( options.username != null && options.username != undefined ) {
xhr.setRequestHeader('Authorization', "Basic " + btoa(options.username + ":" + options.password));
}
},
});
};
/**
* Make a batch-call by using a callback.
*
* The callback will get an object "batch" as only argument. On batch, you can call the methods
* "call" and "notify" just as if it was a normal $.JsonRpcClient object, and all calls will be
* sent as a batch call then the callback is done.
*
* @fn batch
* @memberof $.JsonRpcClient
*
* @param callback The main function which will get a batch handler to run call and notify on.
* @param all_done_cb A callback function to call after all results have been handled.
* @param error_cb A callback function to call if there is an error from the server.
* Note, that batch calls should always get an overall success, and the
* only error
*/
$.JsonRpcClient.prototype.batch = function(callback, all_done_cb, error_cb) {
var batch = new $.JsonRpcClient._batchObject(this, all_done_cb, error_cb);
callback(batch);
batch._execute();
};
/**
* The default getSocket handler.
*
* @param onmessage_cb The callback to be bound to onmessage events on the socket.
*
* @fn _getSocket
* @memberof $.JsonRpcClient
*/
$.JsonRpcClient.prototype._getSocket = function(onmessage_cb) {
// If there is no ws url set, we don't have a socket.
// Likewise, if there is no window.WebSocket.
if (this.options.socketUrl === null || !("WebSocket" in window)) return null;
if (this._ws_socket === null || this._ws_socket.readyState > 1) {
// No socket, or dying socket, let's get a new one.
this._ws_socket = new WebSocket(this.options.socketUrl);
// Set up onmessage handler.
this._ws_socket.onmessage = onmessage_cb;
}
return this._ws_socket;
};
/**
* Internal handler to dispatch a JRON-RPC request through a websocket.
*
* @fn _wsCall
* @memberof $.JsonRpcClient
*/
$.JsonRpcClient.prototype._wsCall = function(socket, request, success_cb, error_cb) {
var request_json = $.toJSON(request);
if (socket.readyState < 1) {
// The websocket is not open yet; we have to set sending of the message in onopen.
self = this; // In closure below, this is set to the WebSocket. Use self instead.
// Set up sending of message for when the socket is open.
socket.onopen = function() {
// Send the request.
socket.send(request_json);
};
}
else {
// We have a socket and it should be ready to send on.
socket.send(request_json);
}
// Setup callbacks. If there is an id, this is a call and not a notify.
if ('id' in request && typeof success_cb !== 'undefined') {
this._ws_callbacks[request.id] = { success_cb: success_cb, error_cb: error_cb };
}
};
/**
* Internal handler for the websocket messages. It determines if the message is a JSON-RPC
* response, and if so, tries to couple it with a given callback. Otherwise, it falls back to
* given external onmessage-handler, if any.
*
* @param event The websocket onmessage-event.
*/
$.JsonRpcClient.prototype._wsOnMessage = function(event) {
// Check if this could be a JSON RPC message.
try {
var response = $.parseJSON(event.data);
/// @todo Make using the jsonrcp 2.0 check optional, to use this on JSON-RPC 1 backends.
if (typeof response === 'object'
&& 'jsonrpc' in response
&& response.jsonrpc === '2.0') {
/// @todo Handle bad response (without id).
// If this is an object with result, it is a response.
if ('result' in response && this._ws_callbacks[response.id]) {
// Get the success callback.
var success_cb = this._ws_callbacks[response.id].success_cb;
// Delete the callback from the storage.
delete this._ws_callbacks[response.id];
// Run callback with result as parameter.
success_cb(response.result);
return;
}
// If this is an object with error, it is an error response.
else if ('error' in response && this._ws_callbacks[response.id]) {
// Get the error callback.
var error_cb = this._ws_callbacks[response.id].error_cb;
// Delete the callback from the storage.
delete this._ws_callbacks[response.id];
// Run callback with the error object as parameter.
error_cb(response.error);
return;
}
}
}
catch (err) {
// Probably an error while parsing a non json-string as json. All real JSON-RPC cases are
// handled above, and the fallback method is called below.
}
// This is not a JSON-RPC response. Call the fallback message handler, if given.
if (typeof this.options.onmessage === 'function') {
this.options.onmessage(event);
}
};
/************************************************************************************************
* Batch object with methods
************************************************************************************************/
/**
* Handling object for batch calls.
*/
$.JsonRpcClient._batchObject = function(jsonrpcclient, all_done_cb, error_cb) {
// Array of objects to hold the call and notify requests. Each objects will have the request
// object, and unless it is a notify, success_cb and error_cb.
this._requests = [];
this.jsonrpcclient = jsonrpcclient;
this.all_done_cb = all_done_cb;
this.error_cb = typeof error_cb === 'function' ? error_cb : function() {};
};
/**
* @sa $.JsonRpcClient.prototype.call
*/
$.JsonRpcClient._batchObject.prototype.call = function(method, params, success_cb, error_cb) {
this._requests.push({
request : {
jsonrpc : '2.0',
method : method,
params : params,
id : this.jsonrpcclient._current_id++ // Use the client's id series.
},
success_cb : success_cb,
error_cb : error_cb
});
};
/**
* @sa $.JsonRpcClient.prototype.notify
*/
$.JsonRpcClient._batchObject.prototype.notify = function(method, params) {
this._requests.push({
request : {
jsonrpc : '2.0',
method : method,
params : params
}
});
};
/**
* Executes the batched up calls.
*/
$.JsonRpcClient._batchObject.prototype._execute = function() {
var self = this;
if (this._requests.length === 0) return; // All done :P
// Collect all request data and sort handlers by request id.
var batch_request = [];
var handlers = {};
// If we have a WebSocket, just send the requests individually like normal calls.
var socket = self.jsonrpcclient.options.getSocket(self.jsonrpcclient.wsOnMessage);
if (socket !== null) {
for (var i = 0; i < this._requests.length; i++) {
var call = this._requests[i];
var success_cb = ('success_cb' in call) ? call.success_cb : undefined;
var error_cb = ('error_cb' in call) ? call.error_cb : undefined;
this._wsCall(socket, call.request, success_cb, error_cb);
}
if (typeof all_done_cb === 'function') all_done_cb(result);
return;
}
for (var i = 0; i < this._requests.length; i++) {
var call = this._requests[i];
batch_request.push(call.request);
// If the request has an id, it should handle returns (otherwise it's a notify).
if ('id' in call.request) {
handlers[call.request.id] = {
success_cb : call.success_cb,
error_cb : call.error_cb
};
}
}
var success_cb = function(data) { self._batchCb(data, handlers, self.all_done_cb); };
// No WebSocket, and no HTTP backend? This won't work.
if (self.jsonrpcclient.options.ajaxUrl === null) {
throw "$.JsonRpcClient.batch used with no websocket and no http endpoint.";
}
// Send request
$.ajax({
url : self.jsonrpcclient.options.ajaxUrl,
data : $.toJSON(batch_request),
dataType : 'json',
contentType: "application/json; charset=utf-8",
cache : false,
type : 'POST',
// Batch-requests should always return 200
error : function(jqXHR, textStatus, errorThrown) {
self.error_cb(jqXHR, textStatus, errorThrown);
},
success : success_cb
});
};
/**
* Internal helper to match the result array from a batch call to their respective callbacks.
*
* @fn _batchCb
* @memberof $.JsonRpcClient
*/
$.JsonRpcClient._batchObject.prototype._batchCb = function(result, handlers, all_done_cb) {
for (var i = 0; i < result.length; i++) {
var response = result[i];
// Handle error
if ('error' in response) {
if (response.id === null || !(response.id in handlers)) {
// An error on a notify? Just log it to the console.
if ('console' in window) console.log(response);
}
else handlers[response.id].error_cb(response.error);
}
else {
// Here we should always have a correct id and no error.
if (!(response.id in handlers) && 'console' in window) console.log(response);
else handlers[response.id].success_cb(response.result);
}
}
if (typeof all_done_cb === 'function') all_done_cb(result);
};
})(jQuery);

6
jquery.min.js vendored

File diff suppressed because one or more lines are too long

9
jquery.mobile-1.3.2.min.js vendored

File diff suppressed because one or more lines are too long

10
jquery.mobile.router.min.js vendored

File diff suppressed because one or more lines are too long

382
jquery.storageapi.js

@ -0,0 +1,382 @@ @@ -0,0 +1,382 @@
/*
* jQuery Storage API Plugin
*
* Copyright (c) 2013 Julien Maurel
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/mit-license.php
*
* Project home:
* https://github.com/julien-maurel/jQuery-Storage-API
*
* Version: 1.5.0
*
*/
(function($){
// Prefix to use with cookie fallback
var cookie_local_prefix="ls_";
var cookie_session_prefix="ss_";
// Get items from a storage
function _get(storage){
var l=arguments.length,s=window[storage],a=arguments,a1=a[1],vi,ret,tmp;
if(l<2) throw new Error('Minimum 2 arguments must be given');
else if($.isArray(a1)){
// If second argument is an array, return an object with value of storage for each item in this array
ret={};
for(var i in a1){
vi=a1[i];
try{
ret[vi]=JSON.parse(s.getItem(vi));
}catch(e){
ret[vi]=s.getItem(vi);
}
}
return ret;
}else if(l==2){
// If only 2 arguments, return value directly
try{
return JSON.parse(s.getItem(a1));
}catch(e){
return s.getItem(a1);
}
}else{
// If more than 2 arguments, parse storage to retrieve final value to return it
// Get first level
try{
ret=JSON.parse(s.getItem(a1));
}catch(e){
throw new ReferenceError(a1+' is not defined in this storage');
}
// Parse next levels
for(var i=2;i<l-1;i++){
ret=ret[a[i]];
if(ret===undefined) throw new ReferenceError([].slice.call(a,1,i+1).join('.')+' is not defined in this storage');
}
// If last argument is an array, return an object with value for each item in this array
// Else return value normally
if($.isArray(a[i])){
tmp=ret;
ret={};
for(var j in a[i]){
ret[a[i][j]]=tmp[a[i][j]];
}
return ret;
}else{
return ret[a[i]];
}
}
}
// Set items of a storage
function _set(storage){
var l=arguments.length,s=window[storage],a=arguments,a1=a[1],a2=a[2],vi,to_store={},tmp;
if(l<2 || !$.isPlainObject(a1) && l<3) throw new Error('Minimum 3 arguments must be given or second parameter must be an object');
else if($.isPlainObject(a1)){
// If first argument is an object, set values of storage for each property of this object
for(var i in a1){
vi=a1[i];
if(!$.isPlainObject(vi)) s.setItem(i,vi);
else s.setItem(i,JSON.stringify(vi));
}
return a1;
}else if(l==3){
// If only 3 arguments, set value of storage directly
if(typeof a2==='object') s.setItem(a1,JSON.stringify(a2));
else s.setItem(a1,a2);
return a2;
}else{
// If more than 3 arguments, parse storage to retrieve final node and set value
// Get first level
try{
tmp=s.getItem(a1);
if(tmp!=null) {
to_store=JSON.parse(tmp);
}
}catch(e){
}
tmp=to_store;
// Parse next levels and set value
for(var i=2;i<l-2;i++){
vi=a[i];
if(!tmp[vi] || !$.isPlainObject(tmp[vi])) tmp[vi]={};
tmp=tmp[vi];
}
tmp[a[i]]=a[i+1];
s.setItem(a1,JSON.stringify(to_store));
return to_store;
}
}
// Remove items from a storage
function _remove(storage){
var l=arguments.length,s=window[storage],a=arguments,a1=a[1],to_store,tmp;
if(l<2) throw new Error('Minimum 2 arguments must be given');
else if($.isArray(a1)){
// If first argument is an array, remove values from storage for each item of this array
for(var i in a1){
s.removeItem(a1[i]);
}
return true;
}else if(l==2){
// If only 2 arguments, remove value from storage directly
s.removeItem(a1);
return true;
}else{
// If more than 2 arguments, parse storage to retrieve final node and remove value
// Get first level
try{
to_store=tmp=JSON.parse(s.getItem(a1));
}catch(e){
throw new ReferenceError(a1+' is not defined in this storage');
}
// Parse next levels and remove value
for(var i=2;i<l-1;i++){
tmp=tmp[a[i]];
if(tmp===undefined) throw new ReferenceError([].slice.call(a,1,i).join('.')+' is not defined in this storage');
}
// If last argument is an array,remove value for each item in this array
// Else remove value normally
if($.isArray(a[i])){
for(var j in a[i]){
delete tmp[a[i][j]];
}
}else{
delete tmp[a[i]];
}
s.setItem(a1,JSON.stringify(to_store));
return true;
}
}
// Remove all items from a storage
function _removeAll(storage, reinit_ns){
var keys=_keys(storage);
for(var i in keys){
_remove(storage,keys[i]);
}
// Reinitialize all namespace storages
if(reinit_ns){
for(var i in $.namespaceStorages){
_createNamespace(i);
}
}
}
// Check if items of a storage are empty
function _isEmpty(storage){
var l=arguments.length,a=arguments,s=window[storage],a1=a[1];
if(l==1){
// If only one argument, test if storage is empty
return (_keys(storage).length==0);
}else if($.isArray(a1)){
// If first argument is an array, test each item of this array and return true only if all items are empty
for(var i=0; i<a1.length;i++){
if(!_isEmpty(storage,a1[i])) return false;
}
return true;
}else{
// If more than 1 argument, try to get value and test it
try{
var v=_get.apply(this, arguments);
// Convert result to an object (if last argument is an array, _get return already an object) and test each item
if(!$.isArray(a[l-1])) v={'totest':v};
for(var i in v){
if(!(
($.isPlainObject(v[i]) && $.isEmptyObject(v[i])) ||
($.isArray(v[i]) && !v[i].length) ||
(!v[i])
)) return false;
}
return true;
}catch(e){
return true;
}
}
}
// Check if items of a storage exist
function _isSet(storage){
var l=arguments.length,a=arguments,s=window[storage],a1=a[1];
if(l<2) throw new Error('Minimum 2 arguments must be given');
if($.isArray(a1)){
// If first argument is an array, test each item of this array and return true only if all items exist
for(var i=0; i<a1.length;i++){
if(!_isSet(storage,a1[i])) return false;
}
return true;
}else{
// For other case, try to get value and test it
try{
var v=_get.apply(this, arguments);
// Convert result to an object (if last argument is an array, _get return already an object) and test each item
if(!$.isArray(a[l-1])) v={'totest':v};
for(var i in v){
if(!(v[i]!==undefined && v[i]!==null)) return false;
}
return true;
}catch(e){
return false;
}
}
}
// Get keys of a storage or of an item of the storage
function _keys(storage){
var l=arguments.length,s=window[storage],a=arguments,a1=a[1],keys=[],o={};
// If more than 1 argument, get value from storage to retrieve keys
// Else, use storage to retrieve keys
if(l>1){
o=_get.apply(this,a);
}else{
o=s;
}
if(o._cookie){
// If storage is a cookie, use $.cookie to retrieve keys
for(var key in $.cookie()){
if(key!='') {
keys.push(key.replace(o._prefix,''));
}
}
}else{
for(var i in o){
keys.push(i);
}
}
return keys;
}
// Create new namespace storage
function _createNamespace(name){
if(!name || typeof name!="string") throw new Error('First parameter must be a string');
if(!window.localStorage.getItem(name)) window.localStorage.setItem(name,'{}');
if(!window.sessionStorage.getItem(name)) window.sessionStorage.setItem(name,'{}');
var ns={
localStorage:$.extend({},$.localStorage,{_ns:name}),
sessionStorage:$.extend({},$.sessionStorage,{_ns:name})
};
if($.cookie){
if(!window.cookieStorage.getItem(name)) window.cookieStorage.setItem(name,'{}');
ns.cookieStorage=$.extend({},$.cookieStorage,{_ns:name});
}
$.namespaceStorages[name]=ns;
return ns;
}
// Namespace object
var storage={
_type:'',
_ns:'',
_callMethod:function(f,a){
var p=[this._type];
if(this._ns) p.push(this._ns);
[].push.apply(p,a);
return f.apply(this,p);
},
// Get items. If no parameters and storage have a namespace, return all namespace
get:function(){
return this._callMethod(_get,arguments);
},
// Set items
set:function(){
var l=arguments.length,a=arguments,a0=a[0];
if(l<1 || !$.isPlainObject(a0) && l<2) throw new Error('Minimum 2 arguments must be given or first parameter must be an object');
// If first argument is an object and storage is a namespace storage, set values individually
if($.isPlainObject(a0) && this._ns){
for(var i in a0){
_set(this._type,this._ns,i,a0[i]);
}
return a0;
}else{
r=this._callMethod(_set,a);
if(this._ns) return r[a0];
else return r;
}
},
// Delete items
remove:function(){
if(arguments.length<1) throw new Error('Minimum 1 argument must be given');
return this._callMethod(_remove,arguments);
},
// Delete all items
removeAll:function(reinit_ns){
if(this._ns){
_set(this._type,this._ns,{});
return true;
}else{
return _removeAll(this._type, reinit_ns);
}
},
// Items empty
isEmpty:function(){
return this._callMethod(_isEmpty,arguments);
},
// Items exists
isSet:function(){
if(arguments.length<1) throw new Error('Minimum 1 argument must be given');
return this._callMethod(_isSet,arguments);
},
// Get keys of items
keys:function(){
return this._callMethod(_keys,arguments);
}
};
// Use jquery.cookie for compatibility with old browsers and give access to cookieStorage
if($.cookie){
// sessionStorage is valid for one window/tab. To simulate that with cookie, we set a name for the window and use it for the name of the cookie
if(!window.name) window.name=Math.floor(Math.random()*100000000);
var cookie_storage={
_cookie:true,
_prefix:'',
_expires:null,
setItem:function(n,v){
$.cookie(this._prefix+n,v,{expires:this._expires});
},
getItem:function(n){
return $.cookie(this._prefix+n);
},
removeItem:function(n){
return $.removeCookie(this._prefix+n);
},
clear:function(){
for(var key in $.cookie()){
if(key!=''){
if(!this._prefix && key.indexOf(cookie_local_prefix)===-1 && key.indexOf(cookie_session_prefix)===-1 || this._prefix && key.indexOf(this._prefix)===0) {
$.removeCookie(key);
}
}
}
},
setExpires:function(e){
this._expires=e;
return this;
}
};
if(!window.localStorage){
window.localStorage=$.extend({},cookie_storage,{_prefix:cookie_local_prefix,_expires:365*10});
window.sessionStorage=$.extend({},cookie_storage,{_prefix:cookie_session_prefix+window.name+'_'});
}
window.cookieStorage=$.extend({},cookie_storage);
// cookieStorage API
$.cookieStorage=$.extend({},storage,{_type:'cookieStorage',setExpires:function(e){window.cookieStorage.setExpires(e); return this;}});
}
// Get a new API on a namespace
$.initNamespaceStorage=function(ns){ return _createNamespace(ns); };
// localStorage API
$.localStorage=$.extend({},storage,{_type:'localStorage'});
// sessionStorage API
$.sessionStorage=$.extend({},storage,{_type:'sessionStorage'});
// List of all namespace storage
$.namespaceStorages={};
// Remove all items in all storages
$.removeAllStorages=function(reinit_ns){
$.localStorage.removeAll(reinit_ns);
$.sessionStorage.removeAll(reinit_ns);
if($.cookieStorage) $.cookieStorage.removeAll(reinit_ns);
if(!reinit_ns){
$.namespaceStorages={};
}
}
})(jQuery);

170
login.html

@ -0,0 +1,170 @@ @@ -0,0 +1,170 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>twister login</title>
<link rel="stylesheet" href="css/style.css" type="text/css"/>
<script src="jquery.min.js"></script>
<script src="jQueryPlugins.js"></script>
<script src="jquery.json-2.4.js"></script>
<script src="jquery.jsonrpcclient.js"></script>
<script src="jquery.storageapi.js"></script>
<script src="mobile_abstract.js"></script>
<script src="twister_io.js"></script>
<script src="twister_network.js"></script>
<script src="twister_user.js"></script>
<script src="interface_common.js"></script>
<script src="interface_login.js"></script>
<script src="polyglot.min.js"></script>
<script src="interface_localization.js"></script>
<script>
$(document).ready(function()
{
initInterfaceLogin();
});
</script>
<link rel="icon" type="image/png" href="img/twister_mini.png" />
</head>
<body>
<!-- MENU SUPERIOR INIT -->
<nav class="userMenu">
<ul>
<li class="userMenu-home"><a href="home.html"></a></li>
<!-- <li class="userMenu-network"><a href="network.html">Network</a></li> -->
<li class="userMenu-profile current"><a href="login.html">Login</a></li>
<li class="userMenu-config">
<a class="userMenu-config-dropdown" href="#">
<div class="config-menu dialog-modal">
<!-- <div class="mini-profile-info">
<div class="mini-profile-photo"><img src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/></div>
<a href="#" class="mini-profile-name">Fulano da Silva</a>
<span class="mini-profile-view">View</span>
</div> -->
<a class="dropdown-menu-item" href="profile-edit.html">Setup account</a>
<a class="dropdown-menu-item" href="following.html">Following users</a>
<a class="dropdown-menu-item" href="network.html">Network config</a>
<a class="dropdown-menu-item" href="login.html">Change user</a>
<!-- <a class="dropdown-menu-item promoted-posts-only" href="#">Switch to Promoted posts</a>
<a class="direct-messages dropdown-menu-item" href="#">Direct Messages</a> -->
</div>
</a>
</li>
</ul>
</nav>
<!-- MENU SUPERIOR END -->
<div class="wrapper">
<!-- LADO ESQUERDO DE MÓDULOS INIT -->
<div class="login">
<!-- <h2> twister login </h2> -->
<div class="module">
<p> Existing local users </p>
<div>
<select class="local-usernames login-user">
</select>
<button class="login-local-username">Login</button>
</div>
</div>
<!-- <span> Or... </span> -->
<div class="module">
<p> Create a new user </p>
<div>
<input class="new-username" type="textbox" placeholder="Type nickname here" cols="16" rows="1"></input>
<button class="check-availability">Check availability</button>
<span class="availability"></span>
</div>
<div>
<button class="create-user disabled" disable="true">Create this nickname</button>
</div>
</div>
<!-- <span> Or... </span> -->
<div class="module">
<div>
<p> Import secret key </p>
<input class="secret-key-import" type="textbox" placeholder="52-characters secret" size="52" rows="1"></input>
</div>
<div>
<p class="with-nickname"> With nickname </p>
<input class="username-import" type="textbox" placeholder="Type nickname here" size="16" rows="1"></input>
</div>
<div>
<button class="import-secret-key disabled" disable="true">Import key</button>
</div>
</div>
</div>
<!-- LADO ESQUERDO DE MÓDULOS END -->
</div>
<!-- TEMPLATES INIT -->
<div id="templates" style="display:none;">
<!-- TEMPLATE DO MODAL GENÉRICO INIT -->
<div class="modal-blackout">
<div class="modal-wrapper">
<div class="modal-header">
<h3></h3>
<span id="closeModal" class="modal-close cancel">&times;</span>
</div>
<div class="modal-content"></div>
<div class="modal-buttons">
<button class="modal-cancel cancel">Cancel</button>
<button class="modal-propagate">Retransmit</button>
</div>
</div>
</div>
<!-- TEMPLATE DO MODAL GENÉRICO END -->
<!-- MODAL DE NEW USER INIT -->
<div id="new-user-modal-template">
<div class="warning">
<div class="text">
A new user was created and it is being propagated to the network.
Please do not close this window, this might take a few minutes.
</div>
<div class="text emphasis">
Your secret key is: <span class="secret-key"></span>
</div>
<div class="text">
It is highly recommended that you take this time to save your secret key.
Print it, do a screenshot, use the camera in your phone or write it down
to a piece of paper.
</div>
<div class="text">
The secret key will be needed to use this account from different computers.
If you ever lose your secret key (remember: this is alpha software and it may
crash, causing loss of data) your nickname will be locked forever.
There is no way to recover a lost key for twister accounts.
</div>
<div class="text"></div>
<div class="text">
Please wait. The 'OK' button will be shown when process completes.
</div>
<div class="text">
<button class="login-created-user" style="display:none;">OK</button>
</div>
</div>
</div>
</div>
<!-- TEMPLATES END -->
</body>
</html>

356
mobile_abstract.js

@ -0,0 +1,356 @@ @@ -0,0 +1,356 @@
// mobile_abstract.js
// 2013 Miguel Freitas
//
// Mobile Abstration Layer
// Try to sort out lowlevel differences between jquery and jquery mobile twister interfaces
var MAL = function()
{
this.postboardLoading = function() {
if( $.hasOwnProperty("mobile") ) {
$.mobile.showPageLoadingMsg();
/*
setTimeout(function(){
$.mobile.hidePageLoadingMsg();
}, 10 * 1000);
*/
} else {
$(".postboard-loading").show();
}
}
this.postboardLoaded = function() {
if( $.hasOwnProperty("mobile") ) {
$.mobile.hidePageLoadingMsg();
var curPage = $.mobile.activePage.attr("id");
$( '#'+curPage+' .content ul.posts').listview('refresh');
installPostboardClick();
} else {
$(".postboard-loading").hide();
}
}
this.dmThreadListLoaded = function() {
if( $.hasOwnProperty("mobile") ) {
$.mobile.hidePageLoadingMsg();
var $dmThreadList = $("#directmsg ul.direct-messages-thread");
$dmThreadList.listview('refresh');
} else {
}
}
this.dmChatListLoaded = function(dmConvo) {
if( $.hasOwnProperty("mobile") ) {
$.mobile.hidePageLoadingMsg();
var $dmChatList = $("#dmchat ul.direct-messages-list");
$dmChatList.listview('refresh');
$.mobile.silentScroll( $(".dm-form").offset().top );
} else {
var modalContent = dmConvo.parents(".modal-content");
modalContent.scrollTop(modalContent[0].scrollHeight);
}
}
this.relatedPostLoaded = function() {
if( $.hasOwnProperty("mobile") ) {
$.mobile.hidePageLoadingMsg();
var curPage = $.mobile.activePage.attr("id");
$( '#'+curPage+' .content ul.posts').listview('refresh');
} else {
$(".postboard-loading").hide();
}
}
this.followingListLoaded = function() {
if( $.hasOwnProperty("mobile") ) {
$.mobile.hidePageLoadingMsg();
$(".following-list").listview('refresh');
} else {
$(".postboard-loading").hide();
}
}
this.searchUserListLoaded = function() {
if( $.hasOwnProperty("mobile") ) {
$.mobile.hidePageLoadingMsg();
$(".userMenu-search-profiles").listview('refresh');
} else {
var $searchResultsModal = $( ".search-results" );
$searchResultsModal.slideDown( "fast" );
}
}
this.reportNewPosts = function(newPosts) {
if( $.hasOwnProperty("mobile") ) {
var newTweetsBar = $(".timeline-refresh .ui-btn-text");
if( newPosts ) {
document.title = "(" + String(newPosts) + ") twister";
newTweetsBar.text("(" + String(newPosts) + ")");
} else {
document.title = "twister";
newTweetsBar.text("Refresh");
}
} else {
var newTweetsBar = $(".postboard-news");
if( newPosts ) {
document.title = "(" + String(newPosts) + ") twister";
newTweetsBar.text(String(newPosts) + " new posts");
newTweetsBar.fadeIn("slow");
} else {
newTweetsBar.hide();
document.title = "twister";
}
}
}
this.getStreamPostsParent = function() {
if( $.hasOwnProperty("mobile") ) {
return $( '.timeline ul');
} else {
return $("#posts");
}
}
this.setPostTemplate = function(p) {
if( $.hasOwnProperty("mobile") ) {
this.postTemplate = p;
}
}
this.getPostTemplate = function() {
if( $.hasOwnProperty("mobile") ) {
return this.postTemplate;
} else {
return $("#post-template");
}
}
// how do we map usernames into urls
this.userUrl = function(username) {
if( $.hasOwnProperty("mobile") ) {
return "#profile?user=" + username;
} else {
return "#profile?user=" + username;
}
}
// recover username from url (only for hash)
this.urlToUser = function(url) {
var dummyUrl = this.userUrl("");
var urlIdx = url.indexOf(dummyUrl);
if( urlIdx >= 0 )
return url.substr(urlIdx + dummyUrl.length);
else
return "";
}
this.followingUrlToUser = function(url) {
var dummyUrl = this.followingUrl("");
var urlIdx = url.indexOf(dummyUrl);
if( urlIdx >= 0 )
return url.substr(urlIdx + dummyUrl.length);
else
return "";
}
this.mentionsUrl = function(username) {
if( $.hasOwnProperty("mobile") ) {
return "#mentions?user=" + username;
} else {
return "#mentions?user=" + username;
}
}
this.hashtagUrl = function(h) {
if( $.hasOwnProperty("mobile") ) {
return "#hashtag?hashtag=" + h;
} else {
return "#hashtag?hashtag=" + h;
}
}
this.dmchatUrl = function(username) {
if( $.hasOwnProperty("mobile") ) {
return "#dmchat?user=" + username;
} else {
return "#dmchat?user=" + username;
}
}
this.followingUrl = function(username) {
if( $.hasOwnProperty("mobile") ) {
return "#following?user=" + username;
} else {
return "#following?user=" + username;
}
}
this.followUrl = function(username) {
if( $.hasOwnProperty("mobile") ) {
return "#following?follow=" + username;
} else {
return "#following?follow=" + username;
}
}
this.unfollowUrl = function(username) {
if( $.hasOwnProperty("mobile") ) {
return "#following?unfollow=" + username;
} else {
return "#following?unfollow=" + username;
}
}
this.newPostToUrl = function(username) {
if( $.hasOwnProperty("mobile") ) {
return "#newmsg?replyto=" + encodeURIComponent("@"+username);
} else {
return "#newmsg?replyto=" + encodeURIComponent("@"+username);
}
}
this.newPostHashtagToUrl = function(hashtag) {
if( $.hasOwnProperty("mobile") ) {
return "#newmsg?replyto=" + encodeURIComponent("#"+hashtag);
} else {
return "#newmsg?replyto=" + encodeURIComponent("#"+hashtag);
}
}
this.updateNewMentionsUI = function(newMentions) {
if( $.hasOwnProperty("mobile") ) {
var $mentionsCounterBtnText = $(".mentions-count .ui-btn-text");
if( newMentions ) {
$mentionsCounterBtnText.text("@ (" + newMentions + ")");
} else {
$mentionsCounterBtnText.text("@");
}
} else {
var $mentionsCounter = $(".userMenu-connections .messages-qtd");
if( newMentions ) {
$mentionsCounter.text(newMentions);
$mentionsCounter.fadeIn();
} else {
$mentionsCounter.hide();
}
}
}
this.updateNewDMsUI = function(newDMs) {
if( $.hasOwnProperty("mobile") ) {
var $DMsCounterAllPages = $(".newdms-count");
for( var i = 0; i < $DMsCounterAllPages.length; i++) {
var $textElement = $DMsCounterAllPages.eq(i).find(".ui-btn-text");
if( !$textElement.length ) $textElement = $DMsCounterAllPages.eq(i);
if( newDMs ) {
$textElement.text("Direct Msg (" + newDMs + ")");
} else {
$textElement.text("Direct Msg");
}
}
} else {
var $DMsCounter = $(".userMenu-messages .messages-qtd");
if( newDMs ) {
$DMsCounter.text(newDMs);
$DMsCounter.fadeIn();
} else {
$DMsCounter.hide();
}
}
}
this.updateMyOwnPostCount = function(numPosts) {
if( $.hasOwnProperty("mobile") ) {
console.log("FIXME: implement MAL_updateMyOwnPostCount");
} else {
$(".mini-profile .posts-count").text(numPosts);
}
}
this.goHome = function(clearTimeline) {
if( $.hasOwnProperty("mobile") ) {
$.mobile.navigate( "#home" );
} else {
window.location.href = "home.html";
}
}
this.goLogin = function() {
if( $.hasOwnProperty("mobile") ) {
$.mobile.navigate( "#login" );
} else {
window.location.href = "login.html";
}
}
this.goNetwork = function() {
if( $.hasOwnProperty("mobile") ) {
$.mobile.navigate( "#network" );
} else {
window.location.href = "network.html";
}
}
this.goProfileEdit = function() {
if( $.hasOwnProperty("mobile") ) {
$.mobile.navigate( "#profile-edit" );
} else {
window.location.href = "profile-edit.html";
}
}
this.setNetworkStatusMsg = function(msg, statusGood) {
if( $.hasOwnProperty("mobile") ) {
$(".network-status").text(msg);
if( statusGood ) {
$(".connection-status").addClass("connected");
} else {
$(".connection-status").removeClass("connected");
}
} else {
$(".network-status").text(msg);
if( statusGood ) {
$(".connection-status").addClass("connected");
} else {
$(".connection-status").removeClass("connected");
}
}
}
this.enableButton = function( $button ) {
if( $.hasOwnProperty("mobile") ) {
$button.button("enable");
} else {
$button.removeClass("disabled");
$button.removeAttr("disabled");
}
}
this.disableButton = function( $button ) {
if( $.hasOwnProperty("mobile") ) {
$button.button("disable");
} else {
$button.addClass("disabled");
$button.attr("disabled","true");
}
}
this.changedUser = function() {
if( $.hasOwnProperty("mobile") ) {
timelineChangedUser();
$("#home .posts").html("");
newmsgsChangedUser();
followingChangedUser();
twisterInitialized = false;
} else {
window.location.href = "home.html";
}
}
}
jQuery.MAL = new MAL;

185
network.html

@ -0,0 +1,185 @@ @@ -0,0 +1,185 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Network status</title>
<link rel="stylesheet" href="css/style.css" type="text/css"/>
<script src="jquery.min.js"></script>
<script src="jQueryPlugins.js"></script>
<script src="jquery.json-2.4.js"></script>
<script src="jquery.jsonrpcclient.js"></script>
<script src="jquery.storageapi.js"></script>
<script src="mobile_abstract.js"></script>
<script src="twister_io.js"></script>
<script src="twister_user.js"></script>
<script src="twister_formatpost.js"></script>
<script src="twister_following.js"></script>
<script src="twister_newmsgs.js"></script>
<script src="polyglot.min.js"></script>
<script src="interface_localization.js"></script>
<script src="twister_network.js"></script>
<script src="interface_common.js"></script>
<script>
$(document).ready(function()
{
initInterfaceNetwork();
});
</script>
<link rel="icon" type="image/png" href="img/twister_mini.png" />
</head>
<body>
<!-- MENU SUPERIOR INIT -->
<nav class="userMenu">
<ul>
<li class="userMenu-home"><a href="home.html"></a></li>
<li class="userMenu-network current"><a href="network.html">Network</a></li>
<!-- <li class="userMenu-profile"><a href="profile-edit.html">Profile</a></li> -->
<li class="userMenu-config">
<a class="userMenu-config-dropdown" href="#">
<div class="config-menu dialog-modal">
<!-- <div class="mini-profile-info">
<div class="mini-profile-photo"><img src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/></div>
<a href="#" class="mini-profile-name">Fulano da Silva</a>
<span class="mini-profile-view">View</span>
</div> -->
<a class="dropdown-menu-item" href="profile-edit.html">Setup account</a>
<a class="dropdown-menu-item" href="following.html">Following users</a>
<a class="dropdown-menu-item" href="network.html">Network config</a>
<a class="dropdown-menu-item" href="login.html">Change user</a>
<!-- <a class="direct-messages" href="#">Direct Messages</a> -->
</div>
</a>
</li>
<!-- <li class="userMenu-connections">
<a href="#">
<span class="messages-qtd" style="display:none;">12</span>
</a>
</li>
<li class="userMenu-messages">
<a href="#">
<span class="messages-qtd" style="display:none;">12</span>
</a>
</li>
<li class="userMenu-search">
<input type="text" class="userMenu-search-field" placeholder="search"/>
</li>-->
</ul>
</nav>
<!-- MENU SUPERIOR END -->
<div class="wrapper">
<div class="network singleBlock">
<h2> Network status </h2>
<div class="module">
<h3> General information</h3>
<ul>
<li>
<span class="network-status highlight connection-status">Updating status...</span>
</li>
<li>
<label>Client Version: </label>
<span class="version">00.00.00.00</label>
</li>
<li>
<label>Terminate Daemon:</label>
<button class="terminate-daemon">Exit</button>
</li>
</ul>
<h3> Detailed information </h3>
<ul>
<li class="connections">
<label>Connections: </label>
<span class="connection-count">0</span>
</li>
<li>
<label>Known peers: </label>
<span class="known-peers">0</span>
</li>
<li>
<label>Active DHT nodes: </label>
<span class="dht-nodes">0</span>
</li>
<li>
<label>Force connection to peer:</label>
<input class="new-peer-addr" type="textbox" placeholder="peer address" size="20"/>
<button class="add-peer disabled" disable="true">Add peer</button>
</li>
<li>
<label>DNS to obtain list of peers:</label>
<input class="new-dns-addr" type="textbox" placeholder="dns address" size="20"/>
<button class="add-dns disabled" disable="true">Add DNS</button>
</li>
</ul>
<h3> Block chain information </h3>
<ul>
<li>
<label>Number of blocks in block chain:</label>
<span class="blocks">2813</span>
</li>
<li>
<label>Time of the last block:</label>
<span class="last-block-time">Fri Nov 08 2013 08:32:48 </span>
</li>
<li>
<label> Mining difficulty: </label>
<span class="mining-difficulty">0.0</span>
</li>
</ul>
</div>
<h2>Configure block generation</h2>
<div class="module">
<h3> Generate blocks (send promoted messages)</h3>
<ul>
<li>
<label> Block generation </label>
<select class="genblock">
<option value="disable">Disable</option>
<option value="enable">Enable</option>
</select>
</li>
<li>
<label> Number of CPUs to use </label>
<input class="genproclimit" type="textbox" value="1" size="3"/>
</li>
<li>
<label> Post to promote: </label>
<textarea class="spam-msg" placeholder="Type message here"></textarea>
</li>
<li>
<label> Send post with username </label>
<select class="local-usernames spam-user">
<option value="nobody">nobody</option>
</select>
<div class="character-limit">
<span class="post-area-remaining">140</span>
<button class="update-spam-msg enabled">Update</button>
</div>
</li>
</ul>
<h3> Block generation status</h3>
<ul>
<li>
<label> Current hash rate: </label>
<span class="mining-hashrate">0</span>
</li>
</ul>
</div>
</div>
</div>
</body>
</html>

17
polyglot.min.js vendored

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
// (c) 2012 Airbnb, Inc.
//
// polyglot.js may be freely distributed under the terms of the BSD
// license. For all licensing information, details, and documention:
// http://airbnb.github.com/polyglot.js
//
//
// Polyglot.js is an I18n helper library written in JavaScript, made to
// work both in the browser and in Node. It provides a simple solution for
// interpolation and pluralization, based off of Airbnb's
// experience adding I18n functionality to its Backbone.js and Node apps.
//
// Polylglot is agnostic to your translation backend. It doesn't perform any
// translation; it simply gives you a way to manage translated phrases from
// your client- or server-side JavaScript application.
//
!function(e){"use strict";function t(e){e=e||{},this.phrases=e.phrases||{},this.currentLocale=e.locale||"en",this.allowMissing=!!e.allowMissing}function s(e){var t,n,r,i={};for(t in e)if(e.hasOwnProperty(t)){n=e[t];for(r in n)i[n[r]]=t}return i}function o(e){var t=/^\s+|\s+$/g;return e.replace(t,"")}function u(e,t,r){var i,s,u;return r!=null&&e?(s=e.split(n),u=s[f(t,r)]||s[0],i=o(u)):i=e,i}function a(e){var t=s(i);return t[e]||t.en}function f(e,t){return r[a(e)](t)}function l(e,t){for(var n in t)n!=="_"&&t.hasOwnProperty(n)&&(e=e.replace(new RegExp("%\\{"+n+"\\}","g"),t[n]));return e}function c(t){e.console&&e.console.warn&&e.console.warn("WARNING: "+t)}function h(e){var t={};for(var n in e)t[n]=e[n];return t}t.VERSION="0.2.0",t.prototype.locale=function(e){return e&&(this.currentLocale=e),this.currentLocale},t.prototype.extend=function(e){for(var t in e)e.hasOwnProperty(t)&&(this.phrases[t]=e[t])},t.prototype.clear=function(){this.phrases={}},t.prototype.replace=function(e){this.clear(),this.extend(e)},t.prototype.t=function(e,t){var n;t=t==null?{}:t,typeof t=="number"&&(t={smart_count:t});var r=this.phrases[e]||t._||(this.allowMissing?e:"");return r===""?(c('Missing translation for key: "'+e+'"'),n=e):(t=h(t),n=u(r,this.currentLocale,t.smart_count),n=l(n,t)),n};var n="||||",r={chinese:function(e){return 0},german:function(e){return e!==1?1:0},french:function(e){return e>1?1:0},russian:function(e){return e%10===1&&e%100!==11?0:e%10>=2&&e%10<=4&&(e%100<10||e%100>=20)?1:2},czech:function(e){return e===1?0:e>=2&&e<=4?1:2},polish:function(e){return e===1?0:e%10>=2&&e%10<=4&&(e%100<10||e%100>=20)?1:2},icelandic:function(e){return e%10!==1||e%100===11?1:0}},i={chinese:["id","ja","ko","ms","th","tr","zh"],german:["da","de","en","es","fi","el","he","hu","it","nl","no","pt","sv"],french:["fr","tl"],russian:["hr","ru"],czech:["cs"],polish:["pl"],icelandic:["is"]};typeof module!="undefined"&&module.exports?module.exports=t:e.Polyglot=t}(this);

105
profile-edit.html

@ -0,0 +1,105 @@ @@ -0,0 +1,105 @@
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>Edit profile</title>
<link rel="stylesheet" href="css/style.css" type="text/css">
<link rel="stylesheet" href="css/profile.css" type="text/css">
<script src="jquery.min.js"></script>
<script src="jQueryPlugins.js"></script>
<script src="jquery.json-2.4.js"></script>
<script src="jquery.jsonrpcclient.js"></script>
<script src="jquery.storageapi.js"></script>
<script src="mobile_abstract.js"></script>
<script src="twister_io.js"></script>
<script src="polyglot.min.js"></script>
<script src="interface_localization.js"></script>
<script src="twister_user.js"></script>
<script src="twister_network.js"></script>
<script src="twister_following.js"></script>
<script src="twister_newmsgs.js"></script>
<script src="interface_common.js"></script>
<script src="interface_profile-edit.js"></script>
<script>
$(document).ready(function()
{
initProfileEdit();
});
</script>
<link rel="icon" type="image/png" href="img/twister_mini.png" />
</head>
<body>
<!-- MENU SUPERIOR INIT -->
<nav class="userMenu">
<ul>
<li class="userMenu-home"><a href="home.html"></a></li>
<!-- <li class="userMenu-network"><a href="network.html">Network</a></li> -->
<li class="userMenu-profile current"><a href="profile-edit.html">Profile</a></li>
<li class="userMenu-config">
<a class="userMenu-config-dropdown" href="#">
<div class="config-menu dialog-modal">
<!-- <div class="mini-profile-info">
<div class="mini-profile-photo"><img src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/></div>
<a href="#" class="mini-profile-name">Fulano da Silva</a>
<span class="mini-profile-view">View</span>
</div> -->
<a class="dropdown-menu-item" href="profile-edit.html">Setup account</a>
<a class="dropdown-menu-item" href="following.html">Following users</a>
<a class="dropdown-menu-item" href="network.html">Network config</a>
<a class="dropdown-menu-item" href="login.html">Change user</a>
<!-- <a class="direct-messages" href="#">Direct Messages</a> -->
</div>
</a>
</li>
<!-- <li class="userMenu-connections">
<a href="#">
<span class="messages-qtd" style="display:none;">12</span>
</a>
</li>
<li class="userMenu-messages">
<a href="#">
<span class="messages-qtd" style="display:none;">12</span>
</a>
</li>
<li class="userMenu-search">
<input type="text" class="userMenu-search-field" placeholder="search">
</li> -->
</ul>
</nav>
<!-- MENU SUPERIOR END -->
<div class="wrapper">
<!-- ÁREA DE PROFILE PHOTO INIT -->
<div class="profile-card forEdition">
<!-- Coloquei a imagem de fundo do card do usuário como background da div abaixo inline na tag para poder ser alterada dinamicamente -->
<div class="profile-card-main">
<img class="profile-card-photo forEdition" src="img/genericPerson.png"/>
<input type="text" class="input-name" placeholder="Full name here"/>
<h2></h2>
<input type="text" class="input-description" placeholder="Describe yourself"/>
<input type="text" class="input-city" placeholder="Location"/>
<input type="text" class="input-website" placeholder="website"/>
</div>
<div class="profile-edition-buttons">
<button class="toggle-priv-key" style="float: left" href="#">Secret Key</button>
<button class="cancel-changes" href="#">Cancel</button>
<button class="submit-changes" href="#">Save Changes</button>
</div>
<div style="font-size:80%;text-align: center;" class="secret-key-container">
<span class="label">Secret key:</span> <span class="secret-key"></span>
</div>
</div>
<!-- ÁREA DE POSTS END -->
<input type="file" id="avatar-file" name="avatar_files" style="display:none;"/>
</body>
</html>

807
tmobile.html

@ -0,0 +1,807 @@ @@ -0,0 +1,807 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<meta charset="utf-8" />
<title>twister mobile</title>
<meta name="viewport" content="width=device-width, user-scalable=no"/>
<link rel="stylesheet" href="css/jquery.mobile-1.3.2.min.css" />
<script src="jquery-1.9.1.min.js"></script>
<script src="jquery.mobile.router.min.js"></script>
<script>
$(document).bind('mobileinit', function () {
$.mobile.allowCrossDomainPages = true;
$.mobile.zoom.enabled = false;
$.mobile.buttonMarkup.hoverDelay = 0; //defaults 200
$.mobile.defaultDialogTransition = 'none';
$.mobile.defaultPageTransition = 'none';
});
</script>
<script src="jquery.mobile-1.3.2.min.js"></script>
<script src="jQueryPlugins.js"></script>
<script src="jquery.json-2.4.js"></script>
<script src="jquery.jsonrpcclient.js"></script>
<script src="jquery.storageapi.js"></script>
<script src="mobile_abstract.js?vr=10"></script>
<script src="twister_io.js?vr=10"></script>
<script src="polyglot.min.js?vr=10"></script>
<script src="interface_localization.js?vr=10"></script>
<script src="twister_network.js?vr=10"></script>
<script src="twister_user.js?vr=10"></script>
<script src="twister_formatpost.js?vr=10"></script>
<script src="twister_following.js?vr=10"></script>
<script src="twister_timeline.js?vr=10"></script>
<script src="twister_newmsgs.js?vr=10"></script>
<script src="twister_actions.js?vr=10"></script>
<script src="twister_directmsg.js?vr=10"></script>
<script src="interface_common.js?vr=10"></script>
<script src="tmobile.js?vr=10"></script>
<script src="jpeg_encoder_basic.js"></script>
<link rel="icon" type="image/png" href="img/twister_mini.png" />
</head>
<body>
<style>
.ui-li-heading { margin: 0em 0 0.6em 0; }
.ui-li-desc { margin: -.5em 0 .6em; text-overflow: ellipsis; overflow: visible; white-space: pre-line; }
.ui-li-thumb, .ui-li-icon { top: 13px; }
.ui-input-search { width: 50% !important;}
.ui-btn-icon-right .ui-icon { position: absolute; top: 30%; margin-top: -9px; }
.no-ellipsis {text-overflow: initial !important; overflow: visible !important;
white-space: normal !important; }
img.avatar {
-webkit-border-radius: 10px;
-mox-border-radius:10px;
border-radius: 10px;
width: 50px; height: 50px;
}
img.size24 {width: 24px; height: 24px;}
#profile-edit img.avatar {display:block; margin-left: auto; margin-right: auto; width: 64px; height: 64px;}
.ui-listview .post a{ color: #0030ff; }
#post li.original { background: #fff; /*top:5px; border-width:5px !important;*/}
#post li.related .post-interactions { display:none; }
#new-user-modal .text { margin: 0 0 15px 0; }
#new-user-modal .emphasis { font-size: 18px; text-align: center; }
#new-user-modal .secret-key { font-weight: bold; word-break:break-all; }
#profile-edit .secret-key { word-break:break-all; }
.header {position:fixed;z-index:10;top:0;width:100%}
.content {padding:15px 20px 20px 20px;}
.footer {position:fixed;z-index:10;bottom:0;width:100%}
}
</style>
<div id="index" data-role="page">
<div class="content" data-role="content" style="">
<div>twister mobile</div>
<a href="#login">login</a>
<a href="#network">network</a>
<a href="#home">home</a>
</div>
</div>
<div id="login" data-role="page">
<div class="myheader" data-role="header" data-position="fixed" data-tap-toggle="false" data-nobackbtn="true">
<h1 class="rtitle">twister login</h1>
</div>
<div class="content" data-role="content" style="">
<div class="login">
<div class="module">
<span> Existing local users </span>
<div class="ui-grid-a">
<div class="ui-block-a">
<select class="local-usernames login-user"></select>
</div>
<div class="ui-block-b">
<button class="login-local-username">Login</button>
</div>
</div>
</div>
<hr/>
<span> Or... </span>
<div class="module">
<span> Create a new user </span>
<div>
<input class="new-username" type="textbox" placeholder="Type nickname here" cols="16" rows="1"></input>
<button class="check-availability">Check availability</button>
<span class="availability"></span>
</div>
<div>
<button class="create-user disabled" disabled="disabled">Create this nickname</button>
</div>
</div>
<hr/>
<span> Or... </span>
<div class="module">
<div>
<span> Import secret key </span>
<input class="secret-key-import" type="textbox" placeholder="52-characters secret" size="52" rows="1"></input>
</div>
<div>
<span> With nickname </span>
<input class="username-import" type="textbox" placeholder="Type nickname here" size="16" rows="1"></input>
</div>
<div>
<button class="import-secret-key disabled" disabled="disabled">Import key</button>
</div>
</div>
</div>
</div>
<div class="footer" data-role="footer" data-position="fixed" data-tap-toggle="false">
<a href="#home" data-role="button">Home</a>
<a href="#network" data-role="button">Network</a>
<a href="#directmsg" data-role="button" class="newdms-count">Direct Msg</a>
<a href="#profile-edit" data-role="button">Profile</a>
</div>
</div>
<div id="home" data-role="page">
<div class="myheader" data-role="header" data-position="fixed" data-tap-toggle="false" data-nobackbtn="true">
<div class="ui-btn-left" data-role="controlgroup" data-type="horizontal">
<a data-role="button" data-icon="refresh" class="timeline-refresh" rel="external">
Refresh
</a>
<a data-role="button" href="#mentions" class="mentions-count">
<!-- <span style="font-weight:normal; font-size: 15px;">@</span> -->
@
</a>
</div>
<h1 class="rtitle"></h1>
<a href="#newmsg" class="ui-btn-right" style="">New post</a>
</div>
<div class="content" data-role="content">
<div class="timeline">
<ul class="posts" data-role="listview">
</ul>
</div>
</div>
<div class="footer" data-role="footer" data-position="fixed" data-tap-toggle="false">
<a href="#login" data-role="button">Login</a>
<a href="#network" data-role="button">Network</a>
<a href="#directmsg" data-role="button" class="newdms-count">Direct Msg</a>
<a href="#profile-edit" data-role="button">Profile</a>
</div>
</div>
<div id="post" data-role="page">
<div class="myheader" data-role="header" data-position="fixed" data-tap-toggle="false" data-nobackbtn="true">
<a href="#search" class="ui-btn-left" style="">Search</a>
<h1 class="rtitle">twister</h1>
<a href="#newmsg" class="ui-btn-right" style="">New post</a>
</div>
<div class="content" data-role="content">
<ul class="posts" data-role="listview">
</ul>
</div>
<div class="footer" data-role="footer" data-position="fixed" data-tap-toggle="false">
<a href="#home" data-role="button">Home</a>
<a href="#network" data-role="button">Network</a>
<a href="#directmsg" data-role="button" class="newdms-count">Direct Msg</a>
<a href="#profile-edit" data-role="button">Profile</a>
</div>
</div>
<div id="mentions" data-role="page">
<div class="myheader" data-role="header" data-position="fixed" data-tap-toggle="false" data-nobackbtn="true">
<a href="#search" class="ui-btn-left" style="">Search</a>
<h1 class="rtitle"></h1>
<a href="#newmsg" class="mention-newmsg ui-btn-right" style="">New post</a>
</div>
<div class="content" data-role="content">
<ul class="posts" data-role="listview">
</ul>
</div>
<div class="footer" data-role="footer" data-position="fixed" data-tap-toggle="false">
<a href="#home" data-role="button">Home</a>
<a href="#network" data-role="button">Network</a>
<a href="#directmsg" data-role="button" class="newdms-count">Direct Msg</a>
<a href="#profile-edit" data-role="button">Profile</a>
</div>
</div>
<div id="hashtag" data-role="page">
<div class="myheader" data-role="header" data-position="fixed" data-tap-toggle="false" data-nobackbtn="true">
<a href="#search" class="ui-btn-left" style="">Search</a>
<h1 class="rtitle"></h1>
<a href="#newmsg" class="hashtag-newmsg ui-btn-right" style="">New post</a>
</div>
<div class="content" data-role="content">
<ul class="posts" data-role="listview">
</ul>
</div>
<div class="footer" data-role="footer" data-position="fixed" data-tap-toggle="false">
<a href="#home" data-role="button">Home</a>
<a href="#network" data-role="button">Network</a>
<a href="#directmsg" data-role="button" class="newdms-count">Direct Msg</a>
<a href="#profile-edit" data-role="button">Profile</a>
</div>
</div>
<div id="newmsg" data-role="page">
<div class="myheader" data-role="header" data-position="fixed" data-tap-toggle="false">
<h1 class="rtitle">New post</h1>
</div>
<div class="content" data-role="content">
<div class="tbox" style="">
<div style="padding-left:5px; width:300px; margin-bottom: 30px;">
<form class="post-area-new open">
<textarea placeholder="New Post..." style="height:85px; width:270px;"></textarea>
<div class="post-area-extras">
<span class="post-area-remaining">140</span>
<button class="post-submit disabled" disabled="disabled"
style="float:right; height:44px; font-weight: bold; font-size: 115%%; margin-bottom: 5px; margin-right: 5px;">
Send</button>
</div>
</form>
</div>
<hr/>
<ul class="reply-original-post" data-role="listview">
</ul>
</div>
</div>
<div class="footer" data-role="footer" data-position="fixed" data-tap-toggle="false">
<a href="#home" data-role="button">Home</a>
<a href="#network" data-role="button">Network</a>
<a href="#directmsg" data-role="button" class="newdms-count">Direct Msg</a>
<a href="#profile-edit" data-role="button">Profile</a>
</div>
</div>
<div id="rt" data-role="page">
<div class="myheader" data-role="header" data-position="fixed" data-tap-toggle="false">
<h1 class="rtitle">Retransmit</h1>
</div>
<div class="content" data-role="content">
<div>
<h3>Retransmit this post to your followers? </h3>
</div>
<div>
<hr/>
<ul class="rt-original-post" data-role="listview">
</ul>
</div>
<div>&nbsp;</div>
<button class="retransmit-confirm" href="#">Confirm</button>
</div>
<div class="footer" data-role="footer" data-position="fixed" data-tap-toggle="false">
<a href="#home" data-role="button">Home</a>
<a href="#network" data-role="button">Network</a>
<a href="#directmsg" data-role="button" class="newdms-count">Direct Msg</a>
<a href="#profile-edit" data-role="button">Profile</a>
</div>
</div>
<div id="profile" data-role="page">
<div class="myheader" data-role="header" data-position="fixed" data-tap-toggle="false">
<a href="#search" class="ui-btn-left" style="">Search</a>
<h1 class="rtitle">Profile</h1>
<a href="#newmsg" class="profile-newmsg ui-btn-right" style="">New post</a>
</div>
<div class="content" data-role="content">
<div class="profile-card" data-screen-name="">
<!-- Coloquei a imagem de fundo do card do usuário como background da div
abaixo inline na tag para poder ser alterada dinamicamente
style="background: url(img/imgBack.png) no-repeat center center;"-->
<div class="profile-card-main">
<img class="profile-card-photo avatar" src="img/grayed_avatar_placeholder_24.png"/>
<h1 class="profile-name"></h1>
<h2 class="profile-screen-name">@<b></b></h2>
<div>
<span class="profile-location"></span>
<a class="profile-url" rel="nofollow" target="_blank"></a>
</div>
<div class="profile-bio">
</div>
</div>
<ul class="module profile-data">
<li><a href="#"><span class="posts-count">&nbsp;</span>Posts</a></li>
<li><a href="#"><span class="following-count">&nbsp;</span>Following</a></li>
<li><a href="#"><span class="followers-count">&nbsp;</span>Followers</a></li>
</ul>
<a data-role="button" data-mini="true" class="follow" href="#following">Follow</a>
<a data-role="button" data-mini="true" class="direct-messages-with-user" href="#dmchat">Direct Message</a>
<a data-role="button" data-mini="true" class="mentions-from-user" href="#mentions">Mentions</a>
<div>&nbsp;</div>
</div>
<div>
<ul class="posts postboard-posts" data-role="listview">
</ul>
</div>
</div>
<div class="footer" data-role="footer" data-position="fixed" data-tap-toggle="false">
<a href="#home" data-role="button">Home</a>
<a href="#network" data-role="button">Network</a>
<a href="#directmsg" data-role="button" class="newdms-count">Direct Msg</a>
<a href="#profile-edit" data-role="button">Profile</a>
</div>
</div>
<div id="profile-edit" data-role="page">
<div class="myheader" data-role="header" data-position="fixed" data-tap-toggle="false">
<a href="#login" class="ui-btn-left" style="">Login</a>
<h1 class="rtitle">Edit Profile</h1>
<a href="#following" class="ui-btn-right" style="">Following</a>
</div>
<div class="content" data-role="content">
<input type="file" id="avatar-file" name="avatar_files"/>
<div class="profile-card forEdition">
<!-- Coloquei a imagem de fundo do card do usuário como background da div abaixo inline na tag para poder ser alterada dinamicamente -->
<div class="profile-card-main" style="background: url(img/img.png) no-repeat center center #b43e34;">
<img class="profile-card-photo forEdition avatar" src="img/genericPerson.png"/>
<input type="text" class="input-name" placeholder="Full name here"/>
<h2></h2>
<input type="text" class="input-description" placeholder="Describe yourself"/>
<input type="text" class="input-city" placeholder="Location"/>
<input type="text" class="input-website" placeholder="website"/>
</div>
<div class="profile-edition-buttons">
<button class="cancel-changes" href="#">Cancel</button>
<button class="submit-changes" href="#">Save Changes</button>
</div>
<div style="font-size:80%;text-align: center;">
Secret key: <span class="secret-key"></span>
</div>
</div>
</div>
<div class="footer" data-role="footer" data-position="fixed" data-tap-toggle="false">
<a href="#home" data-role="button">Home</a>
<a href="#network" data-role="button">Network</a>
<a href="#directmsg" data-role="button" class="newdms-count">Direct Msg</a>
<a href="#profile-edit" data-role="button">Profile</a>
</div>
</div>
<div id="following" data-role="page">
<div class="myheader" data-role="header" data-position="fixed" data-tap-toggle="false">
<a href="#search" class="ui-btn-left" style="">Search</a>
<h1 class="rtitle">Following</h1>
</div>
<div class="content" data-role="content">
<ul class="following-list" data-role="listview">
<li id="following-user-template" style="display: none;">
<div class="mini-profile-info" data-screen-name="">
<a href="#" class="open-profile-modal">
<img class="mini-profile-photo avatar" src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/>
<span class="mini-profile-name">Fulano da Silva</span>
<span>&nbsp;</span>
<span class="mini-screen-name">@
<b class="following-screen-name"></b>
</span>
</a>
<div>
<a data-role="button" data-mini="true" class="unfollow">Unfollow</a>
<a data-role="button" data-mini="true" class="direct-messages-with-user" href="#dmchat">Direct Message</a>
<!-- <a data-role="button" data-mini="true" class="public-following">Public</button> -->
</div>
<div>
<span class="swarm-status" style="display: none;"></span>
</div>
</div>
</li>
</ul>
<div class="not-following-any" style="display:none;">
<h3>
<div> Not following any users! </div>
<div> Use "Search" button to find and follow some.</div>
</h3>
</div>
</div>
<div class="footer" data-role="footer" data-position="fixed" data-tap-toggle="false">
<a href="#home" data-role="button">Home</a>
<a href="#network" data-role="button">Network</a>
<a href="#directmsg" data-role="button" class="newdms-count">Direct Msg</a>
<a href="#profile-edit" data-role="button">Profile</a>
</div>
</div>
<div id="network" data-role="page">
<div class="myheader" data-role="header" data-position="fixed" data-tap-toggle="false">
<a href="#login" class="ui-btn-left" style="">Login</a>
<h1 class="rtitle">twister</h1>
</div>
<div class="content" data-role="content">
<div class="network singleBlock">
<h2> Network status </h2>
<div class="module">
<h3> General information</h3>
<ul style="background: center center #ffff00; font-weight:bold">
<div>&nbsp;</div>
<div>
<span class="network-status highlight connection-status">Updating status...</span>
</div>
<div>&nbsp;</div>
</ul>
<button class="terminate-daemon">Terminate Daemon</button>
<h3> Detailed information </h3>
<ul>
<li class="connections">
<label>Connections: </label>
<span class="connection-count">1</span>
</li>
<li>
<label>Known peers: </label>
<span class="known-peers">6</span>
</li>
<li>
<label>Active DHT nodes: </label>
<span class="dht-nodes">0</span>
</li>
<li>
<label>Force connection to peer:</label>
<input class="new-peer-addr" type="textbox" placeholder="peer address" size="20"/>
<button class="add-peer disabled" disabled="disabled">Add peer</button>
</li>
<li>
<label>DNS to obtain list of peers:</label>
<input class="new-dns-addr" type="textbox" placeholder="dns address" size="20"/>
<button class="add-dns disabled" disabled="disabled">Add DNS</button>
</li>
</ul>
<h3> Block chain information </h3>
<ul>
<li>
<label>Number of blocks in block chain:</label>
<span class="blocks">2813</span>
</li>
<li>
<label>Time of the last block:</label>
<span class="last-block-time">Fri Nov 08 2013 08:32:48 </span>
</li>
</ul>
</div>
<h2>Configure block generation</h2>
<div class="module">
<h3> Generate blocks (send promoted messages)</h3>
<ul>
<li>
<label> Block generation </label>
<select class="genblock">
<option value="disable">Disable</option>
<option value="enable">Enable</option>
</select>
</li>
<li>
<label> Number of CPUs to use </label>
<input class="genproclimit" type="textbox" value="1" size="3"/>
</li>
<li>
<label> Post to promote: </label>
<textarea class="spam-msg" placeholder="Type message here"></textarea>
</li>
<li>
<label> Send post with username </label>
<select class="local-usernames spam-user">
<option value="nobody">nobody</option>
</select>
<div class="character-limit">
<span class="post-area-remaining">140</span>
<button class="update-spam-msg enabled">Update</button>
</div>
</li>
</ul>
</div>
</div>
</div>
<div class="footer" data-role="footer" data-position="fixed" data-tap-toggle="false">
<a href="#home" data-role="button">Home</a>
<a href="#network" data-role="button">Network</a>
<a href="#directmsg" data-role="button" class="newdms-count">Direct Msg</a>
<a href="#profile-edit" data-role="button">Profile</a>
</div>
</div>
<div id="directmsg" data-role="page">
<div class="myheader" data-role="header" data-position="fixed" data-tap-toggle="false" data-nobackbtn="true">
<h1 class="rtitle">Direct Messages</h1>
</div>
<div class="content" data-role="content">
<ul class="direct-messages-thread" data-role="listview">
</ul>
</div>
<div class="footer" data-role="footer" data-position="fixed" data-tap-toggle="false">
<a href="#home" data-role="button">Home</a>
<a href="#network" data-role="button">Network</a>
<a href="#directmsg" data-role="button" class="newdms-count">Direct Msg</a>
<a href="#profile-edit" data-role="button">Profile</a>
</div>
</div>
<div id="dmchat" data-role="page">
<div class="myheader" data-role="header" data-position="fixed" data-tap-toggle="false" data-nobackbtn="true">
<h1 class="rtitle">Chat with</h1>
</div>
<div class="content" data-role="content">
<ul class="direct-messages-list" data-role="listview">
</ul>
<div class="dm-form" style="padding:15px 20px 20px 20px;">
<form class="post-area-new open">
<textarea placeholder="New direct message..."></textarea>
<div class="post-area-extras">
<span class="post-area-remaining">140</span>
<button class="dm-submit disabled" disabled="disabled">send</button>
</div>
</form>
</div>
</div>
<div class="footer" data-role="footer" data-position="fixed" data-tap-toggle="false">
<a href="#home" data-role="button">Home</a>
<a href="#network" data-role="button">Network</a>
<a href="#directmsg" data-role="button" class="newdms-count">Direct Msg</a>
<a href="#profile-edit" data-role="button">Profile</a>
</div>
</div>
<div id="search" data-role="page">
<div class="myheader" data-role="header" data-position="fixed" data-tap-toggle="false" data-nobackbtn="true">
<h1 class="rtitle">Search users</h1>
</div>
<div class="content" data-role="content">
<div>
<input type="text" class="userMenu-search-field" placeholder="Type partial username here"/>
</div>
<div>&nbsp;</div>
<div>
<ul class="userMenu-search-profiles" data-role="listview">
<li id="search-profile-template" style="display: none;">
<a class="mini-profile-info open-profile-modal" data-screen-name="">
<img class="mini-profile-photo avatar" src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/>
<span class="mini-screen-name">@<b></b></span>
<span class="mini-profile-name"></span>
<!-- <button class="follow">Follow</button> -->
</a>
</li>
</ul>
</div>
</div>
<div class="footer" data-role="footer" data-position="fixed" data-tap-toggle="false">
<a href="#home" data-role="button">Home</a>
<a href="#network" data-role="button">Network</a>
<a href="#directmsg" data-role="button" class="newdms-count">Direct Msg</a>
<a href="#profile-edit" data-role="button">Profile</a>
</div>
</div>
<div id="new-user-modal" data-role="page" style="max-width:400px;" class="ui-corner-all">
<div data-role="header" class="ui-corner-top">
<h1>New user created</h1>
</div>
<div role="main" class="ui-corner-bottom ui-content">
<div class="text">
A new user was created and it is being propagated to the network.
Please do not close this window, this might take a few minutes.
</div>
<div class="text emphasis">
Your secret key is:
<div class="secret-key"></div>
</div>
<div class="text">
It is highly recommended that you take this time to save your secret key.
Print it, do a screenshot, use the camera in your phone or write it down
to a piece of paper.
</div>
<div class="text">
The secret key will be needed to use this account from different computers.
If you ever lose your secret key (remember: this is alpha software and it may
crash, causing loss of data) your nickname will be locked forever.
There is no way to recover a lost key for twister accounts.
</div>
<div class="text"></div>
<div class="text">
Please wait. This page will be refreshed automatically.
</div>
<!-- <a href="#" data-role="button" data-inline="true" data-rel="back">Ok</a> -->
</div>
</div>
<div id="dialog_page" data-role="page" style="background:gray;">
<div class="myheader" data-role="header" data-position="fixed" data-tap-toggle="false">
<a class="back-btn" href="#home" data-icon="arrow-l" data-rel="back">Back</a>
<h1 class="page-title"></h1>
</div>
<div class="content" data-role="content" style="">
<div class="ui-body-c" style="padding:10px; border-radius: 10px;">
<div class="dialog-title" style="font-size: 105%; font-weight:bold; margin-bottom:10px; text-align:center;"></div>
<div class="dialog-description" style="width:100%; margin-bottom:20px; text-align:center;"></div>
<div class="dialog-buttons" style="width:100%;"></div>
</div>
</div>
<div class="footer" data-role="footer" data-position="fixed" data-tap-toggle="false">
<a href="#" data-role="button" class="logoutButton">Logout</a>
</div>
</div>
<div id="anywhere"></div>
<div id="templates" style="display:none;">
<li id="post-template-home" class="post-li module post" data-time="">
<img class="avatar" src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/>
<div class="post-data" data-userpost="" data-content_to_rt="" data-content_to_sigrt=""
data-screen-name="" data-id="" data-text="" data-text-mentions="">
<h4 class="post-info">
<a href="#" class="post-info-name open-profile-modal status_reply"></a>
<span class="post-info-tag"></span>
<span style='font-weight:lighter;font-size:12px;' class="post-info-time ui-li-aside"></span>
</h4>
<p class="post-text no-ellipsis"></p>
<div class="post-context" style="display: none;">
<i class="post-retransmited-icon"></i>
<span style="font-size:60%;">Retransmitted by
<a class="post-retransmited-by open-profile-modal" href=""></a>
</span>
</div>
</div>
</li> <!-- post-template -->
<li id="post-template-post" class="module post" data-time="">
<img class="avatar" src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/>
<div class="post-data" data-userpost="" data-content_to_rt="" data-content_to_sigrt=""
data-screen-name="" data-id="" data-text="" data-text-mentions="">
<h4 class="post-info">
<a href="#" class="post-info-name open-profile-modal"></a>
<span class="post-info-tag"></span>
<span style='font-weight:lighter;font-size:12px;' class="post-info-time ui-li-aside"></span>
</h4>
<p class="post-text no-ellipsis"></p>
<div class="post-context" style="display: none;">
<i class="post-retransmited-icon"></i>
<span style="font-size:60%;">Retransmitted by
<a class="post-retransmited-by open-profile-modal" href=""></a>
</span>
</div>
<!-- elementos de interação com o post que são exibidos no hover -->
<fieldset class="post-interactions ui-grid-a" data-role="fieldcontain">
<div class="ui-block-a">
<button class="post-reply" data-mini="true">Reply</button>
</div>
<div class="ui-block-b">
<button class="post-propagate" data-mini="true">RT</button>
</div>
</fieldset>
<div class="expanded-content">
<span class="post-stats" style="display: none;">
<span class="stat-count" style="font-size:80%;">
<span class="stat-count-value"></span>
<span>Retransmits</span>
</span>
<span class="avatar-row">
<!-- use "avatar-row-template" here -->
</span>
</span>
</div> <!-- expanded-content -->
</div> <!-- post-data -->
</li> <!-- post-template -->
<!-- template for user links in message (open profile modal) -->
<a id="msg-user-link-template" class="open-profile-modal"></a>
<!-- template for user links in message (open profile modal) -->
<a id="external-page-link-template" rel="nofollow" target="_blank"></a>
<!-- template for user links in message (open profile modal) -->
<a id="hashtag-link-template" class="open-hashtag-modal"></a>
<!-- template para ir dentro de avatar-row -->
<a id="avatar-row-template" class="open-profile-modal" href="">
<img class="avatar size24" src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/>
</a>
<!-- template for dm-snippet (dm threads list) -->
<li id="dm-snippet-template" class="module post message" data-icon="arrow-r">
<a class="dm-chat-link post-photo" href="#dmchat">
<img class="avatar" src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/>
<h4 class="post-info">
<span class="post-info-name open-profile-modal"></span>
<span class="post-info-tag">@</span>
<span style='font-weight:lighter;font-size:12px;' class="post-info-time ui-li-aside"></span>
</h4>
<!-- o texto do post (restrição de caracteres?) -->
<p class="post-text"></p>
</a>
</li>
<!-- template for each dm (chat window) -->
<li id="dm-chat-template" class="module post message">
<h4 class="post-photo">
<img class="avatar" src="img/grayed_avatar_placeholder_24.png" alt="user-photo"/>
<span style='font-weight:lighter;font-size:12px;' class="post-info-time ui-li-aside"></span>
</h4>
<p class="post-text no-ellipsis"></p>
</li>
</div> <!-- templates -->
</body>
</html>

567
tmobile.js

@ -0,0 +1,567 @@ @@ -0,0 +1,567 @@
// tmobile.js
// 2013 Miguel Freitas
//
// mobile interface for twister using jquery mobile + router
var twisterInitialized = false;
var handlersInstalled = false;
function initializeTwister( redirectNetwork, redirectLogin, cbFunc, cbArg ) {
if( !handlersInstalled ) {
interfaceNetworkHandlers();
interfaceCommonLoginHandlers();
installUserSearchHandler();
installProfileEditHandlers();
// install scrollbottom handler to load more posts as needed
$(window).scroll(function(){
if ($(window).scrollTop() >= $(document).height() - $(window).height() - 20){
reachedScrollBottom();
}
});
// home screen timeline refresh button
$( ".timeline-refresh").click(function(e) {
$.MAL.setPostTemplate( $("#post-template-home") );
requestTimelineUpdate("latest",postsPerRefresh,followingUsers);
$.mobile.silentScroll(0);
});
// reply text counter both newmsg and dmchat
var $replyText = $( ".post-area-new textarea" );
$replyText.unbind('keyup').keyup( replyTextKeypress );
setInterval("tmobileTick()", 2000);
handlersInstalled = true;
}
if( twisterInitialized ) {
if( cbFunc )
cbFunc(cbArg);
} else {
networkUpdate( function() {
if( redirectNetwork && !twisterdConnectedAndUptodate ) {
$.MAL.goNetwork();
return;
}
initUser(function() {
if( redirectLogin && !defaultScreenName ) {
$.MAL.goLogin();
return;
}
if( defaultScreenName ) {
loadFollowing( function(args) {
requestLastHave();
initMentionsCount();
initDMsCount();
twisterInitialized = true;
if( cbFunc )
cbFunc(cbArg);
});
} else {
if( cbFunc )
cbFunc(cbArg);
}
});
});
}
}
var router=new $.mobile.Router(
[
{ "#index" : {handler: "index", events: "bc"} },
{ "#home": {handler: "home", events: "bs" } },
{ "#profile": {handler: "profile", events: "bs" } },
{ "#profile-edit": {handler: "profileedit", events: "bs" } },
{ "#following": {handler: "following", events: "bs" } },
{ "#post": {handler: "post", events: "bs" } },
{ "#newmsg": {handler: "newmsg", events: "bs" } },
{ "#rt": {handler: "rt", events: "bs" } },
{ "#mentions": {handler: "mentions", events: "bs" } },
{ "#hashtag": {handler: "hashtag", events: "bs" } },
{ "#login": {handler: "login", events: "bs" } },
{ "#network": {handler: "network", events: "bs" } },
{ "#directmsg": {handler: "directmsg", events: "bs" } },
{ "#dmchat": {handler: "dmchat", events: "bs" } },
{ "#search": {handler: "search", events: "bs" } },
{ "#new-user-modal": {handler: "newusermodal", events: "bs" } },
],{
index: function(type,match,ui) {
$.mobile.showPageLoadingMsg();
initializeTwister( true, true, function() {
//$.MAL.goHome();
});
},
home: function(type,match,ui) {
$.mobile.showPageLoadingMsg();
initializeTwister( true, true, function() {
if( !$("#home .posts").children().length ) {
$.mobile.showPageLoadingMsg();
getFullname( defaultScreenName, $("#home .rtitle"));
$.MAL.setPostTemplate( $("#post-template-home") );
requestTimelineUpdate("latestFirstTime",postsPerRefresh,followingUsers);
}
});
},
profile: function(type,match,ui) {
var params=router.getParams(match[1]);
clearProfilePage();
$.mobile.showPageLoadingMsg();
initializeTwister( true, true, function() {
var user;
if( params && params.hasOwnProperty("user") ) {
user = params.user;
} else {
user = defaultScreenName;
}
var $newmsgLink = $("a.profile-newmsg");
if( user != defaultScreenName ) {
$newmsgLink.attr("href",$.MAL.newPostToUrl(user)).show();
} else {
$newmsgLink.hide();
}
$.mobile.showPageLoadingMsg();
$.MAL.setPostTemplate( $("#post-template-home") );
updateProfileData( $("#profile"), user);
});
},
profileedit: function(type,match,ui) {
$.mobile.showPageLoadingMsg();
initializeTwister( true, true, function() {
loadAvatarForEdit();
loadProfileForEdit();
dumpPrivkey(defaultScreenName, function(args, key) {
$(".secret-key").text(key);
}, {});
});
},
following: function(type,match,ui) {
var params=router.getParams(match[1]);
clearProfilePage();
$.mobile.showPageLoadingMsg();
initializeTwister( true, true, function() {
var user;
if( params && params.hasOwnProperty("user") ) {
user = params.user;
} else {
user = defaultScreenName;
}
if( params && params.hasOwnProperty("follow") ) {
follow(params.follow);
}
if( params && params.hasOwnProperty("unfollow") ) {
unfollow(params.unfollow);
}
$.mobile.showPageLoadingMsg();
$("#following a.ui-btn").removeClass("ui-btn-active");
showFollowingUsers();
});
},
post: function(type,match,ui) {
var params=router.getParams(match[1]);
initializeTwister( true, true, function() {
var $ulPost = $("#post ul.posts");
$ulPost.text("");
$.MAL.setPostTemplate( $("#post-template-post") );
var originalLi = postToElem($.evalJSON(params.userpost), "original");
$ulPost.append(originalLi);
$ulPost.find(".post-interactions").trigger('create');
$ulPost.listview('refresh');
installReplyClick();
installRetransmitClick();
// insert replies to this post after
requestRepliesAfter(originalLi);
// RTs faces and counter
requestRTs(originalLi);
});
},
newmsg: function(type,match,ui) {
var params=router.getParams(match[1]);
initializeTwister( true, true, function() {
var $replyTextarea = $("#newmsg .post-area-new textarea");
$replyTextarea.attr("placeholder", polyglot.t("New Post..."));
if( params && params.hasOwnProperty("replyto") ) {
$replyTextarea.val(params.replyto);
} else {
$replyTextarea.val("");
}
$.MAL.disableButton($("#newmsg .post-submit"));
var $replyOriginal = $(".reply-original-post")
$replyOriginal.html("");
if( params && params.hasOwnProperty("userpost") ) {
$.MAL.setPostTemplate( $("#post-template-home") );
var originalLi = postToElem($.evalJSON(params.userpost), "original");
$replyOriginal.append(originalLi);
$replyOriginal.listview('refresh');
}
installSubmitClick();
});
},
rt: function(type,match,ui) {
var params=router.getParams(match[1]);
initializeTwister( true, true, function() {
var $rtOriginal = $(".rt-original-post")
$rtOriginal.html("");
$.MAL.setPostTemplate( $("#post-template-home") );
var originalLi = postToElem($.evalJSON(params.userpost), "original");
$rtOriginal.append(originalLi);
$rtOriginal.listview('refresh');
installRetransmitConfirmClick();
});
},
mentions: function(type,match,ui) {
var params=router.getParams(match[1]);
$.mobile.showPageLoadingMsg();
initializeTwister( true, true, function() {
var user;
var $newmsgLink = $("a.mention-newmsg");
if( params && params.hasOwnProperty("user") ) {
user = params.user;
$newmsgLink.attr("href",$.MAL.newPostToUrl(user));
} else {
user = defaultScreenName;
$newmsgLink.attr("href","#newmsg");
resetMentionsCount();
}
$("#mentions .rtitle").text(polyglot.t("mentions_at", { user: user }));
var $ulMentions = $("#mentions ul.posts");
setupHashtagOrMention( $ulMentions, user, "mention");
});
},
hashtag: function(type,match,ui) {
var params=router.getParams(match[1]);
$.mobile.showPageLoadingMsg();
initializeTwister( true, true, function() {
$("#hashtag .rtitle").text("#" + params.hashtag);
$("a.hashtag-newmsg").attr("href",$.MAL.newPostHashtagToUrl(params.hashtag));
var $ulHashtag = $("#hashtag ul.posts");
setupHashtagOrMention($ulHashtag,params.hashtag,"hashtag");
});
},
login: function(type,match,ui) {
$.mobile.showPageLoadingMsg();
initializeTwister( true, false, function() {
$.mobile.hidePageLoadingMsg();
$("select.local-usernames.login-user").selectmenu("refresh", true);
installCreateUserClick();
});
},
network: function(type,match,ui) {
$.mobile.showPageLoadingMsg();
initializeTwister( false, false, function() {
$.mobile.hidePageLoadingMsg();
$("select.local-usernames.spam-user").selectmenu("refresh", true);
getSpamMsg();
getGenerate();
});
},
directmsg: function(type,match,ui) {
$.mobile.showPageLoadingMsg();
initializeTwister( true, true, function() {
var $dmThreadList = $("#directmsg ul.direct-messages-thread");
$.mobile.showPageLoadingMsg();
requestDMsnippetList($dmThreadList);
});
},
dmchat: function(type,match,ui) {
var params=router.getParams(match[1]);
$.mobile.showPageLoadingMsg();
initializeTwister( true, true, function() {
var user = params.user;
var dmConvo = $("#dmchat ul.direct-messages-list");
$("#dmchat .rtitle").text("Chat @" + user);
$("#dmchat textarea").val("");
dmConvo.html("");
installDMSendClick();
$.mobile.showPageLoadingMsg();
dmChatUser = user;
requestDmConversation(dmConvo,user);
});
},
search: function(type,match,ui) {
initializeTwister( true, true, function() {
/**/
});
},
newusermodal: function(type,match,ui) {
initializeTwister( false, false, function() {
/* dumpPrivkey(defaultScreenName, function(args, key) {
$(".secret-key").text(key);
}, {}); */
});
},
}, {
defaultHandler: function(type, ui, page) {
console.log("Default handler called due to unknown route ("
+ type + ", " + ui + ", " + page + ")" );
console.log(ui);
console.log(page);
},
defaultHandlerEvents: "s",
defaultArgsRe: true
}
);
function installPostboardClick() {
var $postDatas = $(".post-data");
$postDatas.unbind('click').click(function(e){
e.stopPropagation();
e.preventDefault();
var userpost = $(this).attr("data-userpost");
var url = "#post?userpost=" + encodeURIComponent(userpost);
$.mobile.showPageLoadingMsg();
$.mobile.navigate( url );
});
$(".post a").unbind('click').click(function(e) {
e.stopPropagation();
// stopPropagation is supposed to be enough, but in Android the
// default action is not called so we reimplement it here as a hack.
e.preventDefault();
$.mobile.showPageLoadingMsg();
$.mobile.navigate( $(this).attr("href") );
});
}
function installReplyClick() {
var $postReply = $("#post .post-reply");
$postReply.unbind('click').click(function(e){
e.stopPropagation();
e.preventDefault();
var $postData = $(this).closest(".post-data");
var userpost = $postData.attr("data-userpost");
var replyTo = $postData.attr("data-reply-to");
var url = "#newmsg?replyto=" + encodeURIComponent(replyTo) +
"&userpost=" + encodeURIComponent(userpost);
$.mobile.showPageLoadingMsg();
$.mobile.navigate( url );
});
}
function installRetransmitClick() {
var $postRt = $("#post .post-propagate");
$postRt.unbind('click').click(function(e){
e.stopPropagation();
e.preventDefault();
var $postData = $(this).closest(".post-data");
var userpost = $postData.attr("data-userpost");
var url = "#rt?userpost=" + encodeURIComponent(userpost);
$.mobile.showPageLoadingMsg();
$.mobile.navigate( url );
});
}
function installSubmitClick() {
var $postSubmit = $(".post-submit");
$postSubmit.unbind('click').click(function(e){
e.stopPropagation();
e.preventDefault();
var $this = $( this );
var $replyText = $this.closest(".post-area-new").find("textarea");
var $postOrig = $("#newmsg .reply-original-post .post-data");
var s = encode_utf8($replyText.val());
newPostMsg(s, $postOrig);
$replyText.val("");
$replyText.attr("placeholder", polyglot.t("Your message was sent!"));
setTimeout( function() {$.MAL.goHome();}, 1000);
});
}
function installDMSendClick() {
var $postSubmit = $(".dm-submit");
$postSubmit.unbind('click').click(function(e){
e.stopPropagation();
e.preventDefault();
var $this = $( this );
var $replyText = $this.closest(".post-area-new").find("textarea");
var $dmConversation = $(".directMessages");
var s = encode_utf8($replyText.val());
newDirectMsg(s, dmChatUser);
$replyText.val("");
});
}
function installRetransmitConfirmClick() {
var $postConfirmRt = $(".retransmit-confirm");
$postConfirmRt.unbind('click').click(function(e){
e.stopPropagation();
e.preventDefault();
var $postOrig = $("#rt .rt-original-post .post-data");
$.mobile.showPageLoadingMsg();
newRtMsg($postOrig);
$.MAL.goHome();
});
}
function installCreateUserClick() {
$( ".create-user").unbind('click').click( function(e) {
createUserClick( function(username, secretKey) {
defaultScreenName = username;
if(defaultScreenName) {
saveScreenName();
}
$(".secret-key").text(secretKey);
sendNewUserTransaction( username, function() {} );
$.mobile.navigate( "#new-user-modal" ); } );
});
}
function installUserSearchHandler() {
var $userSearchField = $( ".userMenu-search-field" );
$userSearchField.unbind('keyup').keyup( userSearchKeypress );
$userSearchField.unbind('click').bind( "click", userSearchKeypress );
}
function installProfileEditHandlers() {
$(".profile-card-photo.forEdition").click( function() { $('#avatar-file').click(); } );
$("#avatar-file").bind( "change", handleAvatarFileSelectMobile);
$(".submit-changes").click( function() {
saveProfile();
setTimeout( function() {$.MAL.goHome();}, 1000);
} );
$(".cancel-changes").click( $.mobile.back );
}
function handleAvatarFileSelectMobile(evt) {
var files = evt.target.files; // FileList object
var f = files[0];
// Only process image files.
if (f.type === undefined || f.type.match('image.*')) {
var reader;
try {
reader = new FileReader();
} catch(e) {
alert(polyglot.t('File APIs not supported in this browser.'));
return;
}
reader.onload=function(e){
var img=new Image();
img.onload=function(){
var MAXWidthHeight=64;
var r=MAXWidthHeight/Math.max(this.width,this.height),
w=Math.round(this.width*r),
h=Math.round(this.height*r),
c=document.createElement("canvas");
c.width=w;c.height=h;
c.getContext("2d").drawImage(this,0,0,w,h);
var imgURL = undefined;
var encoder = new JPEGEncoder();
for(var q = 90; (!imgURL || imgURL.length > 4096) && q > 10; q -= 1) {
imgURL = encoder.encode(c.getContext("2d").getImageData(0,0,w,h), q);
console.log( "q: " + q + " url size: " + imgURL.length );
}
$(".profile-card-photo.forEdition").attr("src", imgURL );
}
img.src=e.target.result;
}
// Read in the image file as a data URL.
reader.readAsDataURL(f);
}
}
function clearProfilePage() {
$("#profile .profile-card-photo").attr("src","img/grayed_avatar_placeholder_24.png");
$("#profile .profile-name").text("");
$("#profile .profile-location").text("");
$("#profile .profile-url").text("");
$("#profile .profile-url").attr("href","");
$("#profile .profile-location").text("");
$("#profile .posts").text("");
}
// handler of scroll bottom to request older posts
function reachedScrollBottom() {
var curPage = $.mobile.activePage.attr("id");
if( curPage == "home" ) {
if( timelineLoaded ) {
$.MAL.setPostTemplate( $("#post-template-home") );
requestTimelineUpdate("older", postsPerRefresh, followingUsers);
}
}
}
function encode_utf8(s) {
// only needed in android 2.3 - why?
var ua = navigator.userAgent;
if( ua.indexOf("Android") >= 0 )
{
var androidversion = parseFloat(ua.slice(ua.indexOf("Android")+8));
if (androidversion < 3.0)
{
return unescape(encodeURIComponent(s));
}
}
return s;
}
var hashtag_elem;
var hashtag_tag;
var hashtag_res;
function setupHashtagOrMention( ulElem, tag, res) {
hashtag_elem = ulElem;
hashtag_tag = tag;
hashtag_res = res;
hashtag_elem.text("");
$.MAL.setPostTemplate( $("#post-template-home") );
$.mobile.showPageLoadingMsg();
clearHashtagProcessed();
if( tag == defaultScreenName && res == "mention" ) {
// obtain already cached mention posts from twister_newmsgs.js
processHashtag(hashtag_elem, defaultScreenName, getMentionsData() );
}
requestHashtag(hashtag_elem,hashtag_tag,hashtag_res);
}
// every 2 seconds do something page specific.
function tmobileTick() {
var curPage = $.mobile.activePage.attr("id");
if( curPage == "network" ) {
networkUpdate();
}
if( curPage == "home" ) {
requestLastHave();
}
if( curPage == "new-user-modal" ) {
dumpPubkey(defaultScreenName, function(args, pubkey) {
//pubkey = "";
if( pubkey.length > 0 ) {
follow('twister', true, function() {
$.MAL.goProfileEdit();
});
}
}, {} );
}
if( curPage == "mentions" || curPage == "hashtag" ) {
autoUpdateHashtag = true;
requestHashtag(hashtag_elem,hashtag_tag,hashtag_res);
}
if( curPage == "dmchat" ) {
requestDmConversation($("#dmchat ul.direct-messages-list"),dmChatUser);
}
}

253
twister_actions.js

@ -0,0 +1,253 @@ @@ -0,0 +1,253 @@
// twister_actions.js
// 2013 Miguel Freitas
//
// This file contains some twister "actions" like requesting
// posts from dht, sending posts, replies. It seems to be a
// intermediate layer between twister_io and interface, but
// separation is not clearly defined. Perhaps it would be better
// to get rid of this file altogether.
// global variables
var postsPerRefresh = 10;
var maxExpandPost = 20;
var _hashtagProcessedMap = {};
var _hashtagPendingPosts = [];
var autoUpdateHashtag = false;
// ----------------
function requestRepliedBefore(postLi)
{
if(postLi.siblings().length >= maxExpandPost)
return;
var originalPost = postLi.find(".post-data");
var reply_n = originalPost.attr('data-replied-to-screen-name');
var reply_k = originalPost.attr('data-replied-to-id');
if( reply_n != undefined && reply_k != undefined ) {
dhtget( reply_n, "post" + reply_k, "s",
function(postLi, postFromJson) {
var newStreamPost = postToElem(postFromJson, "related");
newStreamPost.hide();
postLi.before(newStreamPost);
newStreamPost.slideDown("fast");
$.MAL.relatedPostLoaded();
requestRepliedBefore(newStreamPost);
}, postLi);
}
}
function requestRepliesAfter(postLi)
{
if(postLi.siblings().length >= maxExpandPost)
return;
var originalPost = postLi.find(".post-data");
var original_n = originalPost.attr('data-screen-name');
var original_k = originalPost.attr('data-id');
if( original_n != undefined && original_k != undefined ) {
dhtget( original_n, "replies" + original_k, "m",
function(postLi, postsFromJson) {
for( var i = 0; i < postsFromJson.length; i++) {
var newStreamPost = postToElem(postsFromJson[i], "related");
newStreamPost.hide();
postLi.after(newStreamPost);
newStreamPost.slideDown("fast");
}
$.MAL.relatedPostLoaded();
}, postLi);
}
}
function requestRTs(postLi)
{
var originalPost = postLi.find(".post-data");
var original_n = originalPost.attr('data-screen-name');
var original_k = originalPost.attr('data-id');
if( original_n != undefined && original_k != undefined ) {
dhtget( original_n, "rts" + original_k, "m",
function(originalPost, postsFromJson) {
if( postsFromJson.length ) {
var statCountValue = originalPost.find(".stat-count-value");
statCountValue.text( postsFromJson.length );
var avatarRow = originalPost.find(".avatar-row");
avatarRow.empty();
for( var i = 0; i < postsFromJson.length && i < 8; i++) {
var n = postsFromJson[i]["userpost"]["n"];
var elemUser = $("#avatar-row-template").clone(true);
elemUser.removeAttr('id');
elemUser.attr('href',$.MAL.userUrl(n));
getFullname(n,elemUser.find(".user-name-tooltip"));
getAvatar(n,elemUser.find(".size24"));
avatarRow.append( elemUser );
}
originalPost.find(".post-stats").slideDown("fast");
}
}, originalPost);
}
}
function requestPostRecursively(containerToAppend,username,resource,count)
{
dhtget( username, resource, "s",
function(args, postFromJson) {
if( postFromJson ) {
var newStreamPost = postToElem(postFromJson, "original");
newStreamPost.hide();
args.containerToAppend.append( newStreamPost );
newStreamPost.slideDown("fast");
$.MAL.postboardLoaded();
if( args.count > 1 ) {
var userpost = postFromJson["userpost"];
var n = userpost["n"];
var lastk = userpost["lastk"];
if( lastk == undefined )
lastk = userpost["k"] - 1; // not true with directmsgs in stream
requestPostRecursively(args.containerToAppend, n, "post"+lastk, count-1);
}
}
}, {containerToAppend:containerToAppend, count:count} );
}
function newPostMsg(msg, $postOrig) {
if( lastPostId != undefined ) {
var params = [defaultScreenName, lastPostId + 1, msg]
if( $postOrig.length ) {
params.push($postOrig.attr('data-screen-name'));
params.push(parseInt($postOrig.attr('data-id')));
}
twisterRpc("newpostmsg", params,
function(arg, ret) { incLastPostId(); }, null,
function(arg, ret) { var msg = ("message" in ret) ? ret.message : ret;
alert(polyglot.t("ajax_error", { error: msg })); }, null);
} else {
alert(polyglot.t("Internal error: lastPostId unknown (following yourself may fix!)"));
}
}
function newRtMsg($postOrig) {
var content_to_rt = $postOrig.attr('data-content_to_rt');
var content_to_sigrt = $postOrig.attr('data-content_to_sigrt');
var sig_userpost = String(content_to_sigrt);
var userpost = $.evalJSON(String(content_to_rt));
var rtObj = { sig_userpost :sig_userpost, userpost : userpost };
if( lastPostId != undefined ) {
var params = [defaultScreenName, lastPostId+1, rtObj]
twisterRpc("newrtmsg", params,
function(arg, ret) { incLastPostId(); }, null,
function(arg, ret) { var msg = ("message" in ret) ? ret.message : ret;
alert(polyglot.t("ajax_error", { error: msg })); }, null);
} else {
alert(polyglot.t("Internal error: lastPostId unknown (following yourself may fix!)"));
}
}
function updateProfileData(profileModalContent, username) {
//profileModalContent.find("a").attr("href",$.MAL.userUrl(username));
profileModalContent.filter(".profile-card").attr("data-screen-name", username);
profileModalContent.find(".profile-screen-name b").text(username);
profileModalContent.find("a.follow").attr("href", $.MAL.followUrl(username));
profileModalContent.find("a.direct-messages-with-user").attr("href", $.MAL.dmchatUrl(username));
profileModalContent.find("a.new-post-to").attr("href", $.MAL.newPostToUrl(username));
profileModalContent.find("a.mentions-from-user").attr("href", $.MAL.mentionsUrl(username));
getFullname( username, profileModalContent.find(".profile-name") );
getLocation( username, profileModalContent.find(".profile-location") );
getWebpage( username, profileModalContent.find(".profile-url") );
getBio( username, profileModalContent.find(".profile-bio") );
getAvatar( username, profileModalContent.find(".profile-card-photo") );
getPostsCount( username, profileModalContent.find(".posts-count") );
getFollowers( username, profileModalContent.find(".followers-count") );
getNumFollowing( username, profileModalContent.find(".following-count") );
profileModalContent.find(".following-count").parent().attr("href", $.MAL.followingUrl(username));
requestPostRecursively(profileModalContent.find(".postboard-posts"),username,"status",10);
}
function updateFollowingData(followingModalContent, username) {
followingModalContent.find(".following-screen-name b").text(username);
loadFollowingIntoList( username, $(followingModalContent[1]) );
}
function clearHashtagProcessed() {
_hashtagProcessedMap = {};
_hashtagPendingPosts = [];
}
function requestHashtag(postboard,hashtag,resource) {
dhtget( hashtag, resource, "m",
function(args, data) {
processHashtag(args.postboard, args.hashtag, data);
}, {postboard:postboard,hashtag:hashtag},
[10000,2000,3]); // use extended timeout parameters (requires twister_core >= 0.9.14)
}
function processHashtag(postboard, hashtag, data) {
if( data ) {
for( var i = data.length-1; i >= 0; i-- ) {
var userpost = data[i]["userpost"];
var key = userpost["n"] + ";" + userpost["time"];
if( !(key in _hashtagProcessedMap) ) {
_hashtagProcessedMap[key] = true;
_hashtagPendingPosts.push(data[i]);
}
}
if( _hashtagPendingPosts.length ) {
if( !postboard.children().length || autoUpdateHashtag ) {
displayHashtagPending(postboard);
} else {
var newTweetsBar = postboard.closest("div").find(".postboard-news");
newTweetsBar.text(polyglot.t("new_posts", _hashtagPendingPosts.length));
newTweetsBar.fadeIn("slow");
}
}
}
}
function displayHashtagPending(postboard) {
for( var i = 0; i < _hashtagPendingPosts.length; i++ ) {
var streamPost = postToElem(_hashtagPendingPosts[i], "original");
var timePost = _hashtagPendingPosts[i]["userpost"]["time"];
var streamItems = postboard.children();
if( streamItems.length == 0) {
postboard.prepend( streamPost );
} else {
var j = 0;
for( j = 0; j < streamItems.length; j++) {
var streamItem = streamItems.eq(j);
var timeItem = streamItem.attr("data-time");
if( timeItem == undefined ||
timePost > parseInt(timeItem) ) {
// this post in stream is older, so post must be inserted above
streamItem.before(streamPost);
break;
}
}
if( j == streamItems.length ) {
postboard.append( streamPost );
}
}
}
$.MAL.postboardLoaded();
_hashtagPendingPosts = [];
}

175
twister_directmsg.js

@ -0,0 +1,175 @@ @@ -0,0 +1,175 @@
// twister_directmsg.js
// 2013 Miguel Freitas
//
// Handle direct messages modal
function requestDMsnippetList(dmThreadList) {
var followList = [];
for( var i = 0; i < followingUsers.length; i++ ) {
followList.push({username:followingUsers[i]});
}
twisterRpc("getdirectmsgs", [defaultScreenName, 1, followList],
function(req, ret) {processDMsnippet(ret, dmThreadList);}, dmThreadList,
function(req, ret) {console.log("ajax error:" + ret);}, null);
}
function processDMsnippet(dmUsers, dmThreadList) {
dmThreadList.empty();
for( var u in dmUsers ) {
if( dmUsers.hasOwnProperty(u) ) {
var dmItem = dmDataToSnippetItem(dmUsers[u][0], u);
dmThreadList.append(dmItem);
}
}
$.MAL.dmThreadListLoaded();
}
function openDmConversation(dm_screenname, dmTitleName, dmConversation) {
getFullname( dm_screenname, dmTitleName );
dmConversation.attr("data-dm-screen-name", dm_screenname);
var dmConvo = dmConversation.find(".direct-messages-thread");
dmConvo.empty();
requestDmConversationModal(dmConvo,dm_screenname);
}
function requestDmConversationModal(dmConvo,dm_screenname) {
if( dmConvo.parents(".modal-blackout").css("display") == 'none' )
return;
requestDmConversation(dmConvo,dm_screenname);
setTimeout( function() {requestDmConversationModal(dmConvo,dm_screenname);}, 1000);
}
function requestDmConversation(dmConvo,dm_screenname) {
var since_id = undefined;
var oldItems = dmConvo.children();
if( oldItems.length ) {
since_id = parseInt(oldItems.eq(oldItems.length-1).attr("data-id"));
}
var userDmReq = [{username:dm_screenname}];
if( since_id != undefined ) userDmReq[0].since_id = since_id;
var count = 100;
twisterRpc("getdirectmsgs", [defaultScreenName,count,userDmReq],
function(args, ret) { processDmConversation(args.dmConvo, args.dmUser, ret); },
{dmConvo:dmConvo,dmUser:dm_screenname},
function(arg, ret) { var msg = ("message" in ret) ? ret.message : ret;
alert(polyglot.t("ajax_error", { error: msg })); }, null);
}
function processDmConversation(dmConvo, dm_screenname, dmData) {
var lastId = undefined;
if(dm_screenname in dmData) {
var dmList = dmData[dm_screenname];
if( dmList.length ) {
for( var i = 0; i < dmList.length; i++) {;
var dmItem = dmDataToConversationItem(dmList[i],defaultScreenName,dm_screenname);
dmItem.attr("data-id",dmList[i].id);
dmConvo.append(dmItem);
lastId = dmList[i].id;
}
$.MAL.dmChatListLoaded(dmConvo);
}
}
if( lastId != undefined ) {
resetNewDMsCountForUser(dm_screenname, lastId);
}
}
function directMsgSubmit(e)
{
e.stopPropagation();
e.preventDefault();
var $this = $( this );
var $replyText = $this.closest(".post-area-new").find("textarea");
var $dmConversation = $(".directMessages");
newDirectMsg($replyText.val(), $dmConversation.attr("data-dm-screen-name"));
$replyText.val("");
}
function newDirectMsg(msg, dm_screenname) {
if( lastPostId != undefined ) {
var params = [defaultScreenName, lastPostId + 1, dm_screenname, msg]
twisterRpc("newdirectmsg", params,
function(arg, ret) { incLastPostId(); }, null,
function(arg, ret) { var msg = ("message" in ret) ? ret.message : ret;
alert("Ajax error: " + msg); }, null);
} else {
alert(polyglot.t("Internal error: lastPostId unknown (following yourself may fix!)"));
}
}
//dispara o modal de direct messages
function directMessagesPopup()
{
var directMessagesClass = "directMessages";
openModal( directMessagesClass );
var directMessagesContent = $( ".direct-messages-template" ).html();
$( directMessagesContent ).clone().appendTo( ".directMessages .modal-content" );
//título do modal
$( ".directMessages h3" ).text( polyglot.t("Direct Messages") );
requestDMsnippetList($(".directMessages").find(".direct-messages-list"));
}
//exibe a thread de mensagens individual
function hideDmSnippetShowDmThread()
{
var $this = $( this );
//escondo a listagem de mensagens diretas...
var containerToAnimate = $this.parents( ".direct-messages-list" );
containerToAnimate.hide();
var dm_screenname = $this.attr("data-dm-screen-name");
openDmWithUserModal(dm_screenname);
}
function directMessagesWithUserPopup()
{
var $userInfo = $(this).closest("[data-screen-name]");
var dm_screenname = $userInfo.attr("data-screen-name");
openDmWithUserModal( dm_screenname );
}
function openDmWithUserModal(dm_screenname)
{
var directMessagesClass = "directMessages";
openModal( directMessagesClass );
//para poder exibir a thread selecionada...
var retweetContent = $( ".messages-thread-template" ).html();
$( retweetContent ).clone().appendTo( ".directMessages .modal-content" ).hide().fadeIn( "fast" );
var dmTitle = $( ".directMessages h3" );
dmTitle.html("Direct messages with <span></span>");
dmTitle = dmTitle.find("span");
var dmConversation = $(".directMessages");
openDmConversation(dm_screenname, dmTitle, dmConversation);
var $dmForm = $( ".dm-form-template" ).children().clone(true);
$dmForm.addClass("open");
$dmForm.appendTo( ".directMessages .modal-wrapper" ).hide().fadeIn( "fast" );
}
function initInterfaceDirectMsg() {
$( ".direct-messages" ).bind( "click", directMessagesPopup );
$( "#dm-snippet-template" ).bind( "click", hideDmSnippetShowDmThread );
$( ".dm-submit").click( directMsgSubmit );
$( ".userMenu-messages a" ).bind( "click", directMessagesPopup );
$( ".direct-messages-with-user" ).bind( "click", directMessagesWithUserPopup );
}

553
twister_following.js

@ -0,0 +1,553 @@ @@ -0,0 +1,553 @@
// twister_following.js
// 2013 Miguel Freitas
//
// Manage list of following users. Load/Save to localstorage and DHT.
// Provides random user suggestions to follow.
var followingUsers = [];
var _isFollowPublic = {};
var _followsPerPage = 200;
var _maxFollowingPages = 50;
var _followingSeqNum = 0;
var _followSuggestions = [];
var _searchingPartialUsers = "";
var _searchKeypressTimer = undefined;
var _lastSearchUsersResults = [];
var _lastLoadFromDhtTime = 0;
// load followingUsers from localStorage
function loadFollowingFromStorage() {
var ns=$.initNamespaceStorage(defaultScreenName);
if( ns.localStorage.isSet("followingUsers") )
followingUsers = ns.localStorage.get("followingUsers");
if( ns.localStorage.isSet("isFollowPublic") )
_isFollowPublic = ns.localStorage.get("isFollowPublic");
if( ns.localStorage.get("followingSeqNum") > _followingSeqNum)
_followingSeqNum = ns.localStorage.get("followingSeqNum");
if( ns.localStorage.isSet("lastLoadFromDhtTime") )
_lastLoadFromDhtTime = ns.localStorage.get("lastLoadFromDhtTime");
// follow ourselves
if(followingUsers.indexOf(defaultScreenName) < 0) {
followingUsers.push(defaultScreenName);
}
}
// save list of following to localStorage
function saveFollowingToStorage() {
var ns=$.initNamespaceStorage(defaultScreenName);
ns.localStorage.set("followingUsers", followingUsers);
ns.localStorage.set("isFollowPublic", _isFollowPublic);
ns.localStorage.set("followingSeqNum", _followingSeqNum);
ns.localStorage.set("lastLoadFromDhtTime", _lastLoadFromDhtTime);
}
// load public list of following users from dht resources
// "following1", "following2" etc.
// it will stop loading when resource is empty
// callback is called as: doneCb(doneArg, followingList, seqNum)
function loadFollowingFromDht(username, pageNumber, followingList, seqNum, doneCb, doneArg) {
if( !pageNumber ) pageNumber = 1;
dhtget( username, "following" + pageNumber, "s",
function(args, following, rawdata) {
if( rawdata ) {
var seq = parseInt(rawdata[0]["p"]["seq"]);
if( seq > args.seqNum ) args.seqNum = seq;
}
if( following ) {
for( var i = 0; i < following.length; i++ ) {
if( args.followingList.indexOf(following[i]) < 0 ) {
args.followingList.push(following[i]);
}
}
}
if( following && following.length && args.pageNumber < _maxFollowingPages) {
loadFollowingFromDht(username, args.pageNumber,
args.followingList, args.seqNum,
args.doneCb, args.doneArg);
} else {
if( args.doneCb )
args.doneCb(args.doneArg, args.followingList, args.seqNum);
}
}, {pageNumber:pageNumber+1, followingList:followingList, seqNum:seqNum,
doneCb:doneCb, doneArg:doneArg});
}
// get number of following from dht and set item.text()
function getNumFollowing( username, item ) {
loadFollowingFromDht( username, 1, [], 0,
function(args, following, seqNum) {
item.text( following.length );
}, null);
}
function loadFollowingIntoList( username, html_list ) {
loadFollowingFromDht( username, 1, [], 0,
function(args, following, seqNum) {
html_list.html("");
$.each(following, function(i, following_user){
var following_user_li = $( "#following-by-user-template" ).children().clone(true);
// link follower to profile page
$(following_user_li.children()[0]).attr("data-screen-name", following_user);
$(following_user_li.children()[0]).attr("href", $.MAL.userUrl(following_user));
following_user_li.find(".following-screen-name b").text(following_user);
getAvatar( following_user, following_user_li.find(".mini-profile-photo") );
var $followingName = following_user_li.find(".mini-following-name");
$followingName.text(following_user);
getFullname( following_user, $followingName );
html_list.append( following_user_li );
});
}, null);
}
// load following list from localStorage and then from the dht resource
function loadFollowing(cbFunc, cbArg) {
loadFollowingFromStorage();
updateFollowing();
var curTime = new Date().getTime() / 1000;
// optimization to avoid costly dht lookup everytime the home is loaded
if( curTime > _lastLoadFromDhtTime + 3600*24 ||
document.URL.indexOf("following") >= 0 ) {
var numFollow = followingUsers.length;
loadFollowingFromDht( defaultScreenName, 1, [], _followingSeqNum,
function(args, following, seqNum) {
var curTime = new Date().getTime() / 1000;
_lastLoadFromDhtTime = curTime;
for( var i = 0; i < following.length; i++ ) {
if( followingUsers.indexOf(following[i]) < 0 ) {
followingUsers.push(following[i]);
}
_isFollowPublic[following[i]] = true;
}
if( args.numFollow != followingUsers.length ||
seqNum != _followingSeqNum ) {
_followingSeqNum = seqNum;
// new following loaded from dht
saveFollowingToStorage();
updateFollowing();
}
if( args.cbFunc )
args.cbFunc(args.cbArg);
}, {numFollow:numFollow, cbFunc:cbFunc, cbArg:cbArg} );
} else {
if( cbFunc )
cbFunc(cbArg);
}
}
// save list of following to dht resource. each page ("following1", following2"...)
// constains up to _followsPerPage elements. alternatively we might keep track
// of total strings size to optimize the maximum storage (8kb in node.cpp, but 4kb is
// probably a good target).
function saveFollowingToDht() {
var following = [];
var pageNumber = 1;
for( var i = 0; i < followingUsers.length; i++ ) {
if( followingUsers[i] in _isFollowPublic &&
_isFollowPublic[followingUsers[i]] ) {
following.push(followingUsers[i]);
}
if( following.length == _followsPerPage || i == followingUsers.length-1) {
dhtput( defaultScreenName, "following" + pageNumber, "s",
following, defaultScreenName, _followingSeqNum+1 );
pageNumber++;
following = [];
}
}
dhtput( defaultScreenName, "following" + pageNumber, "s",
following, defaultScreenName, _followingSeqNum+1 );
_followingSeqNum++;
}
// save following to local storage, dht and json rpc
function saveFollowing(cbFunc, cbArg) {
saveFollowingToStorage();
saveFollowingToDht();
updateFollowing(cbFunc, cbArg);
}
// update json rpc with current list of following
function updateFollowing(cbFunc, cbArg) {
twisterRpc("follow", [defaultScreenName,followingUsers],
function(args, ret) {
if( args.cbFunc )
args.cbFunc(args.cbArg, true);
}, {cbFunc:cbFunc, cbArg:cbArg},
function(args, ret) {
console.log("ajax error:" + ret);
if( args.cbFunc )
args.cbFunc(args.cbArg, false);
}, cbArg);
}
// follow a new single user.
// it is safe to call this even if username is already in followingUsers.
// may also be used to set/clear publicFollow.
function follow(user, publicFollow, cbFunc, cbArg) {
if( followingUsers.indexOf(user) < 0 ) {
followingUsers.push(user);
}
if( publicFollow == undefined || publicFollow )
_isFollowPublic[user] = true;
else
delete _isFollowPublic[user];
saveFollowing(cbFunc, cbArg);
}
// unfollow a single user
function unfollow(user, cbFunc, cbArg) {
var i = followingUsers.indexOf(user);
if( i >= 0 ) {
followingUsers.splice(i,1);
}
delete _isFollowPublic[user];
saveFollowing();
twisterRpc("unfollow", [defaultScreenName,[user]],
function(args, ret) {
if( args.cbFunc )
args.cbFunc(args.cbArg, true);
}, {cbFunc:cbFunc, cbArg:cbArg},
function(args, ret) {
console.log("ajax error:" + ret);
if( args.cbFunc )
args.cbFunc(args.cbArg, false);
}, {cbFunc:cbFunc, cbArg:cbArg});
}
// check if public following
function isPublicFollowing(user) {
if( followingUsers.indexOf(user) < 0 ) {
return false;
}
if( (user in _isFollowPublic) && _isFollowPublic[user] == true )
return true;
else
return false;
}
// check if following list is empty
function followingEmptyOrMyself() {
if( followingUsers.length == 0 ||
followingUsers.length == 1 && followingUsers[0] == defaultScreenName )
return true;
else
return false;
}
// randomly choose a user we follow, get "following1" from him and them
// choose a suggestion from their list. this function could be way better, but
// that's about the simplest we may get to start with.
function getRandomFollowSuggestion(cbFunc, cbArg) {
if( followingEmptyOrMyself() ) {
cbFunc(cbArg, null, null);
return;
}
var i;
do{
var i = parseInt( Math.random() * followingUsers.length );
} while( i < followingUsers.length && followingUsers[i] == defaultScreenName);
if( i < followingUsers.length ) {
loadFollowingFromDht( followingUsers[i], 1, [], 0,
function(args, following, seqNum) {
if( following ) {
var suggested = false;
var j = parseInt( Math.random() * following.length );
for( ; j < following.length; j++ ) {
if( followingUsers.indexOf(following[j]) < 0 &&
_followSuggestions.indexOf(following[j]) < 0 ) {
args.cbFunc(args.cbArg, following[j], args.followedBy);
_followSuggestions.push(following[j]);
suggested = true;
break;
}
}
if( !suggested ) {
args.cbFunc(args.cbArg, null, null);
}
}
}, {cbFunc:cbFunc, cbArg:cbArg, followedBy:followingUsers[i]});
} else {
cbFunc(cbArg, null, null);
}
}
// adds following users to the interface (following.html)
function showFollowingUsers(){
var $notFollowing = $(".not-following-any");
if( followingEmptyOrMyself() ) {
$notFollowing.show();
} else {
$notFollowing.hide();
}
var $followingList = $(".following-list");
var $template = $("#following-user-template").detach();
$followingList.empty();
$followingList.append($template);
for( var i = 0; i < followingUsers.length; i++ ) {
var resItem = $template.clone(true);
resItem.removeAttr('id');
resItem.show();
resItem.find(".mini-profile-info").attr("data-screen-name", followingUsers[i]);
resItem.find(".following-screen-name").text(followingUsers[i]);
resItem.find("a.open-profile-modal").attr("href",$.MAL.userUrl(followingUsers[i]));
resItem.find("a.unfollow").attr("href",$.MAL.unfollowUrl(followingUsers[i]));
resItem.find("a.direct-messages-with-user").attr("href", $.MAL.dmchatUrl(followingUsers[i]));
resItem.find(".public-following").prop("checked",isPublicFollowing(followingUsers[i]));
getAvatar(followingUsers[i],resItem.find(".mini-profile-photo"));
getFullname(followingUsers[i],resItem.find(".mini-profile-name"));
if( followingUsers[i] == defaultScreenName ) {
resItem.find("button").hide();
}
resItem.appendTo($followingList);
}
$.MAL.followingListLoaded();
}
function processSuggestion(arg, suggestion, followedBy) {
var dashboard = $(".follow-suggestions");
if( suggestion ) {
var item = $("#follow-suggestion-template").clone(true);
item.removeAttr("id");
item.find(".twister-user-info").attr("data-screen-name", suggestion);
item.find(".twister-user-name").attr("href", $.MAL.userUrl(suggestion));
item.find(".twister-by-user-name").attr("href", $.MAL.userUrl(followedBy));
item.find(".twister-user-tag").text("@" + suggestion);
getAvatar(suggestion,item.find(".twister-user-photo"));
getFullname(suggestion,item.find(".twister-user"));
$spanFollowedBy = item.find(".followed-by");
$spanFollowedBy.text(followedBy);
getFullname(followedBy,$spanFollowedBy);
dashboard.append(item);
}
}
function closeSearchDialog()
{
var $this = $(".userMenu-search-field");//$( this );
$( this ).siblings().slideUp( "fast" );
removeUsersFromDhtgetQueue( _lastSearchUsersResults );
_lastSearchUsersResults = [];
}
function userSearchKeypress(item) {
var partialName = $(".userMenu-search-field").val().toLowerCase();
if ( partialName.substr( 0, 1 ) == '@' ) {
partialName = partialName.substr( 1 );
}
//var partialName = item.val();
if( !partialName.length ) {
closeSearchDialog();
} else {
if( _searchKeypressTimer !== undefined )
clearTimeout(_searchKeypressTimer);
if( _searchingPartialUsers.length ) {
_searchingPartialUsers = partialName;
} else {
_searchKeypressTimer = setTimeout( function() {
_searchKeypressTimer = undefined;
searchPartialUsername(partialName);
}, 600);
}
}
}
function searchPartialUsername(partialName) {
_searchingPartialUsers = partialName;
twisterRpc("listusernamespartial", [partialName,10],
function(partialName, ret) {
processDropdownUserResults(partialName, ret)
}, partialName,
function(cbArg, ret) {
console.log("ajax error:" + ret);
}, {});
}
function processDropdownUserResults(partialName, results){
if( partialName != _searchingPartialUsers ) {
searchPartialUsername( _searchingPartialUsers );
return;
}
removeUsersFromDhtgetQueue( _lastSearchUsersResults );
_lastSearchUsersResults = results;
var typeaheadAccounts = $(".userMenu-search-profiles");
var template = $("#search-profile-template").detach();
typeaheadAccounts.empty();
typeaheadAccounts.append(template);
if( results.length ) {
for( var i = 0; i < results.length; i++ ) {
if( results[i] == defaultScreenName )
continue;
var resItem = template.clone(true);
resItem.removeAttr('id');
resItem.show();
resItem.find(".mini-profile-info").attr("data-screen-name", results[i]);
resItem.find(".mini-screen-name b").text(results[i]);
resItem.find("a.open-profile-modal").attr("href",$.MAL.userUrl(results[i]));
getAvatar(results[i],resItem.find(".mini-profile-photo"));
getFullname(results[i],resItem.find(".mini-profile-name"));
resItem.appendTo(typeaheadAccounts);
}
$.MAL.searchUserListLoaded();
} else {
closeSearchDialog();
}
_searchingPartialUsers = "";
}
function userClickFollow(e) {
e.stopPropagation();
e.preventDefault();
var $this = $(this);
var $userInfo = $this.closest("[data-screen-name]");
var username = $userInfo.attr("data-screen-name");
follow(username, true, function() {
// delay reload so dhtput may do it's job
window.setTimeout("location.reload();",500);
});
}
function initUserSearch() {
var $userSearchField = $( ".userMenu-search-field" );
$userSearchField.keyup( userSearchKeypress );
$userSearchField.bind( "click", userSearchKeypress );
$userSearchField.clickoutside( closeSearchDialog );
$("button.follow").bind( "click", userClickFollow );
}
function followingListUnfollow(e) {
e.stopPropagation();
e.preventDefault();
var $this = $(this);
var username = $this.closest(".mini-profile-info").attr("data-screen-name");
unfollow(username, function() {
showFollowingUsers();
});
}
function followingListPublicCheckbox(e) {
e.stopPropagation();
var $this = $(this);
var username = $this.closest(".mini-profile-info").attr("data-screen-name");
var public = false;
$this.toggleClass( "private" );
if( $this.hasClass( "private" ) ) {
$this.text( polyglot.t("Private") );
} else {
$this.text( polyglot.t("Public") );
public = true;
}
follow(username, public);
}
function requestSwarmProgress() {
twisterRpc("getlasthave", [defaultScreenName],
function(args, ret) {processSwarmProgressPartial(ret);}, null,
function(args, ret) {console.log("ajax error:" + ret);}, null);
}
function processSwarmProgressPartial(lastHaves)
{
if( defaultScreenName in lastHaves ) {
incLastPostId(lastHaves[defaultScreenName]);
}
twisterRpc("getnumpieces", [defaultScreenName],
function(args, ret) {processSwarmProgressFinal(args.lastHaves, ret);},
{lastHaves:lastHaves},
function(args, ret) {console.log("ajax error:" + ret);}, null);
}
function processSwarmProgressFinal(lastHaves, numPieces)
{
for( var user in lastHaves ) {
if( lastHaves.hasOwnProperty(user) && numPieces.hasOwnProperty(user) ) {
var $userDiv = $(".mini-profile-info[data-screen-name='" + user + "']");
if( $userDiv.length ) {
var $status = $userDiv.find(".swarm-status");
$status.text(polyglot.t("download_posts_status", { portion: numPieces[user] + "/" + (lastHaves[user]+1) }));
$status.fadeIn();
}
}
}
window.setTimeout("requestSwarmProgress();",2000);
}
function followingChangedUser() {
followingUsers = [];
_isFollowPublic = {};
_followingSeqNum = 0;
_followSuggestions = [];
_lastLoadFromDhtTime = 0;
}
function initInterfaceFollowing() {
initInterfaceCommon();
initUserSearch();
initInterfaceDirectMsg();
$("button.unfollow").bind( "click", followingListUnfollow );
$(".public-following").bind( "click", followingListPublicCheckbox );
$(".mentions-from-user").bind( "click", openMentionsModal );
initUser( function() {
if( !defaultScreenName ) {
alert(polyglot.t("username_undefined"));
$.MAL.goLogin();
return;
}
checkNetworkStatusAndAskRedirect();
$(".postboard-loading").fadeIn();
loadFollowing( function(args) {
showFollowingUsers();
requestSwarmProgress();
});
initMentionsCount();
initDMsCount();
});
}

231
twister_formatpost.js

@ -0,0 +1,231 @@ @@ -0,0 +1,231 @@
// twister_formatpost.js
// 2013 Miguel Freitas
//
// Format JSON posts and DMs to HTML.
// format "userpost" to html element
// kind = "original"/"ancestor"/"descendant"
function postToElem( post, kind ) {
/*
"userpost" :
{
"n" : username,
"k" : seq number,
"t" : "post" / "dm" / "rt"
"msg" : message (post/rt)
"time" : unix utc
"height" : best height at user
"dm" : encrypted message (dm) -opt
"rt" : original userpost - opt
"sig_rt" : sig of rt - opt
"reply" : - opt
{
"n" : reference username
"k" : reference k
}
}
"sig_userpost" : signature by userpost.n
*/
// Obtain data from userpost
var postJson = $.toJSON(post);
var userpost = post["userpost"];
if( "rt" in userpost ) {
var rt = userpost["rt"];
var n = rt["n"];
var k = rt["k"];
var t = rt["time"];
var msg = rt["msg"];
var content_to_rt = $.toJSON(rt);
var content_to_sigrt = userpost["sig_rt"];
var retweeted_by = userpost["n"];
} else {
var n = userpost["n"];
var k = userpost["k"];
var t = userpost["time"];
var msg = userpost["msg"]
var content_to_rt = $.toJSON(userpost);
var content_to_sigrt = post["sig_userpost"];
var retweeted_by = undefined;
}
// Now create the html elements
var elem = $.MAL.getPostTemplate().clone(true);
elem.removeAttr('id');
elem.addClass(kind);
elem.attr('data-time', t);
var postData = elem.find(".post-data");
postData.addClass(kind);
postData.attr('data-userpost', postJson);
postData.attr('data-content_to_rt', content_to_rt);
postData.attr('data-content_to_sigrt', content_to_sigrt);
postData.attr('data-screen-name', n);
postData.attr('data-id', k);
postData.attr('data-text', msg);
if( "reply" in userpost ) {
postData.attr('data-replied-to-screen-name', userpost["reply"]["n"]);
postData.attr('data-replied-to-id', userpost["reply"]["k"]);
}
var postInfoName = elem.find(".post-info-name");
postInfoName.attr('href',$.MAL.userUrl(n));
postInfoName.text(n);
getFullname( n, postInfoName );
elem.find(".post-info-tag").text = "@" + n;
getAvatar( n, elem.find(".avatar") );
elem.find(".post-info-time").text(timeGmtToText(t));
elem.find(".post-info-time").attr("title",timeSincePost(t));
var mentions = [];
htmlFormatMsg( msg, elem.find(".post-text"), mentions);
postData.attr('data-text-mentions', mentions);
var replyTo = "";
if( n != defaultScreenName )
replyTo += "@" + n + " ";
for( var i = 0; i < mentions.length; i++ ) {
if( mentions[i] != n && mentions[i] != defaultScreenName ) {
replyTo += "@" + mentions[i] + " ";
}
}
elem.find(".post-area-new textarea").attr("placeholder", polyglot.t("reply_to", { fullname: replyTo })+ "...");
elem.find(".post-area-new textarea").attr("data-reply-to",replyTo);
postData.attr("data-reply-to",replyTo);
if( retweeted_by != undefined ) {
elem.find(".post-context").show();
var retweetedByElem = elem.find(".post-retransmited-by");
retweetedByElem.attr("href", $.MAL.userUrl(retweeted_by));
retweetedByElem.text(retweeted_by);
}
return elem;
}
// format dmdata (returned by getdirectmsgs) to display in "snippet" per user list
function dmDataToSnippetItem(dmData, remoteUser) {
var dmItem = $("#dm-snippet-template").clone(true);
dmItem.removeAttr('id');
dmItem.attr("data-dm-screen-name",remoteUser);
dmItem.attr("data-last_id", dmData.id);
dmItem.find(".post-info-tag").text("@" + remoteUser);
dmItem.find("a.post-info-name").attr("href", $.MAL.userUrl(remoteUser));
dmItem.find("a.dm-chat-link").attr("href", $.MAL.dmchatUrl(remoteUser));
getAvatar( remoteUser, dmItem.find(".post-photo").find("img") );
getFullname( remoteUser, dmItem.find("a.post-info-name") );
dmItem.find(".post-text").html(escapeHtmlEntities(dmData.text));
dmItem.find(".post-info-time").text(timeGmtToText(dmData.time));
dmItem.find(".post-info-time").attr("title",timeSincePost(dmData.time));
return dmItem;
}
// format dmdata (returned by getdirectmsgs) to display in conversation thread
function dmDataToConversationItem(dmData, localUser, remoteUser) {
var classDm = dmData.fromMe ? "sent" : "received";
var dmItem = $("#dm-chat-template").clone(true);
dmItem.removeAttr('id');
dmItem.addClass(classDm);
getAvatar(dmData.fromMe ? localUser : remoteUser, dmItem.find(".post-photo").find("img") );
dmItem.find(".post-info-time").text(timeGmtToText(dmData.time));
dmItem.find(".post-info-time").attr("title",timeSincePost(dmData.time));
var mentions = [];
htmlFormatMsg( dmData.text, dmItem.find(".post-text"), mentions);
return dmItem;
}
// convert message text to html, featuring @users and links formating.
// todo: hashtags
function htmlFormatMsg( msg, output, mentions ) {
var tmp;
var match = null;
var index;
var reAll = new RegExp("(#|@|http[s]?://)");
var reHttp = new RegExp("http[s]?://");
msg = escapeHtmlEntities(msg);
while( msg != undefined && msg.length ) {
match = reAll.exec(msg);
if( match ) {
if( match[0] == "@" ) {
output.append(msg.substr(0, match.index));
tmp = msg.substr(match.index+1);
var username = _extractUsername(tmp);
if( username.length ) {
if( mentions.indexOf(username) < 0 )
mentions.push(username);
var userLinkTemplate = $("#msg-user-link-template").clone(true);
userLinkTemplate.removeAttr("id");
userLinkTemplate.attr("href",$.MAL.userUrl(username));
userLinkTemplate.text("@"+username);
output.append(userLinkTemplate);
msg = tmp.substr(String(username).length);
continue;
}
}
if( reHttp.exec(match[0]) ) {
output.append(msg.substr(0, match.index));
tmp = msg.substring(match.index);
var space = tmp.indexOf(" ");
var url;
if( space != -1 ) url = tmp.substring(0,space); else url = tmp;
if( url.length ) {
var extLinkTemplate = $("#external-page-link-template").clone(true);
extLinkTemplate.removeAttr("id");
extLinkTemplate.attr("href",url);
extLinkTemplate.text(url);
extLinkTemplate.attr("title",url);
output.append(extLinkTemplate);
msg = tmp.substr(String(url).length);
continue;
}
}
if( match[0] == "#" ) {
output.append(msg.substr(0, match.index));
tmp = msg.substr(match.index+1);
var hashtag = _extractUsername(tmp);
if( hashtag.length ) {
var hashtagLinkTemplate = $("#hashtag-link-template").clone(true);
hashtagLinkTemplate.removeAttr("id");
hashtagLinkTemplate.attr("href",$.MAL.hashtagUrl(hashtag));
hashtagLinkTemplate.text("#"+hashtag);
output.append(hashtagLinkTemplate);
msg = tmp.substr(String(hashtag).length);
continue;
}
}
}
output.append(msg);
msg = "";
}
}
// internal function for htmlFormatMsg
function _extractUsername(s) {
var username = "";
for( var i = 0; i < s.length; i++ ) {
var c = s.charCodeAt(i);
if( (c >= 'a'.charCodeAt(0) && c <= 'z'.charCodeAt(0)) ||
(c >= 'A'.charCodeAt(0) && c <= 'Z'.charCodeAt(0)) ||
(c >= '0'.charCodeAt(0) && c <= '9'.charCodeAt(0)) ||
c == '_'.charCodeAt(0) ) {
username += s[i];
} else {
break;
}
}
return username.toLowerCase();
}
function escapeHtmlEntities(str) {
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;');
}

368
twister_io.js

@ -0,0 +1,368 @@ @@ -0,0 +1,368 @@
// twister_io.js
// 2013 Miguel Freitas
//
// low-level twister i/o.
// implements requests of dht resources. multiple pending requests to the same resource are joined.
// cache results (profile, avatar, etc) in memory.
// avatars are cached in localstored (expiration = 24 hours)
// main json rpc method. receives callbacks for success and error
function twisterRpc(method, params, resultFunc, resultArg, errorFunc, errorArg) {
var foo = new $.JsonRpcClient({ ajaxUrl: '/', username: 'user', password: 'pwd'});
foo.call(method, params,
function(ret) { resultFunc(resultArg, ret); },
function(ret) { if(ret != null) errorFunc(errorArg, ret); }
);
}
// join multiple dhtgets to the same resources in this map
var _dhtgetPendingMap = {};
// memory cache for profile and avatar
var _profileMap = {};
var _avatarMap = {};
// number of dhtgets in progress (requests to the daemon)
var _dhtgetsInProgress = 0;
// keep _maxDhtgets smaller than the number of daemon/browser sockets
// most browsers limit to 6 per domain (see http://www.browserscope.org/?category=network)
var _maxDhtgets = 5;
// requests not yet sent to the daemon due to _maxDhtgets limit
var _queuedDhtgets = [];
// private function to define a key in _dhtgetPendingMap
function _dhtgetLocator(username, resource, multi) {
return username+";"+resource+";"+multi;
}
function _dhtgetAddPending(locator, cbFunc, cbArg)
{
if( !(locator in _dhtgetPendingMap) ) {
_dhtgetPendingMap[locator] = [];
}
_dhtgetPendingMap[locator].push( {cbFunc:cbFunc, cbArg:cbArg} );
}
function _dhtgetProcessPending(locator, multi, ret)
{
if( locator in _dhtgetPendingMap ) {
for( var i = 0; i < _dhtgetPendingMap[locator].length; i++) {
var cbFunc = _dhtgetPendingMap[locator][i].cbFunc;
var cbArg = _dhtgetPendingMap[locator][i].cbArg;
if( multi == 's' ) {
if( ret[0] != undefined ) {
cbFunc(cbArg, ret[0]["p"]["v"], ret);
} else {
cbFunc(cbArg, null);
}
} else {
var multiret = [];
for (var j = 0; j < ret.length; j++) {
multiret.push(ret[j]["p"]["v"]);
}
cbFunc(cbArg, multiret, ret);
}
}
delete _dhtgetPendingMap[locator];
} else {
console.log("warning: _dhtgetProcessPending with unknown locator "+locator);
}
}
function _dhtgetAbortPending(locator)
{
if( locator in _dhtgetPendingMap ) {
for( var i = 0; i < _dhtgetPendingMap[locator].length; i++) {
var cbFunc = _dhtgetPendingMap[locator][i].cbFunc;
var cbArg = _dhtgetPendingMap[locator][i].cbArg;
cbFunc(cbArg, null);
}
delete _dhtgetPendingMap[locator];
} else {
console.log("warning: _dhtgetAbortPending with unknown locator "+locator);
}
}
// get data from dht resource
// the value ["v"] is extracted from response and returned to callback
// null is passed to callback in case of an error
function dhtget( username, resource, multi, cbFunc, cbArg, timeoutArgs ) {
var locator = _dhtgetLocator(username, resource, multi);
if( locator in _dhtgetPendingMap) {
_dhtgetAddPending(locator, cbFunc, cbArg);
} else {
_dhtgetAddPending(locator, cbFunc, cbArg);
// limit the number of simultaneous dhtgets.
// this should leave some sockets for other non-blocking daemon requests.
if( _dhtgetsInProgress < _maxDhtgets ) {
_dhtgetInternal( username, resource, multi, timeoutArgs );
} else {
// just queue the locator. it will be unqueue when some dhtget completes.
_queuedDhtgets.push(locator);
}
}
}
function _dhtgetInternal( username, resource, multi, timeoutArgs ) {
var locator = _dhtgetLocator(username, resource, multi);
_dhtgetsInProgress++;
argsList = [username,resource,multi];
if( typeof timeoutArgs !== 'undefined' ) {
argsList = argsList.concat(timeoutArgs);
}
twisterRpc("dhtget", argsList,
function(args, ret) {
_dhtgetsInProgress--;
_dhtgetProcessPending(args.locator, args.multi, ret);
_dhtgetDequeue();
}, {locator:locator,multi:multi},
function(cbArg, ret) {
console.log("ajax error:" + ret);
_dhtgetsInProgress--;
_dhtgetAbortPending(locator);
_dhtgetDequeue();
}, locator);
}
function _dhtgetDequeue() {
if( _queuedDhtgets.length ) {
var locatorSplit = _queuedDhtgets.pop().split(";");
_dhtgetInternal(locatorSplit[0], locatorSplit[1], locatorSplit[2]);
}
}
// removes queued dhtgets (requests that have not been made to the daemon)
// this is used by user search dropdown to discard old users we are not interested anymore
function removeUserFromDhtgetQueue(username) {
var resources = ["profile","avatar"]
for (var i = 0; i < resources.length; i++) {
var locator = _dhtgetLocator(username,resources[i],"s");
var locatorIndex = _queuedDhtgets.indexOf(locator);
if( locatorIndex > -1 ) {
_queuedDhtgets.splice(locatorIndex,1);
delete _dhtgetPendingMap[locator];
}
}
}
function removeUsersFromDhtgetQueue(users) {
for (var i = 0; i < users.length; i++ ) {
removeUserFromDhtgetQueue( users[i] );
}
}
// store value at the dht resource
function dhtput( username, resource, multi, value, sig_user, seq, cbFunc, cbArg ) {
twisterRpc("dhtput", [username,resource,multi, value, sig_user, seq],
function(args, ret) {
if( args.cbFunc )
args.cbFunc(args.cbArg, true);
}, {cbFunc:cbFunc, cbArg:cbArg},
function(args, ret) {
console.log("ajax error:" + ret);
if( args.cbFunc )
args.cbFunc(args.cbArg, false);
}, cbArg);
}
// get something from profile and store it in item.text or do callback
function getProfileResource( username, resource, item, cbFunc, cbArg ){
var profile = undefined;
if( username in _profileMap ) {
profile = _profileMap[username];
} else {
profile = _getResourceFromStorage("profile:" + username);
}
if( profile ) {
_profileMap[username] = profile;
if( item )
item.text(profile[resource]);
if( cbFunc )
cbFunc(cbArg, profile[resource]);
} else {
dhtget( username, "profile", "s",
function(args, profile) {
if( profile ) {
_profileMap[args.username] = profile;
_putResourceIntoStorage("profile:" + username, profile);
if( args.item )
args.item.text(profile[resource]);
if( args.cbFunc )
args.cbFunc(args.cbArg, profile[resource]);
} else {
if( args.cbFunc )
args.cbFunc(args.cbArg, null);
}
}, {username:username,item:item,cbFunc:cbFunc,cbArg:cbArg});
}
}
// get fullname and store it in item.text
function getFullname( username, item ){
// Set the username first in case the profile has no fullname
item.text(username);
getProfileResource( username, "fullname", undefined,
function(args, value) {
if( value ) {
value.replace(/^\s+|\s+$/g, '');
if( value.length )
args.item.text(value);
}
}, {item: item} );
}
// get bio and store it in item.text
function getBio( username, item ){
getProfileResource( username, "bio", item);
}
// get location and store it in item.text
function getLocation( username, item ){
getProfileResource( username, "location", item);
}
// get location and store it in item.text
function getWebpage( username, item ){
getProfileResource( username, "url", item,
function(args, val) {
if( val.indexOf("://") < 0 ) {
val = "http://" + val;
}
args.item.attr("href", val);
}, {item:item} );
}
// we must cache avatar results to disk to lower bandwidth on
// other peers. dht server limits udp rate so requesting too much
// data will only cause new requests to fail.
function _getResourceFromStorage(locator) {
var storage = $.localStorage;
if( storage.isSet(locator) ) {
var storedResource = storage.get(locator);
var curTime = new Date().getTime() / 1000;
// avatar is downloaded once per day
if( storedResource.time + 3600*24 > curTime ) {
return storedResource.data;
}
}
return null;
}
function _putResourceIntoStorage(locator, data) {
var curTime = new Date().getTime() / 1000;
var storedResource = {time: curTime, data: data};
var storage = $.localStorage;
storage.set(locator, storedResource);
}
// get avatar and set it in img.attr("src")
function getAvatar( username, img ){
if( username == "nobody" ) {
img.attr('src', "img/tornado_avatar.png");
return;
}
if( username in _avatarMap ) {
//img.attr('src', "data:image/jpg;base64,"+avatarMap[username]);
img.attr('src', _avatarMap[username]);
} else {
var data = _getResourceFromStorage("avatar:" + username);
if( data ) {
_avatarMap[username] = data;
img.attr('src', data);
} else {
dhtget( username, "avatar", "s",
function(args, imagedata) {
if( imagedata && imagedata.length ) {
_avatarMap[args.username] = imagedata;
_putResourceIntoStorage("avatar:" + username, imagedata);
args.img.attr('src', imagedata);
}
}, {username:username,img:img} );
}
}
}
function clearAvatarAndProfileCache(username) {
var storage = $.localStorage;
storage.remove("avatar:" + username);
storage.remove("profile:" + username);
if( username in _avatarMap ) {
delete _avatarMap[username];
}
if( username in _profileMap ) {
delete _profileMap[username];
}
}
// get estimative for number of followers (use known peers of torrent tracker)
function getFollowers( username, item ) {
dhtget( username, "tracker", "m",
function(args, ret) {
if( ret && ret.length && ret[0]["followers"] ) {
args.item.text(ret[0]["followers"])
}
}, {username:username,item:item} );
}
function getPostsCount( username, item ) {
dhtget( username, "status", "s",
function(args, v) {
var count = 0;
if( v && v["userpost"] ) {
count = v["userpost"]["k"]+1;
}
var oldCount = parseInt(args.item.text());
if( !oldCount || count > oldCount ) {
args.item.text(count);
}
if( username == defaultScreenName && count ) {
incLastPostId( v["userpost"]["k"] );
}
}, {username:username,item:item} );
}
function checkPubkeyExists(username, cbFunc, cbArg) {
// pubkey is checked in block chain db.
// so only accepted registrations are reported (local wallet users are not)
twisterRpc("dumppubkey", [username],
function(args, ret) {
args.cbFunc(args.cbArg, ret.length > 0);
}, {cbFunc:cbFunc, cbArg:cbArg},
function(args, ret) {
alert(polyglot.t("error_connecting_to_daemon"));
}, {cbFunc:cbFunc, cbArg:cbArg});
}
// pubkey is obtained from block chain db.
// so only accepted registrations are reported (local wallet users are not)
// cbFunc is called as cbFunc(cbArg, pubkey)
// if user doesn't exist then pubkey.length == 0
function dumpPubkey(username, cbFunc, cbArg) {
twisterRpc("dumppubkey", [username],
function(args, ret) {
args.cbFunc(args.cbArg, ret);
}, {cbFunc:cbFunc, cbArg:cbArg},
function(args, ret) {
alert(polyglot.t("error_connecting_to_daemon"));
}, {cbFunc:cbFunc, cbArg:cbArg});
}
// privkey is obtained from wallet db
// so privkey is returned even for unsent transactions
function dumpPrivkey(username, cbFunc, cbArg) {
twisterRpc("dumpprivkey", [username],
function(args, ret) {
args.cbFunc(args.cbArg, ret);
}, {cbFunc:cbFunc, cbArg:cbArg},
function(args, ret) {
args.cbFunc(args.cbArg, "");
console.log("dumpprivkey: user unknown");
}, {cbFunc:cbFunc, cbArg:cbArg});
}

277
twister_network.js

@ -0,0 +1,277 @@ @@ -0,0 +1,277 @@
// twister_network.js
// 2013 Miguel Freitas
//
// Provides functions for periodic network status check
// Interface to network.html page.
var twisterdConnections = 0;
var twisterdAddrman = 0;
var twisterDhtNodes = 0;
var twisterdBlocks = 0;
var twisterdLastBlockTime = 0;
var twisterdConnectedAndUptodate = false;
// ---
function requestNetInfo(cbFunc, cbArg) {
twisterRpc("getinfo", [],
function(args, ret) {
twisterdConnections = ret.connections;
twisterdAddrman = ret.addrman_total;
twisterdBlocks = ret.blocks;
twisterDhtNodes = ret.dht_nodes;
twisterVersion = ("0000000" + ret.version).slice(-8);
twisterDisplayVersion = twisterVersion.slice(0,2) + '.' +
twisterVersion.slice(2,4) + '.' +
twisterVersion.slice(4,6) + '.' +
twisterVersion.slice(6,8);
$(".connection-count").text(twisterdConnections);
$(".known-peers").text(twisterdAddrman);
$(".blocks").text(twisterdBlocks);
$(".dht-nodes").text(twisterDhtNodes);
$(".version").text(twisterDisplayVersion);
if( !twisterdConnections ) {
$.MAL.setNetworkStatusMsg(polyglot.t("Connection lost."), false);
twisterdConnectedAndUptodate = false;
}
if( args.cbFunc )
args.cbFunc(args.cbArg);
}, {cbFunc:cbFunc, cbArg:cbArg},
function(args, ret) {
console.log("Error connecting to local twister daemon.");
}, {});
}
function peerKeypress() {
var peer = $(".new-peer-addr").val();
var $button = $(".add-peer");
if( peer.length ) {
$.MAL.enableButton( $button );
} else {
$.MAL.disableButton( $button );
}
}
function dnsKeypress() {
var peer = $(".new-dns-addr").val();
var $button = $(".add-dns");
if( peer.length ) {
$.MAL.enableButton( $button );
} else {
$.MAL.disableButton( $button );
}
}
function addPeerClick() {
var peer = $(".new-peer-addr").val();
twisterRpc("addnode", [peer, "onetry"],
function(args, ret) {
$(".new-peer-addr").val("")
}, {},
function(args, ret) {
alert(polyglot.t("error", { error: ret.message }));
}, {});
}
function addDNSClick() {
var dns = $(".new-dns-addr").val();
twisterRpc("adddnsseed", [dns],
function(args, ret) {
$(".new-dns-addr").val("")
}, {},
function(args, ret) {
alert(polyglot.t("error", { error: ret.message }));
}, {});
}
function requestBestBlock(cbFunc, cbArg) {
twisterRpc("getbestblockhash", [],
function(args, hash) {
requestBlock(hash, args.cbFunc, args.cbArg);
}, {cbFunc:cbFunc, cbArg:cbArg},
function(args, ret) {
console.log("getbestblockhash error");
}, {});
}
function requestBlock(hash, cbFunc, cbArg) {
twisterRpc("getblock", [hash],
function(args, block) {
twisterdLastBlockTime = block.time;
$(".last-block-time").text( timeGmtToText(twisterdLastBlockTime) );
if( args.cbFunc )
args.cbFunc(args.cbArg);
}, {cbFunc:cbFunc, cbArg:cbArg},
function(args, ret) {
console.log("requestBlock error");
}, {});
}
function networkUpdate(cbFunc, cbArg) {
requestNetInfo(function () {
requestBestBlock(function(args) {
var curTime = new Date().getTime() / 1000;
if( twisterdConnections ) {
if( twisterdLastBlockTime > curTime + 3600 ) {
$.MAL.setNetworkStatusMsg(polyglot.t("Last block is ahead of your computer time, check your clock."), false);
twisterdConnectedAndUptodate = false;
} else if( twisterdLastBlockTime > curTime - (2 * 3600) ) {
if( twisterDhtNodes ) {
$.MAL.setNetworkStatusMsg(polyglot.t("Block chain is up-to-date, twister is ready to use!"), true);
twisterdConnectedAndUptodate = true;
} else {
$.MAL.setNetworkStatusMsg(polyglot.t("DHT network down."), false);
twisterdConnectedAndUptodate = true;
}
} else {
var daysOld = (curTime - twisterdLastBlockTime) / (3600*24);
$.MAL.setNetworkStatusMsg(polyglot.t("downloading_block_chain", { days: daysOld.toFixed(2) }), false);
twisterdConnectedAndUptodate = false;
}
}
if( args.cbFunc )
args.cbFunc(args.cbArg)
}, {cbFunc:cbFunc, cbArg:cbArg} );
});
}
function getMiningInfo(cbFunc, cbArg) {
twisterRpc("getmininginfo", [],
function(args, ret) {
miningDifficulty = ret.difficulty;
miningHashRate = ret.hashespersec;
$(".mining-difficulty").text(miningDifficulty);
$(".mining-hashrate").text(miningHashRate);
/*
if( !twisterdConnections ) {
$.MAL.setNetworkStatusMsg("Connection lost.", false);
twisterdConnectedAndUptodate = false;
}
*/
if( args.cbFunc )
args.cbFunc(args.cbArg);
}, {cbFunc:cbFunc, cbArg:cbArg},
function(args, ret) {
console.log("Error connecting to local twister daemon.");
}, {});
}
function miningUpdate(cbFunc, cbArg) {
getMiningInfo(cbFunc, cbArg);
}
function getGenerate() {
twisterRpc("getgenerate", [],
function(args, ret) {
var $genblock = $("select.genblock");
if( ret ) {
$genblock.val("enable");
} else {
$genblock.val("disable");
}
}, {},
function(args, ret) {
console.log("getgenerate error");
}, {});
}
function setGenerate() {
var params = [];
params.push($("select.genblock").val() == "enable");
params.push(parseInt($(".genproclimit").val()));
twisterRpc("setgenerate", params,
function(args, ret) {
console.log("setgenerate updated");
}, {},
function(args, ret) {
console.log("getgenerate error");
}, {});
}
function getSpamMsg() {
twisterRpc("getspammsg", [],
function(args, ret) {
var $postArea = $(".spam-msg");
var $localUsersList = $("select.local-usernames.spam-user");
$postArea.val(ret[1]);
$localUsersList.val(ret[0]);
}, {},
function(args, ret) {
console.log("getgenerate error");
}, {});
}
function setSpamMsg() {
var $postArea = $(".spam-msg");
var $localUsersList = $("select.local-usernames.spam-user");
var params = [$localUsersList.val(), $postArea.val()]
twisterRpc("setspammsg", params,
function(args, ret) {
console.log("setspammsg updated");
}, {},
function(args, ret) {
console.log("setspammsg error");
}, {});
}
function exitDaemon() {
if (confirm('Are you sure you want to exit the daemon?\nThe Twister client will stop working.')) {
$( ".terminate-daemon").text("Exiting...");
$( ".terminate-daemon").addClass("disabled");
$.MAL.disableButton( $( ".terminate-daemon") );
twisterRpc("stop", undefined,
function(args, ret) {
console.log("daemon exiting");
setTimeout(function _reload_after_exit() {
window.location.href = '/abort.html';
}, 2000);
}, {},
function(args, ret) {
console.log("error while exiting daemon");
}, {});
}
}
// handlers common to both desktop and mobile
function interfaceNetworkHandlers() {
$( ".new-peer-addr" ).keyup( peerKeypress );
$( ".new-dns-addr" ).keyup( dnsKeypress );
$( ".add-peer").bind( "click", addPeerClick );
$( ".add-dns").bind( "click", addDNSClick );
$( "select.genblock").change( setGenerate );
$( ".update-spam-msg").bind( "click", setSpamMsg );
$( ".terminate-daemon").bind( "click", exitDaemon )
}
function initInterfaceNetwork() {
initInterfaceCommon();
initUser( function () {
getSpamMsg();
if( defaultScreenName ) {
loadFollowing( function() {
initMentionsCount();
initDMsCount();
});
}
});
networkUpdate();
setInterval("networkUpdate()", 2000);
miningUpdate();
setInterval("miningUpdate()", 2000);
getGenerate();
interfaceNetworkHandlers();
}

182
twister_newmsgs.js

@ -0,0 +1,182 @@ @@ -0,0 +1,182 @@
// twister_newmsgs.js
// 2013 Miguel Freitas
//
// Periodically check for new mentions and private messages (DMs)
// Update UI counters in top bar. Load/save state to localStorage.
// --- mentions ---
var _knownMentions = {}
var _lastMentionTime = 0;
var _newMentions = 0;
var PURGE_OLD_MENTIONS_TIMEOUT = 3600 * 24 * 30; // one month
// process a mention received to check if it is really new
function processMention(user, mentionTime, data) {
var key = user + ";" + mentionTime;
var curTime = new Date().getTime() / 1000;
if( mentionTime > curTime + 3600 ) {
console.log("mention from the future will be ignored");
} else {
if( !(key in _knownMentions) ) {
// mention must be somewhat recent compared to last known one to be considered new
if( mentionTime + 3600 > _lastMentionTime ) {
_newMentions++;
_lastMentionTime = mentionTime;
}
_knownMentions[key] = {mentionTime:mentionTime, data:data};
purgeOldMentions();
saveMentionsToStorage();
}
}
}
function purgeOldMentions() {
for( var key in _knownMentions ) {
if( _knownMentions.hasOwnProperty(key) ) {
if( !_knownMentions[key].mentionTime || !_knownMentions[key].data ||
_knownMentions[key].mentionTime + PURGE_OLD_MENTIONS_TIMEOUT < _lastMentionTime ) {
delete _knownMentions[key];
}
}
}
}
function saveMentionsToStorage() {
var ns=$.initNamespaceStorage(defaultScreenName);
ns.localStorage.set("knownMentions", _knownMentions);
ns.localStorage.set("lastMentionTime", _lastMentionTime);
ns.localStorage.set("newMentions", _newMentions);
}
function loadMentionsFromStorage() {
var ns=$.initNamespaceStorage(defaultScreenName);
if( ns.localStorage.isSet("knownMentions") )
_knownMentions = ns.localStorage.get("knownMentions");
if( ns.localStorage.isSet("lastMentionTime") )
_lastMentionTime = ns.localStorage.get("lastMentionTime");
if( ns.localStorage.isSet("newMentions") )
_newMentions = ns.localStorage.get("newMentions");
}
function requestMentionsCount() {
dhtget( defaultScreenName, "mention", "m",
function(args, data) {
if( data ) {
for( var i = 0; i < data.length; i++ ) {
var userpost = data[i]["userpost"];
processMention( userpost["n"], userpost["time"], data[i]);
}
$.MAL.updateNewMentionsUI(_newMentions);
}
}, {},
[10000,2000,3]); // use extended timeout parameters (requires twister_core >= 0.9.14)
}
function resetMentionsCount() {
_newMentions = 0;
saveMentionsToStorage();
$.MAL.updateNewMentionsUI(_newMentions);
}
function initMentionsCount() {
// polling mentions is a temporary solution
loadMentionsFromStorage();
$.MAL.updateNewMentionsUI(_newMentions);
requestMentionsCount();
setInterval("requestMentionsCount()", 10000);
}
function getMentionsData() {
mentions = []
for( var key in _knownMentions ) {
if( _knownMentions.hasOwnProperty(key) && _knownMentions[key].data ) {
mentions.push(_knownMentions[key].data);
}
}
return mentions;
}
// --- direct messages ---
var _lastDMIdPerUser = {};
var _newDMsPerUser = {};
function saveDMsToStorage() {
var ns=$.initNamespaceStorage(defaultScreenName);
ns.localStorage.set("lastDMIdPerUser", _lastDMIdPerUser);
ns.localStorage.set("newDMsPerUser", _newDMsPerUser);
}
function loadDMsFromStorage() {
var ns=$.initNamespaceStorage(defaultScreenName);
if( ns.localStorage.isSet("lastDMIdPerUser") )
_lastDMIdPerUser = ns.localStorage.get("lastDMIdPerUser");
if( ns.localStorage.isSet("newDMsPerUser") )
_newDMsPerUser = ns.localStorage.get("newDMsPerUser");
}
function requestDMsCount() {
var followList = [];
for( var i = 0; i < followingUsers.length; i++ ) {
followList.push({username:followingUsers[i]});
}
twisterRpc("getdirectmsgs", [defaultScreenName, 1, followList],
function(req, dmUsers) {
var updated = false;
for( var u in dmUsers ) {
if( dmUsers.hasOwnProperty(u) ) {
var dmData = dmUsers[u][0];
if( (u in _lastDMIdPerUser) && (u in _newDMsPerUser) ) {
if( dmData.id != _lastDMIdPerUser[u] ) {
_newDMsPerUser[u] += (dmData.id - _lastDMIdPerUser[u]);
updated = true;
}
} else {
_newDMsPerUser[u] = dmData.id+1;
updated = true;
}
_lastDMIdPerUser[u] = dmData.id;
}
}
if( updated ) {
saveDMsToStorage();
$.MAL.updateNewDMsUI(getNewDMsCount());
}
}, null,
function(req, ret) {console.log("ajax error:" + ret);}, null);
}
function getNewDMsCount() {
var newDMs = 0;
for( var key in _newDMsPerUser ) {
if( _newDMsPerUser.hasOwnProperty(key) ) {
newDMs += _newDMsPerUser[key];
}
}
return newDMs;
}
function resetNewDMsCountForUser(user, lastId) {
_newDMsPerUser[user] = 0;
_lastDMIdPerUser[user] = lastId;
saveDMsToStorage();
$.MAL.updateNewDMsUI(getNewDMsCount());
}
function initDMsCount() {
loadDMsFromStorage();
$.MAL.updateNewDMsUI(getNewDMsCount());
requestDMsCount();
//polling not needed: processNewPostsConfirmation will call requestDMsCount.
//setInterval("requestDMsCount()", 5000);
}
function newmsgsChangedUser() {
_knownMentions = {}
_lastMentionTime = 0;
_newMentions = 0;
}

291
twister_timeline.js

@ -0,0 +1,291 @@ @@ -0,0 +1,291 @@
// twister_timeline.js
// 2013 Miguel Freitas
//
// Provides objects to keep track of timeline display state to request new posts efficiently.
//
// Currently is being used only for "home" timeline, but the list of users can be an arbitrary
// subset of users we follow. In other words: this objects may be used for displaying profiles
// of those users more efficiently than iterating through dht posts.
var _idTrackerMap = {};
var _idTrackerSpam = new idTrackerObj();
var _lastHaveMap = {};
var _refreshInProgress = false;
var _newPostsPending = 0;
var timelineLoaded = false;
/* object to keep tracking of post ids for a given user, that is, which
* posts have already been received, processed, shown + which ones to request.
* modes of operation:
* "latestFirstTime" this is the first time the timeline is obtained, we known
* nothing about the last post ids. there will be no gap since
* timeline is empty on screen.
* "latest" this is used when we have a timeline on screen but we want to update
* it with the latest posts. since getposts rpc may limit the number of
* posts to receive, a gap may be created. that is, between the most
* recent post of the previous update and the lower id received by getposts.
* "fillgap" this is used to fill the gap after "latest" was used.
* "older" this is used to scroll down the timeline, to older posts than are
* currently being shown on screen.
*/
function idTrackerObj()
{
this.latest = -1;
this.oldest = -1;
this.gapHigh = -1;
this.gapLow = -1;
// getRequest method creates a single user item of getposts rpc list parameter
this.getRequest = function (mode) {
if( mode == 'latest' || mode == 'latestFirstTime' ) {
this.gapHigh = -1;
this.gapLow = this.latest;
return { since_id: this.latest };
} else if( mode == 'fillgap') {
return { max_id: this.gapHigh-1, since_id: this.gapLow };
} else if( mode == 'older') {
return ( this.oldest >= 0 ) ? { max_id: this.oldest-1 } : {};
} else {
console.log("getRequest: unknown mode");
}
}
// receiveId method notifies that a post was received (and possibly shown)
this.receivedId = function (mode, id, shown){
if( id > this.latest ) this.latest = id;
if( shown ) {
if( this.oldest < 0 || id < this.oldest ) this.oldest = id;
}
if( mode == 'latest' ||
mode == 'latestFirstTime' ||
mode == 'fillgap') {
if( this.gapHigh < 0 || id < this.gapHigh )
this.gapHigh = id;
} else if( mode == 'older') {
// no gaps: posts are already received in descending order
} else {
console.log("receivedId: unknown mode");
}
}
}
/* object to maintain a request state for several users.
* each user is tracked by idTrackerObj in global _idTrackerMap.
*/
function requestObj(users, mode, count, getspam)
{
this.users = users;
this.mode = mode; // 'latest', 'latestFirstTime' or 'older'
this.count = count;
this.getspam = getspam;
// getRequest method returns the list parameter expected by getposts rpc
this.getRequest = function() {
var req = [];
if( this.mode == 'done')
return req;
if( this.getspam ) {
return _idTrackerSpam.getRequest(this.mode);
}
for( var i = 0; i < this.users.length; i++ ) {
var user = this.users[i];
if( !(user in _idTrackerMap) )
_idTrackerMap[user] = new idTrackerObj();
var r = _idTrackerMap[user].getRequest(this.mode);
r.username = user;
req.push(r);
}
return req;
}
// receiveId method notifies that a post was received (and possibly shown)
this.reportProcessedPost = function(user, id, shown) {
if( this.getspam ) {
_idTrackerSpam.receivedId(this.mode, id, shown);
}
if( this.users.indexOf(user) >= 0 ) {
_idTrackerMap[user].receivedId(this.mode, id, shown);
}
}
// doneReportProcessing is called after an getposts response is processed
// mode changing may require a new request (to fill gaps)
this.doneReportProcessing = function(receivedCount) {
if( this.mode == 'latest') this.mode = 'fillgap';
if( this.mode == 'latestFirstTime') this.mode = 'done';
if( this.mode == 'older') this.mode = 'done';
if( receivedCount < this.count ) this.mode = 'done';
}
}
// json rpc with requestObj as parameter
function requestGetposts(req)
{
var r = req.getRequest();
if( !req.getspam ) {
if( r.length ) {
twisterRpc("getposts", [req.count,r],
function(req, posts) {processReceivedPosts(req, posts);}, req,
function(req, ret) {console.log("ajax error:" + ret);}, req);
}
} else {
twisterRpc("getspamposts", [req.count,r.max_id?r.max_id:-1,r.since_id?r.since_id:-1],
function(req, posts) {processReceivedPosts(req, posts);}, req,
function(req, ret) {console.log("ajax error:" + ret);}, req);
}
}
// callback to getposts rpc when updating the timeline
// process the received posts (adding them to screen) and do another
// request if needed
function processReceivedPosts(req, posts)
{
for( var i = 0; i < posts.length; i++ ) {
var post = posts[i];
var streamPost = postToElem(post, "original");
var timePost = post["userpost"]["time"];
streamPost.attr("data-time",timePost);
// post will only be shown if appended to the stream list
var streamPostAppended = false;
// insert the post in timeline ordered by (you guessed) time
// FIXME: lame! searching everything everytime. please optimize!
var streamItemsParent = $.MAL.getStreamPostsParent();
var streamItems = streamItemsParent.children();
if( streamItems.length == 0) {
// timeline is empty
streamItemsParent.append( streamPost );
streamPostAppended = true;
} else {
var j = 0;
for( j = 0; j < streamItems.length; j++) {
var streamItem = streamItems.eq(j);
var timeItem = streamItem.attr("data-time");
if( timeItem == undefined ||
timePost > parseInt(timeItem) ) {
// this post in stream is older, so post must be inserted above
streamItem.before(streamPost);
streamPostAppended = true;
break;
}
}
if( j == streamItems.length ) {
// no older posts in stream, so post is to be inserted below
if( req.mode == "older" || req.mode == "latestFirstTime" ) {
// note: when filling gaps, the post must be discarded (not
// shown) since it can never be older than what we already
// have on timeline. this is a problem due to requesting from
// several users at the same time, as some older posts might
// be included to complete the <count> in getposts because
// other users may have already been excluded by since_id.
streamItemsParent.append( streamPost );
streamPostAppended = true;
}
}
}
if( streamPostAppended ) {
streamPost.show();
}
req.reportProcessedPost(post["userpost"]["n"],post["userpost"]["k"], streamPostAppended);
}
req.doneReportProcessing(posts.length);
if( req.mode == "done" ) {
timelineLoaded = true;
$.MAL.postboardLoaded();
_refreshInProgress = false;
} else {
requestGetposts(req);
}
}
// request timeline update for a given list of users
function requestTimelineUpdate(mode, count, timelineUsers, getspam)
{
if( _refreshInProgress )
return;
$.MAL.postboardLoading();
_refreshInProgress = true;
if( timelineUsers.length ) {
var req = new requestObj(timelineUsers, mode, count, getspam);
requestGetposts(req);
} else {
console.log("requestTimelineUpdate: not following any users");
}
if( mode == "latest" || mode == "latestFirstTime" ) {
_newPostsPending = 0;
$.MAL.reportNewPosts(0);
}
}
// getlasthave is called every second to check if followed users have posted anything new
function requestLastHave() {
twisterRpc("getlasthave", [defaultScreenName],
function(req, ret) {processLastHave(ret);}, null,
function(req, ret) {console.log("ajax error:" + ret);}, null);
}
// handle getlasthave response. the increase in lasthave cannot be assumed to
// produce new items for timeline since some posts might be directmessages (which
// won't be returned by getposts, normally).
function processLastHave(userHaves)
{
var reqConfirmNewPosts = [];
var newPostsLocal = 0;
for( var user in userHaves ) {
if( userHaves.hasOwnProperty(user) ) {
// checks for _idTrackerMap as well. the reason is that getlasthave
// returns all users we follow, but the current timeline might be
// for just a single user.
if( user in _lastHaveMap && user in _idTrackerMap) {
if( userHaves[user] > _lastHaveMap[user] ) {
newPostsLocal += userHaves[user] - _lastHaveMap[user];
reqConfirmNewPosts.push( {username:user, since_id:_lastHaveMap[user]} );
}
}
_lastHaveMap[user] = userHaves[user];
if( user == defaultScreenName ) {
if( lastPostId == undefined || userHaves[user] > lastPostId ) {
incLastPostId(userHaves[user]);
}
}
}
}
// now do a getposts to confirm the number of new haves with are effectively new public posts
if( newPostsLocal ) {
twisterRpc("getposts", [newPostsLocal, reqConfirmNewPosts],
function(expected, posts) {processNewPostsConfirmation(expected, posts);}, newPostsLocal,
function(req, ret) {console.log("ajax error:" + ret);}, null);
}
}
// callback for getposts to update the number of new pending posts not shown in timeline
function processNewPostsConfirmation(expected, posts)
{
_newPostsPending += posts.length;
if( _newPostsPending ) {
$.MAL.reportNewPosts(_newPostsPending);
}
if( posts.length < expected ) {
// new DMs have probably been produced by users we follow.
// check with getdirectmsgs
requestDMsCount();
}
// TODO: possibly cache this response
}
function timelineChangedUser()
{
_idTrackerMap = {};
_idTrackerSpam = new idTrackerObj();
_lastHaveMap = {};
_refreshInProgress = false;
_newPostsPending = 0;
timelineLoaded = false;
}

268
twister_user.js

@ -0,0 +1,268 @@ @@ -0,0 +1,268 @@
// twister_user.js
// 2013 Miguel Freitas
//
// Load/save current user to localStorage
// keep track of lastPostId (used for posting as defaultScreenName)
// Load/save profile (profile-edit.html)
var defaultScreenName = undefined;
var localUsernames = [];
var lastPostId = undefined;
// basic user functions
// -------------------------------
function initUser(cbFunc, cbArg) {
loadWalletlUsers( function() {
var $localUsersList = $("select.local-usernames");
if( $localUsersList.length ) {
for( var i = 0; i < localUsernames.length; i++ ) {
var $existingOption = $localUsersList.find("option[value='" + localUsernames[i] + "']");
if( !$existingOption.length ) {
var $userOption = $("<option/>");
$userOption.val(localUsernames[i]);
$userOption.text(localUsernames[i]);
$localUsersList.append($userOption);
}
}
}
loadScreenName();
if( !defaultScreenName || localUsernames.indexOf(defaultScreenName) < 0 ) {
defaultScreenName = undefined;
} else {
var $localUsersLogin = $("select.local-usernames.login-user");
if( $localUsersLogin.length ) {
$localUsersLogin.val(defaultScreenName);
}
var $userMenuConfig = $(".userMenu-config");
if( $userMenuConfig.length ) {
$userMenuConfig.find("a.mini-profile-name").attr("href",$.MAL.userUrl(defaultScreenName));
$userMenuConfig.find(".mini-profile-name").text(defaultScreenName);
getFullname( defaultScreenName, $userMenuConfig.find(".mini-profile-name") );
}
}
lastPostId = undefined;
if( cbFunc )
cbFunc(cbArg);
});
}
function incLastPostId( optionalNewValue ) {
if( optionalNewValue != undefined ) {
if( lastPostId == undefined || optionalNewValue > lastPostId ) {
lastPostId = optionalNewValue;
}
} else {
lastPostId++;
}
$.MAL.updateMyOwnPostCount(lastPostId+1);
}
function loadWalletlUsers(cbFunc, cbArg) {
twisterRpc("listwalletusers", [],
function(args, ret) {
localUsernames = ret;
args.cbFunc(args.cbArg);
}, {cbFunc:cbFunc, cbArg:cbArg},
function(args, ret) {
alert(polyglot.t("error_connecting_to_daemon"));
}, {});
}
function loadScreenName() {
if( $.localStorage.isSet("defaultScreenName") ) {
defaultScreenName = $.localStorage.get("defaultScreenName");
}
}
function saveScreenName() {
$.localStorage.set("defaultScreenName", defaultScreenName);
}
// user-related functions used by login page (desktop/mobile)
// ----------------------------------------------------------
function loginLocalUsername() {
defaultScreenName = $("select.local-usernames.login-user").val();
if(defaultScreenName) {
saveScreenName();
$.MAL.changedUser();
$.MAL.goHome();
}
}
function checkUsernameAvailability() {
var $newUsername = $(".new-username");
var username = $newUsername.val().toLowerCase();
$newUsername.val(username);
var $availField = $(".availability");
if( !username.length )
return;
if( username.length > 16 ) {
$availField.text(polyglot.t("Must be 16 characters or less."));
return;
}
//Check for non-alphabetic characters and space
if(username.search(/[^a-z0-9_]/) != -1) {
$availField.text(polyglot.t("Only alphanumeric and underscore allowed."));
return;
}
$availField.text(polyglot.t("Checking..."));
dumpPubkey(username, function(dummy, pubkey) {
var notAvailable = pubkey.length > 0
var $availField = $(".availability");
if( notAvailable ) {
$availField.text(polyglot.t("Not available"));
} else {
$availField.text(polyglot.t("Available"));
var $createButton = $(".create-user");
$.MAL.enableButton( $createButton );
}
}, null);
}
function newUserNameKeypress() {
var $availField = $(".availability");
$availField.text("");
var $createButton = $(".create-user");
$.MAL.disableButton( $createButton );
}
// create user and call cbFunc(username, privkey)
function createUserClick(cbFunc) {
var $newUsername = $(".new-username");
var username = $newUsername.val().toLowerCase();
if( localUsernames.indexOf(username) < 0 ) {
twisterRpc("createwalletuser", [username],
function(args, ret) {
args.cbFunc(args.username, ret);
}, {username:username, cbFunc:cbFunc},
function(args, ret) {
alert(polyglot.t("Error in 'createwalletuser' RPC."));
}, {cbFunc:cbFunc});
} else {
// user exists in wallet but transaction not sent
dumpPrivkey(username,
function(args, ret) {
args.cbFunc(args.username, ret);
}, {username:username, cbFunc:cbFunc});
}
}
function sendNewUserTransaction(username, cbFunc) {
twisterRpc("sendnewusertransaction", [username],
function(args, ret) {
args.cbFunc();
}, {cbFunc:cbFunc},
function(args, ret) {
alert(polyglot.t("Error in 'sendnewusertransaction' RPC."));
}, {});
}
function importSecretKeypress() {
var secretKey = $(".secret-key-import").val();
var username = $(".username-import").val();
var $importButton = $(".import-secret-key");
if( secretKey.length == 52 && username.length ) {
$.MAL.enableButton( $importButton );
} else {
$.MAL.disableButton( $importButton );
}
}
function importSecretKeyClick() {
var secretKey = $(".secret-key-import").val();
var username = $(".username-import").val();
twisterRpc("importprivkey", [secretKey,username],
function(args, ret) {
processNewSecretKeyImported(args.username);
}, {username:username},
function(args, ret) {
alert(polyglot.t("Error in 'importprivkey'", {rpc: ret.message }));
}, {});
}
function processNewSecretKeyImported(username) {
defaultScreenName = username;
saveScreenName();
$.MAL.changedUser();
$.MAL.goHome();
}
// handlers common to both desktop and mobile
function interfaceCommonLoginHandlers() {
$( ".login-local-username" ).bind( "click", loginLocalUsername );
$( ".check-availability").bind( "click", checkUsernameAvailability );
/* must specialize: $( ".create-user").bind( "click", function() { createUserClick( processCreateUser ); } ); */
/* must specialize: $( ".login-created-user").bind( "click", loginCreatedUser ); */
$( ".new-username" ).keyup( newUserNameKeypress );
$( ".secret-key-import" ).keyup( importSecretKeypress );
$( ".username-import" ).keyup( importSecretKeypress );
$( ".import-secret-key").bind( "click", importSecretKeyClick );
}
// profile-related functions used by profile-edit
// ----------------------------------------------
var avatarSeqNum = 0;
var profileSeqNum = 0;
function loadAvatarForEdit() {
dhtget( defaultScreenName, "avatar", "s",
function(args, imagedata, rawdata) {
if( rawdata ) {
var seq = parseInt(rawdata[0]["p"]["seq"]);
if( seq > avatarSeqNum ) avatarSeqNum = seq;
}
if( imagedata && imagedata.length ) {
$(".profile-card-photo.forEdition").attr("src", imagedata);
}
}, {} );
}
function loadProfileForEdit() {
dhtget( defaultScreenName, "profile", "s",
function(args, profile, rawdata) {
if( rawdata ) {
var seq = parseInt(rawdata[0]["p"]["seq"]);
if( seq > profileSeqNum ) profileSeqNum = seq;
}
if( profile ) {
if( "fullname" in profile)
$(".input-name").val(profile.fullname);
if( "bio" in profile)
$(".input-description").val(profile.bio);
if( "location" in profile)
$(".input-city").val(profile.location);
if( "url" in profile)
$(".input-website").val(profile.url);
}
}, {} );
}
function saveProfile(e)
{
var profile = {};
profile["fullname"] = $(".input-name").val();
profile["bio"] = $(".input-description").val();
profile["location"] = $(".input-city").val();
profile["url"] = $(".input-website").val();
dhtput( defaultScreenName, "profile", "s",
profile, defaultScreenName, ++profileSeqNum );
var avatarData = $(".profile-card-photo.forEdition").attr("src");
dhtput( defaultScreenName, "avatar", "s",
avatarData, defaultScreenName, ++avatarSeqNum );
clearAvatarAndProfileCache(defaultScreenName);
}
Loading…
Cancel
Save