From 8dedef7002fae74f9eab5c560aab40bde600690a Mon Sep 17 00:00:00 2001 From: JC Brand Date: Tue, 21 Oct 2014 13:18:26 +0200 Subject: [PATCH 01/41] Create new PO file for Polish. Also update documentation to mention msginit. --- docs/source/index.rst | 12 + locale/converse.pot | 366 +++++++------- locale/pl/LC_MESSAGES/converse.po | 781 ++++++++++++++++++++++++++++++ 3 files changed, 976 insertions(+), 183 deletions(-) create mode 100644 locale/pl/LC_MESSAGES/converse.po diff --git a/docs/source/index.rst b/docs/source/index.rst index 162201e84..6bf2eeb50 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -808,8 +808,20 @@ After adding the string, you'll need to regenerate the POT file, like so: make pot +To create a new PO file for a language in which converse.js is not yet +translated into, do the following + +.. note:: In this example we use Polish (pl), you need to substitute 'pl' to your own language's code. + +:: + + mkdir -p ./locale/pl/LC_MESSAGES + msginit -i ./locale/converse.pot -o ./locale/pl/LC_MESSAGES/converse.po -l pl + You can then create or update the PO file for a specific language by doing the following: +.. note:: In this example we use German (de), you need to substitute 'de' to your own language's code. + :: msgmerge ./locale/de/LC_MESSAGES/converse.po ./locale/converse.pot -U diff --git a/locale/converse.pot b/locale/converse.pot index eb73aad5b..569aa1a48 100644 --- a/locale/converse.pot +++ b/locale/converse.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: Converse.js 0.7.0\n" +"Project-Id-Version: Converse.js 0.8.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-09-22 18:14+0200\n" +"POT-Creation-Date: 2014-10-21 13:12+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,123 +17,123 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" -#: converse.js:338 +#: converse.js:314 msgid "unencrypted" msgstr "" -#: converse.js:339 +#: converse.js:315 msgid "unverified" msgstr "" -#: converse.js:340 +#: converse.js:316 msgid "verified" msgstr "" -#: converse.js:341 +#: converse.js:317 msgid "finished" msgstr "" -#: converse.js:344 +#: converse.js:320 msgid "This contact is busy" msgstr "" -#: converse.js:345 +#: converse.js:321 msgid "This contact is online" msgstr "" -#: converse.js:346 +#: converse.js:322 msgid "This contact is offline" msgstr "" -#: converse.js:347 +#: converse.js:323 msgid "This contact is unavailable" msgstr "" -#: converse.js:348 +#: converse.js:324 msgid "This contact is away for an extended period" msgstr "" -#: converse.js:349 +#: converse.js:325 msgid "This contact is away" msgstr "" -#: converse.js:351 +#: converse.js:327 msgid "Click to hide these contacts" msgstr "" -#: converse.js:353 +#: converse.js:329 msgid "My contacts" msgstr "" -#: converse.js:354 +#: converse.js:330 msgid "Pending contacts" msgstr "" -#: converse.js:355 +#: converse.js:331 msgid "Contact requests" msgstr "" -#: converse.js:356 +#: converse.js:332 msgid "Ungrouped" msgstr "" -#: converse.js:358 +#: converse.js:334 msgid "Contacts" msgstr "" -#: converse.js:359 +#: converse.js:335 msgid "Groups" msgstr "" -#: converse.js:441 +#: converse.js:417 msgid "Reconnecting" msgstr "" -#: converse.js:476 +#: converse.js:452 msgid "Disconnected" msgstr "" -#: converse.js:484 +#: converse.js:460 msgid "Error" msgstr "" -#: converse.js:486 +#: converse.js:462 msgid "Connecting" msgstr "" -#: converse.js:489 +#: converse.js:465 msgid "Connection Failed" msgstr "" -#: converse.js:491 +#: converse.js:467 msgid "Authenticating" msgstr "" -#: converse.js:494 +#: converse.js:470 msgid "Authentication Failed" msgstr "" -#: converse.js:499 +#: converse.js:475 msgid "Disconnecting" msgstr "" -#: converse.js:638 converse.js:684 +#: converse.js:614 converse.js:660 msgid "Online Contacts" msgstr "" -#: converse.js:802 +#: converse.js:778 msgid "Re-establishing encrypted session" msgstr "" -#: converse.js:814 +#: converse.js:790 msgid "Generating private key." msgstr "" -#: converse.js:815 +#: converse.js:791 msgid "Your browser might become unresponsive." msgstr "" -#: converse.js:850 +#: converse.js:826 msgid "" "Authentication request from %1$s\n" "\n" @@ -143,67 +143,67 @@ msgid "" "%2$s" msgstr "" -#: converse.js:859 +#: converse.js:835 msgid "Could not verify this user's identify." msgstr "" -#: converse.js:898 +#: converse.js:874 msgid "Exchanging private key with buddy." msgstr "" -#: converse.js:1045 +#: converse.js:1023 msgid "Personal message" msgstr "" -#: converse.js:1077 +#: converse.js:1055 msgid "Are you sure you want to clear the messages from this room?" msgstr "" -#: converse.js:1099 +#: converse.js:1077 msgid "me" msgstr "" -#: converse.js:1154 +#: converse.js:1131 msgid "is typing" msgstr "" -#: converse.js:1157 +#: converse.js:1134 msgid "has stopped typing" msgstr "" -#: converse.js:1199 converse.js:2331 +#: converse.js:1176 converse.js:2314 msgid "Show this menu" msgstr "" -#: converse.js:1200 +#: converse.js:1177 msgid "Write in the third person" msgstr "" -#: converse.js:1201 converse.js:2330 +#: converse.js:1178 converse.js:2313 msgid "Remove messages" msgstr "" -#: converse.js:1285 +#: converse.js:1262 msgid "Are you sure you want to clear the messages from this chat box?" msgstr "" -#: converse.js:1320 +#: converse.js:1297 msgid "Your message could not be sent" msgstr "" -#: converse.js:1323 +#: converse.js:1300 msgid "We received an unencrypted message" msgstr "" -#: converse.js:1326 +#: converse.js:1303 msgid "We received an unreadable encrypted message" msgstr "" -#: converse.js:1335 +#: converse.js:1312 msgid "This user has requested an encrypted session." msgstr "" -#: converse.js:1357 +#: converse.js:1334 msgid "" "Here are the fingerprints, please confirm them with %1$s, outside of this " "chat.\n" @@ -216,7 +216,7 @@ msgid "" "Cancel." msgstr "" -#: converse.js:1370 +#: converse.js:1347 msgid "" "You will be prompted to provide a security question and then an answer to " "that question.\n" @@ -225,555 +225,555 @@ msgid "" "exact same answer (case sensitive), their identity will be verified." msgstr "" -#: converse.js:1371 +#: converse.js:1348 msgid "What is your security question?" msgstr "" -#: converse.js:1373 +#: converse.js:1350 msgid "What is the answer to the security question?" msgstr "" -#: converse.js:1377 +#: converse.js:1354 msgid "Invalid authentication scheme provided" msgstr "" -#: converse.js:1488 +#: converse.js:1465 msgid "Your messages are not encrypted anymore" msgstr "" -#: converse.js:1490 +#: converse.js:1467 msgid "" "Your messages are now encrypted but your buddy's identity has not been " "verified." msgstr "" -#: converse.js:1492 +#: converse.js:1469 msgid "Your buddy's identify has been verified." msgstr "" -#: converse.js:1494 +#: converse.js:1471 msgid "Your buddy has ended encryption on their end, you should do the same." msgstr "" -#: converse.js:1503 +#: converse.js:1480 msgid "Your messages are not encrypted. Click here to enable OTR encryption." msgstr "" -#: converse.js:1505 +#: converse.js:1482 msgid "Your messages are encrypted, but your buddy has not been verified." msgstr "" -#: converse.js:1507 +#: converse.js:1484 msgid "Your messages are encrypted and your buddy verified." msgstr "" -#: converse.js:1509 +#: converse.js:1486 msgid "" "Your buddy has closed their end of the private session, you should do the " "same" msgstr "" -#: converse.js:1519 +#: converse.js:1496 msgid "Clear all messages" msgstr "" -#: converse.js:1520 +#: converse.js:1497 msgid "End encrypted conversation" msgstr "" -#: converse.js:1521 +#: converse.js:1498 msgid "Hide the list of participants" msgstr "" -#: converse.js:1522 +#: converse.js:1499 msgid "Refresh encrypted conversation" msgstr "" -#: converse.js:1523 +#: converse.js:1500 msgid "Start a call" msgstr "" -#: converse.js:1524 +#: converse.js:1501 msgid "Start encrypted conversation" msgstr "" -#: converse.js:1525 +#: converse.js:1502 msgid "Verify with fingerprints" msgstr "" -#: converse.js:1526 +#: converse.js:1503 msgid "Verify with SMP" msgstr "" -#: converse.js:1527 +#: converse.js:1504 msgid "What's this?" msgstr "" -#: converse.js:1618 +#: converse.js:1595 msgid "Online" msgstr "" -#: converse.js:1619 +#: converse.js:1596 msgid "Busy" msgstr "" -#: converse.js:1620 +#: converse.js:1597 msgid "Away" msgstr "" -#: converse.js:1621 +#: converse.js:1598 msgid "Offline" msgstr "" -#: converse.js:1622 +#: converse.js:1599 msgid "Log out" msgstr "" -#: converse.js:1628 +#: converse.js:1605 msgid "Contact name" msgstr "" -#: converse.js:1629 +#: converse.js:1606 msgid "Search" msgstr "" -#: converse.js:1633 +#: converse.js:1610 msgid "Contact username" msgstr "" -#: converse.js:1634 +#: converse.js:1611 msgid "Add" msgstr "" -#: converse.js:1639 +#: converse.js:1616 msgid "Click to add new chat contacts" msgstr "" -#: converse.js:1640 +#: converse.js:1617 msgid "Add a contact" msgstr "" -#: converse.js:1664 +#: converse.js:1641 msgid "No users found" msgstr "" -#: converse.js:1670 +#: converse.js:1647 msgid "Click to add as a chat contact" msgstr "" -#: converse.js:1725 +#: converse.js:1702 msgid "Room name" msgstr "" -#: converse.js:1726 +#: converse.js:1703 msgid "Nickname" msgstr "" -#: converse.js:1727 +#: converse.js:1704 msgid "Server" msgstr "" -#: converse.js:1728 +#: converse.js:1705 msgid "Join" msgstr "" -#: converse.js:1729 +#: converse.js:1706 msgid "Show rooms" msgstr "" -#: converse.js:1749 +#: converse.js:1726 msgid "Rooms" msgstr "" #. For translators: %1$s is a variable and will be replaced with the XMPP server name -#: converse.js:1756 +#: converse.js:1733 msgid "No rooms on %1$s" msgstr "" #. For translators: %1$s is a variable and will be #. replaced with the XMPP server name -#: converse.js:1771 +#: converse.js:1748 msgid "Rooms on %1$s" msgstr "" -#: converse.js:1780 +#: converse.js:1757 msgid "Click to open this room" msgstr "" -#: converse.js:1781 +#: converse.js:1758 msgid "Show more information on this room" msgstr "" -#: converse.js:1843 +#: converse.js:1820 msgid "Description:" msgstr "" -#: converse.js:1844 +#: converse.js:1821 msgid "Occupants:" msgstr "" -#: converse.js:1845 +#: converse.js:1822 msgid "Features:" msgstr "" -#: converse.js:1846 +#: converse.js:1823 msgid "Requires authentication" msgstr "" -#: converse.js:1847 +#: converse.js:1824 msgid "Hidden" msgstr "" -#: converse.js:1848 +#: converse.js:1825 msgid "Requires an invitation" msgstr "" -#: converse.js:1849 +#: converse.js:1826 msgid "Moderated" msgstr "" -#: converse.js:1850 +#: converse.js:1827 msgid "Non-anonymous" msgstr "" -#: converse.js:1851 +#: converse.js:1828 msgid "Open room" msgstr "" -#: converse.js:1852 +#: converse.js:1829 msgid "Permanent room" msgstr "" -#: converse.js:1853 +#: converse.js:1830 msgid "Public" msgstr "" -#: converse.js:1854 +#: converse.js:1831 msgid "Semi-anonymous" msgstr "" -#: converse.js:1855 +#: converse.js:1832 msgid "Temporary room" msgstr "" -#: converse.js:1856 +#: converse.js:1833 msgid "Unmoderated" msgstr "" -#: converse.js:2085 +#: converse.js:2062 msgid "This user is a moderator" msgstr "" -#: converse.js:2086 +#: converse.js:2063 msgid "This user can send messages in this room" msgstr "" -#: converse.js:2087 +#: converse.js:2064 msgid "This user can NOT send messages in this room" msgstr "" -#: converse.js:2119 +#: converse.js:2096 msgid "Invite..." msgstr "" -#: converse.js:2120 +#: converse.js:2097 msgid "Occupants" msgstr "" -#: converse.js:2185 +#: converse.js:2162 msgid "You are about to invite %1$s to the chat room \"%2$s\". " msgstr "" -#: converse.js:2186 +#: converse.js:2163 msgid "" "You may optionally include a message, explaining the reason for the " "invitation." msgstr "" -#: converse.js:2269 +#: converse.js:2246 msgid "Message" msgstr "" -#: converse.js:2307 +#: converse.js:2282 msgid "Error: could not execute the command" msgstr "" -#: converse.js:2329 +#: converse.js:2312 msgid "Ban user from room" msgstr "" -#: converse.js:2332 +#: converse.js:2315 msgid "Kick user from room" msgstr "" -#: converse.js:2333 +#: converse.js:2316 msgid "Write in 3rd person" msgstr "" -#: converse.js:2334 +#: converse.js:2317 msgid "Remove user's ability to post messages" msgstr "" -#: converse.js:2335 +#: converse.js:2318 msgid "Change your nickname" msgstr "" -#: converse.js:2336 +#: converse.js:2319 msgid "Set room topic" msgstr "" -#: converse.js:2337 +#: converse.js:2320 msgid "Allow muted user to post messages" msgstr "" -#: converse.js:2441 converse.js:4262 +#: converse.js:2423 converse.js:4250 msgid "Save" msgstr "" -#: converse.js:2442 +#: converse.js:2424 msgid "Cancel" msgstr "" -#: converse.js:2487 +#: converse.js:2469 msgid "An error occurred while trying to save the form." msgstr "" -#: converse.js:2531 +#: converse.js:2513 msgid "This chatroom requires a password" msgstr "" -#: converse.js:2532 +#: converse.js:2514 msgid "Password: " msgstr "" -#: converse.js:2533 +#: converse.js:2515 msgid "Submit" msgstr "" -#: converse.js:2568 +#: converse.js:2550 msgid "This room is not anonymous" msgstr "" -#: converse.js:2569 +#: converse.js:2551 msgid "This room now shows unavailable members" msgstr "" -#: converse.js:2570 +#: converse.js:2552 msgid "This room does not show unavailable members" msgstr "" -#: converse.js:2571 +#: converse.js:2553 msgid "Non-privacy-related room configuration has changed" msgstr "" -#: converse.js:2572 +#: converse.js:2554 msgid "Room logging is now enabled" msgstr "" -#: converse.js:2573 +#: converse.js:2555 msgid "Room logging is now disabled" msgstr "" -#: converse.js:2574 +#: converse.js:2556 msgid "This room is now non-anonymous" msgstr "" -#: converse.js:2575 +#: converse.js:2557 msgid "This room is now semi-anonymous" msgstr "" -#: converse.js:2576 +#: converse.js:2558 msgid "This room is now fully-anonymous" msgstr "" -#: converse.js:2577 +#: converse.js:2559 msgid "A new room has been created" msgstr "" -#: converse.js:2581 converse.js:2681 +#: converse.js:2563 converse.js:2663 msgid "You have been banned from this room" msgstr "" -#: converse.js:2582 +#: converse.js:2564 msgid "You have been kicked from this room" msgstr "" -#: converse.js:2583 +#: converse.js:2565 msgid "You have been removed from this room because of an affiliation change" msgstr "" -#: converse.js:2584 +#: converse.js:2566 msgid "" "You have been removed from this room because the room has changed to members-" "only and you're not a member" msgstr "" -#: converse.js:2585 +#: converse.js:2567 msgid "" "You have been removed from this room because the MUC (Multi-user chat) " "service is being shut down." msgstr "" -#: converse.js:2599 +#: converse.js:2581 msgid "%1$s has been banned" msgstr "" -#: converse.js:2600 +#: converse.js:2582 msgid "%1$s's nickname has changed" msgstr "" -#: converse.js:2601 +#: converse.js:2583 msgid "%1$s has been kicked out" msgstr "" -#: converse.js:2602 +#: converse.js:2584 msgid "%1$s has been removed because of an affiliation change" msgstr "" -#: converse.js:2603 +#: converse.js:2585 msgid "%1$s has been removed for not being a member" msgstr "" -#: converse.js:2607 +#: converse.js:2589 msgid "Your nickname has been automatically changed to: %1$s" msgstr "" -#: converse.js:2608 +#: converse.js:2590 msgid "Your nickname has been changed to: %1$s" msgstr "" -#: converse.js:2656 converse.js:2666 +#: converse.js:2638 converse.js:2648 msgid "The reason given is: \"" msgstr "" -#: converse.js:2679 +#: converse.js:2661 msgid "You are not on the member list of this room" msgstr "" -#: converse.js:2685 +#: converse.js:2667 msgid "No nickname was specified" msgstr "" -#: converse.js:2689 +#: converse.js:2671 msgid "You are not allowed to create new rooms" msgstr "" -#: converse.js:2691 +#: converse.js:2673 msgid "Your nickname doesn't conform to this room's policies" msgstr "" -#: converse.js:2695 +#: converse.js:2677 msgid "Your nickname is already taken" msgstr "" -#: converse.js:2697 +#: converse.js:2679 msgid "This room does not (yet) exist" msgstr "" -#: converse.js:2699 +#: converse.js:2681 msgid "This room has reached it's maximum number of occupants" msgstr "" -#: converse.js:2736 +#: converse.js:2723 msgid "Topic set by %1$s to: %2$s" msgstr "" -#: converse.js:2818 +#: converse.js:2805 msgid "%1$s has invited you to join a chat room: %2$s" msgstr "" -#: converse.js:2822 +#: converse.js:2809 msgid "" "%1$s has invited you to join a chat room: %2$s, and left the following " "reason: \"%3$s\"" msgstr "" -#: converse.js:3058 +#: converse.js:3044 msgid "Click to restore this chat" msgstr "" -#: converse.js:3202 +#: converse.js:3188 msgid "Minimized" msgstr "" -#: converse.js:3274 +#: converse.js:3262 msgid "Are you sure you want to remove this contact?" msgstr "" -#: converse.js:3297 +#: converse.js:3285 msgid "Are you sure you want to decline this contact request?" msgstr "" -#: converse.js:3341 converse.js:3359 +#: converse.js:3329 converse.js:3347 msgid "Click to remove this contact" msgstr "" -#: converse.js:3348 +#: converse.js:3336 msgid "Click to accept this contact request" msgstr "" -#: converse.js:3349 +#: converse.js:3337 msgid "Click to decline this contact request" msgstr "" -#: converse.js:3358 +#: converse.js:3346 msgid "Click to chat with this contact" msgstr "" -#: converse.js:3874 +#: converse.js:3862 msgid "Type to filter" msgstr "" #. For translators: the %1$s part gets replaced with the status #. Example, I am online -#: converse.js:4233 converse.js:4310 +#: converse.js:4221 converse.js:4298 msgid "I am %1$s" msgstr "" -#: converse.js:4235 converse.js:4315 +#: converse.js:4223 converse.js:4303 msgid "Click here to write a custom status message" msgstr "" -#: converse.js:4236 converse.js:4316 +#: converse.js:4224 converse.js:4304 msgid "Click to change your chat status" msgstr "" -#: converse.js:4261 +#: converse.js:4249 msgid "Custom status" msgstr "" -#: converse.js:4290 converse.js:4298 +#: converse.js:4278 converse.js:4286 msgid "online" msgstr "" -#: converse.js:4292 +#: converse.js:4280 msgid "busy" msgstr "" -#: converse.js:4294 +#: converse.js:4282 msgid "away for long" msgstr "" -#: converse.js:4296 +#: converse.js:4284 msgid "away" msgstr "" -#: converse.js:4419 +#: converse.js:4407 msgid "XMPP/Jabber Username:" msgstr "" -#: converse.js:4420 +#: converse.js:4408 msgid "Password:" msgstr "" -#: converse.js:4421 +#: converse.js:4409 msgid "Log In" msgstr "" -#: converse.js:4428 +#: converse.js:4416 msgid "Sign in" msgstr "" -#: converse.js:4488 +#: converse.js:4476 msgid "Toggle chat" msgstr "" diff --git a/locale/pl/LC_MESSAGES/converse.po b/locale/pl/LC_MESSAGES/converse.po new file mode 100644 index 000000000..ba498fb80 --- /dev/null +++ b/locale/pl/LC_MESSAGES/converse.po @@ -0,0 +1,781 @@ +# Polish translations for Converse.js package. +# Copyright (C) 2014 Jan-Carel Brand +# This file is distributed under the same license as the Converse.js package. +# Translators: +# Dev Account , 2014. +# +msgid "" +msgstr "" +"Project-Id-Version: Converse.js 0.8.3\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-10-21 13:12+0200\n" +"PO-Revision-Date: 2014-10-21 13:13+0200\n" +"Last-Translator: Dev Account \n" +"Language-Team: Polish\n" +"Language: pl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ASCII\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " +"|| n%100>=20) ? 1 : 2);\n" + +#: converse.js:314 +msgid "unencrypted" +msgstr "" + +#: converse.js:315 +msgid "unverified" +msgstr "" + +#: converse.js:316 +msgid "verified" +msgstr "" + +#: converse.js:317 +msgid "finished" +msgstr "" + +#: converse.js:320 +msgid "This contact is busy" +msgstr "" + +#: converse.js:321 +msgid "This contact is online" +msgstr "" + +#: converse.js:322 +msgid "This contact is offline" +msgstr "" + +#: converse.js:323 +msgid "This contact is unavailable" +msgstr "" + +#: converse.js:324 +msgid "This contact is away for an extended period" +msgstr "" + +#: converse.js:325 +msgid "This contact is away" +msgstr "" + +#: converse.js:327 +msgid "Click to hide these contacts" +msgstr "" + +#: converse.js:329 +msgid "My contacts" +msgstr "" + +#: converse.js:330 +msgid "Pending contacts" +msgstr "" + +#: converse.js:331 +msgid "Contact requests" +msgstr "" + +#: converse.js:332 +msgid "Ungrouped" +msgstr "" + +#: converse.js:334 +msgid "Contacts" +msgstr "" + +#: converse.js:335 +msgid "Groups" +msgstr "" + +#: converse.js:417 +msgid "Reconnecting" +msgstr "" + +#: converse.js:452 +msgid "Disconnected" +msgstr "" + +#: converse.js:460 +msgid "Error" +msgstr "" + +#: converse.js:462 +msgid "Connecting" +msgstr "" + +#: converse.js:465 +msgid "Connection Failed" +msgstr "" + +#: converse.js:467 +msgid "Authenticating" +msgstr "" + +#: converse.js:470 +msgid "Authentication Failed" +msgstr "" + +#: converse.js:475 +msgid "Disconnecting" +msgstr "" + +#: converse.js:614 converse.js:660 +msgid "Online Contacts" +msgstr "" + +#: converse.js:778 +msgid "Re-establishing encrypted session" +msgstr "" + +#: converse.js:790 +msgid "Generating private key." +msgstr "" + +#: converse.js:791 +msgid "Your browser might become unresponsive." +msgstr "" + +#: converse.js:826 +msgid "" +"Authentication request from %1$s\n" +"\n" +"Your buddy is attempting to verify your identity, by asking you the question " +"below.\n" +"\n" +"%2$s" +msgstr "" + +#: converse.js:835 +msgid "Could not verify this user's identify." +msgstr "" + +#: converse.js:874 +msgid "Exchanging private key with buddy." +msgstr "" + +#: converse.js:1023 +msgid "Personal message" +msgstr "" + +#: converse.js:1055 +msgid "Are you sure you want to clear the messages from this room?" +msgstr "" + +#: converse.js:1077 +msgid "me" +msgstr "" + +#: converse.js:1131 +msgid "is typing" +msgstr "" + +#: converse.js:1134 +msgid "has stopped typing" +msgstr "" + +#: converse.js:1176 converse.js:2314 +msgid "Show this menu" +msgstr "" + +#: converse.js:1177 +msgid "Write in the third person" +msgstr "" + +#: converse.js:1178 converse.js:2313 +msgid "Remove messages" +msgstr "" + +#: converse.js:1262 +msgid "Are you sure you want to clear the messages from this chat box?" +msgstr "" + +#: converse.js:1297 +msgid "Your message could not be sent" +msgstr "" + +#: converse.js:1300 +msgid "We received an unencrypted message" +msgstr "" + +#: converse.js:1303 +msgid "We received an unreadable encrypted message" +msgstr "" + +#: converse.js:1312 +msgid "This user has requested an encrypted session." +msgstr "" + +#: converse.js:1334 +msgid "" +"Here are the fingerprints, please confirm them with %1$s, outside of this " +"chat.\n" +"\n" +"Fingerprint for you, %2$s: %3$s\n" +"\n" +"Fingerprint for %1$s: %4$s\n" +"\n" +"If you have confirmed that the fingerprints match, click OK, otherwise click " +"Cancel." +msgstr "" + +#: converse.js:1347 +msgid "" +"You will be prompted to provide a security question and then an answer to " +"that question.\n" +"\n" +"Your buddy will then be prompted the same question and if they type the " +"exact same answer (case sensitive), their identity will be verified." +msgstr "" + +#: converse.js:1348 +msgid "What is your security question?" +msgstr "" + +#: converse.js:1350 +msgid "What is the answer to the security question?" +msgstr "" + +#: converse.js:1354 +msgid "Invalid authentication scheme provided" +msgstr "" + +#: converse.js:1465 +msgid "Your messages are not encrypted anymore" +msgstr "" + +#: converse.js:1467 +msgid "" +"Your messages are now encrypted but your buddy's identity has not been " +"verified." +msgstr "" + +#: converse.js:1469 +msgid "Your buddy's identify has been verified." +msgstr "" + +#: converse.js:1471 +msgid "Your buddy has ended encryption on their end, you should do the same." +msgstr "" + +#: converse.js:1480 +msgid "Your messages are not encrypted. Click here to enable OTR encryption." +msgstr "" + +#: converse.js:1482 +msgid "Your messages are encrypted, but your buddy has not been verified." +msgstr "" + +#: converse.js:1484 +msgid "Your messages are encrypted and your buddy verified." +msgstr "" + +#: converse.js:1486 +msgid "" +"Your buddy has closed their end of the private session, you should do the " +"same" +msgstr "" + +#: converse.js:1496 +msgid "Clear all messages" +msgstr "" + +#: converse.js:1497 +msgid "End encrypted conversation" +msgstr "" + +#: converse.js:1498 +msgid "Hide the list of participants" +msgstr "" + +#: converse.js:1499 +msgid "Refresh encrypted conversation" +msgstr "" + +#: converse.js:1500 +msgid "Start a call" +msgstr "" + +#: converse.js:1501 +msgid "Start encrypted conversation" +msgstr "" + +#: converse.js:1502 +msgid "Verify with fingerprints" +msgstr "" + +#: converse.js:1503 +msgid "Verify with SMP" +msgstr "" + +#: converse.js:1504 +msgid "What's this?" +msgstr "" + +#: converse.js:1595 +msgid "Online" +msgstr "" + +#: converse.js:1596 +msgid "Busy" +msgstr "" + +#: converse.js:1597 +msgid "Away" +msgstr "" + +#: converse.js:1598 +msgid "Offline" +msgstr "" + +#: converse.js:1599 +msgid "Log out" +msgstr "" + +#: converse.js:1605 +msgid "Contact name" +msgstr "" + +#: converse.js:1606 +msgid "Search" +msgstr "" + +#: converse.js:1610 +msgid "Contact username" +msgstr "" + +#: converse.js:1611 +msgid "Add" +msgstr "" + +#: converse.js:1616 +msgid "Click to add new chat contacts" +msgstr "" + +#: converse.js:1617 +msgid "Add a contact" +msgstr "" + +#: converse.js:1641 +msgid "No users found" +msgstr "" + +#: converse.js:1647 +msgid "Click to add as a chat contact" +msgstr "" + +#: converse.js:1702 +msgid "Room name" +msgstr "" + +#: converse.js:1703 +msgid "Nickname" +msgstr "" + +#: converse.js:1704 +msgid "Server" +msgstr "" + +#: converse.js:1705 +msgid "Join" +msgstr "" + +#: converse.js:1706 +msgid "Show rooms" +msgstr "" + +#: converse.js:1726 +msgid "Rooms" +msgstr "" + +#. For translators: %1$s is a variable and will be replaced with the XMPP server name +#: converse.js:1733 +msgid "No rooms on %1$s" +msgstr "" + +#. For translators: %1$s is a variable and will be +#. replaced with the XMPP server name +#: converse.js:1748 +msgid "Rooms on %1$s" +msgstr "" + +#: converse.js:1757 +msgid "Click to open this room" +msgstr "" + +#: converse.js:1758 +msgid "Show more information on this room" +msgstr "" + +#: converse.js:1820 +msgid "Description:" +msgstr "" + +#: converse.js:1821 +msgid "Occupants:" +msgstr "" + +#: converse.js:1822 +msgid "Features:" +msgstr "" + +#: converse.js:1823 +msgid "Requires authentication" +msgstr "" + +#: converse.js:1824 +msgid "Hidden" +msgstr "" + +#: converse.js:1825 +msgid "Requires an invitation" +msgstr "" + +#: converse.js:1826 +msgid "Moderated" +msgstr "" + +#: converse.js:1827 +msgid "Non-anonymous" +msgstr "" + +#: converse.js:1828 +msgid "Open room" +msgstr "" + +#: converse.js:1829 +msgid "Permanent room" +msgstr "" + +#: converse.js:1830 +msgid "Public" +msgstr "" + +#: converse.js:1831 +msgid "Semi-anonymous" +msgstr "" + +#: converse.js:1832 +msgid "Temporary room" +msgstr "" + +#: converse.js:1833 +msgid "Unmoderated" +msgstr "" + +#: converse.js:2062 +msgid "This user is a moderator" +msgstr "" + +#: converse.js:2063 +msgid "This user can send messages in this room" +msgstr "" + +#: converse.js:2064 +msgid "This user can NOT send messages in this room" +msgstr "" + +#: converse.js:2096 +msgid "Invite..." +msgstr "" + +#: converse.js:2097 +msgid "Occupants" +msgstr "" + +#: converse.js:2162 +msgid "You are about to invite %1$s to the chat room \"%2$s\". " +msgstr "" + +#: converse.js:2163 +msgid "" +"You may optionally include a message, explaining the reason for the " +"invitation." +msgstr "" + +#: converse.js:2246 +msgid "Message" +msgstr "" + +#: converse.js:2282 +msgid "Error: could not execute the command" +msgstr "" + +#: converse.js:2312 +msgid "Ban user from room" +msgstr "" + +#: converse.js:2315 +msgid "Kick user from room" +msgstr "" + +#: converse.js:2316 +msgid "Write in 3rd person" +msgstr "" + +#: converse.js:2317 +msgid "Remove user's ability to post messages" +msgstr "" + +#: converse.js:2318 +msgid "Change your nickname" +msgstr "" + +#: converse.js:2319 +msgid "Set room topic" +msgstr "" + +#: converse.js:2320 +msgid "Allow muted user to post messages" +msgstr "" + +#: converse.js:2423 converse.js:4250 +msgid "Save" +msgstr "" + +#: converse.js:2424 +msgid "Cancel" +msgstr "" + +#: converse.js:2469 +msgid "An error occurred while trying to save the form." +msgstr "" + +#: converse.js:2513 +msgid "This chatroom requires a password" +msgstr "" + +#: converse.js:2514 +msgid "Password: " +msgstr "" + +#: converse.js:2515 +msgid "Submit" +msgstr "" + +#: converse.js:2550 +msgid "This room is not anonymous" +msgstr "" + +#: converse.js:2551 +msgid "This room now shows unavailable members" +msgstr "" + +#: converse.js:2552 +msgid "This room does not show unavailable members" +msgstr "" + +#: converse.js:2553 +msgid "Non-privacy-related room configuration has changed" +msgstr "" + +#: converse.js:2554 +msgid "Room logging is now enabled" +msgstr "" + +#: converse.js:2555 +msgid "Room logging is now disabled" +msgstr "" + +#: converse.js:2556 +msgid "This room is now non-anonymous" +msgstr "" + +#: converse.js:2557 +msgid "This room is now semi-anonymous" +msgstr "" + +#: converse.js:2558 +msgid "This room is now fully-anonymous" +msgstr "" + +#: converse.js:2559 +msgid "A new room has been created" +msgstr "" + +#: converse.js:2563 converse.js:2663 +msgid "You have been banned from this room" +msgstr "" + +#: converse.js:2564 +msgid "You have been kicked from this room" +msgstr "" + +#: converse.js:2565 +msgid "You have been removed from this room because of an affiliation change" +msgstr "" + +#: converse.js:2566 +msgid "" +"You have been removed from this room because the room has changed to members-" +"only and you're not a member" +msgstr "" + +#: converse.js:2567 +msgid "" +"You have been removed from this room because the MUC (Multi-user chat) " +"service is being shut down." +msgstr "" + +#: converse.js:2581 +msgid "%1$s has been banned" +msgstr "" + +#: converse.js:2582 +msgid "%1$s's nickname has changed" +msgstr "" + +#: converse.js:2583 +msgid "%1$s has been kicked out" +msgstr "" + +#: converse.js:2584 +msgid "%1$s has been removed because of an affiliation change" +msgstr "" + +#: converse.js:2585 +msgid "%1$s has been removed for not being a member" +msgstr "" + +#: converse.js:2589 +msgid "Your nickname has been automatically changed to: %1$s" +msgstr "" + +#: converse.js:2590 +msgid "Your nickname has been changed to: %1$s" +msgstr "" + +#: converse.js:2638 converse.js:2648 +msgid "The reason given is: \"" +msgstr "" + +#: converse.js:2661 +msgid "You are not on the member list of this room" +msgstr "" + +#: converse.js:2667 +msgid "No nickname was specified" +msgstr "" + +#: converse.js:2671 +msgid "You are not allowed to create new rooms" +msgstr "" + +#: converse.js:2673 +msgid "Your nickname doesn't conform to this room's policies" +msgstr "" + +#: converse.js:2677 +msgid "Your nickname is already taken" +msgstr "" + +#: converse.js:2679 +msgid "This room does not (yet) exist" +msgstr "" + +#: converse.js:2681 +msgid "This room has reached it's maximum number of occupants" +msgstr "" + +#: converse.js:2723 +msgid "Topic set by %1$s to: %2$s" +msgstr "" + +#: converse.js:2805 +msgid "%1$s has invited you to join a chat room: %2$s" +msgstr "" + +#: converse.js:2809 +msgid "" +"%1$s has invited you to join a chat room: %2$s, and left the following " +"reason: \"%3$s\"" +msgstr "" + +#: converse.js:3044 +msgid "Click to restore this chat" +msgstr "" + +#: converse.js:3188 +msgid "Minimized" +msgstr "" + +#: converse.js:3262 +msgid "Are you sure you want to remove this contact?" +msgstr "" + +#: converse.js:3285 +msgid "Are you sure you want to decline this contact request?" +msgstr "" + +#: converse.js:3329 converse.js:3347 +msgid "Click to remove this contact" +msgstr "" + +#: converse.js:3336 +msgid "Click to accept this contact request" +msgstr "" + +#: converse.js:3337 +msgid "Click to decline this contact request" +msgstr "" + +#: converse.js:3346 +msgid "Click to chat with this contact" +msgstr "" + +#: converse.js:3862 +msgid "Type to filter" +msgstr "" + +#. For translators: the %1$s part gets replaced with the status +#. Example, I am online +#: converse.js:4221 converse.js:4298 +msgid "I am %1$s" +msgstr "" + +#: converse.js:4223 converse.js:4303 +msgid "Click here to write a custom status message" +msgstr "" + +#: converse.js:4224 converse.js:4304 +msgid "Click to change your chat status" +msgstr "" + +#: converse.js:4249 +msgid "Custom status" +msgstr "" + +#: converse.js:4278 converse.js:4286 +msgid "online" +msgstr "" + +#: converse.js:4280 +msgid "busy" +msgstr "" + +#: converse.js:4282 +msgid "away for long" +msgstr "" + +#: converse.js:4284 +msgid "away" +msgstr "" + +#: converse.js:4407 +msgid "XMPP/Jabber Username:" +msgstr "" + +#: converse.js:4408 +msgid "Password:" +msgstr "" + +#: converse.js:4409 +msgid "Log In" +msgstr "" + +#: converse.js:4416 +msgid "Sign in" +msgstr "" + +#: converse.js:4476 +msgid "Toggle chat" +msgstr "" From 7e42783c620b9b67301753362dd9b628b4db2c92 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Fri, 24 Oct 2014 18:55:32 +0200 Subject: [PATCH 02/41] Don't remove requesting users when clearing the roster cache. updates #262 --- converse.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/converse.js b/converse.js index 5900c16dc..07593cde2 100644 --- a/converse.js +++ b/converse.js @@ -638,7 +638,9 @@ if (this.debug) { this.connection.xmlInput = function (body) { console.log(body); }; this.connection.xmlOutput = function (body) { console.log(body); }; - Strophe.log = function (level, msg) { console.log(level+' '+msg); }; + Strophe.log = function (level, msg) { + console.log(level+' '+msg); + }; Strophe.error = function (msg) { console.log('ERROR: '+msg); }; @@ -3473,14 +3475,15 @@ id = this.models[i].get('id'); if (_.indexOf(_.pluck(items, 'jid'), id) === -1) { contact = this.get(id); - if (contact) { + if (contact && !contact.get('requesting')) { contact.destroy(); } } } }, - rosterHandler: function (items) { + // TODO: see if we can only use 2nd item par + rosterHandler: function (items, item) { converse.emit('roster', items); this.clearCache(items); _.each(items, function (item, index, items) { From 5e4d6bd3b29e795fc4a0b42423567eaf6db7beae Mon Sep 17 00:00:00 2001 From: JC Brand Date: Fri, 24 Oct 2014 18:58:42 +0200 Subject: [PATCH 03/41] Initial work on adding profiling tests. --- main.js | 1 + spec/controlbox.js | 47 +++++++++++++++++++++++++++++++++++++ spec/profiling.js | 58 ++++++++++++++++++++++++++++++++++++++++++++++ tests/main.js | 8 +++---- tests/mock.js | 38 +++++++++++++++++++++++++----- tests/utils.js | 11 +++++++++ 6 files changed, 153 insertions(+), 10 deletions(-) create mode 100644 spec/profiling.js diff --git a/main.js b/main.js index adca6f286..c0e54fe66 100644 --- a/main.js +++ b/main.js @@ -139,6 +139,7 @@ config = { 'crypto.sha1': { deps: ['crypto.core'] }, 'crypto.sha256': { deps: ['crypto.core'] }, 'bigint': { deps: ['crypto'] }, + 'strophe': { exports: 'Strophe' }, 'strophe.disco': { deps: ['strophe'] }, 'strophe.muc': { deps: ['strophe'] }, 'strophe.roster': { deps: ['strophe'] }, diff --git a/spec/controlbox.js b/spec/controlbox.js index 74bcab738..8948b6328 100644 --- a/spec/controlbox.js +++ b/spec/controlbox.js @@ -385,6 +385,11 @@ }, converse)); it("can be removed by the user", $.proxy(function () { + // XXX + // This tests fails because "remove" function in strophe.roster + // (line 292) gets called and it then tries to actually remove + // the user which is not in the roster... + // We'll perhaps have to first add the user again... _addContacts(); var name = mock.pend_names[0]; var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost'; @@ -764,6 +769,48 @@ // There should now be one less contact expect(this.roster.length).toEqual(mock.req_names.length-1); }, converse)); + + it("are persisted even if other contacts' change their presence ", $.proxy(function() { + /* This is a regression test. + * https://github.com/jcbrand/converse.js/issues/262 + */ + this.rosterview.model.reset(); + spyOn(this.roster, 'clearCache').andCallThrough(); + expect(this.roster.pluck('jid').length).toBe(0); + + var stanza = $pres({from: 'data@enterprise/resource', type: 'subscribe'}); + this.connection._dataRecv(test_utils.createRequest(stanza)); + expect(this.roster.pluck('jid').length).toBe(1); + expect(_.contains(this.roster.pluck('jid'), 'data@enterprise')).toBeTruthy(); + + // Taken from the spec + // http://xmpp.org/rfcs/rfc3921.html#rfc.section.7.3 + stanza = $iq({ + to: this.connection.jid, + type: 'result', + id: 'roster_1' + }).c('query', { + xmlns: 'jabber:iq:roster', + }).c('item', { + jid: 'romeo@example.net', + name: 'Romeo', + subscription:'both' + }).c('group').t('Friends').up().up() + .c('item', { + jid: 'mercutio@example.org', + name: 'Mercutio', + subscription:'from' + }).c('group').t('Friends').up().up() + .c('item', { + jid: 'benvolio@example.org', + name: 'Benvolio', + subscription:'both' + }).c('group').t('Friends'); + this.connection.roster._onReceiveRosterSuccess(null, stanza.tree()); + expect(this.roster.clearCache).toHaveBeenCalled(); + expect(_.contains(this.roster.pluck('jid'), 'data@enterprise')).toBeTruthy(); + }, converse)); + }, converse)); describe("All Contacts", $.proxy(function () { diff --git a/spec/profiling.js b/spec/profiling.js new file mode 100644 index 000000000..d195ba99c --- /dev/null +++ b/spec/profiling.js @@ -0,0 +1,58 @@ +(function (root, factory) { + define([ + "jquery", + "mock", + "test_utils" + ], function ($, mock, test_utils) { + return factory($, mock, test_utils); + } + ); +} (this, function ($, mock, test_utils) { + describe("Profiling", function() { + var roster; + beforeEach(function() { + roster = converse.connection.roster; + converse.connection._changeConnectStatus(Strophe.Status.CONNECTED); + }); + + it("adds contacts on presence stanza", $.proxy(function() { + spyOn(this.roster, 'clearCache').andCallThrough(); + expect(this.roster.pluck('jid').length).toBe(0); + + var stanza = $pres({from: 'data@enterprise/resource', type: 'subscribe'}); + this.connection._dataRecv(test_utils.createRequest(stanza)); + expect(this.roster.pluck('jid').length).toBe(1); + expect(_.contains(this.roster.pluck('jid'), 'data@enterprise')).toBeTruthy(); + + // Taken from the spec + // http://xmpp.org/rfcs/rfc3921.html#rfc.section.7.3 + stanza = $iq({ + to: this.connection.jid, + type: 'result', + id: 'roster_1' + }).c('query', { + xmlns: 'jabber:iq:roster', + }).c('item', { + jid: 'romeo@example.net', + name: 'Romeo', + subscription:'both' + }).c('group').t('Friends').up().up() + .c('item', { + jid: 'mercutio@example.org', + name: 'Mercutio', + subscription:'from' + }).c('group').t('Friends').up().up() + .c('item', { + jid: 'benvolio@example.org', + name: 'Benvolio', + subscription:'both' + }).c('group').t('Friends'); + this.connection.roster._onReceiveRosterSuccess(null, stanza.tree()); + expect(this.roster.clearCache).toHaveBeenCalled(); + + expect(_.contains(this.roster.pluck('jid'), 'data@enterprise')).toBeTruthy(); + }, converse)); + + }); + +})); diff --git a/tests/main.js b/tests/main.js index 1821b7cf5..a50b339aa 100644 --- a/tests/main.js +++ b/tests/main.js @@ -40,8 +40,6 @@ require([ window.converse_api = converse; window.localStorage.clear(); window.sessionStorage.clear(); - // XXX: call this to initialize Strophe plugins - new Strophe.Connection('localhost'); converse.initialize({ prebind: false, @@ -49,7 +47,8 @@ require([ auto_subscribe: false, animate: false, connection: mock.mock_connection, - no_trimming: true + no_trimming: true, + debug: true }, function (converse) { window.converse = converse; window.crypto = { @@ -68,7 +67,8 @@ require([ "spec/controlbox", "spec/chatbox", "spec/chatroom", - "spec/minchats" + "spec/minchats", + "spec/profiling" ], function () { // Make sure this callback is only called once. delete converse.callback; diff --git a/tests/mock.js b/tests/mock.js index 213edb797..8e4dc5dab 100644 --- a/tests/mock.js +++ b/tests/mock.js @@ -36,11 +36,37 @@ 'preventDefault': function () {} }; - mock.mock_connection = { - '_proto': {}, - 'connected': true, - 'authenticated': true, - 'mock': true, + mock.mock_connection = function () { + Strophe.Bosh.prototype._processRequest = function () {}; // Don't attempt to send out stanzas + var c = new Strophe.Connection('jasmine tests'); + c.authenticated = true; + c.connected = true; + c.mock = true; + c.jid = 'dummy@localhost/resource'; + c.vcard = { + 'get': function (callback, jid) { + var fullname; + if (!jid) { + jid = 'dummy@localhost'; + fullname = 'Max Mustermann' ; + } else { + var name = jid.split('@')[0].replace(/\./g, ' ').split(' '); + var last = name.length-1; + name[0] = name[0].charAt(0).toUpperCase()+name[0].slice(1); + name[last] = name[last].charAt(0).toUpperCase()+name[last].slice(1); + fullname = name.join(' '); + } + var vcard = $iq().c('vCard').c('FN').t(fullname); + callback(vcard.tree()); + } + }; + c._changeConnectStatus(Strophe.Status.CONNECTED); + c.attach(c.jid); + return c; + }(); + + /* + { 'muc': { 'listRooms': function () {}, 'join': function () {}, @@ -49,7 +75,6 @@ 'groupchat': function () {return String((new Date()).getTime()); } }, 'service': 'jasmine tests', - 'jid': 'dummy@localhost', 'addHandler': function (handler, ns, name, type, id, from, options) { return function () {}; }, @@ -87,5 +112,6 @@ 'items': function () {} } }; + */ return mock; })); diff --git a/tests/utils.js b/tests/utils.js index a1c37f9c2..cf9736637 100644 --- a/tests/utils.js +++ b/tests/utils.js @@ -8,6 +8,17 @@ }); }(this, function ($, mock) { var utils = {}; + + utils.createRequest = function (iq) { + iq = typeof iq.tree == "function" ? iq.tree() : iq; + var req = new Strophe.Request(iq, function() {}); + req.getResponse = function() { + var env = new Strophe.Builder('env', {type: 'mock'}).tree(); + env.appendChild(iq); + return env; + }; + return req; + }; utils.closeAllChatBoxes = function () { var i, chatbox; From cdb86788a3b330d0bb342798129e7886bea4bc3b Mon Sep 17 00:00:00 2001 From: JC Brand Date: Fri, 24 Oct 2014 21:45:48 +0200 Subject: [PATCH 04/41] Fix tests. In the previous commit, the mock connection object was refactored to use a real Strophe.Connection object. This caused a test in spec/controlbox.js to fail (due to a method that was now no longer mocked). Added a quick workaround (via monkeypatch) for now. --- spec/controlbox.js | 19 +++++++++++++------ spec/profiling.js | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/spec/controlbox.js b/spec/controlbox.js index 8948b6328..ee4a6549d 100644 --- a/spec/controlbox.js +++ b/spec/controlbox.js @@ -385,11 +385,15 @@ }, converse)); it("can be removed by the user", $.proxy(function () { - // XXX - // This tests fails because "remove" function in strophe.roster - // (line 292) gets called and it then tries to actually remove - // the user which is not in the roster... - // We'll perhaps have to first add the user again... + /* FIXME: Monkepatch + * After refactoring the mock connection to use a + * Strophe.Connection object, these tests fail because "remove" + * function in strophe.roster (line 292) gets called and it + * then tries to actually remove the user which is not in the roster... + */ + var old_remove = this.connection.roster.remove; + this.connection.roster.remove = function (jid, callback) { callback(); }; + _addContacts(); var name = mock.pend_names[0]; var jid = name.replace(/ /g,'.').toLowerCase() + '@localhost'; @@ -407,6 +411,9 @@ expect(this.connection.roster.unauthorize).toHaveBeenCalled(); expect(this.rosterview.model.remove).toHaveBeenCalled(); expect(converse.rosterview.$el.find(".pending-contact-name:contains('"+name+"')").length).toEqual(0); + + /* XXX Restore Monkeypatch */ + this.connection.roster.remove = old_remove; }, converse)); it("do not have a header if there aren't any", $.proxy(function () { @@ -822,7 +829,7 @@ }, converse)); it("are saved to, and can be retrieved from, browserStorage", $.proxy(function () { - var new_attrs, old_attrs, attrs, old_roster; + var new_attrs, old_attrs, attrs; var num_contacts = this.roster.length; new_roster = new this.RosterContacts(); // Roster items are yet to be fetched from browserStorage diff --git a/spec/profiling.js b/spec/profiling.js index d195ba99c..0421f37c9 100644 --- a/spec/profiling.js +++ b/spec/profiling.js @@ -15,7 +15,7 @@ converse.connection._changeConnectStatus(Strophe.Status.CONNECTED); }); - it("adds contacts on presence stanza", $.proxy(function() { + xit("adds contacts on presence stanza", $.proxy(function() { spyOn(this.roster, 'clearCache').andCallThrough(); expect(this.roster.pluck('jid').length).toBe(0); From 6088417df70cdfd431310d38816d23df728ce0d8 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Fri, 24 Oct 2014 22:24:05 +0200 Subject: [PATCH 05/41] Add an outline to the profiling tests. --- spec/profiling.js | 50 +++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/spec/profiling.js b/spec/profiling.js index 0421f37c9..78c167e3b 100644 --- a/spec/profiling.js +++ b/spec/profiling.js @@ -15,42 +15,40 @@ converse.connection._changeConnectStatus(Strophe.Status.CONNECTED); }); - xit("adds contacts on presence stanza", $.proxy(function() { + it("adds hundreds of contacts to the roster", $.proxy(function() { + }, converse)); + + it("adds hundreds of contacts to the roster, with roster groups", $.proxy(function() { + // XXX: Try with groups for now (might also add a test without groups) + converse.roster_groups = true; + spyOn(this.roster, 'clearCache').andCallThrough(); expect(this.roster.pluck('jid').length).toBe(0); - var stanza = $pres({from: 'data@enterprise/resource', type: 'subscribe'}); - this.connection._dataRecv(test_utils.createRequest(stanza)); - expect(this.roster.pluck('jid').length).toBe(1); - expect(_.contains(this.roster.pluck('jid'), 'data@enterprise')).toBeTruthy(); - - // Taken from the spec - // http://xmpp.org/rfcs/rfc3921.html#rfc.section.7.3 - stanza = $iq({ + var stanza = $iq({ to: this.connection.jid, type: 'result', id: 'roster_1' }).c('query', { - xmlns: 'jabber:iq:roster', - }).c('item', { - jid: 'romeo@example.net', - name: 'Romeo', - subscription:'both' - }).c('group').t('Friends').up().up() - .c('item', { - jid: 'mercutio@example.org', - name: 'Mercutio', - subscription:'from' - }).c('group').t('Friends').up().up() - .c('item', { - jid: 'benvolio@example.org', - name: 'Benvolio', - subscription:'both' - }).c('group').t('Friends'); + xmlns: 'jabber:iq:roster' + }); + + _.each(['Friends', 'Colleagues', 'Family', 'Acquaintances'], function (group) { + var i, prefix = group.toLowerCase(); + for (i=0; i<100; i++) { + stanza = stanza.c('item', { + jid: prefix+i+'@example.net', + subscription:'both' + }).c('group').t('Friends').up().up(); + } + }); this.connection.roster._onReceiveRosterSuccess(null, stanza.tree()); expect(this.roster.clearCache).toHaveBeenCalled(); - expect(_.contains(this.roster.pluck('jid'), 'data@enterprise')).toBeTruthy(); + expect(this.roster.pluck('jid').length).toBe(400); + }, converse)); + + it("contacts in a very large roster change their statuses", $.proxy(function() { }, converse)); }); From f4ff6370b9746613a4dabda2b24059dd8b236190 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Sat, 25 Oct 2014 09:57:05 +0200 Subject: [PATCH 06/41] Performance fix. Debounce the update method on the roster. --- converse.js | 4 ++-- spec/profiling.js | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/converse.js b/converse.js index 07593cde2..e0865331d 100644 --- a/converse.js +++ b/converse.js @@ -3851,14 +3851,14 @@ this.model.on("reset", this.reset, this); }, - update: function () { + update: _.debounce(function () { var $count = $('#online-count'); $count.text('('+converse.roster.getNumOnlineContacts()+')'); if (!$count.is(':visible')) { $count.show(); } return this.showHideFilter(); - }, + }, 300), render: function () { this.$el.html(converse.templates.roster({ diff --git a/spec/profiling.js b/spec/profiling.js index 78c167e3b..f7f742bdf 100644 --- a/spec/profiling.js +++ b/spec/profiling.js @@ -50,7 +50,5 @@ it("contacts in a very large roster change their statuses", $.proxy(function() { }, converse)); - }); - })); From 3d32bfefc3fafe0deac102bd392cb5076d0db1aa Mon Sep 17 00:00:00 2001 From: JC Brand Date: Sat, 25 Oct 2014 12:33:08 +0200 Subject: [PATCH 07/41] Don't add test users alphabetically, but rather randomly --- spec/profiling.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/profiling.js b/spec/profiling.js index f7f742bdf..5ed822ea2 100644 --- a/spec/profiling.js +++ b/spec/profiling.js @@ -34,12 +34,12 @@ }); _.each(['Friends', 'Colleagues', 'Family', 'Acquaintances'], function (group) { - var i, prefix = group.toLowerCase(); + var i; for (i=0; i<100; i++) { stanza = stanza.c('item', { - jid: prefix+i+'@example.net', + jid: Math.random().toString().replace('0.', '')+'@example.net', subscription:'both' - }).c('group').t('Friends').up().up(); + }).c('group').t(group).up().up(); } }); this.connection.roster._onReceiveRosterSuccess(null, stanza.tree()); From 2f968f7095551c61499b0f63c891ab22f30de2cd Mon Sep 17 00:00:00 2001 From: JC Brand Date: Sat, 25 Oct 2014 12:33:24 +0200 Subject: [PATCH 08/41] More optimizations. Don't sort the global ContactRosters collection (we only need to sort the individual groups). Only add the roster to the DOM once the users have been added. --- converse.js | 18 +++++++++++------- src/templates/roster.html | 1 - 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/converse.js b/converse.js index e0865331d..a0fd66b8b 100644 --- a/converse.js +++ b/converse.js @@ -3356,13 +3356,13 @@ this.RosterContacts = Backbone.Collection.extend({ model: converse.RosterContact, - comparator: function (contact1, contact2) { - var name1 = contact1.get('fullname').toLowerCase(); + var name1, name2; var status1 = contact1.get('chat_status') || 'offline'; - var name2 = contact2.get('fullname').toLowerCase(); var status2 = contact2.get('chat_status') || 'offline'; if (STATUS_WEIGHTS[status1] === STATUS_WEIGHTS[status2]) { + name1 = contact1.get('fullname').toLowerCase(); + name2 = contact2.get('fullname').toLowerCase(); return name1 < name2 ? -1 : (name1 > name2? 1 : 0); } else { return STATUS_WEIGHTS[status1] < STATUS_WEIGHTS[status2] ? -1 : 1; @@ -3503,7 +3503,7 @@ groups: item.groups, jid: item.jid, subscription: item.subscription - }); + }, {sort: false}); } else { if ((item.subscription === 'none') && (item.ask === null)) { // This user is no longer in our roster @@ -3849,6 +3849,7 @@ converse.roster.on("remove", this.update, this); this.model.on("add", this.onGroupAdd, this); this.model.on("reset", this.reset, this); + this.$roster = $('
'); }, update: _.debounce(function () { @@ -3857,6 +3858,9 @@ if (!$count.is(':visible')) { $count.show(); } + if (this.$roster.parent().length === 0) { + this.$el.append(this.$roster); + } return this.showHideFilter(); }, 300), @@ -3942,7 +3946,7 @@ // Don't hide if user is currently filtering. return; } - if (this.$('.roster-contacts').hasScrollBar()) { + if (this.$roster.hasScrollBar()) { if (!visible) { $filter.show(); $type.show(); @@ -4047,7 +4051,7 @@ this.add(group.get('name'), view.render()); } if (idx === 0) { - this.$('.roster-contacts').append(view.$el); + this.$roster.append(view.$el); } else { this.appendGroup(view); } @@ -4060,7 +4064,7 @@ */ var index = this.model.indexOf(view.model); if (index === 0) { - this.$('.roster-contacts').prepend(view.$el); + this.$roster.prepend(view.$el); } else if (index == (this.model.length-1)) { this.appendGroup(view); } else { diff --git a/src/templates/roster.html b/src/templates/roster.html index b3e44979e..9cb1da749 100644 --- a/src/templates/roster.html +++ b/src/templates/roster.html @@ -3,4 +3,3 @@ -
From 30ccc0437c2f40fbc6b0d391fe565b121a063321 Mon Sep 17 00:00:00 2001 From: Deuteu Date: Sun, 26 Oct 2014 15:52:27 +0100 Subject: [PATCH 09/41] Remove unnecessary commas for ie8 compatibility --- converse.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/converse.js b/converse.js index 07593cde2..41b41be00 100644 --- a/converse.js +++ b/converse.js @@ -1599,7 +1599,7 @@ label_away: __('Away'), label_offline: __('Offline'), label_logout: __('Log out'), - allow_logout: converse.allow_logout, + allow_logout: converse.allow_logout }); this.$tabs.append(converse.templates.contacts_tab({label_contacts: LABEL_CONTACTS})); if (converse.xhr_user_search) { @@ -2081,7 +2081,7 @@ initialize: function (options) { this.browserStorage = new Backbone.BrowserStorage[converse.storage]( b64_sha1('converse.occupants'+converse.bare_jid+options.nick)); - }, + } }); this.ChatRoomOccupantsView = Backbone.Overview.extend({ @@ -2171,7 +2171,7 @@ $(ev.target).typeahead('val', ''); }, this)); return this; - }, + } }); @@ -2212,7 +2212,7 @@ this); this.occupantsview = new converse.ChatRoomOccupantsView({ - model: new converse.ChatRoomOccupants({nick: this.model.get('nick')}), + model: new converse.ChatRoomOccupants({nick: this.model.get('nick')}) }); this.occupantsview.chatroomview = this; this.render(); @@ -2245,7 +2245,7 @@ .append( converse.templates.chatarea({ 'show_toolbar': converse.show_toolbar, - 'label_message': __('Message'), + 'label_message': __('Message') })) .append(this.occupantsview.render().$el); this.renderToolbar(); @@ -2558,7 +2558,7 @@ 172: __('This room is now non-anonymous'), 173: __('This room is now semi-anonymous'), 174: __('This room is now fully-anonymous'), - 201: __('A new room has been created'), + 201: __('A new room has been created') }, disconnectMessages: { @@ -3170,7 +3170,7 @@ this.set({ 'collapsed': this.get('collapsed') || false, 'num_minimized': this.get('num_minimized') || 0, - 'num_unread': this.get('num_unread') || 0, + 'num_unread': this.get('num_unread') || 0 }); } }); @@ -3196,7 +3196,7 @@ this.$flyout.show(); } return this.$el; - }, + } }); this.RosterContact = Backbone.Model.extend({ @@ -3336,7 +3336,7 @@ this.$el.html(converse.templates.requesting_contact( _.extend(item.toJSON(), { 'desc_accept': __("Click to accept this contact request"), - 'desc_decline': __("Click to decline this contact request"), + 'desc_decline': __("Click to decline this contact request") }) )); converse.controlboxtoggle.showControlBox(); @@ -4681,6 +4681,6 @@ }, 'registerPlugin': function (name, callback) { converse.plugins[name] = callback; - }, + } }; })); From 695c8f441d640571307a507820f4f9b3ccabdaca Mon Sep 17 00:00:00 2001 From: JC Brand Date: Sun, 26 Oct 2014 17:10:58 +0100 Subject: [PATCH 10/41] Minimize debounce time for update method to 100ms. --- converse.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/converse.js b/converse.js index a0fd66b8b..da9259ad7 100644 --- a/converse.js +++ b/converse.js @@ -1935,7 +1935,16 @@ b64_sha1('converse.roster.groups'+converse.bare_jid)); converse.rosterview = new converse.RosterView({model: rostergroups}); this.contactspanel.$el.append(converse.rosterview.$el); + // TODO: + // See if we shouldn't also fetch the roster here... otherwise + // the roster is always populated by the rosterHandler method, + // which appears to be a less economic way. + // i.e. from what it seems, only groups are fetched from + // browserStorage, and no contacts. + // converse.roster.fetch() converse.rosterview.render().fetch().update(); + // TODO: See if we can optimize here by not calling this method + // on every page load. converse.connection.roster.get(function () {}); return this; }, @@ -3861,8 +3870,9 @@ if (this.$roster.parent().length === 0) { this.$el.append(this.$roster); } + console.log('update called'); return this.showHideFilter(); - }, 300), + }, 100), render: function () { this.$el.html(converse.templates.roster({ From 015367af6242bb104f6ba7c6b56ec15a2460eaf1 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Sun, 26 Oct 2014 17:12:59 +0100 Subject: [PATCH 11/41] Add another profiling method for adding users without groups --- spec/profiling.js | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/spec/profiling.js b/spec/profiling.js index 5ed822ea2..3ebe61463 100644 --- a/spec/profiling.js +++ b/spec/profiling.js @@ -9,22 +9,15 @@ ); } (this, function ($, mock, test_utils) { describe("Profiling", function() { - var roster; beforeEach(function() { - roster = converse.connection.roster; + converse.connection.roster.items = []; converse.connection._changeConnectStatus(Strophe.Status.CONNECTED); }); it("adds hundreds of contacts to the roster", $.proxy(function() { - }, converse)); - - it("adds hundreds of contacts to the roster, with roster groups", $.proxy(function() { - // XXX: Try with groups for now (might also add a test without groups) - converse.roster_groups = true; - + converse.roster_groups = false; spyOn(this.roster, 'clearCache').andCallThrough(); expect(this.roster.pluck('jid').length).toBe(0); - var stanza = $iq({ to: this.connection.jid, type: 'result', @@ -32,7 +25,6 @@ }).c('query', { xmlns: 'jabber:iq:roster' }); - _.each(['Friends', 'Colleagues', 'Family', 'Acquaintances'], function (group) { var i; for (i=0; i<100; i++) { @@ -44,7 +36,31 @@ }); this.connection.roster._onReceiveRosterSuccess(null, stanza.tree()); expect(this.roster.clearCache).toHaveBeenCalled(); + expect(this.roster.pluck('jid').length).toBe(400); + }, converse)); + it("adds hundreds of contacts to the roster, with roster groups", $.proxy(function() { + converse.roster_groups = true; + spyOn(this.roster, 'clearCache').andCallThrough(); + expect(this.roster.pluck('jid').length).toBe(0); + var stanza = $iq({ + to: this.connection.jid, + type: 'result', + id: 'roster_1' + }).c('query', { + xmlns: 'jabber:iq:roster' + }); + _.each(['Friends', 'Colleagues', 'Family', 'Acquaintances'], function (group) { + var i; + for (i=0; i<100; i++) { + stanza = stanza.c('item', { + jid: Math.random().toString().replace('0.', '')+'@example.net', + subscription:'both' + }).c('group').t(group).up().up(); + } + }); + this.connection.roster._onReceiveRosterSuccess(null, stanza.tree()); + expect(this.roster.clearCache).toHaveBeenCalled(); expect(this.roster.pluck('jid').length).toBe(400); }, converse)); From 07186bcecd9428837aa6f01a4029c2ef91c3ca9c Mon Sep 17 00:00:00 2001 From: JC Brand Date: Sun, 26 Oct 2014 20:20:05 +0100 Subject: [PATCH 12/41] Performance fix. Set display "none" on the roster element. --- converse.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/converse.js b/converse.js index da9259ad7..4ea2072d0 100644 --- a/converse.js +++ b/converse.js @@ -1941,6 +1941,8 @@ // which appears to be a less economic way. // i.e. from what it seems, only groups are fetched from // browserStorage, and no contacts. + // XXX: Make sure that if fetch is called, we don't sort on + // each item add... // converse.roster.fetch() converse.rosterview.render().fetch().update(); // TODO: See if we can optimize here by not calling this method @@ -3858,7 +3860,7 @@ converse.roster.on("remove", this.update, this); this.model.on("add", this.onGroupAdd, this); this.model.on("reset", this.reset, this); - this.$roster = $('
'); + this.$roster = $(''); }, update: _.debounce(function () { @@ -3868,7 +3870,7 @@ $count.show(); } if (this.$roster.parent().length === 0) { - this.$el.append(this.$roster); + this.$el.append(this.$roster.show()); } console.log('update called'); return this.showHideFilter(); From 902e833dec24afb5ac9b3f908d0de0141d4cb81f Mon Sep 17 00:00:00 2001 From: JC Brand Date: Sun, 26 Oct 2014 23:10:43 +0100 Subject: [PATCH 13/41] Some refactoring. Add showInRoster method which checks if a contact should appear in the roster (depends on show_only_online_users setting) --- converse.js | 41 ++++++++++++++++++++++------------------- spec/profiling.js | 5 +++-- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/converse.js b/converse.js index 4ea2072d0..332043b1c 100644 --- a/converse.js +++ b/converse.js @@ -3223,6 +3223,10 @@ 'status': '' }, attributes); this.set(attrs); + }, + + showInRoster: function () { + return (!converse.show_only_online_users || this.get('chat_status') === 'online'); } }); @@ -3237,24 +3241,12 @@ }, initialize: function () { - this.model.on("change", this.onChange, this); + this.model.on("change", this.render, this); this.model.on("remove", this.remove, this); this.model.on("destroy", this.remove, this); this.model.on("open", this.openChat, this); }, - onChange: function () { - if (converse.show_only_online_users) { - if (this.model.get('chat_status') !== 'online') { - this.$el.hide(); - } else { - this.$el.show(); - } - } else { - this.render(); - } - }, - openChat: function (ev) { if (ev && ev.preventDefault) { ev.preventDefault(); } // XXX: Can this.model.attributes be used here, instead of @@ -3304,6 +3296,12 @@ }, render: function () { + if (!this.model.showInRoster()) { + this.$el.hide(); + return this; + } else if (this.$el[0].style.display === "none") { + this.$el.show(); + } var item = this.model, ask = item.get('ask'), chat_status = item.get('chat_status'), @@ -3697,11 +3695,13 @@ var view = new converse.RosterContactView({model: contact}); this.add(contact.get('id'), view); view = this.positionContact(contact).render(); - if (this.model.get('state') === CLOSED) { - view.$el.hide(); - this.$el.show(); - } else { - this.show(); + if (contact.showInRoster()) { + if (this.model.get('state') === CLOSED) { + if (view.$el[0].style.display !== "none") { view.$el.hide(); } + if (this.$el[0].style.display === "none") { this.$el.show(); } + } else { + if (this.$el[0].style.display !== "block") { this.show(); } + } } }, @@ -3723,6 +3723,9 @@ }, show: function () { + // FIXME: There's a bug here, if show_only_online_users is true + // Possible solution, get the group, call _.each and check + // showInRoster this.$el.nextUntil('dt').addBack().show(); }, @@ -3740,7 +3743,7 @@ if (q.length === 0) { if (this.model.get('state') === OPENED) { this.model.contacts.each($.proxy(function (item) { - if (!(converse.show_only_online_users && item.get('chat_status') === 'online')) { + if (item.showInRoster()) { this.get(item.get('id')).$el.show(); } }, this)); diff --git a/spec/profiling.js b/spec/profiling.js index 3ebe61463..22c8659a7 100644 --- a/spec/profiling.js +++ b/spec/profiling.js @@ -40,6 +40,7 @@ }, converse)); it("adds hundreds of contacts to the roster, with roster groups", $.proxy(function() { + // converse.show_only_online_users = true; converse.roster_groups = true; spyOn(this.roster, 'clearCache').andCallThrough(); expect(this.roster.pluck('jid').length).toBe(0); @@ -52,7 +53,7 @@ }); _.each(['Friends', 'Colleagues', 'Family', 'Acquaintances'], function (group) { var i; - for (i=0; i<100; i++) { + for (i=0; i<500; i++) { stanza = stanza.c('item', { jid: Math.random().toString().replace('0.', '')+'@example.net', subscription:'both' @@ -61,7 +62,7 @@ }); this.connection.roster._onReceiveRosterSuccess(null, stanza.tree()); expect(this.roster.clearCache).toHaveBeenCalled(); - expect(this.roster.pluck('jid').length).toBe(400); + //expect(this.roster.pluck('jid').length).toBe(400); }, converse)); it("contacts in a very large roster change their statuses", $.proxy(function() { From d03a9a019229c6896688e504cbd9179a7b5899bd Mon Sep 17 00:00:00 2001 From: JC Brand Date: Sun, 26 Oct 2014 23:11:58 +0100 Subject: [PATCH 14/41] bugfix in positionGroup. Index must always be 0 if there aren't any group elements in the dom yet. --- converse.js | 5 +++-- spec/profiling.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/converse.js b/converse.js index 332043b1c..749bb8758 100644 --- a/converse.js +++ b/converse.js @@ -4077,13 +4077,14 @@ /* Place the group's DOM element in the correct alphabetical * position amongst the other groups in the roster. */ - var index = this.model.indexOf(view.model); + var $groups = this.$('.roster-group'), + index = $groups.length ? this.model.indexOf(view.model) : 0; if (index === 0) { this.$roster.prepend(view.$el); } else if (index == (this.model.length-1)) { this.appendGroup(view); } else { - $(this.$('.roster-group').eq(index)).before(view.$el); + $($groups.eq(index)).before(view.$el); } return this; }, diff --git a/spec/profiling.js b/spec/profiling.js index 22c8659a7..7e082f901 100644 --- a/spec/profiling.js +++ b/spec/profiling.js @@ -53,7 +53,7 @@ }); _.each(['Friends', 'Colleagues', 'Family', 'Acquaintances'], function (group) { var i; - for (i=0; i<500; i++) { + for (i=0; i<100; i++) { stanza = stanza.c('item', { jid: Math.random().toString().replace('0.', '')+'@example.net', subscription:'both' From 8998a057dab5edfb6fd869078dfb48d03b550601 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Mon, 27 Oct 2014 18:48:54 +0100 Subject: [PATCH 15/41] Make sure that subscribeToRosterSuggestions is ... called sequentially with enough time between each call. --- converse.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/converse.js b/converse.js index 749bb8758..96bf3ce6d 100644 --- a/converse.js +++ b/converse.js @@ -1935,7 +1935,7 @@ b64_sha1('converse.roster.groups'+converse.bare_jid)); converse.rosterview = new converse.RosterView({model: rostergroups}); this.contactspanel.$el.append(converse.rosterview.$el); - // TODO: + // TODO: // See if we shouldn't also fetch the roster here... otherwise // the roster is always populated by the rosterHandler method, // which appears to be a less economic way. @@ -3379,15 +3379,13 @@ }, subscribeToSuggestedItems: function (msg) { - $(msg).find('item').each(function () { + $(msg).find('item').each(function (i, items) { var $this = $(this), jid = $this.attr('jid'), action = $this.attr('action'), fullname = $this.attr('name'); if (action === 'add') { - converse.connection.roster.add(jid, fullname, [], function (iq) { - converse.connection.roster.subscribe(jid, null, converse.xmppstatus.get('fullname')); - }); + converse.connection.roster.subscribe(jid, null, converse.xmppstatus.get('fullname')); } }); return true; @@ -3725,7 +3723,7 @@ show: function () { // FIXME: There's a bug here, if show_only_online_users is true // Possible solution, get the group, call _.each and check - // showInRoster + // showInRoster this.$el.nextUntil('dt').addBack().show(); }, @@ -3988,8 +3986,19 @@ }, registerRosterXHandler: function () { + var t = 0; converse.connection.addHandler( - $.proxy(converse.roster.subscribeToSuggestedItems, converse.roster), + function (msg) { + window.setTimeout( + function () { + converse.connection.flush(); + $.proxy(converse.roster.subscribeToSuggestedItems, converse.roster)(msg); + }, + t + ); + t += $(msg).find('item').length*250; + return true; + }, 'http://jabber.org/protocol/rosterx', 'message', null); }, From ec00a04068e0fd409c10f875f3febbac989a9b41 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Mon, 27 Oct 2014 21:35:06 +0100 Subject: [PATCH 16/41] Bugfix. .roster-group elements must be found relative to --- converse.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/converse.js b/converse.js index 96bf3ce6d..bf258c8b6 100644 --- a/converse.js +++ b/converse.js @@ -3981,8 +3981,8 @@ registerRosterHandler: function () { // Register handlers that depend on the roster converse.connection.roster.registerCallback( - $.proxy(converse.roster.rosterHandler, converse.roster), - null, 'presence', null); + $.proxy(converse.roster.rosterHandler, converse.roster) + ); }, registerRosterXHandler: function () { @@ -4086,7 +4086,7 @@ /* Place the group's DOM element in the correct alphabetical * position amongst the other groups in the roster. */ - var $groups = this.$('.roster-group'), + var $groups = this.$roster.find('.roster-group'), index = $groups.length ? this.model.indexOf(view.model) : 0; if (index === 0) { this.$roster.prepend(view.$el); @@ -4101,7 +4101,7 @@ appendGroup: function (view) { /* Add the group at the bottom of the roster */ - var $last = this.$('.roster-group').last(); + var $last = this.$roster.find('.roster-group').last(); var $siblings = $last.siblings('dd'); if ($siblings.length > 0) { $siblings.last().after(view.$el); From c2063eb1a71a936935780ea5c7f1bed309a58442 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Mon, 27 Oct 2014 21:35:25 +0100 Subject: [PATCH 17/41] Keep on getting disconnected. This fixed it. Seems like the RID gets incremented once too many (outside of upper bound of expected window), and then server responds with item-not-found. See: http://xmpp.org/extensions/xep-0124.html --- converse.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/converse.js b/converse.js index bf258c8b6..e72e2454e 100644 --- a/converse.js +++ b/converse.js @@ -4592,8 +4592,6 @@ sid = this.session.get('sid'); jid = this.session.get('jid'); if (rid && jid && sid) { - // We have the necessary tokens for resuming a session - rid += 1; this.session.save({rid: rid}); // The RID needs to be increased with each request. this.connection.attach(jid, sid, rid, this.onConnect); } else if (this.prebind) { From b2b4474439017ca3b4c7e053b50b7eea19e78d68 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Mon, 27 Oct 2014 21:41:41 +0100 Subject: [PATCH 18/41] Performance fix. Don't loop through whole roster when only one item changed. updates #151 --- converse.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/converse.js b/converse.js index e72e2454e..8434a3978 100644 --- a/converse.js +++ b/converse.js @@ -3493,7 +3493,8 @@ rosterHandler: function (items, item) { converse.emit('roster', items); this.clearCache(items); - _.each(items, function (item, index, items) { + var new_items = item ? [item] : items; + _.each(new_items, function (item, index, items) { if (this.isSelf(item.jid)) { return; } var model = this.get(item.jid); if (!model) { From 37eefa67d0f96b80689d971450a19a1bcb91dbd3 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Mon, 27 Oct 2014 21:53:05 +0100 Subject: [PATCH 19/41] Performance fix. Don't query for the roster on each page load. Instead, just populate the roster from sessionStorage if available. --- converse.js | 47 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/converse.js b/converse.js index 8434a3978..044085a62 100644 --- a/converse.js +++ b/converse.js @@ -541,6 +541,7 @@ }; this.clearSession = function () { + this.roster.browserStorage._clear(); this.session.browserStorage._clear(); // XXX: this should perhaps go into the beforeunload handler converse.chatboxes.get('controlbox').save({'connected': false}); @@ -1945,9 +1946,6 @@ // each item add... // converse.roster.fetch() converse.rosterview.render().fetch().update(); - // TODO: See if we can optimize here by not calling this method - // on every page load. - converse.connection.roster.get(function () {}); return this; }, @@ -3889,10 +3887,47 @@ fetch: function () { this.model.fetch({ - silent: true, - success: $.proxy(this.positionFetchedGroups, this) + silent: true, // We use the success handler to handle groups that were added, + // we need to first have all groups before positionFetchedGroups + // will work properly. + success: $.proxy(function (collection, resp, options) { + if (collection.length !== 0) { + this.positionFetchedGroups(collection, resp, options); + } + converse.roster.fetch({ + add: true, + success: function (collection) { + // XXX: Bit of a hack. + // strophe.roster expects .get to be called for + // every page load so that its "items" attr + // gets populated. + // This is very inefficient for large rosters, + // and we already have the roster cached in + // sessionStorage. + // Therefore we manually populate the "items" + // attr. + // Ideally we should eventually replace + // strophe.roster with something better. + if (collection.length > 0) { + collection.each(function (item) { + converse.connection.roster.items.push({ + name : item.get('fullname'), + jid : item.get('jid'), + subscription : item.get('subscription'), + ask : item.get('ask'), + groups : item.get('groups'), + resources : item.get('resources') + }); + }); + converse.initial_presence_sent = 1; + converse.xmppstatus.sendPresence(); + } else { + converse.connection.roster.get(); + } + } + }); + }, this) }); - converse.roster.fetch({add: true}); return this; }, From 7e4c1d6d8d75460aa8faffa4af7abccb8b315247 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Mon, 27 Oct 2014 21:54:00 +0100 Subject: [PATCH 20/41] Keep a local customized copy of strophe.roster.js The new changes made to strophe.roster.js are incompatible with the way converse.js works. Will likely replace strophe.roster.js completely. --- bower.json | 3 +- main.js | 2 +- src/strophe.roster.js | 447 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 449 insertions(+), 3 deletions(-) create mode 100644 src/strophe.roster.js diff --git a/bower.json b/bower.json index 594e1c7bf..efac268a7 100644 --- a/bower.json +++ b/bower.json @@ -16,7 +16,6 @@ "backbone.browserStorage": "*", "backbone.overview": "*", "strophe": "~1.1.3", - "strophe.roster": "https://raw.github.com/strophe/strophejs-plugins/b1f364eb6e854ffe844c57add38e885cfeb9b498/roster/strophe.roster.js", "strophe.muc": "https://raw.githubusercontent.com/strophe/strophejs-plugins/master/muc/strophe.muc.js", "otr": "0.2.12", "crypto-js-evanvosberg": "~3.1.2", @@ -30,7 +29,7 @@ "bootstrapJS": "https://raw.githubusercontent.com/jcbrand/bootstrap/7d96a5f60d26c67b5348b270a775518b96a702c8/dist/js/bootstrap.js", "fontawesome": "~4.1.0", "typeahead.js": "https://raw.githubusercontent.com/jcbrand/typeahead.js/eedfb10505dd3a20123d1fafc07c1352d83f0ab3/dist/typeahead.jquery.js", - "strophejs-plugins": "~0.0.4" + "strophejs-plugins": "git@github.com:strophe/strophejs-plugins.git#a56421ff4ecf0807113ab48c46728715597df599" }, "exportsOverride": {} } diff --git a/main.js b/main.js index c0e54fe66..0946c251f 100644 --- a/main.js +++ b/main.js @@ -17,7 +17,7 @@ config = { "strophe": "components/strophe/strophe", "strophe.disco": "components/strophejs-plugins/disco/strophe.disco", "strophe.muc": "components/strophe.muc/index", - "strophe.roster": "components/strophe.roster/index", + "strophe.roster": "src/strophe.roster", "strophe.vcard": "components/strophejs-plugins/vcard/strophe.vcard", "text": 'components/requirejs-text/text', "tpl": 'components/requirejs-tpl-jcbrand/tpl', diff --git a/src/strophe.roster.js b/src/strophe.roster.js new file mode 100644 index 000000000..79182d118 --- /dev/null +++ b/src/strophe.roster.js @@ -0,0 +1,447 @@ +/* + Copyright 2010, François de Metz +*/ +/** + * Roster Plugin + * Allow easily roster management + * + * Features + * * Get roster from server + * * handle presence + * * handle roster iq + * * subscribe/unsubscribe + * * authorize/unauthorize + * * roster versioning (xep 237) + */ +Strophe.addConnectionPlugin('roster', +{ + /** Function: init + * Plugin init + * + * Parameters: + * (Strophe.Connection) conn - Strophe connection + */ + init: function(conn) + { + this._connection = conn; + this._callbacks = []; + /** Property: items + * Roster items + * [ + * { + * name : "", + * jid : "", + * subscription : "", + * ask : "", + * groups : ["", ""], + * resources : { + * myresource : { + * show : "", + * status : "", + * priority : "" + * } + * } + * } + * ] + */ + this.items = []; + /** Property: ver + * current roster revision + * always null if server doesn't support xep 237 + */ + this.ver = null; + // Override the connect and attach methods to always add presence and roster handlers. + // They are removed when the connection disconnects, so must be added on connection. + var oldCallback, roster = this, _connect = conn.connect, _attach = conn.attach; + var newCallback = function(status) + { + if (status == Strophe.Status.ATTACHED || status == Strophe.Status.CONNECTED) + { + try + { + // Presence subscription + conn.addHandler(roster._onReceivePresence.bind(roster), null, 'presence', null, null, null); + conn.addHandler(roster._onReceiveIQ.bind(roster), Strophe.NS.ROSTER, 'iq', "set", null, null); + } + catch (e) + { + Strophe.error(e); + } + } + if (typeof oldCallback === "function") { + oldCallback.apply(this, arguments); + } + }; + conn.connect = function(jid, pass, callback, wait, hold) + { + oldCallback = callback; + if (typeof jid == "undefined") + jid = null; + if (typeof pass == "undefined") + pass = null; + callback = newCallback; + _connect.apply(conn, [jid, pass, callback, wait, hold]); + }; + conn.attach = function(jid, sid, rid, callback, wait, hold, wind) + { + oldCallback = callback; + if (typeof jid == "undefined") + jid = null; + if (typeof sid == "undefined") + sid = null; + if (typeof rid == "undefined") + rid = null; + callback = newCallback; + _attach.apply(conn, [jid, sid, rid, callback, wait, hold, wind]); + }; + + Strophe.addNamespace('ROSTER_VER', 'urn:xmpp:features:rosterver'); + Strophe.addNamespace('NICK', 'http://jabber.org/protocol/nick'); + }, + /** Function: supportVersioning + * return true if roster versioning is enabled on server + */ + supportVersioning: function() + { + return (this._connection.features && this._connection.features.getElementsByTagName('ver').length > 0); + }, + /** Function: get + * Get Roster on server + * + * Parameters: + * (Function) userCallback - callback on roster result + * (String) ver - current rev of roster + * (only used if roster versioning is enabled) + * (Array) items - initial items of ver + * (only used if roster versioning is enabled) + * In browser context you can use sessionStorage + * to store your roster in json (JSON.stringify()) + */ + get: function(userCallback, ver, items) + { + var attrs = {xmlns: Strophe.NS.ROSTER}; + if (this.supportVersioning()) + { + // empty rev because i want an rev attribute in the result + attrs.ver = ver || ''; + this.items = items || []; + } + var iq = $iq({type: 'get', 'id' : this._connection.getUniqueId('roster')}).c('query', attrs); + return this._connection.sendIQ(iq, + this._onReceiveRosterSuccess.bind(this, userCallback), + this._onReceiveRosterError.bind(this, userCallback)); + }, + /** Function: registerCallback + * register callback on roster (presence and iq) + * + * Parameters: + * (Function) call_back + */ + registerCallback: function(call_back) + { + this._callbacks.push(call_back); + }, + /** Function: findItem + * Find item by JID + * + * Parameters: + * (String) jid + */ + findItem : function(jid) + { + try { + for (var i = 0; i < this.items.length; i++) + { + if (this.items[i] && this.items[i].jid == jid) + { + return this.items[i]; + } + } + } catch (e) + { + Strophe.error(e); + } + return false; + }, + /** Function: removeItem + * Remove item by JID + * + * Parameters: + * (String) jid + */ + removeItem : function(jid) + { + for (var i = 0; i < this.items.length; i++) + { + if (this.items[i] && this.items[i].jid == jid) + { + this.items.splice(i, 1); + return true; + } + } + return false; + }, + /** Function: subscribe + * Subscribe presence + * + * Parameters: + * (String) jid + * (String) message (optional) + * (String) nick (optional) + */ + subscribe: function(jid, message, nick) { + var pres = $pres({to: jid, type: "subscribe"}); + if (message && message !== "") { + pres.c("status").t(message).up(); + } + if (nick && nick !== "") { + pres.c('nick', {'xmlns': Strophe.NS.NICK}).t(nick).up(); + } + this._connection.send(pres); + }, + /** Function: unsubscribe + * Unsubscribe presence + * + * Parameters: + * (String) jid + * (String) message + */ + unsubscribe: function(jid, message) + { + var pres = $pres({to: jid, type: "unsubscribe"}); + if (message && message !== "") + pres.c("status").t(message); + this._connection.send(pres); + }, + /** Function: authorize + * Authorize presence subscription + * + * Parameters: + * (String) jid + * (String) message + */ + authorize: function(jid, message) + { + var pres = $pres({to: jid, type: "subscribed"}); + if (message && message !== "") + pres.c("status").t(message); + this._connection.send(pres); + }, + /** Function: unauthorize + * Unauthorize presence subscription + * + * Parameters: + * (String) jid + * (String) message + */ + unauthorize: function(jid, message) + { + var pres = $pres({to: jid, type: "unsubscribed"}); + if (message && message !== "") + pres.c("status").t(message); + this._connection.send(pres); + }, + /** Function: add + * Add roster item + * + * Parameters: + * (String) jid - item jid + * (String) name - name + * (Array) groups + * (Function) call_back + */ + add: function(jid, name, groups, call_back) + { + var iq = $iq({type: 'set'}).c('query', {xmlns: Strophe.NS.ROSTER}).c('item', {jid: jid, + name: name}); + for (var i = 0; i < groups.length; i++) + { + iq.c('group').t(groups[i]).up(); + } + this._connection.sendIQ(iq, call_back, call_back); + }, + /** Function: update + * Update roster item + * + * Parameters: + * (String) jid - item jid + * (String) name - name + * (Array) groups + * (Function) call_back + */ + update: function(jid, name, groups, call_back) + { + var item = this.findItem(jid); + if (!item) + { + throw "item not found"; + } + var newName = name || item.name; + var newGroups = groups || item.groups; + var iq = $iq({type: 'set'}).c('query', {xmlns: Strophe.NS.ROSTER}).c('item', {jid: item.jid, + name: newName}); + for (var i = 0; i < newGroups.length; i++) + { + iq.c('group').t(newGroups[i]).up(); + } + return this._connection.sendIQ(iq, call_back, call_back); + }, + /** Function: remove + * Remove roster item + * + * Parameters: + * (String) jid - item jid + * (Function) call_back + */ + remove: function(jid, call_back) + { + var item = this.findItem(jid); + if (!item) + { + throw "item not found"; + } + var iq = $iq({type: 'set'}).c('query', {xmlns: Strophe.NS.ROSTER}).c('item', {jid: item.jid, + subscription: "remove"}); + this._connection.sendIQ(iq, call_back, call_back); + }, + /** PrivateFunction: _onReceiveRosterSuccess + * + */ + _onReceiveRosterSuccess: function(userCallback, stanza) + { + this._updateItems(stanza); + if (typeof userCallback === "function") { + userCallback(this.items); + } + }, + /** PrivateFunction: _onReceiveRosterError + * + */ + _onReceiveRosterError: function(userCallback, stanza) + { + userCallback(this.items); + }, + /** PrivateFunction: _onReceivePresence + * Handle presence + */ + _onReceivePresence : function(presence) + { + // TODO: from is optional + var jid = presence.getAttribute('from'); + var from = Strophe.getBareJidFromJid(jid); + var item = this.findItem(from); + // not in roster + if (!item) + { + return true; + } + var type = presence.getAttribute('type'); + if (type == 'unavailable') + { + delete item.resources[Strophe.getResourceFromJid(jid)]; + } + else if (!type) + { + // TODO: add timestamp + item.resources[Strophe.getResourceFromJid(jid)] = { + show : (presence.getElementsByTagName('show').length !== 0) ? Strophe.getText(presence.getElementsByTagName('show')[0]) : "", + status : (presence.getElementsByTagName('status').length !== 0) ? Strophe.getText(presence.getElementsByTagName('status')[0]) : "", + priority : (presence.getElementsByTagName('priority').length !== 0) ? Strophe.getText(presence.getElementsByTagName('priority')[0]) : "" + }; + } + else + { + // Stanza is not a presence notification. (It's probably a subscription type stanza.) + return true; + } + this._call_backs(this.items, item); + return true; + }, + /** PrivateFunction: _call_backs + * + */ + _call_backs : function(items, item) + { + for (var i = 0; i < this._callbacks.length; i++) // [].forEach my love ... + { + this._callbacks[i](items, item); + } + }, + /** PrivateFunction: _onReceiveIQ + * Handle roster push. + */ + _onReceiveIQ : function(iq) + { + var id = iq.getAttribute('id'); + var from = iq.getAttribute('from'); + // Receiving client MUST ignore stanza unless it has no from or from = user's JID. + if (from && from !== "" && from != this._connection.jid && from != Strophe.getBareJidFromJid(this._connection.jid)) + return true; + var iqresult = $iq({type: 'result', id: id, from: this._connection.jid}); + this._connection.send(iqresult); + this._updateItems(iq); + return true; + }, + /** PrivateFunction: _updateItems + * Update items from iq + */ + _updateItems : function(iq) + { + var query = iq.getElementsByTagName('query'); + if (query.length !== 0) + { + this.ver = query.item(0).getAttribute('ver'); + var self = this; + Strophe.forEachChild(query.item(0), 'item', + function (item) + { + self._updateItem(item); + } + ); + } + this._call_backs(this.items); + }, + /** PrivateFunction: _updateItem + * Update internal representation of roster item + */ + _updateItem : function(item) + { + var jid = item.getAttribute("jid"); + var name = item.getAttribute("name"); + var subscription = item.getAttribute("subscription"); + var ask = item.getAttribute("ask"); + var groups = []; + Strophe.forEachChild(item, 'group', + function(group) + { + groups.push(Strophe.getText(group)); + } + ); + + if (subscription == "remove") + { + this.removeItem(jid); + return; + } + + item = this.findItem(jid); + if (!item) + { + this.items.push({ + name : name, + jid : jid, + subscription : subscription, + ask : ask, + groups : groups, + resources : {} + }); + } + else + { + item.name = name; + item.subscription = subscription; + item.ask = ask; + item.groups = groups; + } + } +}); From 55e32c735d48bd1d0146313232549ec508107954 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Mon, 27 Oct 2014 23:06:11 +0100 Subject: [PATCH 21/41] Fix tests --- converse.js | 3 +- spec/chatbox.js | 53 +-- spec/controlbox.js | 657 ++++++++++++++++++++++---------------- spec/profiling.js | 4 +- src/templates/roster.html | 4 +- 5 files changed, 422 insertions(+), 299 deletions(-) diff --git a/converse.js b/converse.js index 044085a62..7031352d5 100644 --- a/converse.js +++ b/converse.js @@ -3874,7 +3874,7 @@ } console.log('update called'); return this.showHideFilter(); - }, 100), + }, converse.animate ? 100 : 0), render: function () { this.$el.html(converse.templates.roster({ @@ -4010,6 +4010,7 @@ reset: function () { converse.roster.reset(); this.removeAll(); + this.$roster = $(''); this.render().update(); return this; }, diff --git a/spec/chatbox.js b/spec/chatbox.js index ac4dc0518..4e0eb6dbc 100644 --- a/spec/chatbox.js +++ b/spec/chatbox.js @@ -57,23 +57,26 @@ expect($("#conversejs .chatbox").length).toBe(1); // Controlbox is open // Test that they can be trimmed - var online_contacts = this.rosterview.$el.find('dt.roster-group').siblings('dd.current-xmpp-contact').find('a.open-chat'); - for (i=0; i