Ijambobanga ryaremwe bundi bushya. Urasabwa kongera ukinjiramo.",
+ "wrong_reset_code.title": "Kode Itari Yo mu Kurema Bundibushya Ijambobanga",
+ "wrong_reset_code.message": "Kode yakiriwe mu kurema bundibushya ijambobanga si yo. Ongera ugerageze cyangwa se usabe indi kode.",
+ "new_password": "Ijambobanga Rishya",
+ "repeat_password": "Emeza Ijambobanga",
+ "enter_email": "Tanga email ukoresha maze tuze kukoherereza ubutumwa bugusobanuria uko uri bureme bundibushya konte yawe.",
+ "enter_email_address": "Shyiramo Email",
+ "password_reset_sent": "Ubusabe bwo Kurema Bundibushya Bwakiriwe",
+ "invalid_email": "Email Itemewe / Email Itabaho!",
+ "password_too_short": "Ijambobanga washyizemo ni rigufi cyane. Gerageza ufate irindi. ",
+ "passwords_do_not_match": "Ijambobanga waryanditse mu buryo bubiri butandukanye kandi bitemewe. ",
+ "password_expired": "Ijambobanga ryawe ryarashaje. Shaka irindi. "
+}
\ No newline at end of file
diff --git a/public/language/rw/search.json b/public/language/rw/search.json
new file mode 100644
index 0000000000..745860b9a1
--- /dev/null
+++ b/public/language/rw/search.json
@@ -0,0 +1,40 @@
+{
+ "results_matching": "Habonetse ibintu (ikintu) %1 gihura na \"%2\". (Byafashe amasegonda %3)",
+ "no-matches": "Nta cyabonetse",
+ "advanced-search": "Gushaka Byisumbuye",
+ "in": "Muri",
+ "titles": "Imitwe",
+ "titles-posts": "Imitwe n'Ibyashyizweho",
+ "posted-by": "Mu Byashyizweho na",
+ "in-categories": "Mu Byiciro bya",
+ "search-child-categories": "Shakira no mu byiciro bikomokaho",
+ "reply-count": "Umubare w'Ibisubizo",
+ "at-least": "Ungana Nibura na",
+ "at-most": "Utarengeje",
+ "post-time": "Igihe Byashyiriweho",
+ "newer-than": "Nyuma ya",
+ "older-than": "Mbere ya",
+ "any-date": "Itariki Yose",
+ "yesterday": "Ejo Hashize",
+ "one-week": "Icyumweru kimwe",
+ "two-weeks": "Ibyumweru bibiri",
+ "one-month": "Ukwezi kumwe",
+ "three-months": "Amezi atatu",
+ "six-months": "Amezi atandatu",
+ "one-year": "Umwaka umwe",
+ "sort-by": "Bigaragare Ukurikije",
+ "last-reply-time": "Igihe baherukira gusubiza",
+ "topic-title": "Umutwe w'ikiganiro",
+ "number-of-replies": "Umubare w'ibisubizo",
+ "number-of-views": "Umubare w'ababirebye",
+ "topic-start-date": "Igihe ikiganiro cyatangijwe",
+ "username": "Izina ry'umukoresha",
+ "category": "Icyiciro",
+ "descending": "Uva ku kinini ujya ku gito",
+ "ascending": "Uva ku gito ujya ku kinini",
+ "save-preferences": "Bika ibyo wahisemo",
+ "clear-preferences": "Hanagura ibyo wahisemo",
+ "search-preferences-saved": "Ibyo wahisemo mu gihe cy'ishaka byabitswe",
+ "search-preferences-cleared": "Ibyo wahisemo mu gihe cy'ishaka byahanaguwe",
+ "show-results-as": "Ibiboneka bigaragazwe nk'"
+}
\ No newline at end of file
diff --git a/public/language/rw/success.json b/public/language/rw/success.json
new file mode 100644
index 0000000000..80f0d8d3ad
--- /dev/null
+++ b/public/language/rw/success.json
@@ -0,0 +1,6 @@
+{
+ "success": "Byaciyemo",
+ "topic-post": "Wabishyizeho nta ngorane. ",
+ "authentication-successful": "Igenzura Ryaciyemo",
+ "settings-saved": "Ibyatunganyijwe byakiriwe!"
+}
\ No newline at end of file
diff --git a/public/language/rw/tags.json b/public/language/rw/tags.json
new file mode 100644
index 0000000000..378fa2b3e8
--- /dev/null
+++ b/public/language/rw/tags.json
@@ -0,0 +1,7 @@
+{
+ "no_tag_topics": "Nta biganiro bifite aka kamenyetso bihari. ",
+ "tags": "Utumenyetso",
+ "enter_tags_here": "Andika akamenyetso bijyanye aha. Buri kamenyetso kagomba kuba kagizwe n'inyuguti hagati ya %1 na %2. ",
+ "enter_tags_here_short": "Shyiraho utumenyetso...",
+ "no_tags": "Nta tumenyetso twari twashyirwaho. "
+}
\ No newline at end of file
diff --git a/public/language/rw/topic.json b/public/language/rw/topic.json
new file mode 100644
index 0000000000..6e4804ed8b
--- /dev/null
+++ b/public/language/rw/topic.json
@@ -0,0 +1,100 @@
+{
+ "topic": "Ikiganiro",
+ "topic_id": "Nimero y'Ikiganiro",
+ "topic_id_placeholder": "Shyiramo nimero y'ikiganiro",
+ "no_topics_found": "Nta kiganiro cyabonetse!",
+ "no_posts_found": "Nta cyashyizweho cyabonetse!",
+ "post_is_deleted": "Ibyari byanditse byakuweho!",
+ "topic_is_deleted": "Iki kiganiro cyakuweho!",
+ "profile": "Ishusho",
+ "posted_by": "Byashyizweho na %1",
+ "posted_by_guest": "Byashyizweho na Umushyitsi",
+ "chat": "Igikari",
+ "notify_me": "Uzajye umenyeshwa ibisubizo bishya kuri iki kiganiro",
+ "quote": "Terura",
+ "reply": "Subiza",
+ "guest-login-reply": "Injiramo maze usubize",
+ "edit": "Hinduraho",
+ "delete": "Siba",
+ "purge": "Sibanganya",
+ "restore": "Garuraho",
+ "move": "Imura",
+ "fork": "Gabanyamo",
+ "link": "Shyiraho Umurongo",
+ "share": "Sangiza",
+ "tools": "Ibikoresho",
+ "flag": "Tambikana",
+ "locked": "Birafungiranye",
+ "bookmark_instructions": "Kanda hano kugirango ugezwe aho wari ugeze usoma. Niba utabishaka, wafunga aka kadirishya. ",
+ "flag_title": "Bimenyeshe ubuyobozi",
+ "flag_confirm": "Wiringiye neza ko ushaka kumenyesha ibi ubuyobozi? ",
+ "flag_success": "Bimaze kumenyeshwa ubuyobozi ngo bikurikiranwe. ",
+ "deleted_message": "Iki kiganiro cyamaze gukurwaho. Abantu babifitiye uburenganzira ni bo bonyine bashobora kukibona. ",
+ "following_topic.message": "Ntabwo uzongera kubimenyeshwa nihagira umuntu ugira icyo yandika kuri iki kiganiro. ",
+ "not_following_topic.message": "Ntabwo uzongera kujya umenyeshwa ku bibera muri iki kiganiro. ",
+ "login_to_subscribe": "Ba umunyamuryango cyangwa winjiremo niba ushaka kwiyandikisha kuri iki kiganiro. ",
+ "markAsUnreadForAll.success": "Ikiganiro kigizwe nk'icyasomwe na bose",
+ "watch": "Cunga",
+ "unwatch": "Rekeraho Gucunga",
+ "watch.title": "Ujye umenyeshwa ibyongerwaho bishya kuri iki kiganiro",
+ "unwatch.title": "Rekera aho gucunga iki kiganiro",
+ "share_this_post": "Sangiza Ibi",
+ "thread_tools.title": "Ibikoresho by'Ikiganiro",
+ "thread_tools.markAsUnreadForAll": "Bigaragaze nk'Ibyasomwe",
+ "thread_tools.pin": "Zamura Ikiganiro",
+ "thread_tools.unpin": "Manura Ikiganiro",
+ "thread_tools.lock": "Fungirana Ikiganiro",
+ "thread_tools.unlock": "Fungurira Ikiganiro",
+ "thread_tools.move": "Imura Ikiganiro",
+ "thread_tools.move_all": "Byimure Byose",
+ "thread_tools.fork": "Gabanyaho ku Kiganiro",
+ "thread_tools.delete": "Kuraho Ikiganiro",
+ "thread_tools.delete_confirm": "Wiringiye neza ko ushaka gukuraho iki kiganiro?",
+ "thread_tools.restore": "Subizaho Ikiganiro",
+ "thread_tools.restore_confirm": "Wiringiye neza ko ushaka kugarura iki kiganiro?",
+ "thread_tools.purge": "Sibanganya Ikiganiro",
+ "thread_tools.purge_confirm": "Wiringiye neza ko ushaka gusibanganya iki kiganiro?",
+ "topic_move_success": "Nta ngorane, iki kiganiro kimaze kwimurirwa muri %1",
+ "post_delete_confirm": "Wiringiye neza ko ushaka gukuraho iki kiganiro?",
+ "post_restore_confirm": "Wiringiye neza ko ushaka kugarura iki kiganiro? ",
+ "post_purge_confirm": "Wiringiye neza ko ushaka gusibangaya iki kiganiro?",
+ "load_categories": "Ibyiciro Biraje",
+ "disabled_categories_note": "Ibyiciro bitagaragazwa birasa n'ibipfutse",
+ "confirm_move": "Imura",
+ "confirm_fork": "Gabanyaho",
+ "favourite": "Tonesha",
+ "favourites": "Ibyatoneshejwe",
+ "favourites.has_no_favourites": "Nta kintu na kimwe wari watonesha. Tonesha ibintu bimwe na bimwe kugirango ujye ubibona aha!",
+ "loading_more_posts": "Ibindi Biraje",
+ "move_topic": "Imura Ikiganiro",
+ "move_topics": "Imura Ibiganiro",
+ "move_post": "Imura Icyashyizweho",
+ "post_moved": "Icyashizweho kirimuwe!",
+ "fork_topic": "Gabanyaho ku Kiganiro",
+ "topic_will_be_moved_to": "Iki kiganiro kirimurirwa mu cyiciro",
+ "fork_topic_instruction": "Kanda ku byashizweho ushaka kugabanyaho",
+ "fork_no_pids": "Nta kintu wahisemo!",
+ "fork_success": "Umaze kugabanyaho ku kiganiro! Kanda hano ugezwe ku kiganiro cyavutse. ",
+ "composer.title_placeholder": "Shyira umutwe w'ikiganiro cyawe aha...",
+ "composer.handle_placeholder": "Izina",
+ "composer.discard": "Byihorere",
+ "composer.submit": "Shyiraho",
+ "composer.replying_to": "Gusubiza %1",
+ "composer.new_topic": "Ikiganiro Gishya",
+ "composer.uploading": "gupakira...",
+ "composer.thumb_url_label": "Omekaho thumbnail URL y'ikiganiro",
+ "composer.thumb_title": "Ongera agafotondanga kuri iki kiganiro",
+ "composer.thumb_url_placeholder": "http://example.com/thumb.png",
+ "composer.thumb_file_label": "Cyangwa upakireho ifayilo ",
+ "composer.thumb_remove": "Hanagura imirongo",
+ "composer.drag_and_drop_images": "Terura Ubundi Utereke Amafoto Aha",
+ "more_users_and_guests": "Abantu (umuntu) banditse barenga %1 n'abashyitsi (umushyitsi) %2 ",
+ "more_users": "Abantu (umuntu) banditse barenga %1 ",
+ "more_guests": "Abashyitsi (umushyitsi) barenga %1 ",
+ "users_and_others": "%1 n'abandi %2 ",
+ "sort_by": "Ubigaragaze Ukurikije",
+ "oldest_to_newest": "Ibya Kera Ujya ku bya Vuba",
+ "newest_to_oldest": "Ibya Vuba Ujya ku bya Kera",
+ "most_votes": "Amajwi yiganje",
+ "most_posts": "Ibyashyizweho byiganje"
+}
\ No newline at end of file
diff --git a/public/language/rw/unread.json b/public/language/rw/unread.json
new file mode 100644
index 0000000000..075fdc7792
--- /dev/null
+++ b/public/language/rw/unread.json
@@ -0,0 +1,10 @@
+{
+ "title": "Ibitarasomwa",
+ "no_unread_topics": "Nta biganiro bitarasomwa bihari. ",
+ "load_more": "Zana Ibindi",
+ "mark_as_read": "Bigire nkaho Byasomwe",
+ "selected": "Ibyatoranyijwe",
+ "all": "Byose",
+ "all_categories": "Ibyiciro Byose",
+ "topics_marked_as_read.success": "Ibiganiro byamaze kugaragazwa nk'ibyasomwe!"
+}
\ No newline at end of file
diff --git a/public/language/rw/user.json b/public/language/rw/user.json
new file mode 100644
index 0000000000..3deea4707a
--- /dev/null
+++ b/public/language/rw/user.json
@@ -0,0 +1,88 @@
+{
+ "banned": "Yarirukanwe",
+ "offline": "Ntari ku Murongo",
+ "username": "Izina ry'Umuntu",
+ "joindate": "Igiye Yaziye",
+ "postcount": "Ingano y'ibyo Yashyizeho",
+ "email": "Email",
+ "confirm_email": "Emeza Email",
+ "ban_account": "Irukana",
+ "ban_account_confirm": "Wiringiye neza ko ushaka kwirukana uyu muntu?",
+ "unban_account": "Garura iyi Konte",
+ "delete_account": "Siba Konte",
+ "delete_account_confirm": "Wiringiye neza ko ushaka gusiba konte yawe?
+ When the logo is clicked, send users to this address. If left blank, user will be sent to the forum index.
+
Numara kuyisiba ntabwo urabasha kwisubira kandi nturabasha kugarura ibyo wari ufiteho
Shyiramo izina ryawe kugirango wemeze ko koko ushaka gusenya iyi konte.",
+ "delete_this_account_confirm": "Wiringiye neza ko ushaka gusiba iyi konte?
Ntabwo uri bubashe kwisubira kandi ntabwo urabasha gusubirana ibyo wari ufiteho numara kuyisiba
",
+ "fullname": "Izina Ryuzuye",
+ "website": "Urubuga",
+ "location": "Ahantu",
+ "age": "Imyaka",
+ "joined": "Yaje",
+ "lastonline": "Aheruka ku Murongo",
+ "profile": "Ishusho",
+ "profile_views": "Ishusho Yarebwe",
+ "reputation": "Amanota",
+ "favourites": "Ibitoneshwa",
+ "watched": "Ibikurikiranwa",
+ "followers": "Abamukurikira",
+ "following": "Akurikira",
+ "aboutme": "Inshamake y'Ubuzima",
+ "signature": "Intero",
+ "gravatar": "Gravatar",
+ "birthday": "Itariki y'Amavuko",
+ "chat": "Mu Gikari",
+ "follow": "Kurikira",
+ "unfollow": "Ntukurikire",
+ "more": "Ibindi",
+ "profile_update_success": "Ishusho yashyizwe ku gihe nta ngorane!",
+ "change_picture": "Hindura Ifoto",
+ "edit": "Hinduraho",
+ "uploaded_picture": "Ifoto Yapakiwe",
+ "upload_new_picture": "Pakira Ifoto Nshya",
+ "upload_new_picture_from_url": "Pakira Ifoto Nshya Ukoresheje URL",
+ "current_password": "Ijambobanga Risanzweho",
+ "change_password": "Hindura Ijambobanga",
+ "change_password_error": "Ijambobanga Ritari Ryo!",
+ "change_password_error_wrong_current": "Ijambobanga ryawe watanze nk'irisanzweho ntabwo ari ryo!",
+ "change_password_error_length": "Ijambobanga watanze ni rigufi cyane!",
+ "change_password_error_match": "Ijambobanga ugomba kuryandukura mu buryo bumwe inshuro ebyiri!",
+ "change_password_error_privileges": "Nta burenganzira ufite bwo guhindura iri jambobanga. ",
+ "change_password_success": "Ijambobanga ryawe ryavuguruwe!",
+ "confirm_password": "Emeza Ijambobanga",
+ "password": "Ijambobanga",
+ "username_taken_workaround": "Izina ushaka kujya ukoresha twasanze ryarafashwe. Ntugire impungenge kuko twakuboneye iryo byenda kumera kimwe. Uzaba uzwi ku izina rya %1",
+ "upload_picture": "Gushyiraho ifoto",
+ "upload_a_picture": "Shyiraho ifoto",
+ "image_spec": "Wemerewe gushyiraho ifoto iri muri foruma ya PNG, JPG, cyangwa GIF ",
+ "settings": "Itunganya",
+ "show_email": "Hagaragazwe Email Yanjye",
+ "show_fullname": "Hagaragazwe Izina Ryuzuye Ryanjye",
+ "restrict_chats": "Emerera ubutumwa buciye mu gikari abantu ukurikira gusa",
+ "digest_label": "Iyandikishe ku Ngingo z'Ingenzi",
+ "digest_description": "Iyandikishe ku makuru aciye kuri email ajyanye n'ibivugirwa aha (amatangazo mashya n'ibiganiro) biciye muri gahunda yagenwe",
+ "digest_off": "Birafunze",
+ "digest_daily": "Buri Munsi",
+ "digest_weekly": "Buri Cyumweru",
+ "digest_monthly": "Buri Kwezi",
+ "send_chat_notifications": "Njye nohererezwa email igihe hari ubutumwa bwo mu gikari banyoherereje ntari ku murongo",
+ "send_post_notifications": "Njye nohererezwa email mu gihe hari abanditse ku biganiro niyandikishijeho",
+ "settings-require-reload": "Hari igihe ibyo watunganyije bitagaragara iyo utongeye ngo upakire paji uriho. Kanda hano upakire iyi paji bundibushya. ",
+ "has_no_follower": "Uyu muntu ntabwo afite abamukurikira :(",
+ "follows_no_one": "Uyu muntu ntabwo akurikira umuntu numwe :(",
+ "has_no_posts": "Uyu muntu nta kintu arashyiraho. ",
+ "has_no_topics": "Uyu muntu nta kiganiro aratangiza na kimwe. ",
+ "has_no_watched_topics": "Uyu muntu ntabwo arakurikira ikiganiro na kimwe.",
+ "email_hidden": "Email Yahishwe",
+ "hidden": "byahishwe",
+ "paginate_description": "Gabanya ibiganiro n'ibyashyizweho mu ma paji aho kugirango umuntu ajye amanuka ubudahagarara ",
+ "topics_per_page": "Ibiganiro kuri Buri Paji",
+ "posts_per_page": "Ibyashyizweho kuri Buri Paji",
+ "notification_sounds": "Hajye humvikana ijwi rikumenyesha ko haje itangazo rishya",
+ "browsing": "Gutunganya Uburyo Usoma",
+ "open_links_in_new_tab": "Fungurira imirongo ijya hanze mu idirishya rishya",
+ "enable_topic_searching": "Emerera Ugushakira mu Kiganiro",
+ "topic_search_help": "Nibyemerwa, ugushakira mu kiganiro bizajya biba ari byo bikorwa maze bitume umuntu abasha gushakira mu kiganiro hose aho gushakira kuri paji igaragarira amaso, imbere yawe gusa",
+ "follow_topics_you_reply_to": "Kurikira ibiganiro ushyiraho ibisubizo",
+ "follow_topics_you_create": "Kurikira ibiganiro uba watangije",
+ "grouptitle": "Hitamo umutwe w'itsinda ushaka ko uzajya ugaragara",
+ "no-group-title": "Nta mutwe w'itsinda"
+}
\ No newline at end of file
diff --git a/public/language/rw/users.json b/public/language/rw/users.json
new file mode 100644
index 0000000000..944183afcb
--- /dev/null
+++ b/public/language/rw/users.json
@@ -0,0 +1,21 @@
+{
+ "latest_users": "Abantu Bashya",
+ "top_posters": "Abashyizeho Byinshi",
+ "most_reputation": "Abafite Amanota Menshi",
+ "search": "Shaka",
+ "enter_username": "Shyiramo izina ryo gushaka",
+ "load_more": "Zana Ibindi",
+ "users-found-search-took": "Habonetse abantu (umuntu) %1! Byatwaye amasegonda %2 gusa.",
+ "filter-by": "Yungurura Ukurikije",
+ "online-only": "Abari ku murongo gusa",
+ "picture-only": "Ifoto gusa",
+ "invite": "Tumira",
+ "invitation-email-sent": "Ubutumire bwa email bwohererejwe %1",
+ "user_list": "Urutonde rw'Abantu",
+ "recent_topics": "Ibiganiro Biheruka",
+ "popular_topics": "Ibiganiro Bikunzwe",
+ "unread_topics": "Ibiganiro Bitarasomwa",
+ "categories": "Ibyiciro",
+ "tags": "Ibimenyetso",
+ "map": "Ikarita"
+}
\ No newline at end of file
diff --git a/public/language/sc/category.json b/public/language/sc/category.json
index 25d11361e2..57d0ccd73e 100644
--- a/public/language/sc/category.json
+++ b/public/language/sc/category.json
@@ -1,9 +1,12 @@
{
+ "category": "Category",
+ "subcategories": "Subcategories",
"new_topic_button": "Arresonada Noa",
"guest-login-post": "Log in to post",
"no_topics": "Non bi sunt arresonadas in custa creze.
Pro ite non nde pones una?",
"browsing": "navighende",
"no_replies": "Perunu at rispostu",
+ "no_new_posts": "No new posts.",
"share_this_category": "Share this category",
"watch": "Watch",
"ignore": "Ignore",
diff --git a/public/language/sc/groups.json b/public/language/sc/groups.json
index 1e1623c38f..3bd63ad94a 100644
--- a/public/language/sc/groups.json
+++ b/public/language/sc/groups.json
@@ -45,5 +45,6 @@
"membership.invitation-pending": "Invitation Pending",
"membership.join-group": "Join Group",
"membership.leave-group": "Leave Group",
- "membership.reject": "Reject"
+ "membership.reject": "Reject",
+ "new-group.group_name": "Group Name:"
}
\ No newline at end of file
diff --git a/public/language/sk/category.json b/public/language/sk/category.json
index 08035d7fdc..2370e4dec8 100644
--- a/public/language/sk/category.json
+++ b/public/language/sk/category.json
@@ -1,9 +1,12 @@
{
+ "category": "Category",
+ "subcategories": "Subcategories",
"new_topic_button": "Nová téma",
"guest-login-post": "Log in to post",
"no_topics": "V tejto kategórií zatiaľ nie sú žiadne príspevky.
Môžeš byť prvý!",
"browsing": "prehliada",
"no_replies": "Nikdo ešte neodpovedal",
+ "no_new_posts": "No new posts.",
"share_this_category": "zdielaj túto kategóriu",
"watch": "Watch",
"ignore": "Ignoruj",
diff --git a/public/language/sk/groups.json b/public/language/sk/groups.json
index 1e1623c38f..3bd63ad94a 100644
--- a/public/language/sk/groups.json
+++ b/public/language/sk/groups.json
@@ -45,5 +45,6 @@
"membership.invitation-pending": "Invitation Pending",
"membership.join-group": "Join Group",
"membership.leave-group": "Leave Group",
- "membership.reject": "Reject"
+ "membership.reject": "Reject",
+ "new-group.group_name": "Group Name:"
}
\ No newline at end of file
diff --git a/public/language/sr/category.json b/public/language/sr/category.json
index 57bcb77b34..5ab350780e 100644
--- a/public/language/sr/category.json
+++ b/public/language/sr/category.json
@@ -1,9 +1,12 @@
{
+ "category": "Category",
+ "subcategories": "Subcategories",
"new_topic_button": "Nova Tema",
"guest-login-post": "Пријавите се за слање порука",
"no_topics": "Ne postoji nijedna tema u ovoj kategoriji.
Zasto ne bi postavio jednu?",
"browsing": "gleda",
"no_replies": "Jos uvek nema odgovora",
+ "no_new_posts": "No new posts.",
"share_this_category": "Podeli ovu kategoriju",
"watch": "Прати",
"ignore": "Игнориши",
diff --git a/public/language/sr/groups.json b/public/language/sr/groups.json
index a4666c6d8f..d0d34f1efd 100644
--- a/public/language/sr/groups.json
+++ b/public/language/sr/groups.json
@@ -45,5 +45,6 @@
"membership.invitation-pending": "Invitation Pending",
"membership.join-group": "Join Group",
"membership.leave-group": "Leave Group",
- "membership.reject": "Reject"
+ "membership.reject": "Reject",
+ "new-group.group_name": "Group Name:"
}
\ No newline at end of file
diff --git a/public/language/sv/category.json b/public/language/sv/category.json
index d82abe3a24..b83dbb74e6 100644
--- a/public/language/sv/category.json
+++ b/public/language/sv/category.json
@@ -1,12 +1,15 @@
{
+ "category": "Category",
+ "subcategories": "Subcategories",
"new_topic_button": "Nytt ämne",
"guest-login-post": "Logga in för att posta",
"no_topics": "Det finns inga ämnen i denna kategori.
Varför skapar inte du ett ämne?",
"browsing": "läser",
"no_replies": "Ingen har svarat",
+ "no_new_posts": "No new posts.",
"share_this_category": "Dela den här kategorin",
- "watch": "Watch",
+ "watch": "Bevaka",
"ignore": "Ignorera",
- "watch.message": "You are now watching updates from this category",
- "ignore.message": "You are now ignoring updates from this category"
+ "watch.message": "Du bevakar nu uppdateringar ifrån denna kategori",
+ "ignore.message": "Du ignorerar nu uppdateringar ifrån denna kategori"
}
\ No newline at end of file
diff --git a/public/language/sv/email.json b/public/language/sv/email.json
index f71376777c..74f1366e24 100644
--- a/public/language/sv/email.json
+++ b/public/language/sv/email.json
@@ -1,15 +1,15 @@
{
"password-reset-requested": "Återställning av lösenord efterfrågat - %1!",
"welcome-to": "Välkommen till %1",
- "invite": "Invitation from %1",
+ "invite": "Inbjudan ifrån %1",
"greeting_no_name": "Hej",
"greeting_with_name": "Hej %1",
"welcome.text1": "Tack för att du registerar dig på %1!",
"welcome.text2": "För att slutföra aktiveringen av ditt konto, behöver vi verifiera att du har tillgång till den epostadress du registrerade dig med.",
- "welcome.text3": "An administrator has accepted your registration application. You can login with your username/password now.",
+ "welcome.text3": "En administrator har accepterat din registreringsansökan. Du kan logga in med ditt användarnamn och lösenord nu.",
"welcome.cta": "Klicka här för att bekräfta din epostadress ",
- "invitation.text1": "%1 has invited you to join %2",
- "invitation.ctr": "Click here to create your account.",
+ "invitation.text1": "%1 har bjudit in dig till %2",
+ "invitation.ctr": "Klicka här för att skapa ditt konto.",
"reset.text1": "Vi fick en förfrågan om att återställa ditt lösenord, möjligen för att du har glömt det. Om detta inte är fallet, så kan du bortse från det här epostmeddelandet. ",
"reset.text2": "För att fortsätta med återställning av lösenordet så kan du klicka på följande länk:",
"reset.cta": "Klicka här för att återställa ditt lösenord",
diff --git a/public/language/sv/error.json b/public/language/sv/error.json
index 38992271d5..109a7e7c6a 100644
--- a/public/language/sv/error.json
+++ b/public/language/sv/error.json
@@ -2,7 +2,7 @@
"invalid-data": "Ogiltig data",
"not-logged-in": "Du verkar inte vara inloggad.",
"account-locked": "Ditt konto har tillfälligt blivit låst",
- "search-requires-login": "Searching requires an account - please login or register.",
+ "search-requires-login": "Sökning kräver ett konto, var god logga in eller registrera dig.",
"invalid-cid": "Ogiltigt id för kategori",
"invalid-tid": "Ogiltigt id för ämne",
"invalid-pid": "Ogiltigt id för inlägg",
@@ -18,14 +18,14 @@
"username-taken": "Användarnamn upptaget",
"email-taken": "Epostadress upptagen",
"email-not-confirmed": "Din epostadress är ännu inte bekräftad. Klicka här för att bekräfta din epostadress.",
- "email-not-confirmed-chat": "You are unable to chat until your email is confirmed, please click here to confirm your email.",
- "no-email-to-confirm": "This forum requires email confirmation, please click here to enter an email",
- "email-confirm-failed": "We could not confirm your email, please try again later.",
- "confirm-email-already-sent": "Confirmation email already sent, please wait %1 minute(s) to send another one.",
+ "email-not-confirmed-chat": "Du kan ej använda chatten förrän din epostadress har blivit bekräftad, var god klicka här för att bekräfta din epostadress.",
+ "no-email-to-confirm": "Detta forum kräver bekräftning av epostadresser, var god klicka här för att fylla i en epostadress",
+ "email-confirm-failed": "Vi kunde ej bekräfta din epostadress, var god försök igen senare.",
+ "confirm-email-already-sent": "Bekräftningsbrev redan skickat, var god vänta %1 minut(er) innan du skickar ett nytt.",
"username-too-short": "Användarnamnet är för kort",
"username-too-long": "Användarnamnet är för långt",
"user-banned": "Användare bannlyst",
- "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post",
+ "user-too-new": "Du måste vänta %1 sekund(er) innan du gör ditt första inlägg",
"no-category": "Kategorin hittades inte",
"no-topic": "Ämnet hittades inte",
"no-post": "Inlägget hittades inte",
@@ -36,52 +36,52 @@
"no-emailers-configured": "Inga tillägg för epostadress har laddats, så något textmeddelande kunde inte skickas",
"category-disabled": "Kategorin inaktiverad",
"topic-locked": "Ämnet låst",
- "post-edit-duration-expired": "You are only allowed to edit posts for %1 second(s) after posting",
+ "post-edit-duration-expired": "Du kan endast ändra inlägg inom %1 sekund(er) efter att ha skickat det",
"still-uploading": "Vänta medan uppladdningen slutförs.",
- "content-too-short": "Please enter a longer post. Posts should contain at least %1 character(s).",
- "content-too-long": "Please enter a shorter post. Posts can't be longer than %1 character(s).",
- "title-too-short": "Please enter a longer title. Titles should contain at least %1 character(s).",
- "title-too-long": "Please enter a shorter title. Titles can't be longer than %1 character(s).",
- "too-many-posts": "You can only post once every %1 second(s) - please wait before posting again",
- "too-many-posts-newbie": "As a new user, you can only post once every %1 second(s) until you have earned %2 reputation - please wait before posting again",
- "tag-too-short": "Please enter a longer tag. Tags should contain at least %1 character(s)",
- "tag-too-long": "Please enter a shorter tag. Tags can't be longer than %1 character(s)",
- "not-enough-tags": "Not enough tags. Topics must have at least %1 tag(s)",
- "too-many-tags": "Too many tags. Topics can't have more than %1 tag(s)",
- "file-too-big": "Maximum allowed file size is %1 kB - please upload a smaller file",
+ "content-too-short": "Skriv ett längre inlägg. Inlägg måste innehålla minst %1 tecken.",
+ "content-too-long": "Skriv ett kortare inlägg. Inlägg kan inte innehålla mer än %1 tecken.",
+ "title-too-short": "Skriv en längre rubrik. Rubriker måste innehålla minst %1 tecken.",
+ "title-too-long": "Skriv en kortare rubrik. Rubriker kan inte innehålla mer än %1 tecken.",
+ "too-many-posts": "Du måste vänta minst %1 sekund(er) mellan varje inlägg",
+ "too-many-posts-newbie": "Som ny användare måste du vänta %1 sekund(er) mellan varje inlägg tills dess du har %2 förtroende",
+ "tag-too-short": "Fyll i ett längre märkord. Märkord måste vara minst %1 tecken långa",
+ "tag-too-long": "Fyll i ett kortare märkord. Märkord kan ej vara längre än %1 tecken långa",
+ "not-enough-tags": "Ej tillräckligt många märkord. Ämnen måste ha minst %1 märkord",
+ "too-many-tags": "För många märkord. Ämnen kan ej har mer än %1 märkord",
+ "file-too-big": "Den maximalt tillåtna filstorleken är %1 kB - ladda upp en mindre fil",
"cant-vote-self-post": "Du kan inte rösta på ditt eget inlägg.",
"already-favourited": "Du har redan favoriserat det här inlägget",
"already-unfavourited": "Du har redan avfavoriserat det här inlägget",
"cant-ban-other-admins": "Du kan inte bannlysa andra administratörer.",
- "cant-remove-last-admin": "You are the only administrator. Add another user as an administrator before removing yourself as admin",
+ "cant-remove-last-admin": "Du är den enda administratören. Lägg till en annan användare som administratör innan du tar bort dig själv.",
"invalid-image-type": "Ogiltig bildtyp. Tillåtna typer är: % 1",
"invalid-image-extension": "Ogiltigt bildformat",
"invalid-file-type": "Ogiltig filtyp. Tillåtna typer är: % 1",
"group-name-too-short": "Gruppnamnet är för kort",
"group-already-exists": "Gruppen existerar redan",
"group-name-change-not-allowed": "Gruppnamnet får inte ändras",
- "group-already-member": "You are already part of this group",
- "group-needs-owner": "This group requires at least one owner",
- "group-already-invited": "This user has already been invited",
- "group-already-requested": "Your membership request has already been submitted",
+ "group-already-member": "Du är redan en del av gruppen",
+ "group-needs-owner": "Gruppen kräver minst en ägare",
+ "group-already-invited": "Användaren har redan bjudits in",
+ "group-already-requested": "Din medlemsskapsförfrågan har redan skickats",
"post-already-deleted": "Inlägget är redan raderat",
"post-already-restored": "Inlägget är redan återställt",
"topic-already-deleted": "Ämnet är redan raderat",
"topic-already-restored": "Ämnet är redan återställt",
- "cant-purge-main-post": "You can't purge the main post, please delete the topic instead",
+ "cant-purge-main-post": "Huvudinlägg kan ej rensas, ta bort ämnet istället",
"topic-thumbnails-are-disabled": "Miniatyrbilder för ämnen är inaktiverat",
"invalid-file": "Ogiltig fil",
"uploads-are-disabled": "Uppladdningar är inaktiverat",
- "signature-too-long": "Sorry, your signature cannot be longer than %1 character(s).",
- "about-me-too-long": "Sorry, your about me cannot be longer than %1 character(s).",
+ "signature-too-long": "Din signatur kan inte vara längre än %1 tecken.",
+ "about-me-too-long": "Din om mig kan inte vara längre än %1 tecken.",
"cant-chat-with-yourself": "Du kan inte chatta med dig själv.",
"chat-restricted": "Denna användaren har begränsat sina chatt-meddelanden. Användaren måste följa dig innan ni kan chatta med varann",
- "too-many-messages": "You have sent too many messages, please wait awhile.",
+ "too-many-messages": "Du har skickat för många meddelanden, var god vänta",
"reputation-system-disabled": "Ryktessystemet är inaktiverat.",
"downvoting-disabled": "Nedröstning är inaktiverat",
"not-enough-reputation-to-downvote": "Du har inte tillräckligt förtroende för att rösta ner det här meddelandet",
"not-enough-reputation-to-flag": "Du har inte tillräckligt förtroende för att flagga det här inlägget.",
- "already-flagged": "You have already flagged this post",
+ "already-flagged": "Du har redan flaggat det här inlägget",
"reload-failed": "NodeBB stötte på problem med att ladda om: \"%1\". NodeBB kommer fortsätta servera den befintliga resurser till klienten, men du borde återställa det du gjorde alldeles innan du försökte ladda om.",
"registration-error": "Registreringsfel",
"parse-error": "Något gick fel vid tolkning av svar från servern",
diff --git a/public/language/sv/global.json b/public/language/sv/global.json
index 647cbec5d0..b7ad673b45 100644
--- a/public/language/sv/global.json
+++ b/public/language/sv/global.json
@@ -3,10 +3,10 @@
"search": "Sök",
"buttons.close": "Stäng",
"403.title": "Tillgång Nekad",
- "403.message": "You seem to have stumbled upon a page that you do not have access to.",
- "403.login": "Perhaps you should try logging in?",
+ "403.message": "Du verkar ha ramlat in på en sida du ej har tillgång till.",
+ "403.login": "Du kanske bör försöka logga in?",
"404.title": "Sidan saknas",
- "404.message": "You seem to have stumbled upon a page that does not exist. Return to the home page.",
+ "404.message": "Du verkar ha ramlat in på en sida som inte finns. Återgå till första sidan.",
"500.title": "Internt fel.",
"500.message": "Hoppsan! Verkar som att något gått snett!",
"register": "Registrera",
@@ -22,13 +22,13 @@
"pagination.out_of": "%1 av %2",
"pagination.enter_index": "Skriv in index ",
"header.admin": "Admin",
- "header.categories": "Categories",
+ "header.categories": "Kategorier",
"header.recent": "Senaste",
"header.unread": "Olästa",
"header.tags": "Märkningar",
"header.popular": "Populära",
"header.users": "Användare",
- "header.groups": "Groups",
+ "header.groups": "Grupper",
"header.chats": "Chattar",
"header.notifications": "Notiser",
"header.search": "Sök",
@@ -51,7 +51,7 @@
"views": "Visningar",
"reputation": "Rykte",
"read_more": "läs mer",
- "more": "More",
+ "more": "Mer",
"posted_ago_by_guest": "inskickad %1 av anonym",
"posted_ago_by": "inskickad %1 av %2",
"posted_ago": "inskickad %1",
@@ -77,7 +77,7 @@
"updated.title": "Forum uppdaterades",
"updated.message": "Det här forumet har nu uppdaterats till senaste versionen. Klicka här för att ladda om sidan.",
"privacy": "Integritet",
- "follow": "Follow",
- "unfollow": "Unfollow",
+ "follow": "Följ",
+ "unfollow": "Sluta följ",
"delete_all": "Ta bort Alla"
}
\ No newline at end of file
diff --git a/public/language/sv/groups.json b/public/language/sv/groups.json
index 2cb6de26df..248beda16e 100644
--- a/public/language/sv/groups.json
+++ b/public/language/sv/groups.json
@@ -1,49 +1,50 @@
{
"groups": "Grupper",
"view_group": "Visa grupp ",
- "owner": "Group Owner",
- "new_group": "Create New Group",
- "no_groups_found": "There are no groups to see",
- "pending.accept": "Accept",
- "pending.reject": "Reject",
- "pending.accept_all": "Accept All",
- "pending.reject_all": "Reject All",
- "pending.none": "There are no pending members at this time",
- "invited.none": "There are no invited members at this time",
- "invited.uninvite": "Rescind Invitation",
- "invited.search": "Search for a user to invite to this group",
- "cover-instructions": "Drag and Drop a photo, drag to position, and hit Save",
- "cover-change": "Change",
- "cover-save": "Save",
- "cover-saving": "Saving",
+ "owner": "Gruppägare",
+ "new_group": "Skapa ny grupp",
+ "no_groups_found": "Det finns inga grupper att se",
+ "pending.accept": "Acceptera",
+ "pending.reject": "Neka",
+ "pending.accept_all": "Acceptera alla",
+ "pending.reject_all": "Neka alla",
+ "pending.none": "Det finns inga väntande medlemmar just nu",
+ "invited.none": "Det finns inga inbjudna medlemmar just nu",
+ "invited.uninvite": "Dra tillbaka inbjudan",
+ "invited.search": "Sök efter en användare att lägga till i denna grupp",
+ "cover-instructions": "Dra och släpp ett foto, dra till position och tryck Spara",
+ "cover-change": "Ändra",
+ "cover-save": "Spara",
+ "cover-saving": "Sparar",
"details.title": "Detaljer för gruppen ",
"details.members": "Medlemmar",
- "details.pending": "Pending Members",
- "details.invited": "Invited Members",
+ "details.pending": "Väntande medlemmar",
+ "details.invited": "Inbjudna medlemmar",
"details.has_no_posts": "Den här gruppens medlemmar har inte skrivit några inlägg.",
"details.latest_posts": "Senaste inlägg",
- "details.private": "Private",
- "details.grant": "Grant/Rescind Ownership",
- "details.kick": "Kick",
- "details.owner_options": "Group Administration",
- "details.group_name": "Group Name",
- "details.member_count": "Member Count",
- "details.creation_date": "Creation Date",
- "details.description": "Description",
- "details.badge_preview": "Badge Preview",
- "details.change_icon": "Change Icon",
- "details.change_colour": "Change Colour",
- "details.badge_text": "Badge Text",
- "details.userTitleEnabled": "Show Badge",
- "details.private_help": "If enabled, joining of groups requires approval from a group owner",
- "details.hidden": "Hidden",
- "details.hidden_help": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
- "details.delete_group": "Delete Group",
- "event.updated": "Group details have been updated",
- "event.deleted": "The group \"%1\" has been deleted",
- "membership.accept-invitation": "Accept Invitation",
- "membership.invitation-pending": "Invitation Pending",
- "membership.join-group": "Join Group",
- "membership.leave-group": "Leave Group",
- "membership.reject": "Reject"
+ "details.private": "Privat",
+ "details.grant": "Ge/Ta ifrån ägarskap",
+ "details.kick": "Sparka ut",
+ "details.owner_options": "Gruppadministration",
+ "details.group_name": "Gruppnamn",
+ "details.member_count": "Medlemsantal",
+ "details.creation_date": "Skapatdatum",
+ "details.description": "Beskrivning",
+ "details.badge_preview": "Förhandsgranskning av märke",
+ "details.change_icon": "Byt ikon",
+ "details.change_colour": "Byt färg",
+ "details.badge_text": "Märkestext",
+ "details.userTitleEnabled": "Visa märke",
+ "details.private_help": "Om aktiverat kommer en gruppägare behöva godkänna nya gruppmedlemmar",
+ "details.hidden": "Dold",
+ "details.hidden_help": "Om aktiverat kommer gruppen inte synas i grupplistan och användare måste bli inbjudna manuellt",
+ "details.delete_group": "Ta bort grupp",
+ "event.updated": "Gruppdetaljerna har uppdaterats",
+ "event.deleted": "Gruppen \"%1\" har tagits bort",
+ "membership.accept-invitation": "Acceptera inbjudan",
+ "membership.invitation-pending": "Inbjudan väntar på svar",
+ "membership.join-group": "Gå med i grupp",
+ "membership.leave-group": "Lämna grupp",
+ "membership.reject": "Neka",
+ "new-group.group_name": "Group Name:"
}
\ No newline at end of file
diff --git a/public/language/sv/login.json b/public/language/sv/login.json
index 363da51e9b..1b3cf37221 100644
--- a/public/language/sv/login.json
+++ b/public/language/sv/login.json
@@ -1,7 +1,7 @@
{
"username-email": "Användarnamn eller epostadress",
"username": "Användarnamn",
- "email": "Email",
+ "email": "Epostadress",
"remember_me": "Kom ihåg mig?",
"forgot_password": "Glömt lösenord?",
"alternative_logins": "Alternativa inloggningssätt",
diff --git a/public/language/sv/modules.json b/public/language/sv/modules.json
index 008af93bc0..a370feb0b1 100644
--- a/public/language/sv/modules.json
+++ b/public/language/sv/modules.json
@@ -15,12 +15,12 @@
"chat.seven_days": "7 Dagar",
"chat.thirty_days": "30 Dagar",
"chat.three_months": "3 Månader",
- "composer.compose": "Compose",
- "composer.show_preview": "Show Preview",
- "composer.hide_preview": "Hide Preview",
+ "composer.compose": "Komponera",
+ "composer.show_preview": "Visa förhandsgranskning",
+ "composer.hide_preview": "Dölj förhandsgranskning",
"composer.user_said_in": "%1 sa i %2:",
"composer.user_said": "%1 sa:",
"composer.discard": "Är du säker på att du vill förkasta det här inlägget?",
- "composer.submit_and_lock": "Submit and Lock",
- "composer.toggle_dropdown": "Toggle Dropdown"
+ "composer.submit_and_lock": "Skicka och lås",
+ "composer.toggle_dropdown": "Visa/Dölj dropdown"
}
\ No newline at end of file
diff --git a/public/language/sv/notifications.json b/public/language/sv/notifications.json
index c95db1b8ee..424b6f4e42 100644
--- a/public/language/sv/notifications.json
+++ b/public/language/sv/notifications.json
@@ -2,7 +2,7 @@
"title": "Notiser",
"no_notifs": "Du har inga nya notiser",
"see_all": "Visa alla notiser",
- "mark_all_read": "Mark all notifications read",
+ "mark_all_read": "Markera alla notiser som lästa",
"back_to_home": "Tillbaka till %1",
"outgoing_link": "Utgående länk",
"outgoing_link_message": "Du lämnar nu %1. ",
@@ -20,7 +20,7 @@
"user_posted_topic": "%1 har skapat ett nytt ämne: %2",
"user_mentioned_you_in": "%1 nämnde dig i %2",
"user_started_following_you": "%1 började följa dig.",
- "new_register": "%1 sent a registration request.",
+ "new_register": "%1 skickade en registreringsförfrågan.",
"email-confirmed": "Epost bekräftad",
"email-confirmed-message": "Tack för att du bekräftat din epostadress. Ditt konto är nu fullt ut aktiverat.",
"email-confirm-error-message": "Det uppstod ett fel med att bekräfta din epostadress. Kanske var koden ogiltig eller har gått ut.",
diff --git a/public/language/sv/pages.json b/public/language/sv/pages.json
index 94c6a26d0e..b05979fb58 100644
--- a/public/language/sv/pages.json
+++ b/public/language/sv/pages.json
@@ -5,17 +5,17 @@
"recent": "Senaste ämnena",
"users": "Registrerade användare",
"notifications": "Notiser",
- "tags": "Tags",
- "tag": "Topics tagged under \"%1\"",
+ "tags": "Märkord",
+ "tag": "Ämnen märkta med \"%1\"",
"user.edit": "Ändrar \"%1\"",
"user.following": "Personer %1 Följer",
"user.followers": "Personer som följer %1",
"user.posts": "Inlägg skapat av %1",
"user.topics": "Ämnen skapade av %1",
- "user.groups": "%1's Groups",
+ "user.groups": "%1s grupper",
"user.favourites": "%1's favorit-inlägg",
"user.settings": "Avnändarinställningar",
- "user.watched": "Topics watched by %1",
+ "user.watched": "Ämnen bevakade av %1",
"maintenance.text": "%1 genomgår underhåll just nu. Vänligen kom tillbaka lite senare.",
"maintenance.messageIntro": "Ytterligare så lämnade administratören detta meddelande:"
}
\ No newline at end of file
diff --git a/public/language/sv/recent.json b/public/language/sv/recent.json
index 28a0d6f599..46d7531d3c 100644
--- a/public/language/sv/recent.json
+++ b/public/language/sv/recent.json
@@ -6,14 +6,14 @@
"year": "År",
"alltime": "Alltid",
"no_recent_topics": "Det finns inga olästa ämnen.",
- "no_popular_topics": "There are no popular topics.",
- "there-is-a-new-topic": "There is a new topic.",
- "there-is-a-new-topic-and-a-new-post": "There is a new topic and a new post.",
- "there-is-a-new-topic-and-new-posts": "There is a new topic and %1 new posts.",
- "there-are-new-topics": "There are %1 new topics.",
- "there-are-new-topics-and-a-new-post": "There are %1 new topics and a new post.",
- "there-are-new-topics-and-new-posts": "There are %1 new topics and %2 new posts.",
- "there-is-a-new-post": "There is a new post.",
- "there-are-new-posts": "There are %1 new posts.",
- "click-here-to-reload": "Click here to reload."
+ "no_popular_topics": "Det finns inga populära ämnen",
+ "there-is-a-new-topic": "Det finns ett nytt ämne",
+ "there-is-a-new-topic-and-a-new-post": "Det finns ett nytt ämne och ett nytt inlägg.",
+ "there-is-a-new-topic-and-new-posts": "Det finns ett nytt ämne och %1 nya inlägg.",
+ "there-are-new-topics": "Det finns %1 nya ämnen.",
+ "there-are-new-topics-and-a-new-post": "Det finns %1 nya ämnen och ett nytt inlägg..",
+ "there-are-new-topics-and-new-posts": "Det finns %1 nya ämnen och %2 nya inlägg.",
+ "there-is-a-new-post": "Det finns ett nytt inlägg.",
+ "there-are-new-posts": "Det finns %1 nya inlägg.",
+ "click-here-to-reload": "Klicka här för att ladda om."
}
\ No newline at end of file
diff --git a/public/language/sv/register.json b/public/language/sv/register.json
index 750db5cc62..6f1aa3764b 100644
--- a/public/language/sv/register.json
+++ b/public/language/sv/register.json
@@ -15,5 +15,5 @@
"alternative_registration": "Alternativ registrering",
"terms_of_use": "Användarvillkor",
"agree_to_terms_of_use": "Jag godkänner användarvillkoren",
- "registration-added-to-queue": "Your registration has been added to the approval queue. You will receive an email when it is accepted by an administrator."
+ "registration-added-to-queue": "Din registrering har lagts till i kön. Du kommer att få ett mail när den accepteras av en administratör."
}
\ No newline at end of file
diff --git a/public/language/sv/reset_password.json b/public/language/sv/reset_password.json
index 17c4447925..9f79207d43 100644
--- a/public/language/sv/reset_password.json
+++ b/public/language/sv/reset_password.json
@@ -11,7 +11,7 @@
"enter_email_address": "Skriv in epostadress",
"password_reset_sent": "Lösenordsåterställning skickad",
"invalid_email": "Felaktig epost / Epost finns inte!",
- "password_too_short": "The password entered is too short, please pick a different password.",
- "passwords_do_not_match": "The two passwords you've entered do not match.",
- "password_expired": "Your password has expired, please choose a new password"
+ "password_too_short": "Lösenordet är för kort, var god välj ett annat lösenord.",
+ "passwords_do_not_match": "De två lösenorden du har fyllt i matchar ej varandra.",
+ "password_expired": "Ditt lösenord har gått ut, var god välj ett nytt lösenord."
}
\ No newline at end of file
diff --git a/public/language/sv/search.json b/public/language/sv/search.json
index c023bf5927..eeb5a36feb 100644
--- a/public/language/sv/search.json
+++ b/public/language/sv/search.json
@@ -1,40 +1,40 @@
{
"results_matching": "%1 resultat matchar \"%2\", (%3 sekunder)",
"no-matches": "Inga träffar",
- "advanced-search": "Advanced Search",
- "in": "In",
- "titles": "Titles",
- "titles-posts": "Titles and Posts",
- "posted-by": "Posted by",
- "in-categories": "In Categories",
- "search-child-categories": "Search child categories",
- "reply-count": "Reply Count",
- "at-least": "At least",
- "at-most": "At most",
- "post-time": "Post time",
- "newer-than": "Newer than",
- "older-than": "Older than",
- "any-date": "Any date",
- "yesterday": "Yesterday",
+ "advanced-search": "Avancerad sökning",
+ "in": "i",
+ "titles": "Ämnen",
+ "titles-posts": "Ämnen och Inlägg",
+ "posted-by": "Skapad av",
+ "in-categories": "I kategorier",
+ "search-child-categories": "Sök i underkategorier",
+ "reply-count": "Svarsantal",
+ "at-least": "Som minst",
+ "at-most": "Som mest",
+ "post-time": "Inläggstid",
+ "newer-than": "Yngre än",
+ "older-than": "Äldre än",
+ "any-date": "Alla datum",
+ "yesterday": "Igår",
"one-week": "En vecka",
"two-weeks": "Två veckor",
"one-month": "En månad",
- "three-months": "Three months",
- "six-months": "Six months",
- "one-year": "One year",
- "sort-by": "Sort by",
- "last-reply-time": "Last reply time",
- "topic-title": "Topic title",
- "number-of-replies": "Number of replies",
- "number-of-views": "Number of views",
- "topic-start-date": "Topic start date",
- "username": "Username",
- "category": "Category",
- "descending": "In descending order",
- "ascending": "In ascending order",
+ "three-months": "Tre månader",
+ "six-months": "Sex månader",
+ "one-year": "Ett år",
+ "sort-by": "Sortera på",
+ "last-reply-time": "Senaste svarstiden",
+ "topic-title": "Ämnestitel",
+ "number-of-replies": "Antal svar",
+ "number-of-views": "Antal visningar",
+ "topic-start-date": "Startdatum för ämne",
+ "username": "Användarnamn",
+ "category": "Kategori",
+ "descending": "I fallande ordning",
+ "ascending": "I stigande ordning",
"save-preferences": "Spara inställningar",
- "clear-preferences": "Clear preferences",
- "search-preferences-saved": "Search preferences saved",
- "search-preferences-cleared": "Search preferences cleared",
- "show-results-as": "Show results as"
+ "clear-preferences": "Rensa inställningar",
+ "search-preferences-saved": "Sökinställningar sparade",
+ "search-preferences-cleared": "Sökinställningar rensade",
+ "show-results-as": "Visa resultat som"
}
\ No newline at end of file
diff --git a/public/language/sv/tags.json b/public/language/sv/tags.json
index d846962ea4..b3fbeebd24 100644
--- a/public/language/sv/tags.json
+++ b/public/language/sv/tags.json
@@ -1,7 +1,7 @@
{
"no_tag_topics": "Det finns inga ämnen med detta märkord.",
"tags": "Märkord",
- "enter_tags_here": "Enter tags here, between %1 and %2 characters each.",
+ "enter_tags_here": "Fyll i märkord på mellan %1 och %2 tecken här.",
"enter_tags_here_short": "Ange taggar...",
"no_tags": "Det finns inga märkord ännu."
}
\ No newline at end of file
diff --git a/public/language/sv/topic.json b/public/language/sv/topic.json
index d589412c3a..15befb34f1 100644
--- a/public/language/sv/topic.json
+++ b/public/language/sv/topic.json
@@ -34,7 +34,7 @@
"not_following_topic.message": "Du kommer inte längre få notiser från detta ämne.",
"login_to_subscribe": "Var god registrera eller logga in för att kunna prenumerera på detta ämne.",
"markAsUnreadForAll.success": "Ämne markerat som oläst av alla.",
- "watch": "Följ",
+ "watch": "Bevaka",
"unwatch": "Sluta bevaka",
"watch.title": "Få notis om nya svar till det här ämnet",
"unwatch.title": "Sluta bevaka detta ämne",
diff --git a/public/language/sv/unread.json b/public/language/sv/unread.json
index b6a20d21ca..0ed0a80ae7 100644
--- a/public/language/sv/unread.json
+++ b/public/language/sv/unread.json
@@ -5,6 +5,6 @@
"mark_as_read": "Markerad som läst",
"selected": "Vald",
"all": "Alla",
- "all_categories": "All categories",
+ "all_categories": "Alla kategorier",
"topics_marked_as_read.success": "Ämnet markerat som läst."
}
\ No newline at end of file
diff --git a/public/language/sv/user.json b/public/language/sv/user.json
index c76476de52..6ccc8a3760 100644
--- a/public/language/sv/user.json
+++ b/public/language/sv/user.json
@@ -6,12 +6,12 @@
"postcount": "Antal inlägg",
"email": "Epost",
"confirm_email": "Bekräfta epostadress ",
- "ban_account": "Ban Account",
- "ban_account_confirm": "Do you really want to ban this user?",
- "unban_account": "Unban Account",
+ "ban_account": "Bannlys konto",
+ "ban_account_confirm": "Vill du verkligen bannlysa den här användaren?",
+ "unban_account": "Ta bort bannlysning",
"delete_account": "Ta bort ämne",
"delete_account_confirm": "Är du säker på att du vill radera ditt konto?
Denna åtgärd går inte att ångra och du kommer inte kunna återställa ditt konto
Skriv in ditt användarnamn för att bekräfta att du vill radera ditt konto.",
- "delete_this_account_confirm": "Are you sure you want to delete this account?
This action is irreversible and you will not be able to recover any data
",
+ "delete_this_account_confirm": "Är du säker på att du vill ta bort detta konto?
Detta går ej att ångra - data går förlorad för alltid
",
"fullname": "Hela namnet",
"website": "Webbsida",
"location": "Plats",
@@ -68,9 +68,9 @@
"settings-require-reload": "Vissa inställningar som ändrades kräver att sidan laddas om. Klicka här för att ladda om sidan.",
"has_no_follower": "Denna användare har inga följare :(",
"follows_no_one": "Denna användare följer ingen :(",
- "has_no_posts": "This user hasn't posted anything yet.",
- "has_no_topics": "This user hasn't posted any topics yet.",
- "has_no_watched_topics": "This user hasn't watched any topics yet.",
+ "has_no_posts": "Användaren har inte skrivit några inlägg ännu",
+ "has_no_topics": "Användaren har inte skrivit några ämnen ännu",
+ "has_no_watched_topics": "Användaren har inte bevakat några ämnen ännu",
"email_hidden": "Epost dold",
"hidden": "dold",
"paginate_description": "Gör så att ämnen och inlägg visas som sidor istället för oändlig skroll",
diff --git a/public/language/sv/users.json b/public/language/sv/users.json
index 1e364986af..5acd1b0f84 100644
--- a/public/language/sv/users.json
+++ b/public/language/sv/users.json
@@ -5,17 +5,17 @@
"search": "Sök",
"enter_username": "Ange ett användarnamn för att söka",
"load_more": "Ladda fler",
- "users-found-search-took": "%1 user(s) found! Search took %2 seconds.",
- "filter-by": "Filter By",
- "online-only": "Online only",
- "picture-only": "Picture only",
- "invite": "Invite",
- "invitation-email-sent": "An invitation email has been sent to %1",
- "user_list": "User List",
- "recent_topics": "Recent Topics",
- "popular_topics": "Popular Topics",
- "unread_topics": "Unread Topics",
- "categories": "Categories",
- "tags": "Tags",
- "map": "Map"
+ "users-found-search-took": "%1 användare hittades! Sökningen tog %2 sekunder.",
+ "filter-by": "Filtrera på",
+ "online-only": "Endast online",
+ "picture-only": "Endast bild",
+ "invite": "Bjud in",
+ "invitation-email-sent": "En inbjudan har skickats till %1",
+ "user_list": "Användarlista",
+ "recent_topics": "Senaste ämnen",
+ "popular_topics": "Populära ämnen",
+ "unread_topics": "Olästa ämnen",
+ "categories": "Kategorier",
+ "tags": "Märkord",
+ "map": "Karta"
}
\ No newline at end of file
diff --git a/public/language/th/category.json b/public/language/th/category.json
index bf35101ba9..66ae73ac7b 100644
--- a/public/language/th/category.json
+++ b/public/language/th/category.json
@@ -1,9 +1,12 @@
{
+ "category": "Category",
+ "subcategories": "Subcategories",
"new_topic_button": "กระทู้",
"guest-login-post": "เข้าสู่ระบบเพื่อโพส",
"no_topics": "ยังไม่มีกระทู้ในหมวดนี้
โพสต์กระทู้แรก?",
"browsing": "เรียกดู",
"no_replies": "ยังไม่มีใครตอบ",
+ "no_new_posts": "No new posts.",
"share_this_category": "แชร์ Category นี้",
"watch": "Watch",
"ignore": "ไม่ต้องสนใจอีก",
diff --git a/public/language/th/groups.json b/public/language/th/groups.json
index 027579e466..e1f567f52e 100644
--- a/public/language/th/groups.json
+++ b/public/language/th/groups.json
@@ -45,5 +45,6 @@
"membership.invitation-pending": "Invitation Pending",
"membership.join-group": "Join Group",
"membership.leave-group": "Leave Group",
- "membership.reject": "Reject"
+ "membership.reject": "Reject",
+ "new-group.group_name": "Group Name:"
}
\ No newline at end of file
diff --git a/public/language/tr/category.json b/public/language/tr/category.json
index b78064fb51..dec48d0741 100644
--- a/public/language/tr/category.json
+++ b/public/language/tr/category.json
@@ -1,9 +1,12 @@
{
+ "category": "Kategori",
+ "subcategories": "Alt kategoriler",
"new_topic_button": "Yeni Başlık",
"guest-login-post": "Göndermek için giriş yapın",
"no_topics": " Bu kategoride hiç konu yok.
Yeni bir konu açmak istemez misiniz?",
"browsing": "gözden geçiriliyor",
"no_replies": "Kimse yanıtlamadı",
+ "no_new_posts": "Yeni ileti yok",
"share_this_category": "Bu kategoriyi paylaş",
"watch": "İzle",
"ignore": "Yoksay",
diff --git a/public/language/tr/email.json b/public/language/tr/email.json
index cca2d80594..da6f0a2da8 100644
--- a/public/language/tr/email.json
+++ b/public/language/tr/email.json
@@ -1,15 +1,15 @@
{
"password-reset-requested": "Parola Değiştirme İsteği Gönderildi",
"welcome-to": "Hoşgeldiniz",
- "invite": "Invitation from %1",
+ "invite": "%1 sizi davet etti",
"greeting_no_name": "Merhaba",
"greeting_with_name": "Merhaba %1",
"welcome.text1": "Kaydolduğunuz için teşekkürler!",
"welcome.text2": "Hesabınızı aktif hale getirmek için, kaydolduğunuz e-posta adresinin size ait olduğunu onaylamamız gerekiyor.",
- "welcome.text3": "An administrator has accepted your registration application. You can login with your username/password now.",
+ "welcome.text3": "Yönetici kayıt olma isteğinizi kabul etti. Kullanıcı adı/şifre ile giriş yapabilirsiniz.",
"welcome.cta": "E-posta adresinizi onaylamak için buraya tıklayın",
- "invitation.text1": "%1 has invited you to join %2",
- "invitation.ctr": "Click here to create your account.",
+ "invitation.text1": "%1 sizi %2 ye katılmaya davet etti",
+ "invitation.ctr": "Hesap oluşturmak için buraya tıklayın",
"reset.text1": "Şifrenizi değiştirmek istediğinize dair bir ileti aldık. Eğer böyle bir istek göndermediyseniz, lütfen bu e-postayı görmezden gelin.",
"reset.text2": "Parola değiştirme işlemine devam etmek için aşağıdaki bağlantıya tıklayın:",
"reset.cta": "Parolanızı değiştirmek için buraya tıklayın",
diff --git a/public/language/tr/error.json b/public/language/tr/error.json
index a530f04e05..9c02802883 100644
--- a/public/language/tr/error.json
+++ b/public/language/tr/error.json
@@ -46,14 +46,14 @@
"too-many-posts-newbie": "Yeni bir kullanıcı olarak, %2 saygınlık kazanana kadar %1 saniye içinde bir ileti gönderebilirsiniz - tekrar ileti göndermeden önce lütfen bekleyin.",
"tag-too-short": "Lütfen daha uzun bir etiket girin. Etiketler en az %1 karakter içermelidir.",
"tag-too-long": "Lütfen daha kısa bir etiket girin. Etiketler %1 karakterden uzun olamaz.",
- "not-enough-tags": "Not enough tags. Topics must have at least %1 tag(s)",
- "too-many-tags": "Too many tags. Topics can't have more than %1 tag(s)",
+ "not-enough-tags": "Yeterince etiket yok. Başlılar en az %1 etikete sahip olmalıdır",
+ "too-many-tags": "Etiket sayısı çok fazla. Başlıklar en fazla %1 etikete sahip olabilir",
"file-too-big": "İzin verilen en büyük dosya boyutu %1 kb - lütfen daha küçük bir dosya yükleyin",
"cant-vote-self-post": "Kendi iletinize oy veremezsiniz",
"already-favourited": "Bu iletiyi zaten favorilerinize eklediniz",
"already-unfavourited": "Bu iletiyi zaten favorilerinizden çıkardınız",
"cant-ban-other-admins": "Başka yöneticileri yasaklayamazsınız!",
- "cant-remove-last-admin": "You are the only administrator. Add another user as an administrator before removing yourself as admin",
+ "cant-remove-last-admin": "Tek yönetici sizsiniz. Kendinizi adminlikten çıkarmadan önce başka bir kullanıcıyı admin olarak ekleyiniz",
"invalid-image-type": "Geçersiz resim uzantısı. Izin verilen uzantılar: %1",
"invalid-image-extension": "Geçersiz resim uzantısı",
"invalid-file-type": "Geçersiz dosya türü. İzin verilenler şunlar : %1",
@@ -62,8 +62,8 @@
"group-name-change-not-allowed": "Grup ismini değiştiremezsiniz",
"group-already-member": "Bu grubun zaten bir parçasısınız.",
"group-needs-owner": "Bu grubu en az bir kişi sahiplenmesi gerekiyor",
- "group-already-invited": "This user has already been invited",
- "group-already-requested": "Your membership request has already been submitted",
+ "group-already-invited": "Bu kullanıcı zaten davet edilmiş",
+ "group-already-requested": "Üyelik isteğiniz zaten gönderildi",
"post-already-deleted": "İleti zaten silinmiş",
"post-already-restored": "İleti zaten geri getirilmiş",
"topic-already-deleted": "Başlık zaten silinmiş",
@@ -81,7 +81,7 @@
"downvoting-disabled": "Aşagı oylama kapatılmış",
"not-enough-reputation-to-downvote": "Bu iletiyi aşagı oylamak için yeterince saygınlığınız yok.",
"not-enough-reputation-to-flag": "Bu iletiyi bayraklamak için yeterince saygınlığınız yok",
- "already-flagged": "You have already flagged this post",
+ "already-flagged": "Bu iletiyi zaten bayrakladınız",
"reload-failed": "NodeBB tekrar yüklenirken bir sorunla karşılaştı: “%1“. NodeBB varolan dosyaları servis etmeye devam edecek.",
"registration-error": "Kayıt Hatası",
"parse-error": "Sunucu yanıtı çözümlemesi sırasında bir şeyler ters gitti",
diff --git a/public/language/tr/global.json b/public/language/tr/global.json
index 7e63e5e3bd..979e9715c5 100644
--- a/public/language/tr/global.json
+++ b/public/language/tr/global.json
@@ -22,7 +22,7 @@
"pagination.out_of": "%1 - %2",
"pagination.enter_index": "İndex gir",
"header.admin": "Yönetim",
- "header.categories": "Categories",
+ "header.categories": "Kategoriler",
"header.recent": "Yeni",
"header.unread": "Okunmamış",
"header.tags": "Etiketler",
@@ -51,7 +51,7 @@
"views": "Görüntülemeler",
"reputation": "Saygınlık",
"read_more": "daha fazla oku",
- "more": "More",
+ "more": "Daha Fazla",
"posted_ago_by_guest": "Ziyaretçi tarafından %1 yayımlandı",
"posted_ago_by": "%2 tarafından %1 yayımlandı",
"posted_ago": "%1 yayımlandı",
diff --git a/public/language/tr/groups.json b/public/language/tr/groups.json
index bac723142e..c1c65af240 100644
--- a/public/language/tr/groups.json
+++ b/public/language/tr/groups.json
@@ -6,12 +6,12 @@
"no_groups_found": "Henüz hiç grup yok",
"pending.accept": "Onayla",
"pending.reject": "Reddet",
- "pending.accept_all": "Accept All",
- "pending.reject_all": "Reject All",
- "pending.none": "There are no pending members at this time",
- "invited.none": "There are no invited members at this time",
- "invited.uninvite": "Rescind Invitation",
- "invited.search": "Search for a user to invite to this group",
+ "pending.accept_all": "Hepsini Kabul Et",
+ "pending.reject_all": "Hepsini Reddet",
+ "pending.none": "Şu anda bekleyen üye yok",
+ "invited.none": "Şu anda davet edilmiş üye yok",
+ "invited.uninvite": "Daveti iptal et",
+ "invited.search": "Gruba davet etmek için kullanıcı ara",
"cover-instructions": "Bir fotoğrafı Sürükleyin ve Bırakın, uygun yere sürükleyip Kaydet'e tıklayın.",
"cover-change": "Değiştir",
"cover-save": "Kaydet",
@@ -19,7 +19,7 @@
"details.title": "Grup Detayları",
"details.members": "Üye Listesi",
"details.pending": "Üyeler bekleniyor",
- "details.invited": "Invited Members",
+ "details.invited": "Davet Edilen Üyeler",
"details.has_no_posts": "Bu grubun üyeleri henüz bir ileti göndermedi.",
"details.latest_posts": "En son iletiler",
"details.private": "Özel",
@@ -38,12 +38,13 @@
"details.private_help": "Gruba katılmak için eğer etkinse grup sahibini onayı gerekir, ",
"details.hidden": "Gizli",
"details.hidden_help": "Bu grup eğer etkinse grup listelerinde bulunmaz, ve kullanıcılar bizzat davet eder",
- "details.delete_group": "Delete Group",
+ "details.delete_group": "Grubu Sil",
"event.updated": "Grup detayları güncellenmiştir",
"event.deleted": "\"%1\" grubu silinmiş",
- "membership.accept-invitation": "Accept Invitation",
- "membership.invitation-pending": "Invitation Pending",
- "membership.join-group": "Join Group",
- "membership.leave-group": "Leave Group",
- "membership.reject": "Reject"
+ "membership.accept-invitation": "Daveti Kabul Et",
+ "membership.invitation-pending": "Davet beklemede",
+ "membership.join-group": "Gruba Katıl",
+ "membership.leave-group": "Gruptan Ayrıl",
+ "membership.reject": "Reddet",
+ "new-group.group_name": "Grup İsmi:"
}
\ No newline at end of file
diff --git a/public/language/tr/notifications.json b/public/language/tr/notifications.json
index 948a1dfeca..d498a13dbe 100644
--- a/public/language/tr/notifications.json
+++ b/public/language/tr/notifications.json
@@ -20,7 +20,7 @@
"user_posted_topic": "%1 yeni bir konu yarattı: %2",
"user_mentioned_you_in": "%1 %2 başlığında sizden bahsetti.",
"user_started_following_you": "%1 sizi takip etmeye başladı.",
- "new_register": "%1 sent a registration request.",
+ "new_register": "%1 kayıt olma isteği gönderdi.",
"email-confirmed": "E-posta onaylandı",
"email-confirmed-message": "E-postanızı onaylandığınız için teşekkürler. Hesabınız tamamen aktive edildi.",
"email-confirm-error-message": "E-posta adresinizi onaylarken bir hata oluştu. Kodunuz geçersiz ya da eski olabilir.",
diff --git a/public/language/tr/register.json b/public/language/tr/register.json
index cd4d6c67c9..c46c27ad32 100644
--- a/public/language/tr/register.json
+++ b/public/language/tr/register.json
@@ -15,5 +15,5 @@
"alternative_registration": "Alternatif Kayıt",
"terms_of_use": "Kullanım Şartları",
"agree_to_terms_of_use": "Kullanım Şartlarını kabul ediyorum",
- "registration-added-to-queue": "Your registration has been added to the approval queue. You will receive an email when it is accepted by an administrator."
+ "registration-added-to-queue": "Kayıt olma isteğiniz kabul listesine eklenmiştir. Yönetici tarafından kabul edildiğinizde mail alacaksınız."
}
\ No newline at end of file
diff --git a/public/language/tr/unread.json b/public/language/tr/unread.json
index 95268cf85c..49b8995dc5 100644
--- a/public/language/tr/unread.json
+++ b/public/language/tr/unread.json
@@ -5,6 +5,6 @@
"mark_as_read": "Okundu Olarak İşaretle",
"selected": "Seçili",
"all": "Hepsi",
- "all_categories": "All categories",
+ "all_categories": "Tüm kategoriler",
"topics_marked_as_read.success": "Başlıklar okundu olarak işaretlendi!"
}
\ No newline at end of file
diff --git a/public/language/tr/user.json b/public/language/tr/user.json
index 2739622bee..ac16ae1970 100644
--- a/public/language/tr/user.json
+++ b/public/language/tr/user.json
@@ -11,7 +11,7 @@
"unban_account": "Hesabı Kullanıma Aç",
"delete_account": "Hesabı Sil",
"delete_account_confirm": "Hesabınızı silmek istediğinize emin misiniz?
Bu işlem geri çevrilemez ve tüm verileriniz sistemden silinecek.
Eğer hesabınızı silmek istiyorsanız lütfen kullanıcı isminizi girerek işlemi onaylayın.",
- "delete_this_account_confirm": "Are you sure you want to delete this account?
This action is irreversible and you will not be able to recover any data
",
+ "delete_this_account_confirm": "Bu hesabı silmek istediğinizden emin misiniz?
Bu işlem geri döndürülemez ve hiç bir veriyi kurtaramazsınız
",
"fullname": "Tam Ad",
"website": "Websitesi",
"location": "Konum",
@@ -68,9 +68,9 @@
"settings-require-reload": "Bazı ayar değişiklikleri sayfayı tekrar yüklemenizi gerektirir. Buraya tıklayarak sayfayı tekrar yükleyebilirsiniz.",
"has_no_follower": "Bu kullanıcının hiç takipçisi yok :(",
"follows_no_one": "Bu kullanıcı kimseyi takip etmiyor :(",
- "has_no_posts": "This user hasn't posted anything yet.",
- "has_no_topics": "This user hasn't posted any topics yet.",
- "has_no_watched_topics": "This user hasn't watched any topics yet.",
+ "has_no_posts": "Bu kullanıcı henüz herhangi bir ileti yazmamış.",
+ "has_no_topics": "Bu kullanıcı henüz hiç bir başlık açmamış.",
+ "has_no_watched_topics": "Bu kullanıcı henüz hiç bir başlık okumamış.",
"email_hidden": "E-posta gizli",
"hidden": "gizli",
"paginate_description": "Sonsuz yükleme yerine konu ve iletileri sayfalara böl",
diff --git a/public/language/tr/users.json b/public/language/tr/users.json
index cee4c4445a..00c8b00be9 100644
--- a/public/language/tr/users.json
+++ b/public/language/tr/users.json
@@ -9,13 +9,13 @@
"filter-by": "Şu şekilde filtrele",
"online-only": "Sadece çevrimiçi",
"picture-only": "Sadece resim",
- "invite": "Invite",
- "invitation-email-sent": "An invitation email has been sent to %1",
- "user_list": "User List",
- "recent_topics": "Recent Topics",
- "popular_topics": "Popular Topics",
- "unread_topics": "Unread Topics",
- "categories": "Categories",
- "tags": "Tags",
- "map": "Map"
+ "invite": "Davet et",
+ "invitation-email-sent": "%1'e bir davet maili gönderildi",
+ "user_list": "Kullanıcı Listesi",
+ "recent_topics": "Güncel Başlıklar",
+ "popular_topics": "Popüler Başlıklar",
+ "unread_topics": "Okunmamış Başlıklar",
+ "categories": "Kategoriler",
+ "tags": "Etiketler",
+ "map": "Harita"
}
\ No newline at end of file
diff --git a/public/language/vi/category.json b/public/language/vi/category.json
index e209512922..ef607a6f36 100644
--- a/public/language/vi/category.json
+++ b/public/language/vi/category.json
@@ -1,9 +1,12 @@
{
+ "category": "Category",
+ "subcategories": "Subcategories",
"new_topic_button": "Chủ đề mới",
"guest-login-post": "Đăng nhập để viết bài",
"no_topics": "Không có bài viết trong danh mục này.
Hãy đăng một bài viết mới.",
"browsing": "đang xem",
"no_replies": "Chưa có bình luận nào",
+ "no_new_posts": "No new posts.",
"share_this_category": "Chia sẻ thư mục này",
"watch": "Watch",
"ignore": "Bỏ qua",
diff --git a/public/language/vi/groups.json b/public/language/vi/groups.json
index 2deebd443e..b44ae1ecc5 100644
--- a/public/language/vi/groups.json
+++ b/public/language/vi/groups.json
@@ -45,5 +45,6 @@
"membership.invitation-pending": "Invitation Pending",
"membership.join-group": "Join Group",
"membership.leave-group": "Leave Group",
- "membership.reject": "Reject"
+ "membership.reject": "Reject",
+ "new-group.group_name": "Group Name:"
}
\ No newline at end of file
diff --git a/public/language/zh_CN/category.json b/public/language/zh_CN/category.json
index 8024d249a1..3867e3b185 100644
--- a/public/language/zh_CN/category.json
+++ b/public/language/zh_CN/category.json
@@ -1,9 +1,12 @@
{
+ "category": "版面",
+ "subcategories": "子版面",
"new_topic_button": "新主题",
"guest-login-post": "登录后发表",
"no_topics": "此版块还没有任何内容。
赶紧来发帖吧!",
"browsing": "正在浏览",
"no_replies": "尚无回复",
+ "no_new_posts": "没有新帖",
"share_this_category": "分享此版块",
"watch": "关注",
"ignore": "忽略",
diff --git a/public/language/zh_CN/error.json b/public/language/zh_CN/error.json
index 7ad720cf56..4e1ecab7ff 100644
--- a/public/language/zh_CN/error.json
+++ b/public/language/zh_CN/error.json
@@ -46,8 +46,8 @@
"too-many-posts-newbie": "因为您是新用户,所以限制每隔 %1 秒才能发帖一次,直到您有 %2 点威望为止 —— 请稍候再发帖",
"tag-too-short": "话题太短,不能少于 %1 个字符",
"tag-too-long": "话题太长,不能超过 %1 个字符",
- "not-enough-tags": "Not enough tags. Topics must have at least %1 tag(s)",
- "too-many-tags": "Too many tags. Topics can't have more than %1 tag(s)",
+ "not-enough-tags": "没有足够的话题标签。主题必须有至少 %1 个话题标签",
+ "too-many-tags": "过多话题标签。主题不能超过 %1 个话题标签",
"file-too-big": "上传文件的大小限制为 %1 KB - 请缩减文件大小",
"cant-vote-self-post": "您不能给自己的帖子投票。",
"already-favourited": "您已收藏该帖",
diff --git a/public/language/zh_CN/global.json b/public/language/zh_CN/global.json
index 91f32b492b..33f4286751 100644
--- a/public/language/zh_CN/global.json
+++ b/public/language/zh_CN/global.json
@@ -22,7 +22,7 @@
"pagination.out_of": "%1 / %2",
"pagination.enter_index": "输入索引",
"header.admin": "管理",
- "header.categories": "Categories",
+ "header.categories": "版面",
"header.recent": "最新",
"header.unread": "未读",
"header.tags": "话题",
diff --git a/public/language/zh_CN/groups.json b/public/language/zh_CN/groups.json
index 0f02408c81..d127f71963 100644
--- a/public/language/zh_CN/groups.json
+++ b/public/language/zh_CN/groups.json
@@ -38,12 +38,13 @@
"details.private_help": "启用此选项后,加入小组需要组长审批。",
"details.hidden": "隐藏",
"details.hidden_help": "启用此选项后,小组将不在小组列表中展现,成员只能通过邀请加入。",
- "details.delete_group": "Delete Group",
+ "details.delete_group": "删除小组",
"event.updated": "小组信息已更新",
"event.deleted": "小组 \"%1\" 已被删除",
- "membership.accept-invitation": "Accept Invitation",
- "membership.invitation-pending": "Invitation Pending",
- "membership.join-group": "Join Group",
- "membership.leave-group": "Leave Group",
- "membership.reject": "Reject"
+ "membership.accept-invitation": "接受邀请",
+ "membership.invitation-pending": "邀请中",
+ "membership.join-group": "加入小组",
+ "membership.leave-group": "退出小组",
+ "membership.reject": "拒绝",
+ "new-group.group_name": "组名: "
}
\ No newline at end of file
diff --git a/public/language/zh_TW/category.json b/public/language/zh_TW/category.json
index 0dff52556a..a1f61a907c 100644
--- a/public/language/zh_TW/category.json
+++ b/public/language/zh_TW/category.json
@@ -1,9 +1,12 @@
{
+ "category": "Category",
+ "subcategories": "Subcategories",
"new_topic_button": "新主題",
"guest-login-post": "登錄後才能發表",
"no_topics": "這個類別還沒有任何主題。
為何不來發點東西呢?",
"browsing": "正在瀏覽",
"no_replies": "還沒有回覆",
+ "no_new_posts": "No new posts.",
"share_this_category": "分享這類別",
"watch": "觀看",
"ignore": "忽略",
diff --git a/public/language/zh_TW/groups.json b/public/language/zh_TW/groups.json
index b69c394a33..17d8092035 100644
--- a/public/language/zh_TW/groups.json
+++ b/public/language/zh_TW/groups.json
@@ -45,5 +45,6 @@
"membership.invitation-pending": "Invitation Pending",
"membership.join-group": "Join Group",
"membership.leave-group": "Leave Group",
- "membership.reject": "Reject"
+ "membership.reject": "Reject",
+ "new-group.group_name": "Group Name:"
}
\ No newline at end of file
diff --git a/public/less/mixins.less b/public/less/mixins.less
index e6ea7ceac4..cadde9db5d 100644
--- a/public/less/mixins.less
+++ b/public/less/mixins.less
@@ -51,4 +51,22 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
+}
+
+.fix-lists {
+ ul {
+ > li {
+ list-style-type: disc;
+
+ ul > li {
+ list-style-type: circle;
+
+ ul > li {
+ list-style-type: square;
+ }
+ }
+ }
+
+ margin-bottom: 10px;
+ }
}
\ No newline at end of file
diff --git a/public/src/admin/admin.js b/public/src/admin/admin.js
index bc6d81d747..22d5319708 100644
--- a/public/src/admin/admin.js
+++ b/public/src/admin/admin.js
@@ -74,7 +74,6 @@
});
Mousetrap.bind('ctrl+shift+a R', function() {
- console.log('[admin] Restarting NodeBB...');
socket.emit('admin.restart');
});
@@ -196,9 +195,9 @@
if (checked) {
checkbox.after('');
- }
+ }
else {
- checkbox.after('');
+ checkbox.after('');
}
checkbox.attr('data-toggle-added', true);
diff --git a/public/src/admin/extend/plugins.js b/public/src/admin/extend/plugins.js
index c12e9f02d7..5a70d29a41 100644
--- a/public/src/admin/extend/plugins.js
+++ b/public/src/admin/extend/plugins.js
@@ -186,22 +186,7 @@ define('admin/extend/plugins', function() {
return app.alertError(err.message);
}
- var targetList = (pluginData.installed ? 'installed' : 'download'),
- otherList = (pluginData.installed ? 'download' : 'installed'),
- payload = {};
-
- payload[targetList] = pluginData;
- templates.parse('admin/partials/' + targetList + '_plugin_item', payload, function(html) {
- var pluginList = $('ul.' + targetList);
-
- pluginList.append(html);
- $('ul.' + otherList).find('li[data-plugin-id="' + pluginID + '"]').slideUp('slow', function() {
- $(this).remove();
- $('html,body').animate({
- scrollTop: pluginList.find('li').last().offset().top - 48
- }, 1000);
- });
- });
+ ajaxify.refresh();
app.alert({
alert_id: 'plugin_toggled',
diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js
index 4fd4586b6b..622bc0afde 100644
--- a/public/src/ajaxify.js
+++ b/public/src/ajaxify.js
@@ -30,15 +30,15 @@ $(document).ready(function() {
ajaxify.currentPage = null;
- ajaxify.go = function (url, callback, quiet, search) {
+ ajaxify.go = function (url, callback, quiet) {
if (!socket.connected) {
if (ajaxify.reconnectAction) {
$(window).off('action:reconnected', ajaxify.reconnectAction);
}
ajaxify.reconnectAction = function(e) {
- ajaxify.go(url, callback, quiet, search);
+ ajaxify.go(url, callback, quiet);
$(window).off(e);
- }
+ };
$(window).on('action:reconnected', ajaxify.reconnectAction);
}
@@ -54,7 +54,7 @@ $(document).ready(function() {
apiXHR.abort();
}
- url = ajaxify.start(url, quiet, search);
+ url = ajaxify.start(url, quiet);
$('#footer, #content').removeClass('hide').addClass('ajaxifying');
@@ -87,10 +87,8 @@ $(document).ready(function() {
};
- ajaxify.start = function(url, quiet, search) {
+ ajaxify.start = function(url, quiet) {
url = ajaxify.removeRelativePath(url.replace(/^\/|\/$/g, ''));
- var hash = window.location.hash;
- search = search || '';
$(window).trigger('action:ajaxify.start', {url: url});
@@ -102,8 +100,8 @@ $(document).ready(function() {
if (window.history && window.history.pushState) {
window.history[!quiet ? 'pushState' : 'replaceState']({
- url: url + search + hash
- }, url, RELATIVE_PATH + '/' + url + search + hash);
+ url: url
+ }, url, RELATIVE_PATH + '/' + url);
}
return url;
};
@@ -255,8 +253,6 @@ $(document).ready(function() {
};
function ajaxifyAnchors() {
- templates.registerLoader(ajaxify.loadTemplate);
-
function hrefEmpty(href) {
return href === undefined || href === '' || href === 'javascript:;';
}
@@ -282,7 +278,6 @@ $(document).ready(function() {
if (window.location.pathname === this.pathname && this.hash.length) {
window.location.hash = this.hash;
} else {
- window.location.hash = '';
if (ajaxify.go(pathname)) {
e.preventDefault();
}
@@ -301,6 +296,8 @@ $(document).ready(function() {
});
}
+ templates.registerLoader(ajaxify.loadTemplate);
+
if (window.history && window.history.pushState) {
// Progressive Enhancement, ajaxify available only to modern browsers
ajaxifyAnchors();
diff --git a/public/src/app.js b/public/src/app.js
index a14a403da5..5bcbb8201d 100644
--- a/public/src/app.js
+++ b/public/src/app.js
@@ -92,7 +92,7 @@ app.cacheBuster = null;
switch(url_parts[0]) {
case 'user':
- room = 'user/' + ajaxify.data.theirid;
+ room = 'user/' + ajaxify.data ? ajaxify.data.theirid : 0;
break;
case 'topic':
room = 'topic_' + url_parts[1];
@@ -185,7 +185,8 @@ app.cacheBuster = null;
enter: room,
username: app.user.username,
userslug: app.user.userslug,
- picture: app.user.picture
+ picture: app.user.picture,
+ status: app.user.status
}, function(err) {
if (err) {
app.alertError(err.message);
@@ -240,6 +241,8 @@ app.cacheBuster = null;
app.processPage = function () {
highlightNavigationLink();
+ utils.overrideTimeago();
+
$('.timeago').timeago();
utils.makeNumbersHumanReadable($('.human-readable-number'));
@@ -380,7 +383,8 @@ app.cacheBuster = null;
};
function createHeaderTooltips() {
- if (utils.findBootstrapEnvironment() === 'xs') {
+ var env = utils.findBootstrapEnvironment();
+ if (env === 'xs' || env === 'sm') {
return;
}
$('#header-menu li a[title]').each(function() {
@@ -454,6 +458,7 @@ app.cacheBuster = null;
return app.alertError(err.message);
}
$('#logged-in-menu #user_label #user-profile-link>i').attr('class', 'fa fa-circle status ' + status);
+ app.user.status = status;
});
e.preventDefault();
});
@@ -464,11 +469,13 @@ app.cacheBuster = null;
return;
}
- translator.translate('[[global:' + status + ']]', function(translated) {
- el.removeClass('online offline dnd away')
- .addClass(status)
- .attr('title', translated)
- .attr('data-original-title', translated);
+ require(['translator'], function(translator) {
+ translator.translate('[[global:' + status + ']]', function(translated) {
+ el.removeClass('online offline dnd away')
+ .addClass(status)
+ .attr('title', translated)
+ .attr('data-original-title', translated);
+ });
});
};
diff --git a/public/src/client/account/settings.js b/public/src/client/account/settings.js
index 434072a0b0..23afc2f5c0 100644
--- a/public/src/client/account/settings.js
+++ b/public/src/client/account/settings.js
@@ -61,6 +61,13 @@ define('forum/account/settings', ['forum/account/header'], function(header) {
return false;
});
+
+ $('#bootswatchSkin').on('change', function() {
+ var css = $('#bootswatchCSS'),
+ val = $(this).val() === 'default' ? config['theme:src'] : 'http://maxcdn.bootstrapcdn.com/bootswatch/latest/' + $(this).val() + '/bootstrap.min.css';
+
+ css.attr('href', val);
+ });
};
return AccountSettings;
diff --git a/public/src/client/chats.js b/public/src/client/chats.js
index 95fa9266b7..53478fdcc5 100644
--- a/public/src/client/chats.js
+++ b/public/src/client/chats.js
@@ -125,8 +125,9 @@ define('forum/chats', ['components', 'string', 'sounds', 'forum/infinitescroll',
};
function onMessagesParsed(html) {
- var newMessage = $(html);
- newMessage.insertBefore($('.user-typing'));
+ var newMessage = $(html),
+ chatContainer = $('.chat-content');
+ newMessage.appendTo(chatContainer);
newMessage.find('.timeago').timeago();
newMessage.find('img:not(".chat-user-image")').addClass('img-responsive');
Chats.scrollToBottom($('.expanded-chat .chat-content'));
@@ -135,11 +136,13 @@ define('forum/chats', ['components', 'string', 'sounds', 'forum/infinitescroll',
Chats.addSocketListeners = function() {
socket.on('event:chats.receive', function(data) {
var typingNotifEl = $('.user-typing'),
- containerEl = $('.expanded-chat ul');
+ containerEl = $('.expanded-chat ul'),
+ lastSpeaker = parseInt(containerEl.find('.chat-message').last().attr('data-uid'), 10);
if (Chats.isCurrentChat(data.withUid)) {
newMessage = data.self === 0;
data.message.self = data.self;
+ data.message.newSet = lastSpeaker !== data.message.fromuid;
Chats.parseMessage(data.message, onMessagesParsed);
} else {
$('.chats-list li[data-uid="' + data.withUid + '"]').addClass('unread');
@@ -148,22 +151,10 @@ define('forum/chats', ['components', 'string', 'sounds', 'forum/infinitescroll',
});
socket.on('event:chats.userStartTyping', function(withUid) {
- var typingNotifEl = $('.user-typing');
-
- if (Chats.isCurrentChat(withUid)) {
- typingNotifEl.removeClass('hide');
- }
-
$('.chats-list li[data-uid="' + withUid + '"]').addClass('typing');
});
socket.on('event:chats.userStopTyping', function(withUid) {
- var typingNotifEl = $('.user-typing');
-
- if (Chats.isCurrentChat(withUid)) {
- typingNotifEl.addClass('hide');
- }
-
$('.chats-list li[data-uid="' + withUid + '"]').removeClass('typing');
});
@@ -173,7 +164,7 @@ define('forum/chats', ['components', 'string', 'sounds', 'forum/infinitescroll',
};
Chats.resizeMainWindow = function() {
- var messagesList = $('.expanded-chat ul');
+ var messagesList = $('.expanded-chat .chat-content');
if (messagesList.length) {
var margin = $('.expanded-chat ul').outerHeight(true) - $('.expanded-chat ul').height(),
diff --git a/public/src/client/groups/list.js b/public/src/client/groups/list.js
index e70eed5789..c02b9904eb 100644
--- a/public/src/client/groups/list.js
+++ b/public/src/client/groups/list.js
@@ -17,7 +17,7 @@ define('forum/groups/list', ['forum/infinitescroll'], function(infinitescroll) {
// Group creation
$('button[data-action="new"]').on('click', function() {
- bootbox.prompt('Group Name:', function(name) {
+ bootbox.prompt('[[group:new-group.group_name]]', function(name) {
if (name && name.length) {
socket.emit('groups.create', {
name: name
diff --git a/public/src/client/notifications.js b/public/src/client/notifications.js
index 2848704541..4f2dfbf197 100644
--- a/public/src/client/notifications.js
+++ b/public/src/client/notifications.js
@@ -7,7 +7,7 @@ define('forum/notifications', ['components', 'notifications'], function(componen
Notifications.init = function() {
var listEl = $('.notifications-list');
- listEl.on('click', 'a', function(e) {
+ listEl.on('click', '[component="notifications/item/link"]', function(e) {
var nid = $(this).parents('[data-nid]').attr('data-nid');
socket.emit('notifications.markRead', nid, function(err) {
if (err) {
diff --git a/public/src/client/topic.js b/public/src/client/topic.js
index 1cdd1cabbc..50a8ebe705 100644
--- a/public/src/client/topic.js
+++ b/public/src/client/topic.js
@@ -116,7 +116,8 @@ define('forum/topic', [
};
function handleBookmark(tid) {
- var bookmark = localStorage.getItem('topic:' + tid + ':bookmark');
+ // use the user's bookmark data if available, fallback to local if available
+ var bookmark = ajaxify.data.bookmark || localStorage.getItem('topic:' + tid + ':bookmark');
var postIndex = getPostIndex();
if (postIndex && window.location.search.indexOf('page=') === -1) {
@@ -128,7 +129,7 @@ define('forum/topic', [
timeout: 0,
type: 'info',
clickfn : function() {
- navigator.scrollToPost(parseInt(bookmark, 10), true);
+ navigator.scrollToPost(parseInt(bookmark - 1, 10), true);
},
closefn : function() {
localStorage.removeItem('topic:' + tid + ':bookmark');
@@ -197,10 +198,28 @@ define('forum/topic', [
}
}
- var currentBookmark = localStorage.getItem('topic:' + ajaxify.data.tid + ':bookmark');
+ var bookmarkKey = 'topic:' + ajaxify.data.tid + ':bookmark';
+ var currentBookmark = ajaxify.data.bookmark || localStorage.getItem(bookmarkKey);
+ if (!currentBookmark || parseInt(postIndex, 10) > parseInt(currentBookmark, 10)) {
+ if (app.user.uid) {
+ var payload = {
+ 'tid': ajaxify.data.tid,
+ 'index': postIndex
+ };
+ socket.emit('topics.bookmark', payload, function(err) {
+ if (err) {
+ console.warn('Error saving bookmark:', err);
+ }
+ ajaxify.data.bookmark = postIndex;
+ });
+ } else {
+ localStorage.setItem(bookmarkKey, postIndex);
+ }
+ }
+
+ // removes the bookmark alert when we get to / past the bookmark
if (!currentBookmark || parseInt(postIndex, 10) >= parseInt(currentBookmark, 10)) {
- localStorage.setItem('topic:' + ajaxify.data.tid + ':bookmark', postIndex);
app.removeAlert('bookmark');
}
diff --git a/public/src/client/topic/browsing.js b/public/src/client/topic/browsing.js
index 0bed58f952..873d95c438 100644
--- a/public/src/client/topic/browsing.js
+++ b/public/src/client/topic/browsing.js
@@ -4,14 +4,14 @@
/* globals define, app, config, socket, ajaxify */
-define('forum/topic/browsing', ['translator'], function(translator) {
+define('forum/topic/browsing', function() {
var Browsing = {};
Browsing.onUpdateUsersInRoom = function(data) {
if (data && data.room.indexOf('topic_' + ajaxify.data.tid) !== -1) {
$('[component="topic/browsing/list"]').parent().toggleClass('hidden', !data.users.length);
- for(var i=0; i');
}
}
diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js
index c47999de0a..41d178a413 100644
--- a/public/src/modules/chat.js
+++ b/public/src/modules/chat.js
@@ -80,7 +80,7 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
if (modal.is(":visible")) {
taskbar.updateActive(modal.attr('UUID'));
- Chats.scrollToBottom(modal.find('#chat-content'));
+ Chats.scrollToBottom(modal.find('.chat-content'));
} else {
module.toggleNew(modal.attr('UUID'), true, true);
}
@@ -111,7 +111,7 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
socket.on('event:chats.userStartTyping', function(withUid) {
var modal = module.getModal(withUid);
- var chatContent = modal.find('#chat-content');
+ var chatContent = modal.find('.chat-content');
if (!chatContent.length) {
return;
}
@@ -142,7 +142,7 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
}
});
- callback(chats);
+ callback(null, chats);
});
socket.on('event:chats.open', function(data) {
@@ -234,7 +234,7 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
return;
}
- chatModal.find('#chat-content').css('height', module.calculateChatListHeight(chatModal));
+ chatModal.find('.chat-content').css('height', module.calculateChatListHeight(chatModal));
});
chatModal.draggable({
@@ -312,8 +312,6 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
}
});
- chatModal.find('.user-typing .text').translateText('[[modules:chat.user_typing, ' + data.username + ']]');
-
taskbar.push('chat', chatModal.attr('UUID'), {
title: data.username,
touid: data.touid,
@@ -375,7 +373,7 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
chatModal.removeClass('hide');
checkStatus(chatModal);
taskbar.updateActive(uuid);
- Chats.scrollToBottom(chatModal.find('#chat-content'));
+ Chats.scrollToBottom(chatModal.find('.chat-content'));
module.bringModalToTop(chatModal);
module.focusInput(chatModal);
socket.emit('modules.chats.markRead', chatModal.attr('touid'));
@@ -389,7 +387,7 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
module.enableMobileBehaviour = function(modalEl) {
app.toggleNavbar(false);
modalEl.attr('data-mobile', '1');
- var messagesEl = modalEl.find('#chat-content');
+ var messagesEl = modalEl.find('.chat-content');
messagesEl.css('height', module.calculateChatListHeight(modalEl));
$(window).on('resize', function() {
@@ -404,7 +402,7 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
module.calculateChatListHeight = function(modalEl) {
var totalHeight = modalEl.find('.modal-content').outerHeight() - modalEl.find('.modal-header').outerHeight(),
padding = parseInt(modalEl.find('.modal-body').css('padding-top'), 10) + parseInt(modalEl.find('.modal-body').css('padding-bottom'), 10),
- contentMargin = parseInt(modalEl.find('#chat-content').css('margin-top'), 10) + parseInt(modalEl.find('#chat-content').css('margin-bottom'), 10),
+ contentMargin = parseInt(modalEl.find('.chat-content').css('margin-top'), 10) + parseInt(modalEl.find('.chat-content').css('margin-bottom'), 10),
sinceHeight = modalEl.find('.since-bar').outerHeight(true),
inputGroupHeight = modalEl.find('.input-group').outerHeight();
@@ -428,7 +426,7 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
function loadChatSince(chatModal, since, callback) {
socket.emit('modules.chats.get', {touid: chatModal.attr('touid'), since: since}, function(err, messages) {
- var chatContent = chatModal.find('#chat-content');
+ var chatContent = chatModal.find('.chat-content');
chatContent.find('.chat-message').remove();
module.appendChatMessage(chatModal, messages, callback);
});
@@ -459,14 +457,18 @@ define('chat', ['components', 'taskbar', 'string', 'sounds', 'forum/chats', 'tra
}
module.appendChatMessage = function(chatModal, data, done) {
- var chatContent = chatModal.find('#chat-content'),
- typingNotif = chatModal.find('.user-typing');
+ var chatContent = chatModal.find('.chat-content'),
+ lastSpeaker = parseInt(chatContent.find('.chat-message').last().attr('data-uid'), 10);
+
+ if (!Array.isArray(data)) {
+ data.newSet = lastSpeaker !== data.fromuid;
+ }
Chats.parseMessage(data, function(html) {
var message = $(html);
message.find('img:not(".chat-user-image")').addClass('img-responsive');
message.find('.timeago').timeago();
- message.insertBefore(typingNotif);
+ message.appendTo(chatContent);
Chats.scrollToBottom(chatContent);
if (typeof done === 'function') {
diff --git a/public/src/modules/settings.js b/public/src/modules/settings.js
index d541b35e84..0092f4ca46 100644
--- a/public/src/modules/settings.js
+++ b/public/src/modules/settings.js
@@ -294,7 +294,6 @@ define('settings', function () {
message: "NodeBB failed to save the settings.",
timeout: 5000
});
- console.log('[settings] Unable to set settings for hash: ', hash);
} else {
app.alert({
title: 'Settings Saved',
@@ -387,7 +386,6 @@ define('settings', function () {
hash: hash
}, function (err, values) {
if (err) {
- console.log('[settings] Unable to load settings for hash: ', hash);
if (typeof callback === 'function') {
callback(err);
}
@@ -455,10 +453,9 @@ define('settings', function () {
hash: hash
}, function (err, values) {
if (err) {
- console.log('[settings] Unable to load settings for hash: ', hash);
return callback(err);
}
-
+
// Parse all values. If they are json, return json
for(var key in values) {
if (values.hasOwnProperty(key)) {
@@ -508,8 +505,6 @@ define('settings', function () {
});
}
});
- } else {
- console.log('[settings] Form not found.');
}
}
};
diff --git a/public/src/modules/sounds.js b/public/src/modules/sounds.js
index fa0eade2b5..3314500ce5 100644
--- a/public/src/modules/sounds.js
+++ b/public/src/modules/sounds.js
@@ -1,5 +1,5 @@
"use strict";
-/* global define, socket, config */
+/* global app, define, socket, config */
define('sounds', ['buzz'], function(buzz) {
var Sounds = {};
@@ -17,7 +17,7 @@ define('sounds', ['buzz'], function(buzz) {
function loadFiles() {
socket.emit('modules.sounds.getSounds', function(err, sounds) {
if (err) {
- return console.log('[sounds] Could not initialise!');
+ return app.alertError('[sounds] Could not initialise!');
}
files = sounds;
@@ -27,7 +27,7 @@ define('sounds', ['buzz'], function(buzz) {
function loadMapping() {
socket.emit('modules.sounds.getMapping', function(err, mapping) {
if (err) {
- return console.log('[sounds] Could not load sound mapping!');
+ return app.alertError('[sounds] Could not load sound mapping!');
}
eventSoundMapping = mapping;
});
@@ -65,7 +65,7 @@ define('sounds', ['buzz'], function(buzz) {
if (loadedSounds[fileName]) {
loadedSounds[fileName].play();
} else {
- console.log('[sounds] Not found:', fileName);
+ app.alertError('[sounds] Not found: ' + fileName);
}
}
diff --git a/public/src/modules/translator.js b/public/src/modules/translator.js
index 42efe5c529..90784747c9 100644
--- a/public/src/modules/translator.js
+++ b/public/src/modules/translator.js
@@ -9,7 +9,7 @@
var languages = {},
regexes = {
- match: /\[\[\w+:[\s\S]+?\]\]/g,
+ match: /\[\[\w+:[^\]]+?\]\]/g,
split: /[,][\s]*/,
replace: /\]+$/
};
@@ -44,81 +44,72 @@
};
translator.prepareDOM = function() {
- // Load the appropriate timeago locale file
- if (config.userLang !== 'en_GB' && config.userLang !== 'en_US') {
- // Correct NodeBB language codes to timeago codes, if necessary
- var languageCode;
- switch(config.userLang) {
- case 'cs':
- languageCode = 'cz';
- break;
+ // Load the appropriate timeago locale file, and correct NodeBB language codes to timeago codes, if necessary
+ var languageCode;
+ switch(config.userLang) {
+ case 'cs':
+ languageCode = 'cz';
+ break;
- case 'fa_IR':
- languageCode = 'fa';
- break;
+ case 'fa_IR':
+ languageCode = 'fa';
+ break;
- case 'pt_BR':
- languageCode = 'pt-br';
- break;
+ case 'pt_BR':
+ languageCode = 'pt-br';
+ break;
- case 'nb':
- languageCode = 'no';
- break;
+ case 'nb':
+ languageCode = 'no';
+ break;
- case 'zh_TW':
- languageCode = 'zh-TW';
- break;
+ case 'zh_TW':
+ languageCode = 'zh-TW';
+ break;
- case 'zh_CN':
- languageCode = 'zh-CN';
- break;
+ case 'zh_CN':
+ languageCode = 'zh-CN';
+ break;
- default:
- languageCode = config.userLang;
- break;
- }
+ default:
+ languageCode = config.userLang;
+ break;
+ }
- $.getScript(RELATIVE_PATH + '/vendor/jquery/timeago/locales/jquery.timeago.' + languageCode + '.js').success(function() {
- $('.timeago').timeago();
+ $.getScript(RELATIVE_PATH + '/vendor/jquery/timeago/locales/jquery.timeago.' + languageCode + '.js').success(function() {
+ $('.timeago').timeago();
+ translator.timeagoShort = $.extend({}, jQuery.timeago.settings.strings);
+
+ // Retrieve the shorthand timeago values as well
+ $.getScript(RELATIVE_PATH + '/vendor/jquery/timeago/locales/jquery.timeago.' + languageCode + '-short.js').success(function() {
+ // Switch back to long-form
+ translator.toggleTimeagoShorthand();
}).fail(function() {
+ $.getScript(RELATIVE_PATH + '/vendor/jquery/timeago/locales/jquery.timeago.en-short.js').success(function() {
+ // Switch back to long-form
+ translator.toggleTimeagoShorthand();
+ });
+ });
+ }).fail(function() {
+ $.getScript(RELATIVE_PATH + '/vendor/jquery/timeago/locales/jquery.timeago.en-short.js').success(function() {
+ // Switch back to long-form
+ translator.toggleTimeagoShorthand();
$.getScript(RELATIVE_PATH + '/vendor/jquery/timeago/locales/jquery.timeago.en.js');
});
+ });
- // Add directional code if necessary
- translator.translate('[[language:dir]]', function(value) {
- if (value) {
- $('html').css('direction', value).attr('data-dir', value);
- }
- });
- }
+ // Add directional code if necessary
+ translator.translate('[[language:dir]]', function(value) {
+ if (value) {
+ $('html').css('direction', value).attr('data-dir', value);
+ }
+ });
};
translator.toggleTimeagoShorthand = function() {
- if (!translator.timeagoStrings) {
- translator.timeagoStrings = $.extend({}, jQuery.timeago.settings.strings);
- jQuery.timeago.settings.strings = {
- prefixAgo: null,
- prefixFromNow: null,
- suffixAgo: "",
- suffixFromNow: "",
- seconds: "1m",
- minute: "1m",
- minutes: "%dm",
- hour: "1h",
- hours: "%dh",
- day: "1d",
- days: "%dd",
- month: "1mo",
- months: "%dmo",
- year: "1yr",
- years: "%dyr",
- wordSeparator: " ",
- numbers: []
- };
- } else {
- jQuery.timeago.settings.strings = $.extend({}, translator.timeagoStrings);
- delete translator.timeagoStrings;
- }
+ var tmp = $.extend({}, jQuery.timeago.settings.strings);
+ jQuery.timeago.settings.strings = $.extend({}, translator.timeagoShort);
+ translator.timeagoShort = $.extend({}, tmp);
};
translator.translate = function (text, language, callback) {
@@ -168,6 +159,7 @@
var variables = key.split(regexes.split);
var parsedKey = key.replace('[[', '').replace(']]', '').split(':');
+ parsedKey = [parsedKey[0]].concat(parsedKey.slice(1).join(':'));
if (!(parsedKey[0] && parsedKey[1])) {
return callback(data);
}
@@ -301,7 +293,9 @@
// Expose a global `translator` object for backwards compatibility
window.translator = {
translate: function() {
- console.warn('[translator] Global invocation of the translator is now deprecated, please `require` the module instead.');
+ if (typeof console !== 'undefined' && console.warn) {
+ console.warn('[translator] Global invocation of the translator is now deprecated, please `require` the module instead.');
+ }
_translator.translate.apply(_translator, arguments);
}
}
diff --git a/public/src/utils.js b/public/src/utils.js
index 930e03501f..66ee10f6cf 100644
--- a/public/src/utils.js
+++ b/public/src/utils.js
@@ -203,6 +203,15 @@
return text.replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,");
},
+ overrideTimeago: function() {
+ var timeagoFn = $.fn.timeago;
+ $.fn.timeago = function() {
+ timeagoFn.apply(this, arguments).each(function() {
+ $(this).attr('title', (new Date($(this).attr('title'))).toString());
+ });
+ };
+ },
+
toISOString: function(timestamp) {
if (!timestamp || !Date.prototype.toISOString) {
return '';
diff --git a/public/src/variables.js b/public/src/variables.js
index 16ad223073..1a4b4e244c 100644
--- a/public/src/variables.js
+++ b/public/src/variables.js
@@ -7,14 +7,14 @@
ajaxify.variables = {};
ajaxify.variables.set = function(key, value) {
- if (console && console.warn) {
+ if (typeof console !== 'undefined' && console.warn) {
console.warn('[deprecated] variables.set is deprecated, please use ajaxify.data, key=' + key);
}
parsedVariables[key] = value;
};
ajaxify.variables.get = function(key) {
- if (console && console.warn) {
+ if (typeof console !== 'undefined' && console.warn) {
console.warn('[deprecated] variables.get is deprecated, please use ajaxify.data, key=' + key);
}
return parsedVariables[key];
diff --git a/public/vendor/jquery/timeago/jquery.timeago.js b/public/vendor/jquery/timeago/jquery.timeago.js
new file mode 100644
index 0000000000..15805a6193
--- /dev/null
+++ b/public/vendor/jquery/timeago/jquery.timeago.js
@@ -0,0 +1,221 @@
+/**
+ * Timeago is a jQuery plugin that makes it easy to support automatically
+ * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
+ *
+ * @name timeago
+ * @version 1.4.1
+ * @requires jQuery v1.2.3+
+ * @author Ryan McGeary
+ * @license MIT License - http://www.opensource.org/licenses/mit-license.php
+ *
+ * For usage and examples, visit:
+ * http://timeago.yarp.com/
+ *
+ * Copyright (c) 2008-2015, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org)
+ */
+
+(function (factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(['jquery'], factory);
+ } else {
+ // Browser globals
+ factory(jQuery);
+ }
+}(function ($) {
+ $.timeago = function(timestamp) {
+ if (timestamp instanceof Date) {
+ return inWords(timestamp);
+ } else if (typeof timestamp === "string") {
+ return inWords($.timeago.parse(timestamp));
+ } else if (typeof timestamp === "number") {
+ return inWords(new Date(timestamp));
+ } else {
+ return inWords($.timeago.datetime(timestamp));
+ }
+ };
+ var $t = $.timeago;
+
+ $.extend($.timeago, {
+ settings: {
+ refreshMillis: 60000,
+ allowPast: true,
+ allowFuture: false,
+ localeTitle: false,
+ cutoff: 0,
+ strings: {
+ prefixAgo: null,
+ prefixFromNow: null,
+ suffixAgo: "ago",
+ suffixFromNow: "from now",
+ inPast: 'any moment now',
+ seconds: "less than a minute",
+ minute: "about a minute",
+ minutes: "%d minutes",
+ hour: "about an hour",
+ hours: "about %d hours",
+ day: "a day",
+ days: "%d days",
+ month: "about a month",
+ months: "%d months",
+ year: "about a year",
+ years: "%d years",
+ wordSeparator: " ",
+ numbers: []
+ }
+ },
+
+ inWords: function(distanceMillis) {
+ if(!this.settings.allowPast && ! this.settings.allowFuture) {
+ throw 'timeago allowPast and allowFuture settings can not both be set to false.';
+ }
+
+ var $l = this.settings.strings;
+ var prefix = $l.prefixAgo;
+ var suffix = $l.suffixAgo;
+ if (this.settings.allowFuture) {
+ if (distanceMillis < 0) {
+ prefix = $l.prefixFromNow;
+ suffix = $l.suffixFromNow;
+ }
+ }
+
+ if(!this.settings.allowPast && distanceMillis >= 0) {
+ return this.settings.strings.inPast;
+ }
+
+ var seconds = Math.abs(distanceMillis) / 1000;
+ var minutes = seconds / 60;
+ var hours = minutes / 60;
+ var days = hours / 24;
+ var years = days / 365;
+
+ function substitute(stringOrFunction, number) {
+ var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;
+ var value = ($l.numbers && $l.numbers[number]) || number;
+ return string.replace(/%d/i, value);
+ }
+
+ var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||
+ seconds < 90 && substitute($l.minute, 1) ||
+ minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||
+ minutes < 90 && substitute($l.hour, 1) ||
+ hours < 24 && substitute($l.hours, Math.round(hours)) ||
+ hours < 42 && substitute($l.day, 1) ||
+ days < 30 && substitute($l.days, Math.round(days)) ||
+ days < 45 && substitute($l.month, 1) ||
+ days < 365 && substitute($l.months, Math.round(days / 30)) ||
+ years < 1.5 && substitute($l.year, 1) ||
+ substitute($l.years, Math.round(years));
+
+ var separator = $l.wordSeparator || "";
+ if ($l.wordSeparator === undefined) { separator = " "; }
+ return $.trim([prefix, words, suffix].join(separator));
+ },
+
+ parse: function(iso8601) {
+ var s = $.trim(iso8601);
+ s = s.replace(/\.\d+/,""); // remove milliseconds
+ s = s.replace(/-/,"/").replace(/-/,"/");
+ s = s.replace(/T/," ").replace(/Z/," UTC");
+ s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400
+ s = s.replace(/([\+\-]\d\d)$/," $100"); // +09 -> +0900
+ return new Date(s);
+ },
+ datetime: function(elem) {
+ var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title");
+ return $t.parse(iso8601);
+ },
+ isTime: function(elem) {
+ // jQuery's `is()` doesn't play well with HTML5 in IE
+ return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
+ }
+ });
+
+ // functions that can be called via $(el).timeago('action')
+ // init is default when no action is given
+ // functions are called with context of a single element
+ var functions = {
+ init: function(){
+ var refresh_el = $.proxy(refresh, this);
+ refresh_el();
+ var $s = $t.settings;
+ if ($s.refreshMillis > 0) {
+ this._timeagoInterval = setInterval(refresh_el, $s.refreshMillis);
+ }
+ },
+ update: function(time){
+ var parsedTime = $t.parse(time);
+ $(this).data('timeago', { datetime: parsedTime });
+ if($t.settings.localeTitle) $(this).attr("title", parsedTime.toLocaleString());
+ refresh.apply(this);
+ },
+ updateFromDOM: function(){
+ $(this).data('timeago', { datetime: $t.parse( $t.isTime(this) ? $(this).attr("datetime") : $(this).attr("title") ) });
+ refresh.apply(this);
+ },
+ dispose: function () {
+ if (this._timeagoInterval) {
+ window.clearInterval(this._timeagoInterval);
+ this._timeagoInterval = null;
+ }
+ }
+ };
+
+ $.fn.timeago = function(action, options) {
+ var fn = action ? functions[action] : functions.init;
+ if(!fn){
+ throw new Error("Unknown function name '"+ action +"' for timeago");
+ }
+ // each over objects here and call the requested function
+ this.each(function(){
+ fn.call(this, options);
+ });
+ return this;
+ };
+
+ function refresh() {
+ //check if it's still visible
+ if(!$.contains(document.documentElement,this)){
+ //stop if it has been removed
+ $(this).timeago("dispose");
+ return this;
+ }
+
+ var data = prepareData(this);
+ var $s = $t.settings;
+
+ if (!isNaN(data.datetime)) {
+ if ( $s.cutoff == 0 || Math.abs(distance(data.datetime)) < $s.cutoff) {
+ $(this).text(inWords(data.datetime));
+ }
+ }
+ return this;
+ }
+
+ function prepareData(element) {
+ element = $(element);
+ if (!element.data("timeago")) {
+ element.data("timeago", { datetime: $t.datetime(element) });
+ var text = $.trim(element.text());
+ if ($t.settings.localeTitle) {
+ element.attr("title", element.data('timeago').datetime.toLocaleString());
+ } else if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) {
+ element.attr("title", text);
+ }
+ }
+ return element.data("timeago");
+ }
+
+ function inWords(date) {
+ return $t.inWords(distance(date));
+ }
+
+ function distance(date) {
+ return (new Date().getTime() - date.getTime());
+ }
+
+ // fix for IE6 suckage
+ document.createElement("abbr");
+ document.createElement("time");
+}));
diff --git a/public/vendor/jquery/timeago/jquery.timeago.min.js b/public/vendor/jquery/timeago/jquery.timeago.min.js
deleted file mode 100644
index a3567b0148..0000000000
--- a/public/vendor/jquery/timeago/jquery.timeago.min.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * Timeago is a jQuery plugin that makes it easy to support automatically
- * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
- *
- * @name timeago
- * @version 1.3.1
- * @requires jQuery v1.2.3+
- * @author Ryan McGeary
- * @license MIT License - http://www.opensource.org/licenses/mit-license.php
- *
- * For usage and examples, visit:
- * http://timeago.yarp.com/
- *
- * Copyright (c) 2008-2013, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org)
- */
-!function(factory){if(typeof define==="function"&&define.amd){define(["jquery"],factory)}else{factory(jQuery)}}(function($){$.timeago=function(timestamp){if(timestamp instanceof Date){return inWords(timestamp)}else if(typeof timestamp==="string"){return inWords($.timeago.parse(timestamp))}else if(typeof timestamp==="number"){return inWords(new Date(timestamp))}else{return inWords($.timeago.datetime(timestamp))}};var $t=$.timeago;$.extend($.timeago,{settings:{refreshMillis:6e4,allowFuture:false,localeTitle:false,cutoff:0,strings:{prefixAgo:null,prefixFromNow:null,suffixAgo:"ago",suffixFromNow:"from now",seconds:"less than a minute",minute:"about a minute",minutes:"%d minutes",hour:"about an hour",hours:"about %d hours",day:"a day",days:"%d days",month:"about a month",months:"%d months",year:"about a year",years:"%d years",wordSeparator:" ",numbers:[]}},inWords:function(distanceMillis){var $l=this.settings.strings;var prefix=$l.prefixAgo;var suffix=$l.suffixAgo;if(this.settings.allowFuture){if(distanceMillis<0){prefix=$l.prefixFromNow;suffix=$l.suffixFromNow}}var seconds=Math.abs(distanceMillis)/1e3;var minutes=seconds/60;var hours=minutes/60;var days=hours/24;var years=days/365;function substitute(stringOrFunction,number){var string=$.isFunction(stringOrFunction)?stringOrFunction(number,distanceMillis):stringOrFunction;var value=$l.numbers&&$l.numbers[number]||number;return string.replace(/%d/i,value)}var words=seconds<45&&substitute($l.seconds,Math.round(seconds))||seconds<90&&substitute($l.minute,1)||minutes<45&&substitute($l.minutes,Math.round(minutes))||minutes<90&&substitute($l.hour,1)||hours<24&&substitute($l.hours,Math.round(hours))||hours<42&&substitute($l.day,1)||days<30&&substitute($l.days,Math.round(days))||days<45&&substitute($l.month,1)||days<365&&substitute($l.months,Math.round(days/30))||years<1.5&&substitute($l.year,1)||substitute($l.years,Math.round(years));var separator=$l.wordSeparator||"";if($l.wordSeparator===undefined){separator=" "}return $.trim([prefix,words,suffix].join(separator))},parse:function(iso8601){var s=$.trim(iso8601);s=s.replace(/\.\d+/,"");s=s.replace(/-/,"/").replace(/-/,"/");s=s.replace(/T/," ").replace(/Z/," UTC");s=s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2");s=s.replace(/([\+\-]\d\d)$/," $100");return new Date(s)},datetime:function(elem){var iso8601=$t.isTime(elem)?$(elem).attr("datetime"):$(elem).attr("title");return $t.parse(iso8601)},isTime:function(elem){return $(elem).get(0).tagName.toLowerCase()==="time"}});var functions={init:function(){var refresh_el=$.proxy(refresh,this);refresh_el();var $s=$t.settings;if($s.refreshMillis>0){this._timeagoInterval=setInterval(refresh_el,$s.refreshMillis)}},update:function(time){var parsedTime=$t.parse(time);$(this).data("timeago",{datetime:parsedTime});if($t.settings.localeTitle)$(this).attr("title",parsedTime.toLocaleString());refresh.apply(this)},updateFromDOM:function(){$(this).data("timeago",{datetime:$t.parse($t.isTime(this)?$(this).attr("datetime"):$(this).attr("title"))});refresh.apply(this)},dispose:function(){if(this._timeagoInterval){window.clearInterval(this._timeagoInterval);this._timeagoInterval=null}}};$.fn.timeago=function(action,options){var fn=action?functions[action]:functions.init;if(!fn){throw new Error("Unknown function name '"+action+"' for timeago")}this.each(function(){fn.call(this,options)});return this};function refresh(){var data=prepareData(this);var $s=$t.settings;if(!isNaN(data.datetime)){if($s.cutoff==0||distance(data.datetime)<$s.cutoff){$(this).text(inWords(data.datetime))}}return this}function prepareData(element){element=$(element);if(!element.data("timeago")){element.data("timeago",{datetime:$t.datetime(element)});var text=$.trim(element.text());if($t.settings.localeTitle){element.attr("title",element.data("timeago").datetime.toLocaleString())}else if(text.length>0&&!($t.isTime(element)&&element.attr("title"))){element.attr("title",text)}}return element.data("timeago")}function inWords(date){return $t.inWords(distance(date))}function distance(date){return(new Date).getTime()-date.getTime()}document.createElement("abbr");document.createElement("time")});
\ No newline at end of file
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.cs.js b/public/vendor/jquery/timeago/locales/jquery.timeago.cs.js
new file mode 100644
index 0000000000..0ce346963b
--- /dev/null
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.cs.js
@@ -0,0 +1,24 @@
+// Czech
+(function() {
+ function f(n, d, a) {
+ return a[d>=0 ? 0 : a.length==2 || n<5 ? 1 : 2];
+ }
+
+ jQuery.timeago.settings.strings = {
+ prefixAgo: 'před',
+ prefixFromNow: 'za',
+ suffixAgo: null,
+ suffixFromNow: null,
+ seconds: function(n, d) {return f(n, d, ['méně než minutou', 'méně než minutu']);},
+ minute: function(n, d) {return f(n, d, ['minutou', 'minutu']);},
+ minutes: function(n, d) {return f(n, d, ['%d minutami', '%d minuty', '%d minut']);},
+ hour: function(n, d) {return f(n, d, ['hodinou', 'hodinu']);},
+ hours: function(n, d) {return f(n, d, ['%d hodinami', '%d hodiny', '%d hodin']);},
+ day: function(n, d) {return f(n, d, ['%d dnem', '%d den']);},
+ days: function(n, d) {return f(n, d, ['%d dny', '%d dny', '%d dní']);},
+ month: function(n, d) {return f(n, d, ['%d měsícem', '%d měsíc']);},
+ months: function(n, d) {return f(n, d, ['%d měsíci', '%d měsíce', '%d měsíců']);},
+ year: function(n, d) {return f(n, d, ['%d rokem', '%d rok']);},
+ years: function(n, d) {return f(n, d, ['%d lety', '%d roky', '%d let']);}
+ };
+})();
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.es-short.js b/public/vendor/jquery/timeago/locales/jquery.timeago.es-short.js
new file mode 100644
index 0000000000..65d85c7154
--- /dev/null
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.es-short.js
@@ -0,0 +1,20 @@
+// Spanish shortened
+jQuery.timeago.settings.strings = {
+ prefixAgo: null,
+ prefixFromNow: null,
+ suffixAgo: "",
+ suffixFromNow: "",
+ seconds: "1m",
+ minute: "1m",
+ minutes: "%dm",
+ hour: "1h",
+ hours: "%dh",
+ day: "1d",
+ days: "%dd",
+ month: "1me",
+ months: "%dme",
+ year: "1a",
+ years: "%da",
+ wordSeparator: " ",
+ numbers: []
+};
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.et.js b/public/vendor/jquery/timeago/locales/jquery.timeago.et.js
index 7d17eb5c61..ca19648b40 100644
--- a/public/vendor/jquery/timeago/locales/jquery.timeago.et.js
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.et.js
@@ -4,15 +4,15 @@ jQuery.timeago.settings.strings = {
prefixFromNow: null,
suffixAgo: "tagasi",
suffixFromNow: "pärast",
- seconds: function(n, d) { return d < 0 ? "vähem kui minuti aja" : "vähem kui minut aega" },
- minute: function(n, d) { return d < 0 ? "umbes minuti aja" : "umbes minut aega" },
- minutes: function(n, d) { return d < 0 ? "%d minuti" : "%d minutit" },
- hour: function(n, d) { return d < 0 ? "umbes tunni aja" : "umbes tund aega" },
- hours: function(n, d) { return d < 0 ? "%d tunni" : "%d tundi" },
- day: function(n, d) { return d < 0 ? "umbes päeva" : "umbes päev" },
- days: function(n, d) { return d < 0 ? "%d päeva" : "%d päeva" },
- month: function(n, d) { return d < 0 ? "umbes kuu aja" : "umbes kuu aega" },
- months: function(n, d) { return d < 0 ? "%d kuu" : "%d kuud" },
- year: function(n, d) { return d < 0 ? "umbes aasta aja" : "umbes aasta aega" },
- years: function(n, d) { return d < 0 ? "%d aasta" : "%d aastat" }
+ seconds: function(n, d) { return d < 0 ? "vähem kui minuti aja" : "vähem kui minut aega"; },
+ minute: function(n, d) { return d < 0 ? "umbes minuti aja" : "umbes minut aega"; },
+ minutes: function(n, d) { return d < 0 ? "%d minuti" : "%d minutit"; },
+ hour: function(n, d) { return d < 0 ? "umbes tunni aja" : "umbes tund aega"; },
+ hours: function(n, d) { return d < 0 ? "%d tunni" : "%d tundi"; },
+ day: function(n, d) { return d < 0 ? "umbes päeva" : "umbes päev"; },
+ days: function(n, d) { return d < 0 ? "%d päeva" : "%d päeva"; },
+ month: function(n, d) { return d < 0 ? "umbes kuu aja" : "umbes kuu aega"; },
+ months: function(n, d) { return d < 0 ? "%d kuu" : "%d kuud"; },
+ year: function(n, d) { return d < 0 ? "umbes aasta aja" : "umbes aasta aega"; },
+ years: function(n, d) { return d < 0 ? "%d aasta" : "%d aastat"; }
};
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.fa.js b/public/vendor/jquery/timeago/locales/jquery.timeago.fa.js
index 2bfbf53ccd..36bdb449f3 100644
--- a/public/vendor/jquery/timeago/locales/jquery.timeago.fa.js
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.fa.js
@@ -1,4 +1,4 @@
-
+
// Persian
// Use DIR attribute for RTL text in Persian Language for ABBR tag .
// By MB.seifollahi@gmail.com
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.he.js b/public/vendor/jquery/timeago/locales/jquery.timeago.he.js
index 9d5b6c6b00..b9d89e1f24 100644
--- a/public/vendor/jquery/timeago/locales/jquery.timeago.he.js
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.he.js
@@ -1,18 +1,16 @@
// Hebrew
jQuery.timeago.settings.strings = {
prefixAgo: "לפני",
- prefixFromNow: "מעכשיו",
- suffixAgo: "",
- suffixFromNow: "",
+ prefixFromNow: "עוד",
seconds: "פחות מדקה",
minute: "דקה",
minutes: "%d דקות",
hour: "שעה",
- hours: "%d שעות",
+ hours: function(number){return (number==2) ? "שעתיים" : "%d שעות";},
day: "יום",
- days: "%d ימים",
+ days: function(number){return (number==2) ? "יומיים" : "%d ימים";},
month: "חודש",
- months: "%d חודשים",
+ months: function(number){return (number==2) ? "חודשיים" : "%d חודשים";},
year: "שנה",
- years: "%d שנים"
-};
\ No newline at end of file
+ years: function(number){return (number==2) ? "שנתיים" : "%d שנים";}
+};
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.ko.js b/public/vendor/jquery/timeago/locales/jquery.timeago.ko.js
index a192b97bf6..95f4f94b31 100644
--- a/public/vendor/jquery/timeago/locales/jquery.timeago.ko.js
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.ko.js
@@ -1,17 +1,20 @@
// Korean
jQuery.timeago.settings.strings = {
+ prefixAgo: null,
+ prefixFromNow: null,
suffixAgo: "전",
suffixFromNow: "후",
- seconds: "1분 이내",
- minute: "1분",
+ seconds: "1분",
+ minute: "약 1분",
minutes: "%d분",
- hour: "1시간",
- hours: "%d시간",
+ hour: "약 1시간",
+ hours: "약 %d시간",
day: "하루",
days: "%d일",
- month: "한 달",
- months: "%d달",
- year: "1년",
+ month: "약 1개월",
+ months: "%d개월",
+ year: "약 1년",
years: "%d년",
- wordSeparator: " "
-};
\ No newline at end of file
+ wordSeparator: " ",
+ numbers: []
+};
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.ky.js b/public/vendor/jquery/timeago/locales/jquery.timeago.ky.js
new file mode 100644
index 0000000000..8663fe0b23
--- /dev/null
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.ky.js
@@ -0,0 +1,34 @@
+// Russian
+(function() {
+ function numpf(n, f, s, t) {
+ // f - 1, 21, 31, ...
+ // s - 2-4, 22-24, 32-34 ...
+ // t - 5-20, 25-30, ...
+ var n10 = n % 10;
+ if ( (n10 == 1) && ( (n == 1) || (n > 20) ) ) {
+ return f;
+ } else if ( (n10 > 1) && (n10 < 5) && ( (n > 20) || (n < 10) ) ) {
+ return s;
+ } else {
+ return t;
+ }
+ }
+
+ jQuery.timeago.settings.strings = {
+ prefixAgo: null,
+ prefixFromNow: "через",
+ suffixAgo: "мурун",
+ suffixFromNow: null,
+ seconds: "1 минуттан аз",
+ minute: "минута",
+ minutes: function(value) { return numpf(value, "%d минута", "%d минута", "%d минут"); },
+ hour: "саат",
+ hours: function(value) { return numpf(value, "%d саат", "%d саат", "%d саат"); },
+ day: "күн",
+ days: function(value) { return numpf(value, "%d күн", "%d күн", "%d күн"); },
+ month: "ай",
+ months: function(value) { return numpf(value, "%d ай", "%d ай", "%d ай"); },
+ year: "жыл",
+ years: function(value) { return numpf(value, "%d жыл", "%d жыл", "%d жыл"); }
+ };
+})();
\ No newline at end of file
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.mk.js b/public/vendor/jquery/timeago/locales/jquery.timeago.mk.js
index 9afdd46268..8d60a30c88 100644
--- a/public/vendor/jquery/timeago/locales/jquery.timeago.mk.js
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.mk.js
@@ -16,5 +16,5 @@
months: "%d месеци",
year: "%d година",
years: "%d години"
- }
+ };
})();
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.nl.js b/public/vendor/jquery/timeago/locales/jquery.timeago.nl.js
index cd68438cca..74d3dc867f 100644
--- a/public/vendor/jquery/timeago/locales/jquery.timeago.nl.js
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.nl.js
@@ -1,9 +1,9 @@
// Dutch
jQuery.timeago.settings.strings = {
prefixAgo: null,
- prefixFromNow: "",
+ prefixFromNow: "over",
suffixAgo: "geleden",
- suffixFromNow: "van nu",
+ suffixFromNow: null,
seconds: "minder dan een minuut",
minute: "ongeveer een minuut",
minutes: "%d minuten",
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.pt-br-short.js b/public/vendor/jquery/timeago/locales/jquery.timeago.pt-br-short.js
new file mode 100644
index 0000000000..8f63db43d1
--- /dev/null
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.pt-br-short.js
@@ -0,0 +1,20 @@
+// Portuguese Brasil shortened
+jQuery.timeago.settings.strings = {
+ prefixAgo: null,
+ prefixFromNow: null,
+ suffixAgo: "",
+ suffixFromNow: "",
+ seconds: "1m",
+ minute: "1m",
+ minutes: "%dm",
+ hour: "1h",
+ hours: "%dh",
+ day: "1d",
+ days: "%dd",
+ month: "1M",
+ months: "%dM",
+ year: "1a",
+ years: "%da",
+ wordSeparator: " ",
+ numbers: []
+};
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.ro.js b/public/vendor/jquery/timeago/locales/jquery.timeago.ro.js
index 883b548950..2cee429902 100644
--- a/public/vendor/jquery/timeago/locales/jquery.timeago.ro.js
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.ro.js
@@ -1,5 +1,5 @@
// Romanian
-$.timeago.settings.strings = {
+jQuery.timeago.settings.strings = {
prefixAgo: "acum",
prefixFromNow: "in timp de",
suffixAgo: "",
@@ -15,4 +15,4 @@ $.timeago.settings.strings = {
months: "%d luni",
year: "un an",
years: "%d ani"
-};
\ No newline at end of file
+};
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.rs.js b/public/vendor/jquery/timeago/locales/jquery.timeago.rs.js
index 1fc16135e6..0809c9101d 100644
--- a/public/vendor/jquery/timeago/locales/jquery.timeago.rs.js
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.rs.js
@@ -39,7 +39,7 @@
months: function (value) {
return numpf(value, "%d mesec", "%d meseca", "%d meseci");
},
- year: "pre godinu dana",
+ year: "godinu dana",
years: function (value) {
return numpf(value, "%d godinu", "%d godine", "%d godina");
},
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.rw.js b/public/vendor/jquery/timeago/locales/jquery.timeago.rw.js
new file mode 100644
index 0000000000..18537672eb
--- /dev/null
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.rw.js
@@ -0,0 +1,20 @@
+// Kinyarwanda
+jQuery.timeago.settings.strings = {
+ prefixAgo: "hashize",
+ prefixFromNow: "mu",
+ suffixAgo: null,
+ suffixFromNow: null,
+ seconds: "amasegonda macye",
+ minute: "umunota",
+ minutes: "iminota %d",
+ hour: "isaha",
+ hours: "amasaha %d",
+ day: "umunsi",
+ days: "iminsi %d",
+ month: "ukwezi",
+ months: "amezi %d",
+ year: "umwaka",
+ years: "imyaka %d",
+ wordSeparator: " ",
+ numbers: []
+};
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.si.js b/public/vendor/jquery/timeago/locales/jquery.timeago.si.js
new file mode 100644
index 0000000000..468bcd7ca9
--- /dev/null
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.si.js
@@ -0,0 +1,18 @@
+// Sinhalese (SI)
+jQuery.timeago.settings.strings = {
+ prefixAgo: null,
+ prefixFromNow: null,
+ suffixAgo: "පෙර",
+ suffixFromNow: "පසුව",
+ seconds: "තත්පර කිහිපයකට",
+ minute: "මිනිත්තුවකට පමණ",
+ minutes: "මිනිත්තු %d කට",
+ hour: "පැයක් පමණ ",
+ hours: "පැය %d කට පමණ",
+ day: "දවසක ට",
+ days: "දවස් %d කට ",
+ month: "මාසයක් පමණ",
+ months: "මාස %d කට",
+ year: "වසරක් පමණ",
+ years: "වසරක් %d කට පමණ"
+};
\ No newline at end of file
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.sl.js b/public/vendor/jquery/timeago/locales/jquery.timeago.sl.js
index e546c0d502..57d4f6020c 100644
--- a/public/vendor/jquery/timeago/locales/jquery.timeago.sl.js
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.sl.js
@@ -1,42 +1,38 @@
// Slovenian with support for dual
(function () {
var numpf;
- numpf = function (n, d, m) {
- if (n == 2) {
- return d;
- } else {
- return m;
- }
+ numpf = function (n, a) {
+ return a[n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0];
};
jQuery.timeago.settings.strings = {
- prefixAgo: "pred",
+ prefixAgo: null,
prefixFromNow: "čez",
- suffixAgo: null,
+ suffixAgo: "nazaj",
suffixFromNow: null,
second: "sekundo",
seconds: function (value) {
- return numpf(value, "%d sekundama", "%d sekundami");
+ return numpf(value, ["%d sekund", "%d sekundo", "%d sekundi", "%d sekunde"]);
},
minute: "minuto",
minutes: function (value) {
- return numpf(value, "%d minutama", "%d minutami");
+ return numpf(value, ["%d minut", "%d minuto", "%d minuti", "%d minute"]);
},
- hour: "uro",
+ hour: "eno uro",
hours: function (value) {
- return numpf(value, "%d urama", "%d urami");
+ return numpf(value, ["%d ur", "%d uro", "%d uri", "%d ure"]);
},
- day: "dnevom",
+ day: "en dan",
days: function (value) {
- return numpf(value, "%d dnevi", "%d dnevi");
+ return numpf(value, ["%d dni", "%d dan", "%d dneva", "%d dni"]);
},
- month: "enim mescem",
+ month: "en mesec",
months: function (value) {
- return numpf(value, "%d mesecema", "%d meseci");
+ return numpf(value, ["%d mescov", "%d mesec", "%d mesca", "%d mesce"]);
},
- year: "enim letom",
+ year: "eno leto",
years: function (value) {
- return numpf(value, "%d letoma", "%d leti");
+ return numpf(value, ["%d let", "%d leto", "%d leti", "%d leta"]);
},
wordSeparator: " "
};
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.tr.js b/public/vendor/jquery/timeago/locales/jquery.timeago.tr.js
index f3e3a67c84..160190d026 100644
--- a/public/vendor/jquery/timeago/locales/jquery.timeago.tr.js
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.tr.js
@@ -1,5 +1,5 @@
// Turkish
-jQuery.extend($.timeago.settings.strings, {
+jQuery.timeago.settings.strings = {
suffixAgo: 'önce',
suffixFromNow: null,
seconds: '1 dakikadan',
@@ -13,4 +13,4 @@ jQuery.extend($.timeago.settings.strings, {
months: '%d ay',
year: '1 yıl',
years: '%d yıl'
-});
\ No newline at end of file
+};
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.uz.js b/public/vendor/jquery/timeago/locales/jquery.timeago.uz.js
index 380f18dc04..d6a0a7fd20 100755
--- a/public/vendor/jquery/timeago/locales/jquery.timeago.uz.js
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.uz.js
@@ -6,14 +6,14 @@ jQuery.timeago.settings.strings = {
suffixFromNow: null,
seconds: "bir necha soniya",
minute: "1 daqiqa",
- minutes: function(value) { return "%d daqiqa" },
+ minutes: function(value) { return "%d daqiqa"; },
hour: "1 soat",
- hours: function(value) { return "%d soat" },
+ hours: function(value) { return "%d soat"; },
day: "1 kun",
- days: function(value) { return "%d kun" },
+ days: function(value) { return "%d kun"; },
month: "1 oy",
- months: function(value) { return "%d oy" },
+ months: function(value) { return "%d oy"; },
year: "1 yil",
- years: function(value) { return "%d yil" },
+ years: function(value) { return "%d yil"; },
wordSeparator: " "
};
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.zh-CN.js b/public/vendor/jquery/timeago/locales/jquery.timeago.zh-CN.js
index f39417ef29..f7b979798a 100644
--- a/public/vendor/jquery/timeago/locales/jquery.timeago.zh-CN.js
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.zh-CN.js
@@ -4,17 +4,17 @@ jQuery.timeago.settings.strings = {
prefixFromNow: "从现在开始",
suffixAgo: "之前",
suffixFromNow: null,
- seconds: "不到 1 分钟",
- minute: "大约 1 分钟",
- minutes: "%d 分钟",
- hour: "大约 1 小时",
- hours: "大约 %d 小时",
- day: "1 天",
- days: "%d 天",
- month: "大约 1 个月",
- months: "%d 月",
- year: "大约 1 年",
- years: "%d 年",
+ seconds: "不到1分钟",
+ minute: "大约1分钟",
+ minutes: "%d分钟",
+ hour: "大约1小时",
+ hours: "大约%d小时",
+ day: "1天",
+ days: "%d天",
+ month: "大约1个月",
+ months: "%d月",
+ year: "大约1年",
+ years: "%d年",
numbers: [],
wordSeparator: ""
-};
\ No newline at end of file
+};
diff --git a/public/vendor/jquery/timeago/locales/jquery.timeago.zh-TW.js b/public/vendor/jquery/timeago/locales/jquery.timeago.zh-TW.js
index c6f8a1b1e6..52633900f3 100644
--- a/public/vendor/jquery/timeago/locales/jquery.timeago.zh-TW.js
+++ b/public/vendor/jquery/timeago/locales/jquery.timeago.zh-TW.js
@@ -4,17 +4,17 @@ jQuery.timeago.settings.strings = {
prefixFromNow: "從現在開始",
suffixAgo: "之前",
suffixFromNow: null,
- seconds: "不到 1 分鐘",
- minute: "大約 1 分鐘",
- minutes: "%d 分鐘",
- hour: "大約 1 小時",
- hours: "%d 小時",
- day: "大約 1 天",
- days: "%d 天",
- month: "大約 1 個月",
- months: "%d 個月",
- year: "大約 1 年",
- years: "%d 年",
+ seconds: "不到1分鐘",
+ minute: "大約1分鐘",
+ minutes: "%d分鐘",
+ hour: "大約1小時",
+ hours: "%d小時",
+ day: "大約1天",
+ days: "%d天",
+ month: "大約1個月",
+ months: "%d個月",
+ year: "大約1年",
+ years: "%d年",
numbers: [],
wordSeparator: ""
};
diff --git a/src/analytics.js b/src/analytics.js
index 49254954bc..8ac91877c3 100644
--- a/src/analytics.js
+++ b/src/analytics.js
@@ -66,4 +66,23 @@ var cronJob = require('cron').CronJob,
Analytics.getUnwrittenPageviews = function() {
return pageViews;
};
+
+ Analytics.getMonthlyPageViews = function(callback) {
+ var thisMonth = new Date();
+ var lastMonth = new Date();
+ thisMonth.setMonth(thisMonth.getMonth(), 1);
+ thisMonth.setHours(0, 0, 0, 0);
+ lastMonth.setMonth(thisMonth.getMonth() - 1, 1);
+ lastMonth.setHours(0, 0, 0, 0);
+
+ var values = [thisMonth.getTime(), lastMonth.getTime()];
+
+ db.sortedSetScores('analytics:pageviews:month', values, function(err, scores) {
+ if (err) {
+ return callback(err);
+ }
+ callback(null, {thisMonth: scores[0] || 0, lastMonth: scores[1] || 0});
+ });
+ };
+
}(exports));
\ No newline at end of file
diff --git a/src/controllers/accounts.js b/src/controllers/accounts.js
index 64a6d26786..fc49f31e1b 100644
--- a/src/controllers/accounts.js
+++ b/src/controllers/accounts.js
@@ -424,6 +424,82 @@ accountsController.accountSettings = function(req, res, next) {
{value: 'month', name: '[[user:digest_monthly]]', selected: 'month' === userData.settings.dailyDigestFreq}
];
+
+ userData.bootswatchSkinOptions = [
+ {
+ "name": "Default",
+ "value": "default"
+ },
+ {
+ "name": "Cerulean",
+ "value": "cerulean"
+ },
+ {
+ "name": "Cosmo",
+ "value": "cosmo"
+ },
+ {
+ "name": "Cyborg",
+ "value": "cyborg"
+ },
+ {
+ "name": "Darkly",
+ "value": "darkly"
+ },
+ {
+ "name": "Flatly",
+ "value": "flatly"
+ },
+ {
+ "name": "Journal",
+ "value": "journal"
+ },
+ {
+ "name": "Lumen",
+ "value": "lumen"
+ },
+ {
+ "name": "Paper",
+ "value": "paper"
+ },
+ {
+ "name": "Readable",
+ "value": "readable"
+ },
+ {
+ "name": "Sandstone",
+ "value": "sandstone"
+ },
+ {
+ "name": "Simplex",
+ "value": "simplex"
+ },
+ {
+ "name": "Slate",
+ "value": "slate"
+ },
+ {
+ "name": "Spacelab",
+ "value": "spacelab"
+ },
+ {
+ "name": "Superhero",
+ "value": "superhero"
+ },
+ {
+ "name": "United",
+ "value": "united"
+ },
+ {
+ "name": "Yeti",
+ "value": "yeti"
+ }
+ ];
+
+ userData.bootswatchSkinOptions.forEach(function(skin) {
+ skin.selected = skin.value === userData.settings.bootswatchSkin;
+ });
+
userData.userGroups.forEach(function(group) {
group.selected = group.name === userData.settings.groupTitle;
});
@@ -498,7 +574,7 @@ accountsController.getChats = function(req, res, next) {
}
async.parallel({
- contacts: async.apply(user.getFollowing, req.user.uid, 0, 19),
+ contacts: async.apply(user.getFollowing, req.user.uid, 0, 199),
recentChats: async.apply(messaging.getRecentChats, req.user.uid, 0, 19)
}, function(err, results) {
if (err) {
diff --git a/src/controllers/admin/uploads.js b/src/controllers/admin/uploads.js
index 88509df0a0..8cdf5f81f3 100644
--- a/src/controllers/admin/uploads.js
+++ b/src/controllers/admin/uploads.js
@@ -38,7 +38,7 @@ uploadsController.uploadFavicon = function(req, res, next) {
return next(err);
}
- res.json([{name: uploadedFile.name, url: nconf.get('relative_path') + image.url}]);
+ res.json([{name: uploadedFile.name, url: image.url}]);
});
}
};
diff --git a/src/controllers/api.js b/src/controllers/api.js
index c09a96490d..cc51bfce52 100644
--- a/src/controllers/api.js
+++ b/src/controllers/api.js
@@ -63,6 +63,7 @@ apiController.getConfig = function(req, res, next) {
config.postsPerPage = meta.config.postsPerPage || 20;
config.maximumFileSize = meta.config.maximumFileSize;
config['theme:id'] = meta.config['theme:id'];
+ config['theme:src'] = meta.config['theme:src'];
config.defaultLang = meta.config.defaultLang || 'en_GB';
config.userLang = req.query.lang || config.defaultLang;
config.environment = process.env.NODE_ENV;
diff --git a/src/controllers/categories.js b/src/controllers/categories.js
index fe44af90a8..bbffcf1a86 100644
--- a/src/controllers/categories.js
+++ b/src/controllers/categories.js
@@ -9,64 +9,12 @@ var categoriesController = {},
privileges = require('../privileges'),
user = require('../user'),
categories = require('../categories'),
- topics = require('../topics'),
meta = require('../meta'),
plugins = require('../plugins'),
pagination = require('../pagination'),
helpers = require('./helpers'),
utils = require('../../public/src/utils');
-categoriesController.recent = function(req, res, next) {
- var stop = (parseInt(meta.config.topicsPerList, 10) || 20) - 1;
- topics.getTopicsFromSet('topics:recent', req.uid, 0, stop, function(err, data) {
- if (err) {
- return next(err);
- }
-
- data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
- data.rssFeedUrl = nconf.get('relative_path') + '/recent.rss';
- data.breadcrumbs = helpers.buildBreadcrumbs([{text: '[[recent:title]]'}]);
- res.render('recent', data);
- });
-};
-
-var anonCache = {}, lastUpdateTime = 0;
-
-categoriesController.popular = function(req, res, next) {
- var terms = {
- daily: 'day',
- weekly: 'week',
- monthly: 'month',
- alltime: 'alltime'
- };
- var term = terms[req.params.term] || 'day';
-
- if (!req.uid) {
- if (anonCache[term] && (Date.now() - lastUpdateTime) < 60 * 60 * 1000) {
- return res.render('popular', anonCache[term]);
- }
- }
-
- topics.getPopular(term, req.uid, meta.config.topicsPerList, function(err, topics) {
- if (err) {
- return next(err);
- }
-
- var data = {
- topics: topics,
- 'feeds:disableRSS': parseInt(meta.config['feeds:disableRSS'], 10) === 1,
- rssFeedUrl: nconf.get('relative_path') + '/popular/' + (req.params.term || 'daily') + '.rss',
- breadcrumbs: helpers.buildBreadcrumbs([{text: '[[global:header.popular]]'}])
- };
-
- if (!req.uid) {
- anonCache[term] = data;
- lastUpdateTime = Date.now();
- }
-
- res.render('popular', data);
- });
-};
categoriesController.list = function(req, res, next) {
async.parallel({
diff --git a/src/controllers/index.js b/src/controllers/index.js
index f08d696765..70854a548a 100644
--- a/src/controllers/index.js
+++ b/src/controllers/index.js
@@ -19,6 +19,8 @@ var Controllers = {
topics: require('./topics'),
categories: require('./categories'),
unread: require('./unread'),
+ recent: require('./recent'),
+ popular: require('./popular'),
tags: require('./tags'),
search: require('./search'),
users: require('./users'),
diff --git a/src/controllers/popular.js b/src/controllers/popular.js
new file mode 100644
index 0000000000..6f1b025bc1
--- /dev/null
+++ b/src/controllers/popular.js
@@ -0,0 +1,51 @@
+
+'use strict';
+
+var nconf = require('nconf'),
+ topics = require('../topics'),
+ meta = require('../meta'),
+ helpers = require('./helpers');
+
+var popularController = {};
+
+var anonCache = {}, lastUpdateTime = 0;
+
+var terms = {
+ daily: 'day',
+ weekly: 'week',
+ monthly: 'month',
+ alltime: 'alltime'
+};
+
+popularController.get = function(req, res, next) {
+
+ var term = terms[req.params.term] || 'day';
+
+ if (!req.uid) {
+ if (anonCache[term] && (Date.now() - lastUpdateTime) < 60 * 60 * 1000) {
+ return res.render('popular', anonCache[term]);
+ }
+ }
+
+ topics.getPopular(term, req.uid, meta.config.topicsPerList, function(err, topics) {
+ if (err) {
+ return next(err);
+ }
+
+ var data = {
+ topics: topics,
+ 'feeds:disableRSS': parseInt(meta.config['feeds:disableRSS'], 10) === 1,
+ rssFeedUrl: nconf.get('relative_path') + '/popular/' + (req.params.term || 'daily') + '.rss',
+ breadcrumbs: helpers.buildBreadcrumbs([{text: '[[global:header.popular]]'}])
+ };
+
+ if (!req.uid) {
+ anonCache[term] = data;
+ lastUpdateTime = Date.now();
+ }
+
+ res.render('popular', data);
+ });
+};
+
+module.exports = popularController;
\ No newline at end of file
diff --git a/src/controllers/recent.js b/src/controllers/recent.js
new file mode 100644
index 0000000000..99b65bab88
--- /dev/null
+++ b/src/controllers/recent.js
@@ -0,0 +1,34 @@
+
+'use strict';
+
+var nconf = require('nconf'),
+ topics = require('../topics'),
+ meta = require('../meta'),
+ helpers = require('./helpers'),
+ plugins = require('../plugins');
+
+var recentController = {};
+
+recentController.get = function(req, res, next) {
+
+ var stop = (parseInt(meta.config.topicsPerList, 10) || 20) - 1;
+
+ topics.getTopicsFromSet('topics:recent', req.uid, 0, stop, function(err, data) {
+ if (err) {
+ return next(err);
+ }
+
+ data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
+ data.rssFeedUrl = nconf.get('relative_path') + '/recent.rss';
+ data.breadcrumbs = helpers.buildBreadcrumbs([{text: '[[recent:title]]'}]);
+
+ plugins.fireHook('filter:recent.build', {req: req, res: res, templateData: data}, function(err, data) {
+ if (err) {
+ return next(err);
+ }
+ res.render('recent', data.templateData);
+ });
+ });
+};
+
+module.exports = recentController;
\ No newline at end of file
diff --git a/src/controllers/unread.js b/src/controllers/unread.js
index 672ec37542..edf663ce8d 100644
--- a/src/controllers/unread.js
+++ b/src/controllers/unread.js
@@ -5,14 +5,14 @@ var async = require('async'),
meta = require('../meta'),
categories = require('../categories'),
+ privileges = require('../privileges'),
user = require('../user'),
topics = require('../topics'),
helpers = require('./helpers');
var unreadController = {};
-
-unreadController.unread = function(req, res, next) {
+unreadController.get = function(req, res, next) {
var stop = (parseInt(meta.config.topicsPerList, 10) || 20) - 1;
var results;
var cid = req.query.cid;
@@ -30,11 +30,15 @@ unreadController.unread = function(req, res, next) {
},
function(_results, next) {
results = _results;
- categories.getMultipleCategoryFields(results.watchedCategories, ['cid', 'name', 'slug', 'icon', 'link', 'disabled'], next);
+
+ privileges.categories.filterCids('read', results.watchedCategories, req.uid, next);
+ },
+ function(cids, next) {
+ categories.getMultipleCategoryFields(cids, ['cid', 'name', 'slug', 'icon', 'link'], next);
},
function(categories, next) {
categories = categories.filter(function(category) {
- return category && !category.link && !category.disabled;
+ return category && !category.link;
});
categories.forEach(function(category) {
category.selected = parseInt(category.cid, 10) === parseInt(cid, 10);
diff --git a/src/controllers/users.js b/src/controllers/users.js
index d416207e29..b2b9c32233 100644
--- a/src/controllers/users.js
+++ b/src/controllers/users.js
@@ -40,7 +40,7 @@ usersController.getOnlineUsers = function(req, res, next) {
}
var userData = {
- 'users:online': true,
+ 'route_users:online': true,
search_display: 'hidden',
loadmore_display: results.count > 50 ? 'block' : 'hide',
users: results.users,
@@ -75,7 +75,7 @@ usersController.getUsers = function(set, start, stop, req, res, next) {
users: data.users,
pagination: pagination.create(1, pageCount)
};
- userData[set] = true;
+ userData['route_' + set] = true;
render(req, res, userData, next);
});
};
diff --git a/src/database/mongo.js b/src/database/mongo.js
index 389a85bf7d..49f2da5edd 100644
--- a/src/database/mongo.js
+++ b/src/database/mongo.js
@@ -177,16 +177,25 @@
};
module.info = function(db, callback) {
- db.stats({scale:1024}, function(err, stats) {
- if(err) {
+ async.parallel({
+ serverStats: function(next) {
+ db.command({'serverStatus': 1}, next);
+ },
+ stats: function(next) {
+ db.stats({scale:1024}, next);
+ }
+ }, function(err, results) {
+ if (err) {
return callback(err);
}
+ var stats = results.stats;
stats.avgObjSize = (stats.avgObjSize / 1024).toFixed(2);
stats.dataSize = (stats.dataSize / 1024).toFixed(2);
stats.storageSize = (stats.storageSize / 1024).toFixed(2);
stats.fileSize = (stats.fileSize / 1024).toFixed(2);
stats.indexSize = (stats.indexSize / 1024).toFixed(2);
+ stats.mem = results.serverStats.mem;
stats.raw = JSON.stringify(stats, null, 4);
stats.mongo = true;
diff --git a/src/database/mongo/main.js b/src/database/mongo/main.js
index 9d8fae3b65..045bc2c5bc 100644
--- a/src/database/mongo/main.js
+++ b/src/database/mongo/main.js
@@ -36,17 +36,19 @@ module.exports = function(db, module) {
}
if (Array.isArray(data.cid) && data.cid.length) {
- if (data.cid.length > 1) {
+ data.cid = data.cid.filter(Boolean);
+ if (data.cid.length > 1) {
searchQuery.cid = {$in: data.cid.map(String)};
- } else {
+ } else if (data.cid[0]) {
searchQuery.cid = data.cid[0].toString();
}
}
if (Array.isArray(data.uid) && data.uid.length) {
+ data.uid = data.uid.filter(Boolean);
if (data.uid.length > 1) {
searchQuery.uid = {$in: data.uid.map(String)};
- } else {
+ } else if (data.uid[0]) {
searchQuery.uid = data.uid[0].toString();
}
}
diff --git a/src/groups.js b/src/groups.js
index 4a0d2a563e..f0f7efe066 100644
--- a/src/groups.js
+++ b/src/groups.js
@@ -105,8 +105,8 @@ var async = require('async'),
}
};
- Groups.getGroups = function(start, stop, callback) {
- db.getSortedSetRevRange('groups:createtime', start, stop, callback);
+ Groups.getGroups = function(set, start, stop, callback) {
+ db.getSortedSetRevRange(set, start, stop, callback);
};
Groups.get = function(groupName, options, callback) {
diff --git a/src/install.js b/src/install.js
index 48e42d5295..caafcbfacc 100644
--- a/src/install.js
+++ b/src/install.js
@@ -478,15 +478,31 @@ function enableDefaultPlugins(next) {
process.stdout.write('Enabling default plugins\n');
var defaultEnabled = [
- 'nodebb-plugin-composer-default',
- 'nodebb-plugin-markdown',
- 'nodebb-plugin-mentions',
- 'nodebb-widget-essentials',
- 'nodebb-rewards-essentials',
- 'nodebb-plugin-soundpack-default',
- 'nodebb-plugin-emoji-extended'
- ];
- var db = require('./database');
+ 'nodebb-plugin-composer-default',
+ 'nodebb-plugin-markdown',
+ 'nodebb-plugin-mentions',
+ 'nodebb-widget-essentials',
+ 'nodebb-rewards-essentials',
+ 'nodebb-plugin-soundpack-default',
+ 'nodebb-plugin-emoji-extended'
+ ],
+ customDefaults = nconf.get('defaultPlugins');
+
+ if (customDefaults && customDefaults.length) {
+ try {
+ customDefaults = JSON.parse(customDefaults);
+ defaultEnabled = defaultEnabled.concat(customDefaults);
+ } catch (e) {
+ // Invalid value received
+ winston.warn('[install/enableDefaultPlugins] Invalid defaultPlugins value received. Ignoring.');
+ }
+ }
+
+ defaultEnabled = defaultEnabled.filter(function(plugin, index, array) {
+ return array.indexOf(plugin) === index;
+ });
+
+ var db = require('./database');
var order = defaultEnabled.map(function(plugin, index) {
return index;
});
diff --git a/src/messaging.js b/src/messaging.js
index 9edd99d836..bb1ce85182 100644
--- a/src/messaging.js
+++ b/src/messaging.js
@@ -164,6 +164,9 @@ var db = require('./database'),
if (index > 0 && parseInt(message.timestamp, 10) > parseInt(messages[index-1].timestamp, 10) + (1000*60*5)) {
// If it's been 5 minutes, this is a new set of messages
message.newSet = true;
+ } else if (index > 0 && message.fromuid !== messages[index-1].fromuid) {
+ // If the previous message was from the other person, this is also a new set
+ message.newSet = true
}
return message;
diff --git a/src/meta/css.js b/src/meta/css.js
index 50369f89fb..c379d410a8 100644
--- a/src/meta/css.js
+++ b/src/meta/css.js
@@ -69,6 +69,7 @@ module.exports = function(Meta) {
source += '\n@import (inline) "..' + path.sep + '..' + path.sep + 'public/vendor/jquery/textcomplete/jquery.textcomplete.css";';
source += '\n@import (inline) "..' + path.sep + '..' + path.sep + 'public/vendor/colorpicker/colorpicker.css";';
source += '\n@import "..' + path.sep + '..' + path.sep + 'public/less/generics.less";';
+ source += '\n@import "..' + path.sep + '..' + path.sep + 'public/less/mixins.less";';
var acpSource = '\n@import "..' + path.sep + 'public/less/admin/admin";\n' + source;
acpSource += '\n@import "..' + path.sep + 'public/less/generics.less";';
diff --git a/src/meta/dependencies.js b/src/meta/dependencies.js
index 3bc9719890..62f978aaaa 100644
--- a/src/meta/dependencies.js
+++ b/src/meta/dependencies.js
@@ -19,6 +19,12 @@ module.exports = function(Meta) {
fs.readFile(path.join(__dirname, '../../node_modules/', module, 'package.json'), {
encoding: 'utf-8'
}, function(err, pkgData) {
+ // If a bundled plugin/theme is not present, skip the dep check (#3384)
+ if (err && err.code === 'ENOENT' && (module.startsWith('nodebb-plugin') || module.startsWith('nodebb-theme'))) {
+ winston.warn('[meta/dependencies] Bundled plugin ' + module + ' not found, skipping dependency check.');
+ return next(true);
+ }
+
try {
var pkgData = JSON.parse(pkgData),
ok = semver.satisfies(pkgData.version, pkg.dependencies[module]);
@@ -30,7 +36,7 @@ module.exports = function(Meta) {
next(false);
}
} catch(e) {
- winston.error('[meta.dependencies] Could not read: ' + module);
+ winston.error('[meta/dependencies] Could not read: ' + module);
process.exit();
}
})
diff --git a/src/meta/js.js b/src/meta/js.js
index 020611e7ce..a022497136 100644
--- a/src/meta/js.js
+++ b/src/meta/js.js
@@ -23,7 +23,7 @@ module.exports = function(Meta) {
base: [
'public/vendor/jquery/js/jquery.js',
'./node_modules/socket.io-client/socket.io.js',
- 'public/vendor/jquery/timeago/jquery.timeago.min.js',
+ 'public/vendor/jquery/timeago/jquery.timeago.js',
'public/vendor/jquery/js/jquery.form.min.js',
'public/vendor/visibility/visibility.min.js',
'public/vendor/bootstrap/js/bootstrap.min.js',
diff --git a/src/middleware/index.js b/src/middleware/index.js
index c7358ca5ac..fedb8b55cb 100644
--- a/src/middleware/index.js
+++ b/src/middleware/index.js
@@ -22,7 +22,7 @@ var middleware = {};
function setupFavicon(app) {
var faviconPath = path.join(__dirname, '../../', 'public', meta.config['brand:favicon'] ? meta.config['brand:favicon'] : 'favicon.ico');
if (fs.existsSync(faviconPath)) {
- app.use(favicon(faviconPath));
+ app.use(nconf.get('relative_path'), favicon(faviconPath));
}
}
@@ -57,6 +57,10 @@ module.exports = function(app) {
cookie.domain = meta.config.cookieDomain;
}
+ if (nconf.get('secure')) {
+ cookie.secure = true;
+ }
+
app.use(session({
store: db.sessionStore,
secret: nconf.get('secret'),
diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js
index 093362c433..6bab0678e6 100644
--- a/src/middleware/middleware.js
+++ b/src/middleware/middleware.js
@@ -211,6 +211,7 @@ middleware.renderHeader = function(req, res, callback) {
'cache-buster': meta.config['cache-buster'] ? 'v=' + meta.config['cache-buster'] : '',
'brand:logo': meta.config['brand:logo'] || '',
'brand:logo:url': meta.config['brand:logo:url'] || '',
+ 'brand:logo:alt': meta.config['brand:logo:alt'] || '',
'brand:logo:display': meta.config['brand:logo']?'':'hide',
allowRegistration: registrationType === 'normal' || registrationType === 'admin-approval',
searchEnabled: plugins.hasListeners('filter:search.query')
@@ -242,6 +243,11 @@ middleware.renderHeader = function(req, res, callback) {
if (err) {
return next(err);
}
+
+ if (settings.bootswatchSkin && settings.bootswatchSkin !== 'default') {
+ templateValues.bootswatchCSS = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + settings.bootswatchSkin + '/bootstrap.min.css';
+ }
+
meta.title.build(req.url.slice(1), settings.userLang, next);
});
} else {
@@ -291,6 +297,7 @@ middleware.renderHeader = function(req, res, callback) {
templateValues.customCSS = results.customCSS;
templateValues.customJS = results.customJS;
templateValues.maintenanceHeader = parseInt(meta.config.maintenanceMode, 10) === 1 && !results.isAdmin;
+ templateValues.defaultLang = res.locals.config.defaultLang;
templateValues.template = {name: res.locals.template};
templateValues.template[res.locals.template] = true;
@@ -407,7 +414,8 @@ middleware.maintenanceMode = function(req, res, next) {
'/templates/[\\w/]+.tpl',
'/api/login',
'/api/?',
- '/language/.+'
+ '/language/.+',
+ '/uploads/system/site-logo.png'
],
render = function() {
res.status(503);
diff --git a/src/plugins.js b/src/plugins.js
index 138948374c..d2a726096b 100644
--- a/src/plugins.js
+++ b/src/plugins.js
@@ -170,9 +170,13 @@ var fs = require('fs'),
require('request')(url, {
json: true
}, function(err, res, body) {
+ if (res.statusCode === 404 || !body.payload) {
+ return callback(err, {});
+ }
+
Plugins.normalise([body.payload], function(err, normalised) {
normalised = normalised.filter(function(plugin) {
- return plugin.id = id;
+ return plugin.id === id;
});
return callback(err, !err ? normalised[0] : undefined);
});
@@ -212,6 +216,10 @@ var fs = require('fs'),
return callback(err);
}
+ installedPlugins = installedPlugins.filter(function(plugin) {
+ return plugin && !plugin.system;
+ });
+
async.each(installedPlugins, function(plugin, next) {
// If it errored out because a package.json or plugin.json couldn't be read, no need to do this stuff
if (plugin.error) {
diff --git a/src/privileges/categories.js b/src/privileges/categories.js
index 4572de2e92..feb3c53bc6 100644
--- a/src/privileges/categories.js
+++ b/src/privileges/categories.js
@@ -92,7 +92,7 @@ module.exports = function(privileges) {
}), next);
},
function(memberSets, next) {
- groups.getGroups(0, -1, function(err, groupNames) {
+ groups.getGroups('groups:createtime', 0, -1, function(err, groupNames) {
if (err) {
return next(err);
}
diff --git a/src/routes/index.js b/src/routes/index.js
index 4935edc372..a70bcec510 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -47,9 +47,9 @@ function tagRoutes(app, middleware, controllers) {
function categoryRoutes(app, middleware, controllers) {
setupPageRoute(app, '/categories', middleware, [], controllers.categories.list);
- setupPageRoute(app, '/popular/:term?', middleware, [], controllers.categories.popular);
- setupPageRoute(app, '/recent', middleware, [], controllers.categories.recent);
- setupPageRoute(app, '/unread', middleware, [middleware.authenticate], controllers.unread.unread);
+ setupPageRoute(app, '/popular/:term?', middleware, [], controllers.popular.get);
+ setupPageRoute(app, '/recent', middleware, [], controllers.recent.get);
+ setupPageRoute(app, '/unread', middleware, [middleware.authenticate], controllers.unread.get);
setupPageRoute(app, '/category/:category_id/:slug/:topic_index', middleware, [], controllers.categories.get);
setupPageRoute(app, '/category/:category_id/:slug?', middleware, [], controllers.categories.get);
diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js
index a9d75b3f89..00fea0c730 100644
--- a/src/socket.io/admin.js
+++ b/src/socket.io/admin.js
@@ -233,7 +233,7 @@ SocketAdmin.analytics.get = function(socket, data, callback) {
getHourlyStatsForSet('analytics:pageviews', data.amount, next);
},
monthlyPageViews: function(next) {
- getMonthlyPageViews(next);
+ analytics.getMonthlyPageViews(next);
}
}, function(err, data) {
data.pastDay = data.pageviews.reduce(function(a, b) {return parseInt(a, 10) + parseInt(b, 10);});
@@ -286,24 +286,6 @@ function getHourlyStatsForSet(set, hours, callback) {
});
}
-function getMonthlyPageViews(callback) {
- var thisMonth = new Date();
- var lastMonth = new Date();
- thisMonth.setMonth(thisMonth.getMonth(), 1);
- thisMonth.setHours(0, 0, 0, 0);
- lastMonth.setMonth(thisMonth.getMonth() - 1, 1);
- lastMonth.setHours(0, 0, 0, 0);
-
- var values = [thisMonth.getTime(), lastMonth.getTime()];
-
- db.sortedSetScores('analytics:pageviews:month', values, function(err, scores) {
- if (err) {
- return callback(err);
- }
- callback(null, {thisMonth: scores[0] || 0, lastMonth: scores[1] || 0});
- });
-}
-
SocketAdmin.getMoreEvents = function(socket, next, callback) {
var start = parseInt(next, 10);
if (start < 0) {
diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js
index 6f2d364b0a..7ee8d59b0b 100644
--- a/src/socket.io/groups.js
+++ b/src/socket.io/groups.js
@@ -43,6 +43,10 @@ SocketGroups.leave = function(socket, data, callback) {
return callback(new Error('[[error:invalid-uid]]'));
}
+ if (data.groupName === 'administrators') {
+ return callback(new Error('[[error:cant-remove-self-as-admin]]'));
+ }
+
groups.leave(data.groupName, socket.uid, callback);
};
@@ -131,7 +135,7 @@ function acceptRejectAll(type, socket, data, callback) {
}
], callback);
});
-};
+}
SocketGroups.issueInvite = function(socket, data, callback) {
if (!data) {
diff --git a/src/socket.io/rooms.js b/src/socket.io/rooms.js
index c32f9a5dc9..615d650cd6 100644
--- a/src/socket.io/rooms.js
+++ b/src/socket.io/rooms.js
@@ -39,19 +39,29 @@ rooms.broadcast = function(socket, room, msg, data, callback) {
callback = callback || function() {};
+ // Filter out socketIds that aren't actually connected
+ socketIds = socketIds.filter(function(id) {
+ return io.server.sockets.connected.hasOwnProperty(id);
+ });
+
async.map(socketIds, function(id, next) {
- var timeout;
+ var timeout,
+ timeoutPassed = false;
+
if (socket.id === id) {
return setImmediate(next, null, []);
}
timeout = setTimeout(function() {
+ timeoutPassed = true;
next(null, []);
}, 500);
- io.server.sockets.connected[id].emit(msg, data || {}, function(chats) {
+ io.server.sockets.connected[id].emit(msg, data || {}, function(err, returnData) {
clearTimeout(timeout);
- next(null, chats);
+ if (!timeoutPassed) {
+ next(null, returnData);
+ }
});
}, callback);
};
diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js
index 6e9cafcf63..dcafbeced8 100644
--- a/src/socket.io/topics.js
+++ b/src/socket.io/topics.js
@@ -96,8 +96,12 @@ SocketTopics.postcount = function(socket, tid, callback) {
topics.getTopicField(tid, 'postcount', callback);
};
+SocketTopics.bookmark = function(socket, payload, callback) {
+ topics.setUserBookmark(payload.tid, socket.uid, payload.index, callback);
+};
+
SocketTopics.markAsRead = function(socket, tids, callback) {
- if(!Array.isArray(tids) || !socket.uid) {
+ if (!Array.isArray(tids) || !socket.uid) {
return callback(new Error('[[error:invalid-data]]'));
}
@@ -123,14 +127,14 @@ SocketTopics.markAsRead = function(socket, tids, callback) {
};
SocketTopics.markTopicNotificationsRead = function(socket, tid, callback) {
- if(!tid || !socket.uid) {
+ if (!tid || !socket.uid) {
return callback(new Error('[[error:invalid-data]]'));
}
topics.markTopicNotificationsRead(tid, socket.uid);
};
SocketTopics.markAllRead = function(socket, data, callback) {
- topics.getLatestTidsFromSet('topics:recent', 0, -1, 'day', function(err, tids) {
+ db.getSortedSetRevRangeByScore('topics:recent', 0, -1, '+inf', Date.now() - topics.unreadCutoff, function(err, tids) {
if (err) {
return callback(err);
}
diff --git a/src/topics.js b/src/topics.js
index ae1222a904..fd9e19ca24 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -97,7 +97,7 @@ var async = require('async'),
user.getSettings(uid, next);
}
}, function(err, results) {
- if(err) {
+ if (err) {
return callback(err);
}
callback(null, Math.ceil((results.index + 1) / results.settings.topicsPerPage));
@@ -107,7 +107,7 @@ var async = require('async'),
Topics.getCategoryData = function(tid, callback) {
Topics.getTopicField(tid, 'cid', function(err, cid) {
if (err) {
- callback(err);
+ return callback(err);
}
categories.getCategoryData(cid, callback);
@@ -144,43 +144,44 @@ var async = require('async'),
return callback(null, []);
}
- Topics.getTopicsData(tids, function(err, topics) {
- function mapFilter(array, field) {
- return array.map(function(topic) {
- return topic && topic[field] && topic[field].toString();
- }).filter(function(value, index, array) {
- return utils.isNumber(value) && array.indexOf(value) === index;
- });
- }
+ var uids, cids, topics;
- if (err) {
- return callback(err);
- }
-
- var uids = mapFilter(topics, 'uid');
- var cids = mapFilter(topics, 'cid');
-
- async.parallel({
- teasers: function(next) {
- Topics.getTeasers(topics, next);
- },
- users: function(next) {
- user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture'], next);
- },
- categories: function(next) {
- categories.getMultipleCategoryFields(cids, ['cid', 'name', 'slug', 'icon', 'bgColor', 'color', 'disabled'], next);
- },
- hasRead: function(next) {
- Topics.hasReadTopics(tids, uid, next);
- },
- tags: function(next) {
- Topics.getTopicsTagsObjects(tids, next);
- }
- }, function(err, results) {
- if (err) {
- return callback(err);
+ async.waterfall([
+ function(next) {
+ Topics.getTopicsData(tids, next);
+ },
+ function(_topics, next) {
+ function mapFilter(array, field) {
+ return array.map(function(topic) {
+ return topic && topic[field] && topic[field].toString();
+ }).filter(function(value, index, array) {
+ return utils.isNumber(value) && array.indexOf(value) === index;
+ });
}
+ topics = _topics;
+ uids = mapFilter(topics, 'uid');
+ cids = mapFilter(topics, 'cid');
+
+ async.parallel({
+ users: function(next) {
+ user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture'], next);
+ },
+ categories: function(next) {
+ categories.getMultipleCategoryFields(cids, ['cid', 'name', 'slug', 'icon', 'bgColor', 'color', 'disabled'], next);
+ },
+ hasRead: function(next) {
+ Topics.hasReadTopics(tids, uid, next);
+ },
+ teasers: function(next) {
+ Topics.getTeasers(topics, next);
+ },
+ tags: function(next) {
+ Topics.getTopicsTagsObjects(tids, next);
+ }
+ }, next);
+ },
+ function(results, next) {
var users = _.object(uids, results.users);
var categories = _.object(cids, results.categories);
@@ -196,7 +197,7 @@ var async = require('async'),
topics[i].locked = parseInt(topics[i].locked, 10) === 1;
topics[i].deleted = parseInt(topics[i].deleted, 10) === 1;
topics[i].unread = !results.hasRead[i];
- topics[i].unreplied = parseInt(topics[i].postcount, 10) <= 1 && meta.config.teaserPost !== 'first';
+ topics[i].unreplied = !topics[i].teaser;
}
}
@@ -204,46 +205,54 @@ var async = require('async'),
return topic && topic.category && !topic.category.disabled;
});
- plugins.fireHook('filter:topics.get', {topics: topics, uid: uid}, function(err, topicData) {
- callback(err, topicData.topics);
- });
- });
- });
+ plugins.fireHook('filter:topics.get', {topics: topics, uid: uid}, next);
+ },
+ function(data, next) {
+ next(null, data.topics);
+ }
+ ], callback);
};
Topics.getTopicWithPosts = function(tid, set, uid, start, stop, reverse, callback) {
- Topics.getTopicData(tid, function(err, topicData) {
- if (err || !topicData) {
- return callback(err || new Error('[[error:no-topic]]'));
- }
-
- async.parallel({
- posts: async.apply(getMainPostAndReplies, topicData, set, uid, start, stop, reverse),
- category: async.apply(Topics.getCategoryData, tid),
- threadTools: async.apply(plugins.fireHook, 'filter:topic.thread_tools', {topic: topicData, uid: uid, tools: []}),
- tags: async.apply(Topics.getTopicTagsObjects, tid),
- isFollowing: async.apply(Topics.isFollowing, [tid], uid)
- }, function(err, results) {
- if (err) {
- return callback(err);
+ var topicData;
+ async.waterfall([
+ function(next) {
+ Topics.getTopicData(tid, next);
+ },
+ function(_topicData, next) {
+ topicData = _topicData;
+ if (!topicData) {
+ return callback(new Error('[[error:no-topic]]'));
}
+ async.parallel({
+ posts: async.apply(getMainPostAndReplies, topicData, set, uid, start, stop, reverse),
+ category: async.apply(Topics.getCategoryData, tid),
+ threadTools: async.apply(plugins.fireHook, 'filter:topic.thread_tools', {topic: topicData, uid: uid, tools: []}),
+ tags: async.apply(Topics.getTopicTagsObjects, tid),
+ isFollowing: async.apply(Topics.isFollowing, [tid], uid),
+ bookmark: async.apply(Topics.getUserBookmark, tid, uid)
+ }, next);
+ },
+ function(results, next) {
topicData.posts = results.posts;
topicData.category = results.category;
topicData.thread_tools = results.threadTools.tools;
topicData.tags = results.tags;
topicData.isFollowing = results.isFollowing[0];
+ topicData.bookmark = results.bookmark;
topicData.unreplied = parseInt(topicData.postcount, 10) === 1;
topicData.deleted = parseInt(topicData.deleted, 10) === 1;
topicData.locked = parseInt(topicData.locked, 10) === 1;
topicData.pinned = parseInt(topicData.pinned, 10) === 1;
- plugins.fireHook('filter:topic.get', {topic: topicData, uid: uid}, function(err, data) {
- callback(err, data ? data.topic : null);
- });
- });
- });
+ plugins.fireHook('filter:topic.get', {topic: topicData, uid: uid}, next);
+ },
+ function(data, next) {
+ next(null, data.topic);
+ }
+ ], callback);
};
function getMainPostAndReplies(topic, set, uid, start, stop, reverse, callback) {
@@ -328,6 +337,14 @@ var async = require('async'),
});
}
+ Topics.getUserBookmark = function (tid, uid, callback) {
+ db.sortedSetScore('tid:' + tid + ':bookmarks', uid, callback);
+ };
+
+ Topics.setUserBookmark = function(tid, uid, index, callback) {
+ db.sortedSetAdd('tid:' + tid + ':bookmarks', index, uid, callback);
+ };
+
Topics.getTopicField = function(tid, field, callback) {
db.getObjectField('topic:' + tid, field, callback);
};
diff --git a/src/topics/delete.js b/src/topics/delete.js
index 47cb384214..30c926e1e5 100644
--- a/src/topics/delete.js
+++ b/src/topics/delete.js
@@ -86,7 +86,8 @@ module.exports = function(Topics) {
db.deleteAll([
'tid:' + tid + ':followers',
'tid:' + tid + ':posts',
- 'tid:' + tid + ':posts:votes'
+ 'tid:' + tid + ':posts:votes',
+ 'tid:' + tid + ':bookmarks'
], next);
},
function(next) {
diff --git a/src/topics/fork.js b/src/topics/fork.js
index 3302797f38..34eb758300 100644
--- a/src/topics/fork.js
+++ b/src/topics/fork.js
@@ -44,7 +44,7 @@ module.exports = function(Topics) {
if (err) {
return callback(err);
}
-
+
Topics.create({uid: results.postData.uid, title: title, cid: results.cid}, function(err, tid) {
if (err) {
return callback(err);
@@ -94,7 +94,7 @@ module.exports = function(Topics) {
}
if (parseInt(post.tid, 10) === parseInt(tid, 10)) {
- return next(new Error('[[error:cant-move-to-same-topic]]'))
+ return next(new Error('[[error:cant-move-to-same-topic]]'));
}
postData = post;
@@ -117,6 +117,12 @@ module.exports = function(Topics) {
Topics.addPostToTopic(tid, pid, postData.timestamp, postData.votes, next);
}
], next);
+ },
+ function(results, next) {
+ async.parallel([
+ async.apply(updateRecentTopic, tid),
+ async.apply(updateRecentTopic, postData.tid)
+ ], next);
}
], function(err) {
if (err) {
@@ -126,4 +132,23 @@ module.exports = function(Topics) {
callback();
});
};
+
+ function updateRecentTopic(tid, callback) {
+ async.waterfall([
+ function(next) {
+ Topics.getLatestUndeletedPid(tid, next);
+ },
+ function(pid, next) {
+ if (!pid) {
+ return callback();
+ }
+ posts.getPostField(pid, 'timestamp', next);
+ },
+ function(timestamp, next) {
+ Topics.updateTimestamp(tid, timestamp, next);
+ }
+ ], callback);
+ }
+
+
};
diff --git a/src/topics/teaser.js b/src/topics/teaser.js
index 2fa6baf8b4..1086a4d0e4 100644
--- a/src/topics/teaser.js
+++ b/src/topics/teaser.js
@@ -22,39 +22,36 @@ module.exports = function(Topics) {
var counts = [];
var teaserPids = [];
+ var postData;
+ var tidToPost = {};
topics.forEach(function(topic) {
counts.push(topic && (parseInt(topic.postcount, 10) || 0));
if (topic) {
- if (meta.config.teaserPost === 'first') {
- teaserPids.push(topic.mainPid);
- } else {
- teaserPids.push(topic.teaserPid);
- }
+ teaserPids.push(meta.config.teaserPost === 'first' ? topic.mainPid : topic.teaserPid);
}
});
- posts.getPostsFields(teaserPids, ['pid', 'uid', 'timestamp', 'tid', 'content'], function(err, postData) {
- if (err) {
- return callback(err);
- }
-
- var uids = postData.map(function(post) {
- return post.uid;
- }).filter(function(uid, index, array) {
- return array.indexOf(uid) === index;
- });
-
- user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture'], function(err, usersData) {
- if (err) {
- return callback(err);
- }
+ async.waterfall([
+ function(next) {
+ posts.getPostsFields(teaserPids, ['pid', 'uid', 'timestamp', 'tid', 'content'], next);
+ },
+ function(_postData, next) {
+ postData = _postData;
+ var uids = postData.map(function(post) {
+ return post.uid;
+ }).filter(function(uid, index, array) {
+ return array.indexOf(uid) === index;
+ });
+ user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture'], next);
+ },
+ function(usersData, next) {
var users = {};
usersData.forEach(function(user) {
users[user.uid] = user;
});
- var tidToPost = {};
+
async.each(postData, function(post, next) {
// If the post author isn't represented in the retrieved users' data, then it means they were deleted, assume guest.
@@ -66,30 +63,29 @@ module.exports = function(Topics) {
post.timestamp = utils.toISOString(post.timestamp);
tidToPost[post.tid] = post;
posts.parsePost(post, next);
- }, function(err) {
- if (err) {
- return callback(err);
+ }, next);
+ },
+ function(next) {
+ var teasers = topics.map(function(topic, index) {
+ if (!topic) {
+ return null;
}
- var teasers = topics.map(function(topic, index) {
- if (!topic) {
- return null;
+ if (tidToPost[topic.tid]) {
+ tidToPost[topic.tid].index = meta.config.teaserPost === 'first' ? 1 : counts[index];
+ if (tidToPost[topic.tid].content) {
+ var s = S(tidToPost[topic.tid].content);
+ tidToPost[topic.tid].content = s.stripTags.apply(s, utils.stripTags).s;
}
- if (tidToPost[topic.tid]) {
- tidToPost[topic.tid].index = meta.config.teaserPost === 'first' ? 1 : counts[index];
- if (tidToPost[topic.tid].content) {
- var s = S(tidToPost[topic.tid].content);
- tidToPost[topic.tid].content = s.stripTags.apply(s, utils.stripTags).s;
- }
- }
- return tidToPost[topic.tid];
- });
-
- plugins.fireHook('filter:teasers.get', {teasers: teasers}, function(err, data) {
- callback(err, data ? data.teasers : null);
- });
+ }
+ return tidToPost[topic.tid];
});
- });
- });
+
+ plugins.fireHook('filter:teasers.get', {teasers: teasers}, next);
+ },
+ function(data, next) {
+ next(null, data.teasers);
+ }
+ ], callback);
};
Topics.getTeasersByTids = function(tids, callback) {
diff --git a/src/topics/unread.js b/src/topics/unread.js
index b0252d02ce..7abaf5ab4c 100644
--- a/src/topics/unread.js
+++ b/src/topics/unread.js
@@ -14,6 +14,8 @@ module.exports = function(Topics) {
var unreadCutoff = 86400000 * 2;
+ Topics.unreadCutoff = unreadCutoff;
+
Topics.getTotalUnread = function(uid, callback) {
Topics.getUnreadTids(0, uid, 0, 20, function(err, tids) {
callback(err, tids ? tids.length : 0);
diff --git a/src/topics/user.js b/src/topics/user.js
index 64651c2fd1..4db380efdf 100644
--- a/src/topics/user.js
+++ b/src/topics/user.js
@@ -2,7 +2,8 @@
'use strict';
-var db = require('../database'),
+var async = require('async'),
+ db = require('../database'),
posts = require('../posts');
@@ -19,24 +20,22 @@ module.exports = function(Topics) {
};
Topics.getUids = function(tid, callback) {
- Topics.getPids(tid, function(err, pids) {
- if (err) {
- return callback(err);
- }
-
- posts.getPostsFields(pids, ['uid'], function(err, postData) {
- if (err) {
- return callback(err);
- }
-
+ async.waterfall([
+ function(next) {
+ Topics.getPids(tid, next);
+ },
+ function(pids, next) {
+ posts.getPostsFields(pids, ['uid'], next);
+ },
+ function(postData, next) {
var uids = postData.map(function(post) {
return post && post.uid;
}).filter(function(uid, index, array) {
- return array.indexOf(uid) === index;
+ return uid && array.indexOf(uid) === index;
});
- callback(null, uids);
- });
- });
+ next(null, uids);
+ }
+ ], callback);
};
};
\ No newline at end of file
diff --git a/src/upgrade.js b/src/upgrade.js
index 33f8dccd1c..6d985264b9 100644
--- a/src/upgrade.js
+++ b/src/upgrade.js
@@ -273,7 +273,7 @@ Upgrade.upgrade = function(callback) {
async.eachLimit(users, 100, function(user, next) {
var newEmail = user.value.replace(/\uff0E/g, '.');
if (newEmail === user.value) {
- return next();
+ return process.nextTick(next);
}
async.series([
async.apply(db.sortedSetRemove, 'email:uid', user.value),
@@ -341,13 +341,13 @@ Upgrade.upgrade = function(callback) {
if (err) {
return callback(err);
}
- var index = 0;
+
+ userData = userData.filter(function(user) {
+ return user && user.value;
+ });
+
async.eachLimit(userData, 500, function(userData, next) {
- if (userData && userData.value) {
- db.sortedSetAdd(set + ':sorted', 0, userData.value.toLowerCase() + ':' + userData.score, next);
- } else {
- next();
- }
+ db.sortedSetAdd(set + ':sorted', 0, userData.value.toLowerCase() + ':' + userData.score, next);
}, function(err) {
callback(err);
});
@@ -390,10 +390,9 @@ Upgrade.upgrade = function(callback) {
return callback(err);
}
+ groupNames = groupNames.filter(Boolean);
+
async.eachLimit(groupNames, 500, function(groupName, next) {
- if (!groupName) {
- return next();
- }
db.getObjectFields('group:' + groupName, ['hidden', 'system', 'createtime', 'memberCount'], function(err, groupData) {
if (err) {
return next(err);
diff --git a/src/user/approval.js b/src/user/approval.js
index d148842204..2c263d2c46 100644
--- a/src/user/approval.js
+++ b/src/user/approval.js
@@ -46,7 +46,7 @@ module.exports = function(User) {
function sendNotificationToAdmins(username, callback) {
notifications.create({
bodyShort: '[[notifications:new_register, ' + username + ']]',
- nid: 'new_register' + username,
+ nid: 'new_register:' + username,
path: '/admin/manage/users/registration'
}, function(err, notification) {
if (err) {
diff --git a/src/user/settings.js b/src/user/settings.js
index ec493457aa..5af9f567c1 100644
--- a/src/user/settings.js
+++ b/src/user/settings.js
@@ -75,6 +75,7 @@ module.exports = function(User) {
settings.sendPostNotifications = parseInt(settings.sendPostNotifications, 10) === 1;
settings.restrictChat = parseInt(settings.restrictChat, 10) === 1;
settings.topicSearchEnabled = parseInt(settings.topicSearchEnabled, 10) === 1;
+ settings.bootswatchSkin = settings.bootswatchSkin || 'default';
callback(null, settings);
});
@@ -107,6 +108,7 @@ module.exports = function(User) {
sendPostNotifications: data.sendPostNotifications,
restrictChat: data.restrictChat,
topicSearchEnabled: data.topicSearchEnabled,
+ bootswatchSkin: data.bootswatchSkin,
groupTitle: data.groupTitle
}, next);
},
diff --git a/src/views/admin/advanced/database.tpl b/src/views/admin/advanced/database.tpl
index 0390e7a681..b868b322a9 100644
--- a/src/views/admin/advanced/database.tpl
+++ b/src/views/admin/advanced/database.tpl
@@ -13,6 +13,10 @@
Storage Size {mongo.storageSize} mb
Index Size {mongo.indexSize} mb
File Size {mongo.fileSize} mb
+
+ Resident Memory {mongo.mem.resident} mb
+ Virtual Memory {mongo.mem.virtual} mb
+ Mapped Memory {mongo.mem.mapped} mb
diff --git a/src/views/admin/settings/general.tpl b/src/views/admin/settings/general.tpl
index ae63b28cbf..05f2237f78 100644
--- a/src/views/admin/settings/general.tpl
+++ b/src/views/admin/settings/general.tpl
@@ -31,25 +31,40 @@