Browse Source

Merge pull request #370 from miguelfreitas/blaster

sync with blaster
master
miguelfreitas 7 years ago committed by GitHub
parent
commit
43d3169047
  1. 1
      home.html
  2. 15
      js/interface_common.js
  3. 799
      js/interface_localization.js
  4. 4
      js/options.js
  5. 60
      js/tmobile.js
  6. 135
      js/twister_actions.js
  7. 186
      js/twister_directmsg.js
  8. 4
      js/twister_formatpost.js
  9. 413
      js/twister_newmsgs.js
  10. 17
      js/twister_timeline.js
  11. 38
      options.html
  12. 2
      theme_nin/css/style.css
  13. 12
      theme_nin/js/theme_option.js
  14. 3
      theme_nin/sass/_tabs.sass

1
home.html

@ -28,6 +28,7 @@
<script src="js/interface_common.js"></script> <script src="js/interface_common.js"></script>
<script src="js/interface_home.js"></script> <script src="js/interface_home.js"></script>
<script src="js/jquery.textcomplete.min.js"></script> <script src="js/jquery.textcomplete.min.js"></script>
<script src="js/twister-crypto-bundle.js"></script>
<link rel="shortcut icon" type="image/png" href="img/twister_mini.png" /> <link rel="shortcut icon" type="image/png" href="img/twister_mini.png" />
</head> </head>

15
js/interface_common.js

