@ -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. |
@ -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> |
After Width: | Height: | Size: 6.1 KiB |
After Width: | Height: | Size: 134 B |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 3.8 KiB |
@ -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; |
||||
} |
@ -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">×</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">×</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"> </span><span class="label">Posts</span></a></li> |
||||
<li><a href="#" class="open-following-modal"><span class="following-count"> </span><span class="label">Following</span></a></li> |
||||
<li><a href="#"><span class="followers-count"> </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> |
@ -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"> </span><span class="label">Posts</span></a></li> |
||||
<li><a href="following.html"><span class="following-count"> </span><span class="label">Following</span></a></li> |
||||
<li><a href="#"><span class="followers-count"> </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">×</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">×</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"> </span><span class="label">Posts</span></a></li> |
||||
<li><a href="#" class="open-following-modal"><span class="following-count"> </span><span class="label">Following</span></a></li> |
||||
<li><a href="#"><span class="followers-count"> </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> |
After Width: | Height: | Size: 456 B |
After Width: | Height: | Size: 405 B |
After Width: | Height: | Size: 353 B |
After Width: | Height: | Size: 369 B |
After Width: | Height: | Size: 707 B |
After Width: | Height: | Size: 292 B |
After Width: | Height: | Size: 616 B |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 270 B |
After Width: | Height: | Size: 691 B |
After Width: | Height: | Size: 287 B |
After Width: | Height: | Size: 299 B |
After Width: | Height: | Size: 264 B |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 452 B |
After Width: | Height: | Size: 452 B |
After Width: | Height: | Size: 7.8 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 78 B |
@ -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> |
@ -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 ); |
||||
} |
@ -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); |
@ -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 ); |
||||
} |
||||
|
||||
|
@ -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); |
||||
} |
||||
}, {} ); |
||||
} |
@ -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); |
@ -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); |
||||
} |
||||
*/ |
@ -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); |
@ -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)); |
@ -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); |
@ -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); |
@ -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">×</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> |
@ -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; |
||||
|
@ -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> |
@ -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); |
@ -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> |
@ -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> </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"> </span>Posts</a></li> |
||||
<li><a href="#"><span class="following-count"> </span>Following</a></li> |
||||
<li><a href="#"><span class="followers-count"> </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> </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> </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> </div> |
||||
<div> |
||||
<span class="network-status highlight connection-status">Updating status...</span> |
||||
</div> |
||||
<div> </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> </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> |
@ -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); |
||||
} |
||||
} |
||||
|
@ -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 = []; |
||||
} |
||||
|
@ -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 ); |
||||
} |
||||
|
@ -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(); |
||||
}); |
||||
} |
@ -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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, '''); |
||||
} |
||||
|
@ -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}); |
||||
} |
||||
|
@ -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(); |
||||
} |
@ -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; |
||||
} |
@ -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; |
||||
} |
@ -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); |
||||
} |
||||
|