@ -672,17 +672,10 @@ function openMentionsModalHandler(peerAlias) {
}); });
var req = queryStart(modal.content.find('.postboard-posts'), peerAlias, 'mention'); var req = queryStart(modal.content.find('.postboard-posts'), peerAlias, 'mention');
modal.content.find('.postboard-news') modal.content.find('.postboard-news').on('click', {req: req}, handleClickDisplayPendingTwists);
.on('click',
{req: req, cbFunc: (peerAlias === defaultScreenName) ? resetMentionsCount : ''},
handleClickDisplayPendingTwists
)
;
if (peerAlias === defaultScreenName) { if (peerAlias === defaultScreenName)
modal.content.on('scroll', handleMentionsModalScroll); modal.content.on('scroll', handleMentionsModalScroll);
resetMentionsCount();
}
} }
function openFollowersModal(peerAlias) { function openFollowersModal(peerAlias) {
@ -943,9 +936,9 @@ function addToCommonDMsList(list, targetAlias, message) {
getFullname(targetAlias, item.find('a.post-info-name')); getFullname(targetAlias, item.find('a.post-info-name'));
} }
if (_newDMsPerUser[targetAlias] > 0) if (twister.DMs[targetAlias].lengthNew > 0)
item.addClass('new') item.addClass('new')
.find('.messages-qtd').text(_newDMsPerUser[targetAlias]).show(); .find('.messages-qtd').text(twister.DMs[targetAlias].lengthNew).show();
var items = list.children(); var items = list.children();
for (var i = 0; i < items.length; i++) { for (var i = 0; i < items.length; i++) {

799
js/interface_localization.js

@ -4,7 +4,7 @@
// uses Polyglot.js ( https://github.com/airbnb/polyglot.js ) to translate interface // uses Polyglot.js ( https://github.com/airbnb/polyglot.js ) to translate interface
// translators: add your language code here such as "es" for Spanish, "ru" for Russian // translators: add your language code here such as "es" for Spanish, "ru" for Russian
var knownLanguages = ["en","es","nl","it","fr","ru","de","zh-CN","ja","pt-BR","tr","uk","cs"]; var knownLanguages = ["en","es","nl","it","fr","ru","de","zh-CN","ja","pt-BR","tr","uk","cs","cmn","yue"];
if ($.Options.locLang.val === 'auto') { if ($.Options.locLang.val === 'auto') {
// detect language with JavaScript // detect language with JavaScript
@ -367,7 +367,8 @@ if(preferredLanguage == "en"){
"Language": "Language", "Language": "Language",
"Sound": "Sound", "Sound": "Sound",
"Users": "Users", "Users": "Users",
"Direct Message's copy to self": "Direct Message's copy to self", 'dm_copy_outgoing_to_self': 'Copy outgoing messages to self',
'dm_encrypt_local_cache': 'Encrypt local data cache',
"Terminate Daemon": "Terminate Daemon", "Terminate Daemon": "Terminate Daemon",
"New post": "New post", "New post": "New post",
"Search": "Search", "Search": "Search",
@ -736,7 +737,8 @@ if(preferredLanguage == "es"){
"Language": "Idioma", "Language": "Idioma",
"Sound": "Sonido", "Sound": "Sonido",
"Users": "Usuarios", "Users": "Usuarios",
"Direct Message's copy to self": "Direct Message's copy to self", 'dm_copy_outgoing_to_self': 'Copy outgoing messages to self',
'dm_encrypt_local_cache': 'Encrypt local data cache',
"Traffic information": "Traffic information", "Traffic information": "Traffic information",
"DHT Torrents:": "DHT Torrents:", "DHT Torrents:": "DHT Torrents:",
"Peers:": "Peers:", "Peers:": "Peers:",
@ -1099,7 +1101,8 @@ if(preferredLanguage == "uk"){
"Language": "Мова", "Language": "Мова",
"Sound": "Звук", "Sound": "Звук",
"Users": "Користувачі", "Users": "Користувачі",
"Direct Message's copy to self": "Повідомлення скопійовано самому собі", 'dm_copy_outgoing_to_self': 'Повідомлення скопійовано самому собі',
'dm_encrypt_local_cache': 'Encrypt local data cache',
"Traffic information": "Статистика трафіку", "Traffic information": "Статистика трафіку",
"Direct messages with": "Співбесіда з", "Direct messages with": "Співбесіда з",
"DHT Torrents:": "DHT Torrents:", "DHT Torrents:": "DHT Torrents:",
@ -1465,7 +1468,8 @@ if(preferredLanguage == "zh-CN"){
"Language": "语言", "Language": "语言",
"Sound": "声音", "Sound": "声音",
"Users": "用户", "Users": "用户",
"Direct Message's copy to self": "将私信发一份副本给我", 'dm_copy_outgoing_to_self': '将私信发一份副本给我',
'dm_encrypt_local_cache': 'Encrypt local data cache',
"Terminate Daemon": "终止后台进程", "Terminate Daemon": "终止后台进程",
"New post": "新推文", "New post": "新推文",
"Search": "搜索", "Search": "搜索",
@ -1835,7 +1839,8 @@ if(preferredLanguage == "nl"){
"Language": "Language", "Language": "Language",
"Sound": "Sound", "Sound": "Sound",
"Users": "Users", "Users": "Users",
"Direct Message's copy to self": "Direct Message's copy to self", 'dm_copy_outgoing_to_self': 'Copy outgoing messages to self',
'dm_encrypt_local_cache': 'Encrypt local data cache',
"Traffic information": "Traffic information", "Traffic information": "Traffic information",
"DHT Torrents:": "DHT Torrents:", "DHT Torrents:": "DHT Torrents:",
"Peers:": "Peers:", "Peers:": "Peers:",
@ -2199,7 +2204,8 @@ if(preferredLanguage == "it"){
"Language": "Language", "Language": "Language",
"Sound": "Sound", "Sound": "Sound",
"Users": "Users", "Users": "Users",
"Direct Message's copy to self": "Direct Message's copy to self", 'dm_copy_outgoing_to_self': 'Copy outgoing messages to self',
'dm_encrypt_local_cache': 'Encrypt local data cache',
"Traffic information": "Traffic information", "Traffic information": "Traffic information",
"DHT Torrents:": "DHT Torrents:", "DHT Torrents:": "DHT Torrents:",
"Peers:": "Peers:", "Peers:": "Peers:",
@ -2565,7 +2571,8 @@ if(preferredLanguage == "fr"){
"Language": "Language", "Language": "Language",
"Sound": "Sound", "Sound": "Sound",
"Users": "Users", "Users": "Users",
"Direct Message's copy to self": "Direct Message's copy to self", 'dm_copy_outgoing_to_self': 'Copy outgoing messages to self',
'dm_encrypt_local_cache': 'Encrypt local data cache',
"Traffic information": "Traffic information", "Traffic information": "Traffic information",
"DHT Torrents:": "DHT Torrents:", "DHT Torrents:": "DHT Torrents:",
"Peers:": "Peers:", "Peers:": "Peers:",
@ -2932,7 +2939,8 @@ if(preferredLanguage == "ru"){
"Language": "Язык", "Language": "Язык",
"Sound": "Звук", "Sound": "Звук",
"Users": "Пользователи", "Users": "Пользователи",
"Direct Message's copy to self": "Синхронизировать отправленные личные сообщения", 'dm_copy_outgoing_to_self': 'Синхронизировать отправленные личные сообщения',
'dm_encrypt_local_cache': 'Шифровать локальный кэш данных',
"Terminate Daemon": "Выключить twister демон", "Terminate Daemon": "Выключить twister демон",
"New post": "Новый пост", "New post": "Новый пост",
"Search": "Поиск", "Search": "Поиск",
@ -3304,7 +3312,8 @@ if(preferredLanguage == "de"){
"Language": "Language", "Language": "Language",
"Sound": "Sound", "Sound": "Sound",
"Users": "Users", "Users": "Users",
"Direct Message's copy to self": "Kopie der Direktnachricht an mich selbst", 'dm_copy_outgoing_to_self': 'Kopie der Direktnachricht an mich selbst',
'dm_encrypt_local_cache': 'Encrypt local data cache',
"Traffic information": "Traffic information", "Traffic information": "Traffic information",
"DHT Torrents:": "DHT Torrents:", "DHT Torrents:": "DHT Torrents:",
"Peers:": "Peers:", "Peers:": "Peers:",
@ -3667,7 +3676,8 @@ if(preferredLanguage == "ja"){
"Language": "Language", "Language": "Language",
"Sound": "Sound", "Sound": "Sound",
"Users": "Users", "Users": "Users",
"Direct Message's copy to self": "Direct Message's copy to self", 'dm_copy_outgoing_to_self': 'Copy outgoing messages to self',
'dm_encrypt_local_cache': 'Encrypt local data cache',
"Traffic information": "Traffic information", "Traffic information": "Traffic information",
"DHT Torrents:": "DHT Torrents:", "DHT Torrents:": "DHT Torrents:",
"Peers:": "Peers:", "Peers:": "Peers:",
@ -4035,7 +4045,8 @@ if(preferredLanguage == "pt-BR"){
"Language": "Language", "Language": "Language",
"Sound": "Sound", "Sound": "Sound",
"Users": "Users", "Users": "Users",
"Direct Message's copy to self": "Direct Message's copy to self", 'dm_copy_outgoing_to_self': 'Copy outgoing messages to self',
'dm_encrypt_local_cache': 'Encrypt local data cache',
"Traffic information": "Traffic information", "Traffic information": "Traffic information",
"DHT Torrents:": "DHT Torrents:", "DHT Torrents:": "DHT Torrents:",
"Peers:": "Peers:", "Peers:": "Peers:",
@ -4400,7 +4411,8 @@ if(preferredLanguage == "tr"){
"Language": "Dil", "Language": "Dil",
"Sound": "Ses", "Sound": "Ses",
"Users": "Kullanıcılar", "Users": "Kullanıcılar",
"Direct Message's copy to self": "Özel iletinin kopyasını sakla", 'dm_copy_outgoing_to_self': 'Özel iletinin kopyasını sakla',
'dm_encrypt_local_cache': 'Encrypt local data cache',
"Traffic information": "Trafik bilgileri", "Traffic information": "Trafik bilgileri",
"DHT Torrents:": "DHT Torrentleri:", "DHT Torrents:": "DHT Torrentleri:",
"Peers:": "Eşler:", "Peers:": "Eşler:",
@ -4765,7 +4777,8 @@ if(preferredLanguage == "cs"){
"Language": "Jazyk", "Language": "Jazyk",
"Sound": "Zvuky", "Sound": "Zvuky",
"Users": "Uživatelé", "Users": "Uživatelé",
"Direct Message's copy to self": "Posílat kopie přímých zpráv sám sobě", 'dm_copy_outgoing_to_self': 'Posílat kopie přímých zpráv sám sobě',
'dm_encrypt_local_cache': 'Encrypt local data cache',
"New post": "Nový příspěvek", "New post": "Nový příspěvek",
"Search": "Hledat", "Search": "Hledat",
"Direct Msg": "Přímá zpráva", "Direct Msg": "Přímá zpráva",
@ -4793,6 +4806,764 @@ if(preferredLanguage == "cs"){
}; };
} }
if(preferredLanguage == "cmn"){
polyglot.locale("cmn");
wordset = {
"Maximum post size to show": "可顯示的最長推文",
"Maximum post size to send": "可發送的最長推文",
"characters": "字元",
"WebTorrent": "網頁訊流",
"WebTorrent support to display shortened URL media": "網頁訊流支援顯示短版網址媒體",
"External IP:": "外部網路位址:",
"External Port 1 (TCP):": "外部通訊埠一(TCP):",
"External Port 2 (TCP+UDP):": "外部通訊埠二(TCP+UDP):",
"Test open port (external site)": "測試打開的通訊埠(外部站臺)",
"Actions ▼": "動作 ▼",
"Active DHT nodes:": "分散式雜湊表活躍節點:",
"Add DNS": "加入 DNS",
"Add peer": "加入對等點",
"ajax_error": "Ajax 錯誤:%{error}", // JavaScript error
"All users publicly followed by": "所有使用者被公開跟隨自",
"Available": "可用", // username is available
"Appearance": "外觀",
"Apply": "套用",
"Block chain information": "區塊鏈資訊",
"Block chain is up-to-date, twister is ready to use!": "區塊鏈已更新,Twister 備妥待用!",
"Block generation": "區塊生成:",
"busted_oh": "噢,不!",
"busted_avowal": "檢測到有人嘗試注入惡意材料",
"btn_ok": "沒問題",
"Cancel": "取消",
'cant_get_requested_resourse': "無法於 %{link} 取得資源\n 狀態:%{status}。",
"clear_cache": "清空快取",
"Confirm": "確認",
"сonfirm_group_leaving_header": "確認要離開群組",
"сonfirm_group_leaving_body": "確定要離開 %{alias} 群組?",
"confirm_switch_to_network":
"本地守護程式還未連線到網路,或者區塊鏈已經過期。\n" +
"如果您待在這個頁面,您的動作可能沒有效用。\n" +
"您要檢查 [網路] 狀態頁面(%{page}) 做為替代嗎?",
"confirm_terminate_daemon": "確定要退離守護程式?\nTwister 客戶端將停止作用。",
"confirm_unfollow_@": "確定要取消跟隨 @%{alias}?",
"confirm_uri_shortener_clear_cache": "確定要清空瀏覽器的短版網址快取?",
"Change user": "變更使用者",
"Checking...": "檢查…", // checking if username is available
"Collapse": "塌縮", // smaller view of a post
"Configure block generation": "組配區塊生成",
"Connections:": "連接:", // to network
"Connection lost.": "連線已經中斷。",
"daemon_is_obsolete": "Twister 守護程式已過時,必須使用 %{versionReq} 或更高版本",
"days": "%{smart_count} 天",
"Detailed information": "詳細資訊",
"DHT network down.": "分散式雜湊表 網路斷線。",
"Direct Messages": "私人訊息",
"Group Messages": "群組訊息",
"Group Messages — New Group Creation": "群組訊息 — 新群組建立",
"Group Messages — Join Group": "群組訊息 — 參與群組",
"group_key_cant_import": "無法匯入私人訊息的群組金鑰",
"group_key_is_invalid_perhaps": "也許金鑰無效",
"group_key_was_imported": "已匯入私人訊息群組 %{alias} 的金鑰。\n"
+"很快就能擷取到它的訊息。",
"direct_messages_with": "與 %{username} 的私人訊息",
"Disable": "停用",
"display_mentions": "顯示提及次數",
"Display retransmissions": "顯示轉推次數",
"DNS to obtain list of peers:": "用來取得對等點列表的 DNS:",
"do_not_show_it_again": "再也不要顯示它",
"downloading_block_chain": "正在下載區塊鏈,繼續之前請稍待 (區塊鏈仍落後 %{days} 天)。",
"download_posts_status": "已下載 %{portion} 則推文", // Downloaded 10/30 posts
"Enable": "啟用",
"error": "錯誤:%{error}",
"error_connecting_to_daemon": "連線到本地 Twister 守護程式時發生錯誤。",
"Error in 'createwalletuser' RPC.": "在 createwalletuser RPC 時發生錯誤。",
"Error in 'importprivkey'": "在 importprivkey RPC 時發生錯誤:%{rpc}",
"Error in 'sendnewusertransaction' RPC.": "在 sendnewusertransaction RPC 時發生錯誤。",
"Expand": "展開", // larger view of a post
"Favorite": "收藏",
"File APIs not supported in this browser.": "這個瀏覽器不支援檔案應用程式介面。",
"Follow": "跟隨",
"Following config": "跟隨組配",
"select_way_to_follow_@": "您想以哪種方式跟隨 @%{alias}",
"Followed by": "跟隨者",
"followed_by": "被 %{username} 跟隨",
"Followers": "跟隨者",
"Followers_of": "@%{alias} 的跟隨者",
"Following": "跟隨中",
"Following users": "跟隨的使用者",
"Force connection to peer:": "強制連接到對等點:",
"General information": "一般資訊",
"Generate blocks (send promoted messages)": "產生區塊 (發送推廣訊息)",
"Home": "首頁", // homepage
"hours": "%{smart_count} 小時",
"Internal error: lastPostId unknown (following yourself may fix!)": "內部錯誤:lastPostId 不明 (跟隨您自己也許就能修正!)",
"Known peers:": "已知對等點:",
"Last block is ahead of your computer time, check your clock.": "最後一段區塊已經超前了您的電腦時間,請檢查您的系統時鐘。",
"mentions_at": "提及 @%{user}",
"minutes": "%{smart_count} 分鐘",
"Must be 16 characters or less.": "必須是 16 個字元或更少。", // username
"Network": "網路",
"Network config": "網路組配",
"Network status": "網路狀態",
"New direct message...": "新的私人訊息…",
"New Post...": "新推文…",
"New group": "新群組",
"Group description": "群組描述",
"Peers to invite": "可邀請的對等點",
"Join group": "參與群組",
"Select group(s)": "選取群組",
"Create": "建立",
"Join": "參與",
"Invite": "邀請",
"Invite peers": "邀請對等點",
"Leave group": "離開群組",
"You got": "您得到",
"in postboard": "於推布欄",
"in search result": "於搜尋結果",
"in top trends": "於熱門趨勢",
"new_posts": "%{smart_count} 則新推文",
"new_mentions": "%{smart_count} 次新提及",
"new_direct_messages": "%{smart_count} 則新的私人訊息",
"new_group_messages": "%{smart_count} 則新的群組訊息",
"nobody": "沒有人", // used to promote a post without attaching the user
"Not available": "無名可用", // username is not available
"warn_following_not_any": "沒有跟隨任何推友!\n請搜尋並跟隨某人。",
"warn_followers_not_all": "好吧,目前還沒有容易的方式,可以知道所有跟隨您的人。\n"
+"計數器衹有指示已知的對等點數量,他們正在共享您的推文訊流。\n"
+"以下列表包含的推友,大多是您所跟隨的人。",
"warn_followers_not_all_of": "好吧,目前還沒有容易的方式,可以知道某人的所有跟隨者。\n"
+"以下列表衹包括幾個,或許是公開跟隨 @%{alias} 的人。",
"notify_desktop_error": "Twister 無法進行桌面通知:發生不明錯誤。",
"notify_desktop_perm_denied": "Twister 無法進行桌面通知:權限被拒。\n\n如果您要獲得通知,請在您的瀏覽器設定值中,允許它們用於 %{this_domain}。",
"notify_desktop_test": "所有人都在推文。\n歡迎您的加入。",
"notify_desktop_title": "注意,這裡是 Twister!",
"post_preview_dummy": '這裡有 *粗體*、~斜體~、-刪除線- 以及 _加底線_ 的文字。\n'
+ '同樣但是例外排除的:`*粗體*、~斜體~、-刪除線- 以及 _加底線_`。\n'
+ '鏈結到 [最棒的圖標](%{logo}) 以及我們華麗的站臺:%{site}。',
"Number of blocks in block chain:": "區塊鏈中的區塊數量:",
"Number of CPUs to use": "使用的中央處理器數量:",
"Only alphanumeric and underscore allowed.": "衹允許文數字與底線。",
"peer address": "對等點位址",
"Private": "私人的",
"Profile": "側寫",
"Postboard": "推布欄",
"post": "推文", // verb - button to post a message
"Post to promote:": "用來推廣的推文:",
"Posts": "推文",
"propagating_nickname": "將暱稱 %{username} 傳播到網路…",
"Public": "公開",
"Refresh": "重新整理",
"retransmit_this": "轉發這則推文給您的跟隨者?",
"Reply": "回覆",
"Reply...": "回覆…",
"reply_to": "回覆給 %{fullname}",
"Retransmit": "轉發",
"Retransmits": "轉發",
"Retransmitted by": "轉發自",
"Switch to Reply": "切換為回覆",
"Switch to Retransmit": "切換為轉發",
"search": "搜尋",
"seconds": "%{smart_count} 秒",
"send": "發送",
"Send post with username": "發送推文所用的使用者名稱:",
"send_DM": "發送私人訊息",
"Sent Post to @": "已發送推文給 @",
"Setup account": "設定帳號",
"shorten_URI": "短版網址",
"shorten_URI_enter_link": "輸入長版網址鏈結。\n"
+"註記:短版網址將為您產生空的推文以包含完整網址。\n"
+"這一則特殊推文不會顯示於 Twister 客戶端,但是您的推文計數將會增加。",
"shorten_URI_its_public_is_it_ok": "您的鏈結將是公開可讀的!確定這樣可以嗎?",
"URI_shortener": "網址縮短器",
"The File APIs are not fully supported in this browser.": "這個瀏覽器並不完全支援檔案應用軟體介面。",
"time_ago": "%{time} 之前", // 5 minutes ago
"Time of the last block:": "最後一段區塊的時間:",
"Type message here": "在這裡輸入訊息",
"Unfollow": "取消跟隨",
"Update": "更新",
"Auto updating": "自動更新",
'updates_are_available': '有可用更新',
'updates_not_available': '沒有可用更新',
'updates_check_client': '檢查有無客戶端軟體更新',
'updates_repo_overview': '目前我們是基於 %{repo} 分支 %{branch} 上於\n %{date} 的定案 %{commit}\n'
+'但是原始碼的最前緣已經位於\n 定案 %{commitUpstream} 的 %{dateUpstream}。',
'updates_checkout_diff': '簽出 [GitHub 上的差異](%{link}) 來瞭解變更了什麼。',
'updates_checkout_diff_nfmt': '簽出 GitHub 上的差異來瞭解變更了什麼:\n %{link}',
'updates_upstream_isnt_changed': '相應分支的原始碼儲存庫似乎沒有變更。',
"Updating status...": "更新狀態…", // status of block chain
'new_account_briefing': '正在將新建立的帳號傳播到網路中。'
+'請不要關閉這個視窗,也許需要幾分鐘。\n\n'
+'您的密鑰是:*%{secretKey}*\n\n'
+'強烈建議您利用這段時間去儲存您的密鑰。'
+'印出來、做螢幕快照、使用您手機中的照相功能,或是將它寫在紙上。\n\n'
+'從不同的電腦使用這個帳號時將會需要密鑰。'
+'如果您一旦遺失密鑰,您的帳號將永遠被鎖住'
+'(註記:~這是開發中軟體,它也許會崩潰而造成資料漏失~)。\n\n'
+'請稍待。當您還沒唸完 ~decentralization~ 之前,就會顯示「確定」按鈕。',
"user_not_yet_accepted": "其他對等點尚未接受這位新使用者。\n"+
"很不幸地,無法儲存側寫\n"+
"或在這個狀態下發送任何推文。\n\n"+
"請稍待幾分鐘再繼續。\n\n"+
"「儲存變更」將在處理完成時\n"+
"自動啟用。(我保證這是您\n"+
"使用 Twister 之前的最後一次\n"+
"等待)。\n\n"+
"祕訣:趁這時選擇您的頭像!",
"users_mentions": "@%{username} 的提及次數",
"users_profile": "%{username} 的側寫",
"username_undefined": "未定義的使用者名稱,這是登入必要項目。",
"View": "檢視",
"View All": "檢視全部",
"Who to Follow": "可以跟隨誰",
"Your message was sent!": "您的訊息已發送!",
"twister login": "Twister 登入",
"Existing local users": "既有本地使用者",
"Or...": "或…",
"Create a new user": "建立新使用者",
"Login": "登入",
"Check availability": "檢查可用程度",
"Create this nickname": "建立這個暱稱",
"Type nickname here": "在這裡輸入暱稱",
"Import secret key": "匯入密鑰",
"52-characters secret": "52 個字元的密鑰",
"With nickname": "與暱稱",
"Import key": "匯入金鑰",
"Client Version:": "客戶端版本:",
"Mining difficulty:": "挖礦難度:",
"Block generation status": "區塊生成狀態",
"Current hash rate:": "目前雜湊比率:",
"Terminate Daemon:": "終止守護程式:",
"Exit": "退離",
"Save Changes": "儲存變更",
"profile_saved": "側寫資料已被儲存到分散式雜湊表。",
"profile_not_saved": "無法儲存側寫資料。",
"Secret key:": "密鑰:",
"You have to log in to post messages.": "您必須登入才能推送訊息。",
"You have to log in to post replies.": "您必須登入才能回覆訊息。",
"You have to log in to retransmit messages.": "您必須登入才能轉發訊息。",
"You have to log in to use direct messages.": "您必須登入才能使用私人訊息。",
"You have to log in to follow users.": "您必須登入才能跟隨使用者。",
"You are not following anyone because you are not logged in.": "您沒有跟隨任何人因為您還未登入。",
"You don't have any followers because you are not logged in.": "您沒有任何跟隨者因為您還未登入。",
"No one can mention you because you are not logged in.": "沒有人可以提及您因為您還未登入。",
"You don't have any profile because you are not logged in.": "您沒有任何側寫因為您還未登入。",
"Options": "選項",
"Switch to Promoted posts": "切換至推廣推文",
"Switch to Normal posts": "切換至一般推文",
"Use language": "使用語言",
"Ignore": "忽略",
"Ignore and clear out": "忽略並清空",
"Theme": "布景主題",
"Keys": "金鑰",
"Notifications": "通知",
"Desktop notifications": "桌面通知",
"Sound notifications": "聲音通知",
"Volume": "音量",
"Test": "測試",
"Send key": "發送金鑰",
"Posts display": "推文顯示",
"Post editor": "推文編輯器",
"Post preview": "推文預覽",
"Inline image preview": "內聯影像預覽",
"Display": "顯示",
"Line feeds": "送列符號",
"Markout": "註明標記",
"Supported punctuations:": "支援的標點符號:",
"Supported emotions:": "支援的表情符號:",
"Supported signs:": "支援的符號:",
"Supported fractions:": "支援的分數:",
"Automatic unicode conversion options": "萬國碼自動轉換選項",
"Convert punctuations to unicode": "轉換標點符號為萬國碼",
"Convert emotions codes to unicode symbols": "轉換表情符號編碼為萬國碼符號",
"Convert common signs to unicode": "轉換一般符號為萬國碼",
"Convert fractions to unicode": "轉換分數為萬國碼",
"Convert all": "轉換全部",
"Auto": "自動",
"Original": "原版",
"none": "無",
"Custom": "自訂",
"Mentions": "提及",
"Use proxy for image preview only": "代理伺服器衹用於影像預覽",
"Use external links behind a proxy": "使用代理伺服器後方的外部鏈結",
"There aren't any posts with this hashtag.": "沒有任何推文包含這個雜湊標籤。",
"Split only new post": "衹分割新的推文",
"Split all": "分割全部",
"Don't split": "不要分割",
"Split long posts": "分割長的推文",
"Posts that begin with mention": "以提及做為開頭的推文",
"Show all": "全部顯示",
"Show only if I am in": "衹顯示有我的推文",
"Show if it's between users I follow": "如果是我跟隨的使用者才顯示",
"Postboard displays": "推布欄顯示",
"RTs those are close to original twist": "與原始推文相近的轉推",
"Show if the original is older than": "衹顯示原始推文出現超過",
"hour(s)": "小時",
"second(s)": "秒",
"only positive numbers!": "衹允許正數!",
"Language filtering": "語言篩選",
"By blacklist": "依照黑名單",
"By whitelist": "依照白名單",
"Comma separated ISO 639-3 language codes": "逗號分隔的 ISO 639-3 語言編碼",
"Accuracy": "準確度",
"Simulation mode": "模擬模式",
"This post is treated by language filter": "這則推文由語言篩選器所 %{treated}。",
"blocked": "阻斷",
"passed": "傳遞",
"not analyzed": "無法分析",
"Reason: this": "原因:%{this}",
"this doesnt contain that": "%{this} 不包含 %{that}",
"this is undefined": "%{this} 未定義",
"blacklist": "黑名單",
"whitelist": "白名單",
"language of this": "語言所屬",
"its undefined language": "它是種未定義的語言",
"its this, blacklisted": "它是 %{this},已列入黑名單",
"its this, whitelisted": "它是 %{this},已列入白名單",
"Most possible language: this": "最可能的語言:%{this}",
"Scope of usage": "使用的範圍",
"Show with every user name": "每位使用者都要顯示",
"Show at profile modal only": "衹有側寫頁面才要顯示",
"Show if a user follows me": "跟隨我的使用者才顯示",
"follows you": "跟隨您",
"Show conversation": "顯示會話",
"Mark all as read": "標記所有為已讀",
"show_more_count": "%{smart_count} 更多…",
"hide": "隱藏",
"Show more in this conversation...": "顯示更多會話內容…",
"conversation_title": "@%{username} 的會話",
"copy_to_clipboard": "按下 Ctrl/Cmd+C 來拷貝,然後按下 Enter 來關閉",
"Normal posts": "一般推文",
"Promoted posts": "推廣推文",
"Messages": "訊息",
"Edit profile": "編輯側寫",
"Top Trends": "熱門趨勢",
"Twistday Reminder": "開推周年紀念日提醒訊息",
"Show upcoming in near future": "顯示不久即將迎來慶祝的人",
"Who's celebrating Twistday": "誰正在慶祝開推周年紀念日",
"Today's luckies:": "今天的幸運兒:",
"Upcoming ones:": "即將迎來慶祝的人:",
"post_rt_sign_prep": "再次推文自",
"post_rt_time_prep": "於",
"undo": "復原",
"Daemon exited...": "已退離守護程式…",
"Secret Key": "密鑰",
"Copy to clipboard": "拷貝到剪貼簿",
"Full name here": "全名",
"Describe yourself": "描述您自己",
"Location": "位置",
"website": "網站",
"Tox address": "Tox 位址",
"Bitmessage address": "Bitmessage 位址",
"Language": "語言",
"Sound": "聲音",
"Users": "使用者",
"Direct Message's copy to self": "私人訊息拷貝給自己",
"Terminate Daemon": "終止守護程式",
"New post": "新推文",
"Search": "搜尋",
"Direct Msg": "私人訊息",
"Traffic information": "流量資訊",
"DHT Torrents:": "分散式雜湊表訊流:",
"Peers:": "對等點:",
"Peer List Size:": "對等點列表大小:",
"Active Requests:": "活躍請求:",
"Download:": "下載:",
"Upload:": "上傳:",
"DHT Download:": "分散式雜湊表下載:",
"DHT Upload:": "分散式雜湊表上傳:",
"IP Overhead Download:": "網際網路協定耗費下載:",
"IP Overhead Upload:": "網際網路協定耗費上傳:",
"Payload Download:": "酬載下載:",
"Payload Upload:": "酬載上傳:",
"No favs here because you are not logged in.": "因為您還沒登入,這裡不會顯示收藏。",
"users_favs": "@%{username} 的收藏",
"Favorites": "收藏",
"You have to log in to favorite messages.": "您必須登入以收藏訊息。",
"fav_this": "它為您所獨有?",
"Last activity": "最近一次活動",
"New Users": "新近使用者",
"Live tracking" : "即時追蹤"
};
}
if(preferredLanguage == "yue"){
polyglot.locale("yue");
wordset = {
"Maximum post size to show": "可顯示其至長推文",
"Maximum post size to send": "可寄個其至長推文",
"characters": "字符",
"WebTorrent": "網頁訊流",
"WebTorrent support to display shortened URL media": "網頁訊流支援顯示短版網址媒體",
"External IP:": "外部網絡位址:",
"External Port 1 (TCP):": "外部通訊接口一(TCP):",
"External Port 2 (TCP+UDP):": "外部通訊接口二(TCP+UDP):",
"Test open port (external site)": "測試開其通訊接口(外部站台)",
"Actions ▼": "動作 ▼",
"Active DHT nodes:": "分散式雜湊表生猛節點:",
"Add DNS": "添加 DNS",
"Add peer": "添加對等點",
"ajax_error": "Ajax 錯誤:%{error}", // JavaScript error
"All users publicly followed by": "所有用家畀公開發摟自",
"Available": "可用", // username is available
"Appearance": "外觀",
"Apply": "改實",
"Block chain information": "區塊捵資訊",
"Block chain is up-to-date, twister is ready to use!": "區塊捵已更加新,Twister 備妥待用!",
"Block generation": "區塊生成:",
"busted_oh": "噢,毋!",
"busted_avowal": "檢測到有人嘗試斟惡意材料",
"btn_ok": "無問題",
"Cancel": "毋做",
'cant_get_requested_resourse': "無法嚮 %{link} 攎資源\n 狀態:%{status}。",
"clear_cache": "清空快取",
"Confirm": "落實",
"сonfirm_group_leaving_header": "落實要行開谷",
"сonfirm_group_leaving_body": "直煞要行開 %{alias} 谷嘛?",
"confirm_switch_to_network":
"本地守護程式尚未連綫到網絡,定抑區塊捵經已過期。\n" +
"若果閣下待繫個頁面,閣下其動作似無效用。\n" +
"閣下要檢查 [網絡] 狀態頁面(%{page}) 做為幫代啊?",
"confirm_terminate_daemon": "直煞要退出守護程式嘛?\nTwister 客戶端將閘住作用。",
"confirm_unfollow_@": "直煞要毋做發摟 @%{alias}嘛?",
"confirm_uri_shortener_clear_cache": "直煞要清空瀏覽器其短版網址快取嘛?",
"Change user": "更改用家",
"Checking...": "檢查…", // checking if username is available
"Collapse": "塌縮", // smaller view of a post
"Configure block generation": "組襯區塊生成",
"Connections:": "接通:", // to network
"Connection lost.": "連綫經已掹開。",
"daemon_is_obsolete": "Twister 守護程式過着時,必須用 %{versionReq} 或更加高版本",
"days": "%{smart_count} 天",
"Detailed information": "詳細資訊",
"DHT network down.": "分散式雜湊表 網絡斷綫。",
"Direct Messages": "私人口訊",
"Group Messages": "谷口訊",
"Group Messages — New Group Creation": "谷口訊 — 新谷建立",
"Group Messages — Join Group": "谷口訊 — 埋份谷",
"group_key_cant_import": "無法匯入私人口訊其谷密碼匙",
"group_key_is_invalid_perhaps": "亦許密碼匙無效",
"group_key_was_imported": "已匯入私人口訊谷 %{alias} 其密碼匙。\n"
+"好快就能擷取到佢其口訊。",
"direct_messages_with": "同 %{username} 其私人口訊",
"Disable": "停用",
"display_mentions": "顯示提及次數",
"Display retransmissions": "顯示轉推次數",
"DNS to obtain list of peers:": "用來攎對等點列表其 DNS:",
"do_not_show_it_again": "再亦毋好顯示佢",
"downloading_block_chain": "取得緊區塊捵,繼續之前請稍待 (區塊捵仍落後 %{days} 天)。",
"download_posts_status": "已取得 %{portion} 則推文", // Downloaded 10/30 posts
"Enable": "啟用",
"error": "錯誤:%{error}",
"error_connecting_to_daemon": "連綫到本地 Twister 守護程式時發生錯誤。",
"Error in 'createwalletuser' RPC.": "繫 createwalletuser RPC 時發生錯誤。",
"Error in 'importprivkey'": "繫 importprivkey RPC 時發生錯誤:%{rpc}",
"Error in 'sendnewusertransaction' RPC.": "繫 sendnewusertransaction RPC 時發生錯誤。",
"Expand": "展開", // larger view of a post
"Favorite": "收匿",
"File APIs not supported in this browser.": "個瀏覽器毋支援快勞應用程式界面。",
"Follow": "發摟",
"Following config": "發摟組襯",
"select_way_to_follow_@": "閣下諗俾焉種方式發摟 @%{alias}",
"Followed by": "發摟者",
"followed_by": "畀 %{username} 發摟",
"Followers": "發摟者",
"Followers_of": "@%{alias} 其發摟者",
"Following": "發摟中",
"Following users": "發摟其用家",
"Force connection to peer:": "夾硬接通到對等點:",
"General information": "一般資訊",
"Generate blocks (send promoted messages)": "產生區塊 (寄個推廣口訊)",
"Home": "頭版", // homepage
"hours": "%{smart_count} 個鐘",
"Internal error: lastPostId unknown (following yourself may fix!)": "內部錯誤:lastPostId 未知 (發摟閣下自己亦許就能修正!)",
"Known peers:": "已知對等點:",
"Last block is ahead of your computer time, check your clock.": "最後一橛區塊經已超前着閣下其電腦時間,請檢查閣下其系統時鐘。",
"mentions_at": "提及 @%{user}",
"minutes": "%{smart_count} 分鐘",
"Must be 16 characters or less.": "必須係 16 個字符或更加少。", // username
"Network": "網絡",
"Network config": "網絡組襯",
"Network status": "網絡狀態",
"New direct message...": "新淨私人口訊…",
"New Post...": "新推文…",
"New group": "新谷",
"Group description": "谷斟",
"Peers to invite": "可邀請其對等點",
"Join group": "埋份谷",
"Select group(s)": "繫度揀谷",
"Create": "建立",
"Join": "埋份",
"Invite": "邀請",
"Invite peers": "邀請對等點",
"Leave group": "行開谷",
"You got": "閣下得到",
"in postboard": "嚮推布欄",
"in search result": "嚮揾結果",
"in top trends": "嚮熱門趨勢",
"new_posts": "%{smart_count} 則新推文",
"new_mentions": "%{smart_count} 次新提及",
"new_direct_messages": "%{smart_count} 則新淨私人口訊",
"new_group_messages": "%{smart_count} 則新淨谷口訊",
"nobody": "無人", // used to promote a post without attaching the user
"Not available": "無名可用", // username is not available
"warn_following_not_any": "無發摟任何推友!\n請揾兼發摟某人。",
"warn_followers_not_all": "好吧,而今尚未易其方式,得知所有發摟閣下其人。\n"
+"計數器惟有指示已知其對等點數量,佢地正係共享閣下其推文訊流。\n"
+"以下列表埋其推友,大多係閣下所發摟其人。",
"warn_followers_not_all_of": "好吧,而今尚未易其方式,得知某人其所有發摟者。\n"
+"以下列表衹埋幾個,一係係公開發摟 @%{alias} 其人。",
"notify_desktop_error": "Twister 無法去枱面通知:發生未知錯誤。",
"notify_desktop_perm_denied": "Twister 無法去枱面通知:權限畀拒。\n\n若果閣下要攎到通知,請繫閣下其瀏覽器設定值中,允許佢用嚮 %{this_domain}。",
"notify_desktop_test": "冚不論都繫推文。\n歡迎閣下其添加。",
"notify_desktop_title": "留意,呢度係 Twister!",
"post_preview_dummy": '呢度有 *粗字*、~斜字~、-刪清綫- 同埋 _加底綫_ 其文字。\n'
+ '同樣但例外飛起其:`*粗字*、~斜字~、-刪清綫- 同埋 _加底綫_`。\n'
+ '連結到 [一流其圖標](%{logo}) 同埋我地華麗其站台:%{site}。',
"Number of blocks in block chain:": "區塊捵中其區塊數量:",
"Number of CPUs to use": "用其中央處理器數量:",
"Only alphanumeric and underscore allowed.": "衹允許文數字同底綫。",
"peer address": "對等點位址",
"Private": "私人其",
"Profile": "個人資料",
"Postboard": "推布欄",
"post": "推文", // verb - button to post a message
"Post to promote:": "用來推廣其推文:",
"Posts": "推文",
"propagating_nickname": "將網名 %{username} 傳播到網絡…",
"Public": "公開",
"Refresh": "整返",
"retransmit_this": "轉發呢則推文畀閣下其發摟者嘛?",
"Reply": "覆返",
"Reply...": "覆返…",
"reply_to": "覆返畀 %{fullname}",
"Retransmit": "轉發",
"Retransmits": "轉發",
"Retransmitted by": "轉發自",
"Switch to Reply": "切換為覆返",
"Switch to Retransmit": "切換為轉發",
"search": "揾耶",
"seconds": "%{smart_count} 秒",
"send": "寄個",
"Send post with username": "寄個推文所用其用家名稱:",
"send_DM": "寄個私人口訊",
"Sent Post to @": "已寄個推文畀 @",
"Setup account": "設定戶口",
"shorten_URI": "短版網址",
"shorten_URI_enter_link": "輸入長版網址連結。\n"
+"註記:短版網址將為閣下產生空其推文俾埋齊煞網址。\n"
+"則特意推文毋會顯示嚮 Twister 客戶端,但閣下其推文計數會提返。",
"shorten_URI_its_public_is_it_ok": "閣下其連結將係公開可讀其!直煞敢得啩?",
"URI_shortener": "網址縮短器",
"The File APIs are not fully supported in this browser.": "個瀏覽器兼毋完全支援快勞應用軟件界面。",
"time_ago": "%{time} 之前", // 5 minutes ago
"Time of the last block:": "最後一橛區塊其時間:",
"Type message here": "繫度輸入口訊",
"Unfollow": "毋做發摟",
"Update": "更加新",
"Auto updating": "自動更加新",
'updates_are_available': '有可用更加新 ',
'updates_not_available': '無可用更加新 ',
'updates_check_client': '檢查有無客戶端軟件更加新 ',
'updates_repo_overview': '而今我地係跟埋 %{repo} 分支 %{branch} 上嚮\n %{date} 其定案 %{commit}\n'
+'但原始碼其至前緣經已位嚮\n 定案 %{commitUpstream} 其 %{dateUpstream}。',
'updates_checkout_diff': '簽走 [GitHub 上其差異](%{link}) 來知更改着麼耶。',
'updates_checkout_diff_nfmt': '簽走 GitHub 上其差異來知更改着麼:\n %{link}',
'updates_upstream_isnt_changed': '相應分支其原始碼保存庫似乎無更改。',
"Updating status...": "更加新狀態…", // status of block chain
'new_account_briefing': '正係將新建立其戶口傳播到網絡中。'
+'請毋好閂埋個視窗,亦許需要幾分鐘。\n\n'
+'閣下其密碼匙係:*%{secretKey}*\n\n'
+'強勍建議閣下利用呢段時間去保存閣下其密碼匙。'
+'顯示來、做熒幕快照、用閣下手提電話中其影相功能,定係將佢寫繫紙上。\n\n'
+'由毋同其電腦用個戶口時會需要密碼匙。'
+'若果閣下一旦遺失密碼匙,閣下其戶口將永遠畀鎖住 '
+'(註記:~ 呢係開發中軟件,佢亦許會冧而做成資料漏失 ~)。\n\n'
+'請稍待。當閣下尚未讀煞 ~decentralization~ 之前,就會顯示「直煞」掣。',
"user_not_yet_accepted": "遞滴對等點未接受呢位新用家。\n"+
"好毋幸地,無法保存個人資料\n"+
"或繫個狀態下寄個任何推文。\n\n"+
"請稍待幾分鐘再繼續。\n\n"+
"「保存更改」將繫處理搞直時\n"+
"自動啟用。(我包呢係閣下\n"+
"用 Twister 之前其最後一次\n"+
"等待)。\n\n"+
"秘訣:趁呢時揀閣下其頭像!",
"users_mentions": "@%{username} 其提及次數",
"users_profile": "%{username} 其個人資料",
"username_undefined": "未定義其用家名稱,呢係登入要寫項目。",
"View": "查看",
"View All": "查看煞",
"Who to Follow": "得發摟焉位",
"Your message was sent!": "閣下其口訊已寄個!",
"twister login": "Twister 登入",
"Existing local users": "既有本地用家",
"Or...": "或…",
"Create a new user": "建立新用家",
"Login": "登入",
"Check availability": "檢查可用程度",
"Create this nickname": "建立個網名",
"Type nickname here": "繫度輸入網名",
"Import secret key": "匯入密碼匙",
"52-characters secret": "52 個字符其密碼匙",
"With nickname": "同網名",
"Import key": "匯入密碼匙",
"Client Version:": "客戶端版本:",
"Mining difficulty:": "掘礦難度:",
"Block generation status": "區塊生成狀態",
"Current hash rate:": "而今雜湊比率:",
"Terminate Daemon:": "終止守護程式:",
"Exit": "退出",
"Save Changes": "保存更改",
"profile_saved": "個人資料資料已畀保存到分散式雜湊表。",
"profile_not_saved": "無法保存個人資料資料。",
"Secret key:": "密碼匙:",
"You have to log in to post messages.": "閣下必須登入先能推送口訊。",
"You have to log in to post replies.": "閣下必須登入先能覆返口訊。",
"You have to log in to retransmit messages.": "閣下必須登入先能轉發口訊。",
"You have to log in to use direct messages.": "閣下必須登入先能用私人口訊。",
"You have to log in to follow users.": "閣下必須登入先能發摟用家。",
"You are not following anyone because you are not logged in.": "閣下無發摟任何人因為閣下尚未登入。",
"You don't have any followers because you are not logged in.": "閣下無發摟者因為閣下尚未登入。",
"No one can mention you because you are not logged in.": "無人得提及閣下因為閣下尚未登入。",
"You don't have any profile because you are not logged in.": "閣下無個人資料因為閣下尚未登入。",
"Options": "隨寫",
"Switch to Promoted posts": "切換至推廣推文",
"Switch to Normal posts": "切換至一般推文",
"Use language": "用語言",
"Ignore": "毋理",
"Ignore and clear out": "毋理兼清空",
"Theme": "布景主題",
"Keys": "密碼匙",
"Notifications": "通知",
"Desktop notifications": "枱面通知",
"Sound notifications": "聲音通知",
"Volume": "音量",
"Test": "測試",
"Send key": "寄個密碼匙",
"Posts display": "推文顯示",
"Post editor": "推文編輯器",
"Post preview": "推文預覽",
"Inline image preview": "內聯錄像預覽",
"Display": "顯示",
"Line feeds": "送列符號",
"Markout": "註明嘜頭",
"Supported punctuations:": "支援其標點符號:",
"Supported emotions:": "支援其表情符號:",
"Supported signs:": "支援其符號:",
"Supported fractions:": "支援其分數:",
"Automatic unicode conversion options": "萬國碼自動轉換隨寫",
"Convert punctuations to unicode": "轉換標點符號為萬國碼",
"Convert emotions codes to unicode symbols": "轉換表情符號編碼為萬國碼符號",
"Convert common signs to unicode": "轉換一般符號為萬國碼",
"Convert fractions to unicode": "轉換分數為萬國碼",
"Convert all": "轉換煞",
"Auto": "自動",
"Original": "原版",
"none": "無",
"Custom": "自訂",
"Mentions": "提及",
"Use proxy for image preview only": "代理伺服器衹用嚮錄像預覽",
"Use external links behind a proxy": "用代理伺服器後方其外部連結",
"There aren't any posts with this hashtag.": "無推文埋個雜湊標籤。",
"Split only new post": "衹斬開新淨推文",
"Split all": "斬開煞",
"Don't split": "毋好斬開",
"Split long posts": "斬開長其推文",
"Posts that begin with mention": "俾提及做為開頭其推文",
"Show all": "冚顯示",
"Show only if I am in": "衹顯示有我其推文",
"Show if it's between users I follow": "若果係我發摟其用家先顯示",
"Postboard displays": "推布欄顯示",
"RTs those are close to original twist": "同原始推文相近其轉推",
"Show if the original is older than": "衹顯示原始推文浮頭超過",
"hour(s)": "個鐘",
"second(s)": "秒",
"only positive numbers!": "衹允許正數!",
"Language filtering": "語言篩揀",
"By blacklist": "依照黑名單",
"By whitelist": "依照白名單",
"Comma separated ISO 639-3 language codes": "撩下號分隔其 ISO 639-3 語言編碼",
"Accuracy": "準確度",
"Simulation mode": "模擬模式",
"This post is treated by language filter": "呢則推文由語言篩揀器所 %{treated}。",
"blocked": "杯葛",
"passed": "傳遞",
"not analyzed": "無法分析",
"Reason: this": "因由:%{this}",
"this doesnt contain that": "%{this} 毋埋 %{that}",
"this is undefined": "%{this} 未定義",
"blacklist": "黑名單",
"whitelist": "白名單",
"language of this": "語言所屬",
"its undefined language": "佢係種未定義其語言",
"its this, blacklisted": "佢係 %{this},已列入黑名單",
"its this, whitelisted": "佢係 %{this},已列入白名單",
"Most possible language: this": "至似其語言:%{this}",
"Scope of usage": "用其範圍",
"Show with every user name": "每位用家都要顯示",
"Show at profile modal only": "惟有個人資料頁面先要顯示",
"Show if a user follows me": "發摟我其用家先顯示",
"follows you": "發摟閣下",
"Show conversation": "顯示會話",
"Mark all as read": "嘜頭所有為已讀",
"show_more_count": "%{smart_count} 更加多…",
"hide": "匿埋",
"Show more in this conversation...": "顯示更加多會話內容…",
"conversation_title": "@%{username} 其會話",
"copy_to_clipboard": "撳 Ctrl/Cmd+C 來複製,跟住撳 Enter 來閂埋",
"Normal posts": "一般推文",
"Promoted posts": "推廣推文",
"Messages": "口訊",
"Edit profile": "編輯個人資料",
"Top Trends": "熱門趨勢",
"Twistday Reminder": "開推周年紀念日提醒口訊",
"Show upcoming in near future": "顯示毋耐來緊迎來慶祝其人",
"Who's celebrating Twistday": "焉位正係慶祝開推周年紀念日",
"Today's luckies:": "今天其幸逳兒:",
"Upcoming ones:": "來緊迎來慶祝其人:",
"post_rt_sign_prep": "再次推文自",
"post_rt_time_prep": "嚮",
"undo": "揾返",
"Daemon exited...": "已退出守護程式…",
"Secret Key": "密碼匙",
"Copy to clipboard": "複製到剪貼簿",
"Full name here": "冚名",
"Describe yourself": "斟閣下自己",
"Location": "地步",
"website": "網站",
"Tox address": "Tox 位址",
"Bitmessage address": "Bitmessage 位址",
"Language": "語言",
"Sound": "聲音",
"Users": "用家",
"Direct Message's copy to self": "私人口訊複製畀自己",
"Terminate Daemon": "終止守護程式",
"New post": "新推文",
"Search": "揾耶",
"Direct Msg": "私人口訊",
"Traffic information": "流量資訊",
"DHT Torrents:": "分散式雜湊表訊流:",
"Peers:": "對等點:",
"Peer List Size:": "對等點列表大細:",
"Active Requests:": "生猛請求:",
"Download:": "取得:",
"Upload:": "上傳:",
"DHT Download:": "分散式雜湊表取得:",
"DHT Upload:": "分散式雜湊表上傳:",
"IP Overhead Download:": "互聯網協定耗費取得:",
"IP Overhead Upload:": "互聯網協定耗費上傳:",
"Payload Download:": "酬載取得:",
"Payload Upload:": "酬載上傳:",
"No favs here because you are not logged in.": "因為閣下尚未登入,呢度毋會顯示收匿。",
"users_favs": "@%{username} 其收匿",
"Favorites": "收匿",
"You have to log in to favorite messages.": "閣下必須登入俾收匿口訊。",
"fav_this": "佢為閣下所獨有嘛?",
"Last activity": "最近一次活動",
"New Users": "新近用家",
"Live tracking" : "即時追蹤"
};
}
// uncomment to see all translated words replaced with filler // uncomment to see all translated words replaced with filler
//for(var word in wordset){ //for(var word in wordset){
// wordset[word] = "AAAA"; // wordset[word] = "AAAA";

4
js/options.js

@ -227,6 +227,10 @@ function twisterOptions() {
name: 'dmCopySelf', name: 'dmCopySelf',
valDefault: 'enable' valDefault: 'enable'
}); });
this.add({
name: 'dmEncryptCache',
valDefault: 'enable'
});
this.add({ this.add({
name: 'hideReplies', name: 'hideReplies',
valDefault: 'following' valDefault: 'following'

60
js/tmobile.js

@ -273,23 +273,37 @@ var router=new $.mobile.Router(
$.mobile.showPageLoadingMsg(); $.mobile.showPageLoadingMsg();
initializeTwister( true, true, function() { initializeTwister( true, true, function() {
$.mobile.showPageLoadingMsg(); $.mobile.showPageLoadingMsg();
requestDMsnippetList($('#directmsg .direct-messages-list')); modalDMsSummaryDraw($('#directmsg .direct-messages-list'));
}); });
}, },
dmchat: function(type,match,ui) { dmchat: function(type,match,ui) {
var params=router.getParams(match[1]); var params=router.getParams(match[1]);
$.mobile.showPageLoadingMsg(); $.mobile.showPageLoadingMsg();
initializeTwister( true, true, function() { initializeTwister( true, true, function() {
var user = params.user; var peerAlias = params.user;
var dmConvo = $('#dmchat .direct-messages-thread'); var board = $('#dmchat .direct-messages-thread').empty();
$("#dmchat .rtitle").text("Chat @" + user);
$('#dmchat .rtitle').text('Chat @' + peerAlias);
$("#dmchat textarea").val(""); $("#dmchat textarea").val("");
dmConvo.html(""); installDMSendClick(peerAlias);
installDMSendClick();
$.mobile.showPageLoadingMsg(); $.mobile.showPageLoadingMsg();
dmChatUser = user;
requestDmConversation(dmConvo,user); tmobileQueryReq = queryStart(board, peerAlias, 'direct', undefined, 2000, {
boardAutoAppend: true,
lastId: 0,
lengthNew: 0,
ready: function (req, peerAlias) {
twister.DMs[peerAlias] = twister.res[req];
},
readyReq: peerAlias,
drawFinish: function (req) {
setTimeout($.MAL.dmConversationLoaded, 200, twister.res[req].board);
},
skidoo: function (req) {
return $.mobile.activePage.attr('id') !== 'dmchat' || req !== tmobileQueryReq;
}
});
}); });
}, },
search: function(type,match,ui) { search: function(type,match,ui) {
@ -390,23 +404,21 @@ function installSubmitClick() {
}); });
} }
function installDMSendClick() { function installDMSendClick(peerAlias) {
var $postSubmit = $(".dm-submit"); $('.dm-submit').off('click').on('click', {peerAlias: peerAlias},
$postSubmit.unbind('click').click(function(e){ function (event) {
e.stopPropagation(); muteEvent(event, true);
e.preventDefault();
var $this = $( this );
var $replyText = $this.closest(".post-area-new").find("textarea");
var $dmConversation = $(".directMessages"); var elemTextArea = $(event.target).closest('.post-area-new').find('textarea');
if (!elemTextArea.val())
return;
var s = encode_utf8($replyText.val()); newDirectMsg(encode_utf8(elemTextArea.val()), event.data.peerAlias);
newDirectMsg(s, dmChatUser); elemTextArea.val('');
$replyText.val(""); }
}); );
} }
function installRetransmitConfirmClick() { function installRetransmitConfirmClick() {
var $postConfirmRt = $(".retransmit-confirm"); var $postConfirmRt = $(".retransmit-confirm");
$postConfirmRt.unbind('click').click(function(e){ $postConfirmRt.unbind('click').click(function(e){
@ -535,15 +547,13 @@ function setupHashtagOrMention(board, query, resource) {
$.mobile.showPageLoadingMsg(); $.mobile.showPageLoadingMsg();
board.empty(); board.empty();
var req = queryStart(board, query, resource, undefined, undefined, { tmobileQueryReq = queryStart(board, query, resource, undefined, undefined, {
boardAutoAppend: true, boardAutoAppend: true,
skidoo: function (req) { skidoo: function (req) {
var curPage = $.mobile.activePage.attr('id'); var curPage = $.mobile.activePage.attr('id');
return (curPage !== 'mentions' && curPage !== 'hashtag') || req !== tmobileQueryReq; return (curPage !== 'mentions' && curPage !== 'hashtag') || req !== tmobileQueryReq;
} }
}); });
tmobileQueryReq = req;
} }
// every 2 seconds do something page specific. // every 2 seconds do something page specific.
@ -565,8 +575,6 @@ function tmobileTick() {
} }
}, {} ); }, {} );
} }
if (curPage === 'dmchat')
requestDmConversation($('#dmchat .direct-messages-thread'), dmChatUser);
} }
$(document).bind('mobileinit', function () { $(document).bind('mobileinit', function () {

135
js/twister_actions.js

@ -436,6 +436,24 @@ function updateProfilePosts(postsView, username, useGetposts) {
}); });
} }
function queryCreateRes(query, resource, extra) {
var req = query + '@' + resource;
twister.res[req] = {
query: query,
resource: resource,
lengthCached: 0,
twists: {
cached: {},
pending: []
}
};
if (extra)
for (i in extra)
twister.res[req][i] = extra[i];
return twister.res[req];
}
function queryStart(board, query, resource, timeoutArgs, intervalTimeout, extra) { function queryStart(board, query, resource, timeoutArgs, intervalTimeout, extra) {
var req = query + '@' + resource; var req = query + '@' + resource;
@ -444,6 +462,7 @@ function queryStart(board, query, resource, timeoutArgs, intervalTimeout, extra)
board: board, board: board,
query: query, query: query,
resource: resource, resource: resource,
lengthCached: 0,
twists: { twists: {
cached: {}, cached: {},
pending: [] pending: []
@ -462,6 +481,15 @@ function queryStart(board, query, resource, timeoutArgs, intervalTimeout, extra)
if (twister.res[req].twists.pending.indexOf(i) === -1) if (twister.res[req].twists.pending.indexOf(i) === -1)
twister.res[req].twists.pending.push(i); twister.res[req].twists.pending.push(i);
if (extra) {
if (typeof extra.drawFinish === 'function') {
twister.res[req].drawFinish = extra.drawFinish;
twister.res[req].drawFinishReq = extra.drawFinishReq;
}
if (typeof extra.skidoo === 'function')
twister.res[req].skidoo = extra.skidoo;
}
queryPendingDraw(req); queryPendingDraw(req);
} }
@ -510,27 +538,45 @@ function queryRequest(req) {
} else if (twister.res[req].resource === 'fav') } else if (twister.res[req].resource === 'fav')
twisterRpc('getfavs', [twister.res[req].query, 1000], twisterRpc('getfavs', [twister.res[req].query, 1000],
queryProcess, req); queryProcess, req);
else else if (twister.res[req].resource === 'direct') {
var lengthStandard = 100; // FIXME there may be the gap between .lastId and the lesser twist.id in response greater than 100 (very rare case)
if (twister.res[req].lengthCached < Math.min(twister.res[req].lastId, lengthStandard)
&& !twister.res[req].triedToReCache) {
twister.res[req].triedToReCache = true;
var length = Math.min(twister.res[req].lastId + 1, lengthStandard);
var query = [{username: twister.res[req].query, max_id: twister.res[req].lastId}];
} else
var length = lengthStandard, query = [{username: twister.res[req].query, since_id: twister.res[req].lastId}];
twisterRpc('getdirectmsgs', [defaultScreenName, length, query],
queryProcess, req,
function (req, res) {
console.warn(polyglot.t('ajax_error', {error: (res && res.message) ? res.message : res}));
}
);
} else
dhtget(twister.res[req].query, twister.res[req].resource, 'm', dhtget(twister.res[req].query, twister.res[req].resource, 'm',
queryProcess, req, twister.res[req].timeoutArgs); queryProcess, req, twister.res[req].timeoutArgs);
} }
function queryProcess(req, twists) { function queryProcess(req, res) {
if (!req || !twister.res[req] || !twists || !twists.length) if (!req || !twister.res[req] || typeof res !== 'object' || $.isEmptyObject(res))
return; return;
var lengthNew = 0; var lengthNew = 0;
var lengthPending = twister.res[req].twists.pending.length; var lengthPending = twister.res[req].twists.pending.length;
if (twister.res[req].resource === 'mention' && twister.res[req].query === defaultScreenName) if (twister.res[req].resource === 'mention' && twister.res[req].query === defaultScreenName)
lengthNew = queryPendingPushMentions(req, twists); lengthNew = queryPendingPushMentions(req, res);
else if (twister.res[req].resource === 'direct')
lengthNew = queryPendingPushDMs(res);
else else
lengthNew = queryPendingPush(req, twists); lengthNew = queryPendingPush(req, res);
if (typeof twister.res[req].skidoo === 'function' && twister.res[req].skidoo(req)) if (typeof twister.res[req].skidoo === 'function' && twister.res[req].skidoo(req))
return; return;
if (lengthNew) if (lengthNew) {
if (twister.res[req].resource === 'mention' && twister.res[req].query === defaultScreenName) { if (twister.res[req].resource === 'mention' && twister.res[req].query === defaultScreenName) {
$.MAL.updateNewMentionsUI(twister.res[req].lengthNew); $.MAL.updateNewMentionsUI(twister.res[req].lengthNew);
$.MAL.soundNotifyMentions(); $.MAL.soundNotifyMentions();
@ -550,6 +596,25 @@ function queryProcess(req, twists) {
$.MAL.showMentions(defaultScreenName); $.MAL.showMentions(defaultScreenName);
}).bind({req: req}) }).bind({req: req})
}); });
} else if (twister.res[req].resource === 'direct') {
if (twister.res[req].query[0] !== '*')
$.MAL.updateNewDMsUI(getNewDMsCount());
else
$.MAL.updateNewGroupDMsUI(getNewGroupDMsCount());
$.MAL.soundNotifyDM();
if (!$.mobile && $.Options.showDesktopNotifDMs.val === 'enable')
$.MAL.showDesktopNotification({
body: twister.res[req].query[0] === '*' ?
polyglot.t('You got') + ' ' + polyglot.t('new_group_messages', getNewGroupDMsCount()) + '.'
: polyglot.t('You got') + ' ' + polyglot.t('new_direct_messages', getNewDMsCount()) + '.',
tag: 'twister_notification_new_DMs',
timeout: $.Options.showDesktopNotifDMsTimer.val,
funcClick: (function () {
focusModalWithElement(twister.res[this.req].board);
}).bind({req: req})
});
// TODO new DMs counters on minimized modals'
} else if (!$.mobile && $.Options.showDesktopNotifPostsModal.val === 'enable' } else if (!$.mobile && $.Options.showDesktopNotifPostsModal.val === 'enable'
&& (twister.res[req].resource !== 'mention' || twister.res[req].query !== defaultScreenName) && (twister.res[req].resource !== 'mention' || twister.res[req].query !== defaultScreenName)
&& twister.res[req].board && isModalWithElemExists(twister.res[req].board) && twister.res[req].board && isModalWithElemExists(twister.res[req].board)
@ -559,7 +624,7 @@ function queryProcess(req, twists) {
+ polyglot.t('in search result') + '.', + polyglot.t('in search result') + '.',
tag: 'twister_notification_new_posts_modal', tag: 'twister_notification_new_posts_modal',
timeout: $.Options.showDesktopNotifPostsModalTimer.val, timeout: $.Options.showDesktopNotifPostsModalTimer.val,
funcClick: (function() { funcClick: (function () {
focusModalWithElement(twister.res[this.req].board, focusModalWithElement(twister.res[this.req].board,
function (req) { function (req) {
twister.res[req].board.closest('.postboard') twister.res[req].board.closest('.postboard')
@ -569,6 +634,7 @@ function queryProcess(req, twists) {
); );
}).bind({req: req}) }).bind({req: req})
}); });
}
if (twister.res[req].twists.pending.length > lengthPending) { // there is some twists may be which are not considered new so lengthNew equals zero (mentions thing) if (twister.res[req].twists.pending.length > lengthPending) { // there is some twists may be which are not considered new so lengthNew equals zero (mentions thing)
if (!twister.res[req].board || (!$.mobile && !isModalWithElemExists(twister.res[req].board))) if (!twister.res[req].board || (!$.mobile && !isModalWithElemExists(twister.res[req].board)))
@ -619,6 +685,7 @@ function queryPendingPush(req, twists) {
lengthNew++; lengthNew++;
twister.res[req].twists.cached[j] = twists[i]; twister.res[req].twists.cached[j] = twists[i];
twister.res[req].lengthCached++;
twister.res[req].twists.pending.push(j); twister.res[req].twists.pending.push(j);
} }
} }
@ -627,13 +694,57 @@ function queryPendingPush(req, twists) {
} }
function queryPendingDraw(req) { function queryPendingDraw(req) {
var twists = []; var twists = [], length = 0;
for (var i = 0; i < twister.res[req].twists.pending.length; i++)
twists.push(twister.res[req].twists.cached[twister.res[req].twists.pending[i]]); if (twister.res[req].resource === 'direct') {
for (var j = 0; j < twister.res[req].twists.pending.length; j++) {
var twist = twister.res[req].twists.cached[twister.res[req].twists.pending[j]];
for (var i = 0; i < length; i++)
if (twist.id < twists[i].id) {
twists.splice(i, 0, twist);
break;
}
if (length === twists.length)
twists.push(twist);
length++;
}
attachPostsToStream(twister.res[req].board, twists, false,
function (twist, req) {
return {item: postToElemDM(twist, req.peerAliasLocal, req.peerAliasRemote)
.attr('data-id', twist.id), time: twist.time};
},
{peerAliasLocal: defaultScreenName, peerAliasRemote: twister.res[req].query}
);
resetNewDMsCountForPeer(twister.res[req].query);
} else {
for (var j = 0; j < twister.res[req].twists.pending.length; j++) {
var twist = twister.res[req].twists.cached[twister.res[req].twists.pending[j]];
for (var i = 0; i < length; i++)
if (twist.userpost.time > twists[i].userpost.time) {
twists.splice(i, 0, twist);
break;
}
if (length === twists.length)
twists.push(twist);
attachPostsToStream(twister.res[req].board, twists, false); length++;
}
attachPostsToStream(twister.res[req].board, twists, true,
function (twist) {
return {item: postToElem(twist, 'original'), time: twist.userpost.time};
}
);
if (twister.res[req].resource === 'mention' && twister.res[req].query === defaultScreenName)
resetMentionsCount();
}
queryPendingClear(req); queryPendingClear(req);
$.MAL.postboardLoaded(); if (typeof twister.res[req].drawFinish === 'function')
twister.res[req].drawFinish(req, twister.res[req].drawFinishReq);
else
$.MAL.postboardLoaded();
} }

186
js/twister_directmsg.js

@ -5,120 +5,6 @@
var _groupMsgInviteToGroupQueue = []; var _groupMsgInviteToGroupQueue = [];
function requestDMsnippetList(elemList, forGroup) {
var followList = [];
for (var i = 0; i < followingUsers.length; i++)
followList.push({username: followingUsers[i]});
for (var i = 0; i < groupChatAliases.length; i++)
followList.push({username: groupChatAliases[i]});
twisterRpc('getdirectmsgs', [defaultScreenName, 1, followList],
processDMsnippet, {elemList: elemList, forGroup: forGroup},
function(req, ret) {console.log('ajax error:' + ret);}, null
);
}
function processDMsnippet(req, DMs) {
req.elemList.empty();
for (var alias in DMs)
if ((req.forGroup && alias[0] === '*') || (!req.forGroup && alias[0] !== '*'))
addToCommonDMsList(req.elemList, alias, DMs[alias][0]);
$.MAL.commonDMsListLoaded();
}
function requestDmConversationModal(postboard, peerAlias) {
if (!isModalWithElemExists(postboard))
return;
requestDmConversation(postboard, peerAlias);
setTimeout(requestDmConversationModal, 1000, postboard, peerAlias);
}
function requestDmConversation(postboard, peerAlias) {
var since_id = undefined;
var oldItems = postboard.children();
if (oldItems.length)
since_id = parseInt(oldItems.eq(oldItems.length - 1).attr('data-id'));
var userDmReq = [{username: peerAlias}];
if (typeof since_id !== 'undefined')
userDmReq[0].since_id = since_id;
var count = 100;
twisterRpc('getdirectmsgs', [defaultScreenName, count, userDmReq],
function(req, ret) {processDmConversation(req.postboard, req.peerAlias, ret);},
{postboard: postboard, peerAlias: peerAlias},
function(req, ret) {
var msg = (ret.message) ? ret.message : ret;
alert(polyglot.t('ajax_error', {error: msg}));
}
);
}
function processDmConversation(stream, peerAlias, posts) {
if (!isModalWithElemExists(stream))
return;
var streamItems = stream.children();
var streamPostsIDs = [];
var newPosts = 0;
for (var i = 0; i < streamItems.length; i++) {
streamPostsIDs.push(parseInt(streamItems.eq(i).attr('data-id')));
}
if (posts[peerAlias] && posts[peerAlias].length) {
for (var i = 0; i < posts[peerAlias].length; i++) {
if (streamPostsIDs.indexOf(posts[peerAlias][i].id) === -1) {
var lastPostID = posts[peerAlias][i].id;
newPosts++;
postToElemDM(posts[peerAlias][i], defaultScreenName, peerAlias)
.attr('data-id', lastPostID)
.appendTo(stream)
;
streamPostsIDs.push(lastPostID);
}
}
$.MAL.dmConversationLoaded(stream);
}
if (newPosts) {
resetNewDMsCountForUser(peerAlias, lastPostID);
if (getHashOfMinimizedModalWithElem(stream)) {
$.MAL.soundNotifyDM();
_newDMsPerUser[peerAlias] += newPosts;
if (peerAlias[0] === '*')
$.MAL.updateNewGroupDMsUI(getNewGroupDMsCount());
else
$.MAL.updateNewDMsUI(getNewDMsCount());
if (!$.hasOwnProperty('mobile') && $.Options.showDesktopNotifDMs.val === 'enable')
$.MAL.showDesktopNotification({
body: peerAlias[0] === '*' ?
polyglot.t('You got') + ' ' + polyglot.t('new_group_messages', newPosts) + '.'
: polyglot.t('You got') + ' ' + polyglot.t('new_direct_messages', newPosts) + '.',
tag: 'twister_notification_new_DMs',
timeout: $.Options.showDesktopNotifDMsTimer.val,
funcClick: (function() {
focusModalWithElement(this.postboard,
function (peerAlias) {
_newDMsPerUser[peerAlias] = 0;
if (peerAlias[0] === '*')
$.MAL.updateNewGroupDMsUI(getNewGroupDMsCount());
else
$.MAL.updateNewDMsUI(getNewDMsCount());
}, this.peerAlias);
}).bind({postboard: stream, peerAlias: peerAlias})
});
// TODO here we need to set new DMs counter on minimized modal button
}
}
}
function directMsgSubmit(e) { function directMsgSubmit(e) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
@ -161,6 +47,20 @@ function newDirectMsg(msg, peerAlias) {
alert(polyglot.t('Internal error: lastPostId unknown (following yourself may fix!)')); alert(polyglot.t('Internal error: lastPostId unknown (following yourself may fix!)'));
} }
function modalDMsSummaryDraw(elem, group) {
elem.empty();
for (var peerAlias in twister.DMs)
if (group ? peerAlias[0] === '*' : peerAlias[0] !== '*')
for (var j in twister.DMs[peerAlias].twists.cached)
if (twister.DMs[peerAlias].lastId === twister.DMs[peerAlias].twists.cached[j].id) {
addToCommonDMsList(elem, peerAlias, twister.DMs[peerAlias].twists.cached[j]);
break;
}
$.MAL.commonDMsListLoaded();
}
// dispara o modal de direct messages // dispara o modal de direct messages
function openCommonDMsModal() { function openCommonDMsModal() {
if (!defaultScreenName) { if (!defaultScreenName) {
@ -174,19 +74,16 @@ function openCommonDMsModal() {
title: polyglot.t('Direct Messages') title: polyglot.t('Direct Messages')
}); });
requestDMsnippetList(modal.content.find('.direct-messages-list')); modalDMsSummaryDraw(modal.content.find('.direct-messages-list'));
modal.self.find('.mark-all-as-read') modal.self.find('.mark-all-as-read')
.css('display', 'inline') .css('display', 'inline')
.attr('title', polyglot.t('Mark all as read')) .attr('title', polyglot.t('Mark all as read'))
.on('click', function (event) { .on('click', function (event) {
for (var user in _newDMsPerUser) { resetNewDMsCount();
if (user[0] !== '*') var elem = $(event.target).closest('.directMessages').find('.direct-messages-list');
_newDMsPerUser[user] = 0; elem.find('.messages-qtd').hide();
} elem.find('.post.new').removeClass('new');
saveDMsToStorage();
$.MAL.updateNewDMsUI(getNewDMsCount());
$(event.target).closest('.directMessages').find('.direct-messages-list .messages-qtd').hide();
}) })
; ;
} }
@ -210,7 +107,21 @@ function openDmWithUserModal(peerAlias) {
else else
getFullname(peerAlias, modal.self.find('.modal-header h3 span')); getFullname(peerAlias, modal.self.find('.modal-header h3 span'));
requestDmConversationModal(modal.self.find('.direct-messages-thread').empty(), peerAlias); queryStart(modal.content.find('.direct-messages-thread'),
peerAlias, 'direct', undefined, 2000, {
boardAutoAppend: true,
lastId: 0,
lengthNew: 0,
ready: function (req, peerAlias) {
twister.DMs[peerAlias] = twister.res[req];
},
readyReq: peerAlias,
drawFinish: function (req) {
$.MAL.dmConversationLoaded(twister.res[req].board);
}
}
);
modal.content.on('scroll', {req: peerAlias}, handleDMsModalScroll);
$('.dm-form-template').children().clone(true) $('.dm-form-template').children().clone(true)
.addClass('open').appendTo(modal.content).fadeIn('fast') .addClass('open').appendTo(modal.content).fadeIn('fast')
@ -232,19 +143,16 @@ function openGroupMessagesModal(groupAlias) {
modal.content.prepend($('#group-messages-profile-modal-control-template').children().clone(true)); modal.content.prepend($('#group-messages-profile-modal-control-template').children().clone(true));
requestDMsnippetList(modal.content.find('.direct-messages-list'), true); modalDMsSummaryDraw(modal.content.find('.direct-messages-list'), true);
modal.self.find('.mark-all-as-read') modal.self.find('.mark-all-as-read')
.css('display', 'inline') .css('display', 'inline')
.attr('title', polyglot.t('Mark all as read')) .attr('title', polyglot.t('Mark all as read'))
.on('click', function (event) { .on('click', function (event) {
for (var user in _newDMsPerUser) { resetNewDMsCountGroup();
if (user[0] === '*') var elem = $(event.target).closest('.groupMessages').find('.direct-messages-list');
_newDMsPerUser[user] = 0; elem.find('.messages-qtd').hide();
} elem.find('.post.new').removeClass('new');
saveDMsToStorage();
$.MAL.updateNewGroupDMsUI(getNewGroupDMsCount());
$(event.target).closest('.groupMessages').find('.direct-messages-list .messages-qtd').hide();
}) })
; ;
} else { } else {
@ -261,7 +169,21 @@ function openGroupMessagesModal(groupAlias) {
function(req, ret) { function(req, ret) {
if (ret && ret.members.indexOf(defaultScreenName) !== -1) { if (ret && ret.members.indexOf(defaultScreenName) !== -1) {
req.modal.content.append($('.messages-thread-template').children().clone(true)); req.modal.content.append($('.messages-thread-template').children().clone(true));
requestDmConversationModal(req.modal.content.find('.direct-messages-thread'), req.groupAlias); queryStart(req.modal.content.find('.direct-messages-thread'),
req.groupAlias, 'direct', undefined, 2000, {
boardAutoAppend: true,
lastId: 0,
lengthNew: 0,
ready: function (req, peerAlias) {
twister.DMs[peerAlias] = twister.res[req];
},
readyReq: req.groupAlias,
drawFinish: function (req) {
$.MAL.dmConversationLoaded(twister.res[req].board);
}
}
);
modal.content.on('scroll', {req: req.groupAlias}, handleDMsModalScroll);
var control = $('#group-messages-messages-modal-control-template').children().clone(true) var control = $('#group-messages-messages-modal-control-template').children().clone(true)
.appendTo(req.modal.content); .appendTo(req.modal.content);

4
js/twister_formatpost.js

@ -266,10 +266,10 @@ function setPostInfoSent(n, k, item) {
// format dmdata (returned by getdirectmsgs) to display in conversation thread // format dmdata (returned by getdirectmsgs) to display in conversation thread
function postToElemDM(dmData, localUser, remoteUser) { function postToElemDM(dmData, localUser, remoteUser) {
var senderAlias = (dmData.from && dmData.from.length && dmData.from.charCodeAt(0)) var senderAlias = (dmData.from && dmData.from.length && dmData.from.charCodeAt(0))
? dmData.from : (dmData.fromMe ? localUser : remoteUser); ? dmData.from : (dmData.fromMe || dmData.from === localUser ? localUser : remoteUser);
var elem = $('#dm-chat-template').clone(true).appendTo(twister.html.detached) var elem = $('#dm-chat-template').clone(true).appendTo(twister.html.detached)
.removeAttr('id') .removeAttr('id')
.addClass(dmData.fromMe ? 'sent' : 'received') .addClass(dmData.fromMe || dmData.from === localUser ? 'sent' : 'received')
; ;
var elemName = elem.find('.post-info-name') var elemName = elem.find('.post-info-name')

413
js/twister_newmsgs.js

@ -42,6 +42,7 @@ function loadMentionsFromStorage() {
var j = mentions.twists[i].userpost.n + '/' + mentions.twists[i].userpost.time; var j = mentions.twists[i].userpost.n + '/' + mentions.twists[i].userpost.time;
if (typeof twister.mentions.twists.cached[j] === 'undefined') { if (typeof twister.mentions.twists.cached[j] === 'undefined') {
twister.mentions.twists.cached[j] = mentions.twists[i]; twister.mentions.twists.cached[j] = mentions.twists[i];
twister.mentions.lengthCached++;
if (twister.mentions.twists.cached[j].isNew) if (twister.mentions.twists.cached[j].isNew)
twister.mentions.lengthNew++; twister.mentions.lengthNew++;
@ -61,6 +62,7 @@ function loadMentionsFromStorage() {
var j = mentions[i].data.userpost.n + '/' + mentions[i].mentionTime; var j = mentions[i].data.userpost.n + '/' + mentions[i].mentionTime;
if (typeof twister.mentions.twists.cached[j] === 'undefined') { if (typeof twister.mentions.twists.cached[j] === 'undefined') {
twister.mentions.twists.cached[j] = mentions[i].data; twister.mentions.twists.cached[j] = mentions[i].data;
twister.mentions.lengthCached++;
if (twister.mentions.twists.cached[j].isNew) if (twister.mentions.twists.cached[j].isNew)
twister.mentions.lengthNew++; twister.mentions.lengthNew++;
@ -104,6 +106,7 @@ function queryPendingPushMentions(req, res) {
var j = res[i].userpost.n + '/' + res[i].userpost.time; var j = res[i].userpost.n + '/' + res[i].userpost.time;
if (typeof twister.res[req].twists.cached[j] === 'undefined') { if (typeof twister.res[req].twists.cached[j] === 'undefined') {
twister.res[req].twists.cached[j] = res[i]; twister.res[req].twists.cached[j] = res[i];
twister.res[req].lengthCached++;
twister.res[req].twists.pending.push(j); twister.res[req].twists.pending.push(j);
// mention must be somewhat recent compared to last known one to be considered new // mention must be somewhat recent compared to last known one to be considered new
@ -125,6 +128,9 @@ function queryPendingPushMentions(req, res) {
} }
function resetMentionsCount() { function resetMentionsCount() {
if (!twister.mentions.lengthNew)
return;
twister.mentions.lengthNew = 0; twister.mentions.lengthNew = 0;
for (var j in twister.mentions.twists.cached) for (var j in twister.mentions.twists.cached)
@ -159,10 +165,10 @@ function handleMentionsModalScroll(event) {
if (elem.scrollTop() >= elem[0].scrollHeight - elem.height() - 50) { if (elem.scrollTop() >= elem[0].scrollHeight - elem.height() - 50) {
twister.mentions.scrollQueryActive = true; twister.mentions.scrollQueryActive = true;
twisterRpc('getmentions', [twister.mentions.query, 10, twisterRpc('getmentions', [twister.mentions.query, postsPerRefresh,
{max_id: twister.mentions.lastTorrentId - twister.mentions.lengthFromTorrent}], {max_id: twister.mentions.lastTorrentId - twister.mentions.lengthFromTorrent}],
function (req, res) { function (req, res) {
twister.mentions.scrollQueryActive = false; twister.res[req].scrollQueryActive = false;
twister.res[req].boardAutoAppend = true; // FIXME all pending twists will be appended twister.res[req].boardAutoAppend = true; // FIXME all pending twists will be appended
queryProcess(req, res); queryProcess(req, res);
twister.res[req].boardAutoAppend = false; twister.res[req].boardAutoAppend = false;
@ -174,116 +180,301 @@ function handleMentionsModalScroll(event) {
// --- direct messages --- // --- direct messages ---
var _lastDMIdPerUser = {};
var _newDMsPerUser = {};
function saveDMsToStorage() { function saveDMsToStorage() {
var ns = $.initNamespaceStorage(defaultScreenName); var pool = {};
ns.localStorage.set('lastDMIdPerUser', _lastDMIdPerUser);
ns.localStorage.set('newDMsPerUser', _newDMsPerUser); for (var peerAlias in twister.DMs) {
var twists = [], length = 0;
for (var j in twister.DMs[peerAlias].twists.cached) {
for (var i = 0; i < length; i++)
if (twister.DMs[peerAlias].twists.cached[j].id > twists[i].id) {
twists.splice(i, 0, twister.DMs[peerAlias].twists.cached[j]);
break;
}
if (length === twists.length)
twists.push(twister.DMs[peerAlias].twists.cached[j]);
length++;
}
pool[peerAlias] = {
twists: twists.slice(0, 100), // TODO add an option to specify number of DMs to cache
lastId: twister.DMs[peerAlias].lastId,
};
}
if ($.Options.get('dmEncryptCache') === 'enable') {
pool = twister.var.key.pub.encrypt(JSON.stringify(pool));
delete pool.orig; // WORKAROUND the decrypt function does .slice(0, orig) but something goes wrong in process of buffer decoding (if original string contains non-ASCII characters) and orig may be smaller than the actual size, if it is undefined .slice gets it whole
}
$.initNamespaceStorage(defaultScreenName).localStorage.set('DMs', pool);
} }
function loadDMsFromStorage() { function loadDMsFromStorage() {
var ns = $.initNamespaceStorage(defaultScreenName); var storage = $.initNamespaceStorage(defaultScreenName).localStorage;
if (ns.localStorage.isSet('lastDMIdPerUser'))
_lastDMIdPerUser = ns.localStorage.get('lastDMIdPerUser');
if (ns.localStorage.isSet('newDMsPerUser'))
_newDMsPerUser = ns.localStorage.get('newDMsPerUser');
}
function requestDMsCount() { if (storage.isSet('DMs')) {
var followList = []; var pool = storage.get('DMs');
for (var i = 0; i < followingUsers.length; i++) if (pool.key && pool.body && pool.mac) {
followList.push({username: followingUsers[i]}); if (pool = twister.var.key.decrypt(pool))
for (var i = 0; i < groupChatAliases.length; i++ ) pool = JSON.parse(pool.toString());
followList.push({username: groupChatAliases[i]}); else
console.warn('can\'t decrypt DMs\' data cache');
twisterRpc('getdirectmsgs', [defaultScreenName, 1, followList], }
function(req, dmUsers) { if (typeof pool === 'object') {
var newDMsUpdated; for (var peerAlias in pool) {
if (!twister.DMs[peerAlias])
for (var u in dmUsers) { twister.DMs[peerAlias] = queryCreateRes(peerAlias, 'direct',
if (dmUsers[u]) { {boardAutoAppend: true, lastId: 0, lengthNew: 0});
var dmData = dmUsers[u][0];
if (u in _lastDMIdPerUser && u in _newDMsPerUser) { for (var i = 0; i < pool[peerAlias].twists.length; i++) {
if (dmData.id !== _lastDMIdPerUser[u]) { var j = pool[peerAlias].twists[i].from + '/' + pool[peerAlias].twists[i].time;
_newDMsPerUser[u] += dmData.id - _lastDMIdPerUser[u]; if (typeof twister.DMs[peerAlias].twists.cached[j] === 'undefined') {
newDMsUpdated = true; twister.DMs[peerAlias].twists.cached[j] = pool[peerAlias].twists[i];
} twister.DMs[peerAlias].lengthCached++;
} else { if (twister.DMs[peerAlias].twists.cached[j].isNew)
_newDMsPerUser[u] = dmData.id + 1; twister.DMs[peerAlias].lengthNew++;
newDMsUpdated = true;
} }
_lastDMIdPerUser[u] = dmData.id;
} }
twister.DMs[peerAlias].lastId = pool[peerAlias].lastId;
}
}
}
// WARN all following storage keys are deprecated (see commit FIXME)
if (storage.isSet('lastDMIdPerUser')) {
var pool = storage.get('lastDMIdPerUser');
if (typeof pool === 'object')
for (var peerAlias in pool) {
if (!twister.DMs[peerAlias])
twister.DMs[peerAlias] = queryCreateRes(peerAlias, 'direct',
{boardAutoAppend: true, lastId: 0, lengthNew: 0});
twister.DMs[peerAlias].lastId = pool[peerAlias];
}
storage.remove('lastDMIdPerUser');
}
if (storage.isSet('newDMsPerUser')) {
var pool = storage.get('newDMsPerUser');
if (typeof pool === 'object')
for (var peerAlias in pool) {
if (!twister.DMs[peerAlias])
twister.DMs[peerAlias] = queryCreateRes(peerAlias, 'direct',
{boardAutoAppend: true, lastId: 0, lengthNew: 0});
twister.DMs[peerAlias].lengthNew = pool[peerAlias];
} }
if (newDMsUpdated) {
saveDMsToStorage(); storage.remove('newDMsPerUser');
var newDMs = getNewDMsCount(); }
if (newDMs) { }
$.MAL.updateNewDMsUI(newDMs);
$.MAL.soundNotifyDM(); function queryPendingPushDMs(res) {
var lengthNew = 0;
if (!$.mobile && $.Options.showDesktopNotifDMs.val === 'enable') { var lengthPending = 0;
$.MAL.showDesktopNotification({
body: polyglot.t('You got') + ' ' + polyglot.t('new_direct_messages', newDMs) + '.', for (var peerAlias in res) {
tag: 'twister_notification_new_DMs', if (!res[peerAlias] || !res[peerAlias].length || !twister.DMs[peerAlias])
timeout: $.Options.showDesktopNotifDMsTimer.val, continue;
funcClick: function () {$.MAL.showDMchat();}
}); for (var i = 0; i < res[peerAlias].length; i++) {
var j = res[peerAlias][i].from + '/' + res[peerAlias][i].time;
if (typeof twister.DMs[peerAlias].twists.cached[j] === 'undefined') {
twister.DMs[peerAlias].twists.cached[j] = res[peerAlias][i];
twister.DMs[peerAlias].lengthCached++;
twister.DMs[peerAlias].twists.pending.push(j);
lengthPending++;
if (twister.DMs[peerAlias].lastId < res[peerAlias][i].id) {
twister.DMs[peerAlias].lastId = res[peerAlias][i].id;
if ((!twister.DMs[peerAlias].board || !twister.DMs[peerAlias].board.is('html *'))
&& !res[peerAlias][i].fromMe && res[peerAlias][i].from !== defaultScreenName) {
lengthNew++;
twister.DMs[peerAlias].lengthNew += 1;
twister.DMs[peerAlias].twists.cached[j].isNew = true;
} }
} }
var newDMs = getNewGroupDMsCount(); }
if (newDMs) { }
$.MAL.updateNewGroupDMsUI(newDMs); }
$.MAL.soundNotifyDM();
if (lengthPending)
if (!$.mobile && $.Options.showDesktopNotifDMs.val === 'enable') { saveDMsToStorage();
$.MAL.showDesktopNotification({
body: polyglot.t('You got') + ' ' + polyglot.t('new_group_messages', newDMs) + '.', return lengthNew;
tag: 'twister_notification_new_DMs', }
timeout: $.Options.showDesktopNotifDMsTimer.val,
funcClick: function () {$.MAL.showDMchat({group: true});} function requestDMsCount() {
}); var list = [];
for (var i = 0; i < followingUsers.length; i++)
list.push({username: followingUsers[i]});
for (var i = 0; i < groupChatAliases.length; i++)
list.push({username: groupChatAliases[i]});
twisterRpc('getdirectmsgs', [defaultScreenName, 1, list],
function (req, res) {
var lengthNew = 0, lengthNewMax = 0;
var list = [];
for (var peerAlias in res) {
if (!res[peerAlias] || !res[peerAlias].length)
continue;
if (!twister.DMs[peerAlias])
twister.DMs[peerAlias] = queryCreateRes(peerAlias, 'direct',
{boardAutoAppend: true, lastId: 0, lengthNew: 0});
if (res[peerAlias][0].id > twister.DMs[peerAlias].lastId) {
lengthNew = res[peerAlias][0].id - twister.DMs[peerAlias].lastId;
if (lengthNewMax < lengthNew)
lengthNewMax = lengthNew;
list.push({username: peerAlias});
} else if (!twister.DMs[peerAlias].lengthCached)
queryPendingPushDMs(res);
}
if (list.length === 1)
queryProcess(list[0].username + '@direct', res);
else if (lengthNewMax === 1) {
if (queryPendingPushDMs(res))
DMsSummaryProcessNew();
} else if (lengthNewMax) {
twisterRpc('getdirectmsgs', [defaultScreenName, lengthNewMax, list],
function (req, res) {
if (typeof res !== 'object' || $.isEmptyObject(res))
return;
if (queryPendingPushDMs(res))
DMsSummaryProcessNew();
}, undefined,
function (req, res) {
console.warn(polyglot.t('ajax_error',
{error: (res && res.message) ? res.message : res}));
} }
} );
} }
}, null, }, undefined,
function(req, ret) {console.warn('ajax error:' + ret);}, null function (req, res) {
console.warn(polyglot.t('ajax_error', {error: (res && res.message) ? res.message : res}));
}
); );
} }
function DMsSummaryProcessNew() {
var lengthNew = getNewDMsCount();
if (lengthNew) {
$.MAL.updateNewDMsUI(lengthNew);
$.MAL.soundNotifyDM();
if (!$.mobile) {
if ($.Options.showDesktopNotifDMs.val === 'enable') {
$.MAL.showDesktopNotification({
body: polyglot.t('You got') + ' ' + polyglot.t('new_direct_messages', lengthNew) + '.',
tag: 'twister_notification_new_DMs',
timeout: $.Options.showDesktopNotifDMsTimer.val,
funcClick: function () {$.MAL.showDMchat();}
});
}
var elem = getElem('.directMessages .direct-messages-list');
if (isModalWithElemExists(elem))
modalDMsSummaryDraw(elem);
} else if ($.mobile.activePage.attr('id') !== 'directmsg')
modalDMsSummaryDraw($('#directmsg .direct-messages-list'));
}
lengthNew = getNewGroupDMsCount();
if (lengthNew) {
$.MAL.updateNewGroupDMsUI(lengthNew);
$.MAL.soundNotifyDM();
if (!$.mobile) {
if ($.Options.showDesktopNotifDMs.val === 'enable') {
$.MAL.showDesktopNotification({
body: polyglot.t('You got') + ' ' + polyglot.t('new_group_messages', lengthNew) + '.',
tag: 'twister_notification_new_DMs',
timeout: $.Options.showDesktopNotifDMsTimer.val,
funcClick: function () {$.MAL.showDMchat({group: true});}
});
}
var elem = getElem('.groupMessages .direct-messages-list');
if (isModalWithElemExists(elem))
modalDMsSummaryDraw(elem, true);
} else if ($.mobile.activePage.attr('id') !== 'directmsg')
modalDMsSummaryDraw($('#directmsg .direct-messages-list'), true);
}
}
function getNewDMsCount() { function getNewDMsCount() {
var newDMs = 0; var lengthNew = 0;
for (var user in _newDMsPerUser) { for (var peerAlias in twister.DMs)
if (user[0] !== '*' && _newDMsPerUser[user]) if (peerAlias[0] !== '*' && twister.DMs[peerAlias].lengthNew)
newDMs += _newDMsPerUser[user]; lengthNew += twister.DMs[peerAlias].lengthNew;
}
return newDMs; return lengthNew;
} }
function getNewGroupDMsCount() { function getNewGroupDMsCount() {
var newDMs = 0; var lengthNew = 0;
for (var user in _newDMsPerUser) { for (var peerAlias in twister.DMs)
if (user[0] === '*' && _newDMsPerUser[user]) if (peerAlias[0] === '*' && twister.DMs[peerAlias].lengthNew)
newDMs += _newDMsPerUser[user]; lengthNew += twister.DMs[peerAlias].lengthNew;
}
return newDMs; return lengthNew;
} }
function resetNewDMsCountForUser(user, lastId) { function resetNewDMsCount() {
_newDMsPerUser[user] = 0; var isNewDetected;
_lastDMIdPerUser[user] = lastId;
for (var peerAlias in twister.DMs)
if (twister.DMs[peerAlias].lengthNew && peerAlias[0] !== '*') {
twister.DMs[peerAlias].lengthNew = 0;
for (var j in twister.DMs[peerAlias].twists.cached)
delete twister.DMs[peerAlias].twists.cached[j].isNew;
isNewDetected = true;
}
if (!isNewDetected)
return;
saveDMsToStorage(); saveDMsToStorage();
$.MAL.updateNewDMsUI(getNewDMsCount()); $.MAL.updateNewDMsUI(getNewDMsCount());
}
function resetNewDMsCountGroup() {
var isNewDetected;
for (var peerAlias in twister.DMs)
if (twister.DMs[peerAlias].lengthNew && peerAlias[0] === '*') {
twister.DMs[peerAlias].lengthNew = 0;
for (var j in twister.DMs[peerAlias].twists.cached)
delete twister.DMs[peerAlias].twists.cached[j].isNew;
isNewDetected = true;
}
if (!isNewDetected)
return;
saveDMsToStorage();
$.MAL.updateNewGroupDMsUI(getNewGroupDMsCount()); $.MAL.updateNewGroupDMsUI(getNewGroupDMsCount());
} }
function resetNewDMsCountForPeer(peerAlias) {
if (!twister.DMs[peerAlias].lengthNew)
return;
twister.DMs[peerAlias].lengthNew = 0;
for (var j in twister.DMs[peerAlias].twists.cached)
delete twister.DMs[peerAlias].twists.cached[j].isNew;
saveDMsToStorage();
if (peerAlias[0] !== '*')
$.MAL.updateNewDMsUI(getNewDMsCount());
else
$.MAL.updateNewGroupDMsUI(getNewGroupDMsCount());
}
function updateGroupList() { function updateGroupList() {
twisterRpc('listgroups', [], twisterRpc('listgroups', [],
function(req, ret) {groupChatAliases = ret;}, null, function(req, ret) {groupChatAliases = ret;}, null,
@ -292,18 +483,58 @@ function updateGroupList() {
} }
function initDMsCount() { function initDMsCount() {
loadDMsFromStorage(); twister.DMs = {};
$.MAL.updateNewDMsUI(getNewDMsCount()); dumpPrivkey(defaultScreenName, function (req, res) {
$.MAL.updateNewGroupDMsUI(getNewGroupDMsCount()); twister.var.key = TwisterCrypto.PrivKey.fromWIF(res);
//quick hack to obtain list of group chat aliases
updateGroupList(); loadDMsFromStorage();
setInterval(updateGroupList, 60000); $.MAL.updateNewDMsUI(getNewDMsCount());
$.MAL.updateNewGroupDMsUI(getNewGroupDMsCount());
setTimeout(requestDMsCount, 200); //quick hack to obtain list of group chat aliases
//polling not needed: processNewPostsConfirmation will call requestDMsCount. updateGroupList();
//setInterval('requestDMsCount()', 5000); setInterval(updateGroupList, 60000);
setTimeout(requestDMsCount, 200);
//polling not needed: processNewPostsConfirmation will call requestDMsCount.
//setInterval('requestDMsCount()', 5000);
});
} }
function newmsgsChangedUser() { function newmsgsChangedUser() {
clearInterval(twister.mentions.interval); clearInterval(twister.mentions.interval);
} }
function handleDMsModalScroll(event) {
if (!event || !event.data.req || !twister.DMs[event.data.req]
|| twister.DMs[event.data.req].scrollQueryActive)
return;
var length = twister.DMs[event.data.req].lastId - twister.DMs[event.data.req].lengthCached + 1;
if (!length)
return;
var elem = $(event.target);
if (elem.scrollTop() < 100) {
twister.DMs[event.data.req].scrollQueryActive = true;
twisterRpc('getdirectmsgs', [defaultScreenName, Math.min(length, postsPerRefresh),
[{username: twister.DMs[event.data.req].query, max_id: length - 1}]],
function (req, res) {
twister.res[req.k].scrollQueryActive = false;
//twister.res[req.k].boardAutoAppend = true; // FIXME all pending twists will be appended
queryProcess(req.k, res);
//twister.res[req.k].boardAutoAppend = false;
if (req.container[0].scrollHeight !== req.containerScrollHeightPrev)
req.container.scrollTop(req.container[0].scrollHeight - req.containerScrollHeightPrev);
}, {
k: twister.DMs[event.data.req].query + '@' + twister.DMs[event.data.req].resource,
container: elem,
containerScrollHeightPrev: elem[0].scrollHeight
},
function (req, res) {
console.warn(polyglot.t('ajax_error',
{error: (res && res.message) ? res.message : res}));
}
);
}
}

17
js/twister_timeline.js

@ -183,13 +183,18 @@ function processReceivedPosts(req, posts)
} }
function updateTimeline(req, posts) { function updateTimeline(req, posts) {
attachPostsToStream($.MAL.getStreamPostsParent(), posts, req.getspam); attachPostsToStream($.MAL.getStreamPostsParent(), posts, true,
function (twist, promoted) {
return {item: postToElem(twist, 'original', promoted), time: twist.userpost.time};
},
req.getspam
);
for (var i = 0; i < posts.length; i++) { for (var i = 0; i < posts.length; i++) {
req.reportProcessedPost(posts[i]['userpost']['n'], posts[i]['userpost']['k'], true); req.reportProcessedPost(posts[i]['userpost']['n'], posts[i]['userpost']['k'], true);
} }
} }
function attachPostsToStream(stream, posts, isPromoted) { function attachPostsToStream(stream, posts, descendingOrder, createElem, createElemReq) {
//console.log('attachPostsToStream:'); //console.log('attachPostsToStream:');
//console.log(posts); //console.log(posts);
@ -204,7 +209,7 @@ function attachPostsToStream(stream, posts, isPromoted) {
for (var i = 0; i < posts.length; i++) { for (var i = 0; i < posts.length; i++) {
//console.log(posts[i]); //console.log(posts[i]);
var isAttached = false; var isAttached = false;
var intrantPost = {item: postToElem(posts[i], 'original', isPromoted), time: posts[i].userpost.time}; var intrantPost = createElem(posts[i], createElemReq);
intrantPost.item.attr('data-time', intrantPost.time); intrantPost.item.attr('data-time', intrantPost.time);
if (streamPosts.length) { if (streamPosts.length) {
@ -213,10 +218,10 @@ function attachPostsToStream(stream, posts, isPromoted) {
if (intrantPost.time === streamPosts[j].time && if (intrantPost.time === streamPosts[j].time &&
intrantPost.item[0].innerHTML === streamPosts[j].item[0].innerHTML) { intrantPost.item[0].innerHTML === streamPosts[j].item[0].innerHTML) {
isAttached = true; isAttached = true;
console.log('appending of duplicate twist prevented'); console.warn('appending of duplicate twist prevented');
break; break;
} else if (intrantPost.time > streamPosts[j].time) { } else if (descendingOrder ?
// this post in stream is older, so post must be inserted above intrantPost.time > streamPosts[j].time : intrantPost.time < streamPosts[j].time) {
intrantPost.item.insertBefore(streamPosts[j].item).show(); intrantPost.item.insertBefore(streamPosts[j].item).show();
streamPosts.splice(j, 0, intrantPost); streamPosts.splice(j, 0, intrantPost);
isAttached = true; isAttached = true;

38
options.html

@ -18,7 +18,6 @@
<script src="js/twister_user.js"></script> <script src="js/twister_user.js"></script>
<script src="js/twister_formatpost.js"></script> <script src="js/twister_formatpost.js"></script>
<script src="js/interface_common.js"></script> <script src="js/interface_common.js"></script>
<script src="js/interface_login.js"></script>
<script src="js/polyglot.min.js"></script> <script src="js/polyglot.min.js"></script>
<script src="js/interface_localization.js"></script> <script src="js/interface_localization.js"></script>
@ -53,6 +52,8 @@
<label for="t-2" class="tabs selectable_theme theme_nin">Theme</label> <label for="t-2" class="tabs selectable_theme theme_nin">Theme</label>
<input id="t-3" name="option_tab" type="radio" class="selectable_theme theme_nin"/> <input id="t-3" name="option_tab" type="radio" class="selectable_theme theme_nin"/>
<label for="t-3" class="tabs selectable_theme theme_nin">Notifications</label> <label for="t-3" class="tabs selectable_theme theme_nin">Notifications</label>
<input id="t-8" name="option_tab" type="radio" class="selectable_theme theme_nin" />
<label for="t-8" class="tabs selectable_theme theme_nin">Direct Messages</label>
<input id="t-4" name="option_tab" type="radio" class="selectable_theme theme_nin"/> <input id="t-4" name="option_tab" type="radio" class="selectable_theme theme_nin"/>
<label for="t-4" class="tabs selectable_theme theme_nin">Keys</label> <label for="t-4" class="tabs selectable_theme theme_nin">Keys</label>
<input id="t-5" name="option_tab" type="radio" class="selectable_theme theme_nin"/> <input id="t-5" name="option_tab" type="radio" class="selectable_theme theme_nin"/>
@ -80,10 +81,12 @@
<option value="de">German</option> <option value="de">German</option>
<option value="it">Italian</option> <option value="it">Italian</option>
<option value="ja">Japanese</option> <option value="ja">Japanese</option>
<option value="cmn">Mandarin Chinese</option>
<option value="ru">Russian</option> <option value="ru">Russian</option>
<option value="es">Spanish</option> <option value="es">Spanish</option>
<option value="tr">Turkish</option> <option value="tr">Turkish</option>
<option value="uk">Ukrainian</option> <option value="uk">Ukrainian</option>
<option value="yue">Yue Chinese</option>
</select> </select>
</form> </form>
</div> </div>
@ -195,6 +198,30 @@
</div> </div>
</div> </div>
<div class="DMs">
<div class="module">
<p class="label label-h">Direct Messages</p>
<div class="container">
<form action="" id="dmCopySelfOpt">
<p class="label">dm_copy_outgoing_to_self</p>
<select name="" id="dmCopySelf" class="container">
<option value="enable">Enable</option>
<option value="disable">Disable</option>
</select>
</form>
</div>
<div class="container">
<form action="" id="dmEncryptCacheOpt">
<p class="label">dm_encrypt_local_cache</p>
<select name="" id="dmEncryptCache" class="container">
<option value="enable">Enable</option>
<option value="disable">Disable</option>
</select>
</form>
</div>
</div>
</div>
<div class="keys"> <div class="keys">
<div class="module"> <div class="module">
<p class="label label-h"> Keys </p> <p class="label label-h"> Keys </p>
@ -458,15 +485,6 @@
</select> </select>
</form> </form>
</div> </div>
<div class="container">
<form action="" id="dmCopySelfOpt">
<p class="label">Direct Message's copy to self</p>
<select name="" id="dmCopySelf" class="container">
<option value="disable">Disable</option>
<option value="enable">Enable</option>
</select>
</form>
</div>
</div> </div>
</div> </div>
</div> </div>

2
theme_nin/css/style.css

@ -1415,7 +1415,7 @@ button:disabled:hover, button.disabled:hover, .mini-profile-actions span.disable
visibility: hidden; visibility: hidden;
} }
/* line 42, ../sass/_tabs.sass */ /* line 42, ../sass/_tabs.sass */
.options input#tab_language:checked ~ .tab-content .language, .options input#t-2:checked ~ .tab-content .theme, .options input#t-3:checked ~ .tab-content .notifications, .options input#t-4:checked ~ .tab-content .keys, .options input#t-5:checked ~ .tab-content .appearance, .options input#t-6:checked ~ .tab-content .users, .options input#t-7:checked ~ .tab-content .webtorrent { .options input#tab_language:checked ~ .tab-content .language, .options input#t-2:checked ~ .tab-content .theme, .options input#t-3:checked ~ .tab-content .notifications, .options input#t-4:checked ~ .tab-content .keys, .options input#t-5:checked ~ .tab-content .appearance, .options input#t-6:checked ~ .tab-content .users, .options input#t-7:checked ~ .tab-content .webtorrent, .options input#t-8:checked ~ .tab-content .DMs {
position: relative; position: relative;
z-index: 10; z-index: 10;
opacity: 1; opacity: 1;

12
theme_nin/js/theme_option.js

@ -53,12 +53,8 @@ $(function(){
}); });
function localizeLabels() { function localizeLabels() {
$("label[for=tab_language]").text(polyglot.t("Language")); $('label.tabs').each(function (i, elem) {
$("label[for=t-2]").text(polyglot.t("Theme")); var elem = $(elem);
$("label[for=t-3]").text(polyglot.t("Notifications")); elem.text(polyglot.t(elem.text()));
$("label[for=t-4]").text(polyglot.t("Keys")); });
$("label[for=t-5]").text(polyglot.t("Appearance"));
$("label[for=t-6]").text(polyglot.t("Users"));
$("label[for=t-7]").text(polyglot.t("WebTorrent"));
} }

3
theme_nin/sass/_tabs.sass

@ -48,6 +48,7 @@
&#t-5:checked ~ .tab-content .appearance, &#t-5:checked ~ .tab-content .appearance,
&#t-6:checked ~ .tab-content .users &#t-6:checked ~ .tab-content .users
&#t-7:checked ~ .tab-content .webtorrent &#t-7:checked ~ .tab-content .webtorrent
&#t-8:checked ~ .tab-content .DMs
position: relative position: relative
z-index: 10 z-index: 10
opacity: 1 opacity: 1
@ -67,5 +68,3 @@
input:checked + label.tabs input:checked + label.tabs
background: $bloc-background-color background: $bloc-background-color
color: $main-color-dark color: $main-color-dark

Loading…
Cancel
Save