diff --git a/package.json b/package.json index 9bc27544ff..2f7aa525a1 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "autoprefixer": "^6.2.3", "bcryptjs": "~2.3.0", "body-parser": "^1.9.0", - "chart.js": "^1.0.2", + "chart.js": "^2.1.0", "colors": "^1.1.0", "compression": "^1.1.0", "connect-ensure-login": "^0.1.1", @@ -57,7 +57,7 @@ "nodebb-plugin-spam-be-gone": "0.4.9", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.13", - "nodebb-theme-persona": "4.1.11", + "nodebb-theme-persona": "4.1.12", "nodebb-theme-vanilla": "5.1.3", "nodebb-widget-essentials": "2.0.10", "nodemailer": "2.0.0", @@ -117,4 +117,4 @@ "url": "https://github.com/barisusakli" } ] -} \ No newline at end of file +} diff --git a/public/language/ar/category.json b/public/language/ar/category.json index d36d7a6977..833873ea84 100644 --- a/public/language/ar/category.json +++ b/public/language/ar/category.json @@ -10,11 +10,11 @@ "share_this_category": "انشر هذه الفئة", "watch": "تابع", "ignore": "تجاهل", - "watching": "Watching", - "ignoring": "Ignoring", - "watching.description": "Show topics in unread", - "ignoring.description": "Do not show topics in unread", - "watch.message": "أنت اﻷن متابع لتحديثات هذه الفئة", + "watching": "متابع", + "ignoring": "متجاهل", + "watching.description": "أظهر المواضيع في غير مقروء", + "ignoring.description": "لا تظهر المواضيع في غير مقروء", + "watch.message": "أنت اﻷن تتابع تحديثات هذه الفئة", "ignore.message": "أنت اﻷن تتجاهل تحديثات هذه الفئة", - "watched-categories": "الفئات المراقبه" + "watched-categories": "الفئات المتابعة" } \ No newline at end of file diff --git a/public/language/ar/error.json b/public/language/ar/error.json index 18c5b611d8..33ff273b6b 100644 --- a/public/language/ar/error.json +++ b/public/language/ar/error.json @@ -119,5 +119,6 @@ "not-in-room": "User not in room", "no-users-in-room": "No users in this room", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/bg/error.json b/public/language/bg/error.json index 67d8b05c34..076b6fb878 100644 --- a/public/language/bg/error.json +++ b/public/language/bg/error.json @@ -119,5 +119,6 @@ "not-in-room": "Потребителят не е в стаята", "no-users-in-room": "Няма потребители в тази стая", "cant-kick-self": "Не можете да изритате себе си от групата", - "no-users-selected": "Няма избран(и) потребител(и)" + "no-users-selected": "Няма избран(и) потребител(и)", + "invalid-home-page-route": "Грешен път към началната страница" } \ No newline at end of file diff --git a/public/language/bn/error.json b/public/language/bn/error.json index fd27a09bdb..9f8be3d140 100644 --- a/public/language/bn/error.json +++ b/public/language/bn/error.json @@ -119,5 +119,6 @@ "not-in-room": "User not in room", "no-users-in-room": "No users in this room", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/cs/error.json b/public/language/cs/error.json index c1f7905a73..086b3b9eca 100644 --- a/public/language/cs/error.json +++ b/public/language/cs/error.json @@ -119,5 +119,6 @@ "not-in-room": "User not in room", "no-users-in-room": "No users in this room", "cant-kick-self": "Nemůžete vyhodit sami sebe ze kupiny", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/da/error.json b/public/language/da/error.json index 589ef4a617..6320e0e990 100644 --- a/public/language/da/error.json +++ b/public/language/da/error.json @@ -119,5 +119,6 @@ "not-in-room": "Bruger er ikke i rummet", "no-users-in-room": "Ingen brugere i rummet", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/de/error.json b/public/language/de/error.json index 4b32d2fa52..6f7bd0e385 100644 --- a/public/language/de/error.json +++ b/public/language/de/error.json @@ -119,5 +119,6 @@ "not-in-room": "Benutzer nicht im Raum", "no-users-in-room": "In diesem Raum befinden sich keine Benutzer.", "cant-kick-self": "Du kannst dich nicht selber aus der Gruppe entfernen.", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/el/error.json b/public/language/el/error.json index d1086fe787..45f9f6b2fb 100644 --- a/public/language/el/error.json +++ b/public/language/el/error.json @@ -119,5 +119,6 @@ "not-in-room": "User not in room", "no-users-in-room": "No users in this room", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/en@pirate/error.json b/public/language/en@pirate/error.json index 85292ab05e..3a95ab7cc6 100644 --- a/public/language/en@pirate/error.json +++ b/public/language/en@pirate/error.json @@ -119,5 +119,6 @@ "not-in-room": "User not in room", "no-users-in-room": "No users in this room", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index 76b2bef1a8..fa73ccfa8a 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -25,6 +25,7 @@ "email-taken": "Email taken", "email-not-confirmed": "Your email has not been confirmed yet, please click here to confirm your email.", "email-not-confirmed-chat": "You are unable to chat until your email is confirmed, please click here to confirm your email.", + "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation 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.", diff --git a/public/language/en_GB/global.json b/public/language/en_GB/global.json index 226589b8af..6641c28261 100644 --- a/public/language/en_GB/global.json +++ b/public/language/en_GB/global.json @@ -116,5 +116,7 @@ "enter_page_number": "Enter page number", "upload_file": "Upload file", "upload": "Upload", - "allowed-file-types": "Allowed file types are %1" + "allowed-file-types": "Allowed file types are %1", + + "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?" } diff --git a/public/language/en_GB/login.json b/public/language/en_GB/login.json index e76658fe3d..97c6dedb37 100644 --- a/public/language/en_GB/login.json +++ b/public/language/en_GB/login.json @@ -7,5 +7,6 @@ "alternative_logins": "Alternative Logins", "failed_login_attempt": "Login Unsuccessful", "login_successful": "You have successfully logged in!", - "dont_have_account": "Don't have an account?" + "dont_have_account": "Don't have an account?", + "logged-out-due-to-inactivity": "You have been logged out due to inactivity" } diff --git a/public/language/en_US/error.json b/public/language/en_US/error.json index 85292ab05e..3a95ab7cc6 100644 --- a/public/language/en_US/error.json +++ b/public/language/en_US/error.json @@ -119,5 +119,6 @@ "not-in-room": "User not in room", "no-users-in-room": "No users in this room", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/es/error.json b/public/language/es/error.json index 486fdc7df8..68696133bc 100644 --- a/public/language/es/error.json +++ b/public/language/es/error.json @@ -119,5 +119,6 @@ "not-in-room": "El usuario no está en la sala", "no-users-in-room": "No hay usuarios en esta sala", "cant-kick-self": "No te puedes expulsar a ti mismo del grupo", - "no-users-selected": "Ningun usuario(s) seleccionado" + "no-users-selected": "Ningun usuario(s) seleccionado", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/et/error.json b/public/language/et/error.json index b697046163..02713277b5 100644 --- a/public/language/et/error.json +++ b/public/language/et/error.json @@ -119,5 +119,6 @@ "not-in-room": "Kasutaja pole ruumis", "no-users-in-room": "Ühtegi kasutajat ei leidu siit ruumist", "cant-kick-self": "Sa ei saa ennast ära visata gruppist", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/fa_IR/error.json b/public/language/fa_IR/error.json index 31c6872ea0..d4661f4c8d 100644 --- a/public/language/fa_IR/error.json +++ b/public/language/fa_IR/error.json @@ -119,5 +119,6 @@ "not-in-room": "هیچ کاربری در این گفتگو نیست", "no-users-in-room": "هیچ کاربری در این گفتگو نیست", "cant-kick-self": "شما نمی توانید خودتان را از گروه کیک کنید", - "no-users-selected": "هیچ کاربر(های) انتخاب نشده" + "no-users-selected": "هیچ کاربر(های) انتخاب نشده", + "invalid-home-page-route": "مسیر صفحه اصلی نامعتبر است" } \ No newline at end of file diff --git a/public/language/fi/error.json b/public/language/fi/error.json index b87d5f0f7b..e728dcfc78 100644 --- a/public/language/fi/error.json +++ b/public/language/fi/error.json @@ -119,5 +119,6 @@ "not-in-room": "Käyttäjä ei ole huoneessa", "no-users-in-room": "Ei käyttäjiä tässä huoneessa", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/fr/error.json b/public/language/fr/error.json index 74fb3a81d4..f62e4043ff 100644 --- a/public/language/fr/error.json +++ b/public/language/fr/error.json @@ -119,5 +119,6 @@ "not-in-room": "L'utilisateur n'est pas dans cette salle", "no-users-in-room": "Aucun utilisateur dans cette salle", "cant-kick-self": "Vous ne pouvez pas vous exclure vous-même du groupe", - "no-users-selected": "Aucun utilisateur sélectionné" + "no-users-selected": "Aucun utilisateur sélectionné", + "invalid-home-page-route": "Route de page d'accueil invalide" } \ No newline at end of file diff --git a/public/language/gl/error.json b/public/language/gl/error.json index 2a62d5eb67..8944015afd 100644 --- a/public/language/gl/error.json +++ b/public/language/gl/error.json @@ -119,5 +119,6 @@ "not-in-room": "O usuario non se encontra nesta sala", "no-users-in-room": "Non hai usuarios nesta sala", "cant-kick-self": "Non te podes expulsar a ti mesmo do grupo", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/he/error.json b/public/language/he/error.json index c30c84bc10..3696f40550 100644 --- a/public/language/he/error.json +++ b/public/language/he/error.json @@ -119,5 +119,6 @@ "not-in-room": "משתמש זה לא בצ'אט", "no-users-in-room": "אין משתמש בחדר הזה", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/hu/error.json b/public/language/hu/error.json index c3a461d679..19a14975ea 100644 --- a/public/language/hu/error.json +++ b/public/language/hu/error.json @@ -119,5 +119,6 @@ "not-in-room": "User not in room", "no-users-in-room": "No users in this room", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/id/error.json b/public/language/id/error.json index 0c8082a88f..111505a95d 100644 --- a/public/language/id/error.json +++ b/public/language/id/error.json @@ -119,5 +119,6 @@ "not-in-room": "User not in room", "no-users-in-room": "No users in this room", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/it/error.json b/public/language/it/error.json index d7af7cfe6b..740211d54e 100644 --- a/public/language/it/error.json +++ b/public/language/it/error.json @@ -119,5 +119,6 @@ "not-in-room": "User not in room", "no-users-in-room": "No users in this room", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/ja/error.json b/public/language/ja/error.json index 14332c43a4..786ca3988a 100644 --- a/public/language/ja/error.json +++ b/public/language/ja/error.json @@ -119,5 +119,6 @@ "not-in-room": "User not in room", "no-users-in-room": "No users in this room", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/ko/error.json b/public/language/ko/error.json index ca530bc67c..45b2c548d8 100644 --- a/public/language/ko/error.json +++ b/public/language/ko/error.json @@ -119,5 +119,6 @@ "not-in-room": "없는 사용자입니다.", "no-users-in-room": "사용자가 없습니다.", "cant-kick-self": "스스로 이 그룹을 탈퇴할 수 없습니다.", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/lt/error.json b/public/language/lt/error.json index c1f30b5596..01c1a1d505 100644 --- a/public/language/lt/error.json +++ b/public/language/lt/error.json @@ -119,5 +119,6 @@ "not-in-room": "User not in room", "no-users-in-room": "No users in this room", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/ms/error.json b/public/language/ms/error.json index 4f95e3308d..f90fd5f3b3 100644 --- a/public/language/ms/error.json +++ b/public/language/ms/error.json @@ -119,5 +119,6 @@ "not-in-room": "Pengguna tiada dalam bilik", "no-users-in-room": "Tiada pengguna dalam bilik ini", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/nb/error.json b/public/language/nb/error.json index c49ab8f47b..416c950043 100644 --- a/public/language/nb/error.json +++ b/public/language/nb/error.json @@ -119,5 +119,6 @@ "not-in-room": "User not in room", "no-users-in-room": "No users in this room", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/nl/category.json b/public/language/nl/category.json index 8c9caafa53..7ca67ad3be 100644 --- a/public/language/nl/category.json +++ b/public/language/nl/category.json @@ -10,10 +10,10 @@ "share_this_category": "Deel deze categorie", "watch": "Volgen", "ignore": "Negeren", - "watching": "Watching", - "ignoring": "Ignoring", - "watching.description": "Show topics in unread", - "ignoring.description": "Do not show topics in unread", + "watching": "Volgend", + "ignoring": "Negerend", + "watching.description": "Toon ongelezen onderwerpen", + "ignoring.description": "Toon geen onderwerpen onder onder ongelezen onderwerpen", "watch.message": "Van deze categorie worden nu meldingen ontvangen", "ignore.message": "Er worden geen meldingen van deze categorie ontvangen", "watched-categories": "Categorieën die bekeken zijn." diff --git a/public/language/nl/error.json b/public/language/nl/error.json index ba9d00c3cb..2720bfc8ea 100644 --- a/public/language/nl/error.json +++ b/public/language/nl/error.json @@ -119,5 +119,6 @@ "not-in-room": "Gebruiker niet in de chat", "no-users-in-room": "Er zijn geen gebruikers in deze chat", "cant-kick-self": "Je kunt jezelf niet uit een groep schoppen", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/nl/topic.json b/public/language/nl/topic.json index 2baaccfceb..d011e95667 100644 --- a/public/language/nl/topic.json +++ b/public/language/nl/topic.json @@ -31,8 +31,8 @@ "flag_success": "Dit bericht is gerapporteerd aan de beheerder.", "deleted_message": "Dit onderwerp is verwijderd. Alleen gebruikers met beheerrechten op onderwerpniveau kunnen dit inzien.", "following_topic.message": "Vanaf nu worden meldingen ontvangen zodra iemand een reactie op dit onderwerp geeft.", - "not_following_topic.message": "You will see this topic in the unread topics list, but you will not receive notifications when somebody posts to this topic.", - "ignoring_topic.message": "You will no longer see this topic in the unread topics list. You will be notified when you are mentioned or your post is up voted.", + "not_following_topic.message": "Dit onderwerp zal verschijnen in de lijst van ongelezen onderwerpen, maar er zullen geen meldingen ontvangen zodra iemand een reactie op dit onderwerp geeft.", + "ignoring_topic.message": "Dit onderwerp zal niet meer verschijnen in de lijst van ongelezen berichten. U zult enkel een melding ontvangen wanneer u wordt genoemd, of wanneer er een positieve stem op uw reactie wordt gegeven.", "login_to_subscribe": "Log in or registreer om dit onderwerp te volgen.", "markAsUnreadForAll.success": "Onderwerp is voor iedereen als ongelezen gemarkeerd.", "mark_unread": "Ongelezen markeren", @@ -45,9 +45,9 @@ "watching": "Gevolgd", "not-watching": "Niet gevolgd", "ignoring": "Genegeerd", - "watching.description": "Notify me of new replies.
Show topic in unread.", - "not-watching.description": "Do not notify me of new replies.
Show topic in unread if category is not ignored.", - "ignoring.description": "Do not notify me of new replies.
Do not show topic in unread.", + "watching.description": "Stuur me een melding bij nieuwe reacties.
Toon onderwerp bij de ongelezen onderwerpen.", + "not-watching.description": "Stuur me geen melding van nieuwe reacties.
Toon onderwerp in ongelezen mits de categorie niet genegeerd wordt.", + "ignoring.description": "Stuur me geen melding van nieuwe reacties.
Toon dit onderwerp niet onder de ongelezen onderwerpen.", "thread_tools.title": "Acties", "thread_tools.markAsUnreadForAll": "Ongelezen markeren", "thread_tools.pin": "Onderwerp vastpinnen", diff --git a/public/language/pl/error.json b/public/language/pl/error.json index 98a2b56a91..627f7e061b 100644 --- a/public/language/pl/error.json +++ b/public/language/pl/error.json @@ -119,5 +119,6 @@ "not-in-room": "Użytkownik nie jest w pokoju", "no-users-in-room": "Brak użytkowników w pokoju", "cant-kick-self": "Nie możesz wyrzucić samego siebie z grupy", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/pt_BR/category.json b/public/language/pt_BR/category.json index 9de5c610a7..ad56253599 100644 --- a/public/language/pt_BR/category.json +++ b/public/language/pt_BR/category.json @@ -10,10 +10,10 @@ "share_this_category": "Compartilhar esta categoria", "watch": "Acompanhar", "ignore": "Ignorar", - "watching": "Watching", - "ignoring": "Ignoring", - "watching.description": "Show topics in unread", - "ignoring.description": "Do not show topics in unread", + "watching": "Assistindo", + "ignoring": "Ignorando", + "watching.description": "Mostrar tópicos em não-lido", + "ignoring.description": "Não mostrar tópicos em não-lido", "watch.message": "Agora você está acompanhando as atualizações desta categoria", "ignore.message": "Agora você está ignorando as atualizações desta categoria", "watched-categories": "Categorias acompanhadas" diff --git a/public/language/pt_BR/error.json b/public/language/pt_BR/error.json index be9e3d7492..48cdbafc36 100644 --- a/public/language/pt_BR/error.json +++ b/public/language/pt_BR/error.json @@ -30,7 +30,7 @@ "user-banned": "Usuário banido", "user-too-new": "Desculpe, é necessário que você aguarde %1 segundo(s) antes de fazer o seu primeiro post.", "blacklisted-ip": "Desculpe, o seu endereço IP foi banido desta comunidade. Se você acha que isso é um engano, por favor contate um administrador.", - "ban-expiry-missing": "Please provide an end date for this ban", + "ban-expiry-missing": "Por favor forneça uma data para o fim deste banimento", "no-category": "A categoria não existe", "no-topic": "O tópico não existe", "no-post": "O post não existe", @@ -47,13 +47,13 @@ "post-edit-duration-expired-hours-minutes": "Você pode apenas editar posts por %1 hora(s) e %2 minuto(s) após postar", "post-edit-duration-expired-days": "Você pode apenas editar posts por %1 dia(s) após postar", "post-edit-duration-expired-days-hours": "Você pode apenas editar posts por %1 dia(s) e %2 hora(s) após postar", - "post-delete-duration-expired": "You are only allowed to delete posts for %1 second(s) after posting", - "post-delete-duration-expired-minutes": "You are only allowed to delete posts for %1 minute(s) after posting", - "post-delete-duration-expired-minutes-seconds": "You are only allowed to delete posts for %1 minute(s) %2 second(s) after posting", - "post-delete-duration-expired-hours": "You are only allowed to delete posts for %1 hour(s) after posting", - "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", - "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", - "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "post-delete-duration-expired": "Você só pode deletar posts por %1 segundo(s) depois de postar", + "post-delete-duration-expired-minutes": "Você só pode deletar posts por %1 minuto(s) depois de postar", + "post-delete-duration-expired-minutes-seconds": "Você só pode deletar posts por %1 minuto(s) e %2 segundo(s) depois de postar", + "post-delete-duration-expired-hours": "Você só pode deletar posts por %1 hora(s) depois de postar", + "post-delete-duration-expired-hours-minutes": "Você só pode deletar posts por %1 hora(s) e %2 minutos(s) depois de postar", + "post-delete-duration-expired-days": "Você só pode deletar posts por %1 dia(s) depois de postar", + "post-delete-duration-expired-days-hours": "Você só pode deletar posts por %1 dia(s) e %2 hora(s) depois de postar", "content-too-short": "Por favor digite um post maior. Posts precisam conter ao menos %1 caractere(s).", "content-too-long": "Por favor digite um post mais curto. Posts não podem ser maiores que %1 caractere(s)", "title-too-short": "Por favor digite um título maior. Títulos devem conter no mínimo %1 caractere(s)", @@ -71,12 +71,12 @@ "already-unfavourited": "Você já removeu este post dos favoritos", "cant-ban-other-admins": "Você não pode banir outros administradores!", "cant-remove-last-admin": "Você é o único administrador. Adicione outro usuário como administrador antes de remover a si mesmo como admin", - "cant-delete-admin": "Remove administrator privileges from this account before attempting to delete it.", + "cant-delete-admin": "Remova o privilégio de administrador desta conta antes de tentar deletá-la.", "invalid-image-type": "Tipo inválido de imagem. Os tipos permitidos são: %1", "invalid-image-extension": "Extensão de imagem inválida", "invalid-file-type": "Tipo de arquivo inválido. Os tipos permitidos são: %1", "group-name-too-short": "Nome do grupo é muito curto", - "group-name-too-long": "Group name too long", + "group-name-too-long": "O nome do grupo é muito extenso", "group-already-exists": "O grupo já existe", "group-name-change-not-allowed": "Sem permissão para alterar nome do grupo", "group-already-member": "Já faz parte deste grupo", @@ -119,5 +119,6 @@ "not-in-room": "O usuário não está na sala", "no-users-in-room": "Nenhum usuário nesta sala", "cant-kick-self": "Você não pode kickar a si mesmo do grupo", - "no-users-selected": "No user(s) selected" + "no-users-selected": "Nenhuma escolha de usuário(s) foi feita", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/pt_BR/global.json b/public/language/pt_BR/global.json index 21a3afd2e4..115d36e0b3 100644 --- a/public/language/pt_BR/global.json +++ b/public/language/pt_BR/global.json @@ -50,9 +50,9 @@ "topics": "Tópicos", "posts": "Posts", "best": "Melhor", - "upvoters": "Upvoters", + "upvoters": "Cimavotadores", "upvoted": "Votado positivamente", - "downvoters": "Downvoters", + "downvoters": "Baixovotadores", "downvoted": "Votado negativamente", "views": "Visualizações", "reputation": "Reputação", @@ -80,7 +80,7 @@ "language": "Idioma", "guest": "Visitante", "guests": "Visitantes", - "updated.title": "Forum Updated", + "updated.title": "Fórum Atualizado", "updated.message": "Este fórum foi atualizado para sua última versão. Clique aqui para atualizar a página.", "privacy": "Privacidade", "follow": "Seguir", diff --git a/public/language/pt_BR/pages.json b/public/language/pt_BR/pages.json index aef2f3ea5e..68b771bdb8 100644 --- a/public/language/pt_BR/pages.json +++ b/public/language/pt_BR/pages.json @@ -12,7 +12,7 @@ "users/sort-posts": "Usuários com mais posts", "users/sort-reputation": "Usuários com maior reputação", "users/banned": "Usuários Banidos", - "users/most-flags": "Most flagged users", + "users/most-flags": "Usuários mais sinalizados", "users/search": "Pesquisa de Usuários", "notifications": "Notificações", "tags": "Tags", @@ -29,7 +29,7 @@ "account/edit/password": "Editando senha de \"%1\"", "account/edit/username": "Editando nome de usuário de \"%1\"", "account/edit/email": "Editando email de \"%1\"", - "account/info": "Account Info", + "account/info": "Informação da Conta", "account/following": "Pessoas que %1 segue", "account/followers": "Pessoas que seguem %1", "account/posts": "Posts feitos por %1", diff --git a/public/language/pt_BR/register.json b/public/language/pt_BR/register.json index 85ac0ffec0..1070dce7ec 100644 --- a/public/language/pt_BR/register.json +++ b/public/language/pt_BR/register.json @@ -1,6 +1,6 @@ { "register": "Cadastrar", - "cancel_registration": "Cancel Registration", + "cancel_registration": "Cancelar Cadastro", "help.email": "Por padrão seu email ficará invisível para o publico.", "help.username_restrictions": "Um nome de usuário único entre %1 e %2 caracteres. Os outros poderão te mencionar digitando @usuário.", "help.minimum_password_length": "Sua senha tem que ter no mínimo %1 caracteres.", @@ -16,8 +16,8 @@ "alternative_registration": "Cadastro Alternativo", "terms_of_use": "Termos de Uso", "agree_to_terms_of_use": "Eu concordo com os Termos de Uso", - "terms_of_use_error": "You must agree to the Terms of Use", + "terms_of_use_error": "Você deve concordar com os Termos de Uso", "registration-added-to-queue": "O seu cadastro foi adicionado à fila de aprovação. Você receberá um email quando ele for aceito por um administrador.", - "interstitial.intro": "We require some additional information before we can create your account.", - "interstitial.errors-found": "We could not complete your registration:" + "interstitial.intro": "Nós pedimos alguma informação adicional antes que você possa criar a sua conta.", + "interstitial.errors-found": "Nós não pudemos completar o seu cadastro:" } \ No newline at end of file diff --git a/public/language/pt_BR/topic.json b/public/language/pt_BR/topic.json index 0a90dec961..f4a9305d1b 100644 --- a/public/language/pt_BR/topic.json +++ b/public/language/pt_BR/topic.json @@ -31,7 +31,7 @@ "flag_success": "Este post foi sinalizado para ser moderado.", "deleted_message": "Este tópico foi deletado. Apenas usuários com privilégios de moderação de tópico podem vê-lo.", "following_topic.message": "Agora você receberá notificações quando alguém responder este tópico.", - "not_following_topic.message": "You will see this topic in the unread topics list, but you will not receive notifications when somebody posts to this topic.", + "not_following_topic.message": "Você verá este tópico na lista de tópicos não-lidos, mas você não receberá notificações quendo alguém posta no tópico.", "ignoring_topic.message": "Você não verá mais este tópico na lista de tópicos não lidos. Você será notificado quando você for mencionado ou sua postagem for votada positivamente.", "login_to_subscribe": "Por favor se cadastre ou entre para assinar à este tópico.", "markAsUnreadForAll.success": "Tópico marcado como não lido para todos.", diff --git a/public/language/pt_BR/user.json b/public/language/pt_BR/user.json index c8b17f7799..b6ee0c97c4 100644 --- a/public/language/pt_BR/user.json +++ b/public/language/pt_BR/user.json @@ -6,7 +6,7 @@ "postcount": "Número de Posts", "email": "Email", "confirm_email": "Confirmar Email", - "account_info": "Account Info", + "account_info": "Informação da Conta", "ban_account": "Banir Conta", "ban_account_confirm": "Você realmente quer banir esse usuario?", "unban_account": "Desbanir Conta", @@ -96,8 +96,8 @@ "delay_image_loading": "Aguardar para Carregar Imagens", "image_load_delay_help": "Se habilitado, imagens em tópicos não serão carregadas até que eles sejam rolados à visão", "scroll_to_my_post": "Após postar uma réplica, mostre o novo post", - "follow_topics_you_reply_to": "Watch topics that you reply to", - "follow_topics_you_create": "Watch topics you create", + "follow_topics_you_reply_to": "Assistir os tópicos que você responde", + "follow_topics_you_create": "Assistir aos tópicos que você cria", "grouptitle": "Título do Grupo", "no-group-title": "Sem título de grupo", "select-skin": "Escolha uma Skin", @@ -109,10 +109,10 @@ "sso.title": "Logar por outros Serviços", "sso.associated": "Associado com", "sso.not-associated": "Clique aqui para associar com", - "info.latest-flags": "Latest Flags", - "info.no-flags": "No Flagged Posts Found", - "info.ban-history": "Recent Ban History", - "info.no-ban-history": "This user has never been banned", - "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.latest-flags": "Últimas Sinalizações", + "info.no-flags": "Nenhum Post Sinalizado Encontrado", + "info.ban-history": "Histórico de Banimentos Recentes", + "info.no-ban-history": "Este usuário nunca foi banido", + "info.banned-until": "Banido até %1", + "info.banned-permanently": "Banido permanentemente" } \ No newline at end of file diff --git a/public/language/pt_BR/users.json b/public/language/pt_BR/users.json index e67604fdba..62aa2b060e 100644 --- a/public/language/pt_BR/users.json +++ b/public/language/pt_BR/users.json @@ -2,7 +2,7 @@ "latest_users": "Últimos Usuários", "top_posters": "Principais Participantes", "most_reputation": "Maior Reputação", - "most_flags": "Most Flags", + "most_flags": "Mais Sinalizações", "search": "Pesquisar", "enter_username": "Digite um nome de usuário para pesquisar", "load_more": "Carregar Mais", diff --git a/public/language/ro/error.json b/public/language/ro/error.json index c9dcb91bf3..0dabe24cbf 100644 --- a/public/language/ro/error.json +++ b/public/language/ro/error.json @@ -119,5 +119,6 @@ "not-in-room": "User not in room", "no-users-in-room": "No users in this room", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/ru/error.json b/public/language/ru/error.json index 941bcb1816..80f601eb1e 100644 --- a/public/language/ru/error.json +++ b/public/language/ru/error.json @@ -119,5 +119,6 @@ "not-in-room": "Пользователь не в комнате", "no-users-in-room": "В этой комнате нет пользователей", "cant-kick-self": "Вы не можете удалить себя сами из группы.", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/rw/error.json b/public/language/rw/error.json index 12f4a26968..abe75583d8 100644 --- a/public/language/rw/error.json +++ b/public/language/rw/error.json @@ -119,5 +119,6 @@ "not-in-room": "User not in room", "no-users-in-room": "Nta muntu uri muri iki gikari", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/sc/error.json b/public/language/sc/error.json index 85292ab05e..3a95ab7cc6 100644 --- a/public/language/sc/error.json +++ b/public/language/sc/error.json @@ -119,5 +119,6 @@ "not-in-room": "User not in room", "no-users-in-room": "No users in this room", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/sk/error.json b/public/language/sk/error.json index 6ab638ca04..8d286eaa7c 100644 --- a/public/language/sk/error.json +++ b/public/language/sk/error.json @@ -119,5 +119,6 @@ "not-in-room": "User not in room", "no-users-in-room": "No users in this room", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/sl/error.json b/public/language/sl/error.json index 56e4d080f8..be36f337e3 100644 --- a/public/language/sl/error.json +++ b/public/language/sl/error.json @@ -119,5 +119,6 @@ "not-in-room": "User not in room", "no-users-in-room": "No users in this room", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/sr/error.json b/public/language/sr/error.json index 093feca88a..3827af2da1 100644 --- a/public/language/sr/error.json +++ b/public/language/sr/error.json @@ -119,5 +119,6 @@ "not-in-room": "User not in room", "no-users-in-room": "No users in this room", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/sv/error.json b/public/language/sv/error.json index 702808d4ca..9096296fbb 100644 --- a/public/language/sv/error.json +++ b/public/language/sv/error.json @@ -119,5 +119,6 @@ "not-in-room": "Användaren finns inte i rummet", "no-users-in-room": "Inga användare i det här rummet", "cant-kick-self": "Du kan inte sparka ut dig själv från gruppen", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/th/error.json b/public/language/th/error.json index 03b006c3bf..fcb210f49c 100644 --- a/public/language/th/error.json +++ b/public/language/th/error.json @@ -119,5 +119,6 @@ "not-in-room": "User not in room", "no-users-in-room": "No users in this room", "cant-kick-self": "You can't kick yourself from the group", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/tr/category.json b/public/language/tr/category.json index 921003afaa..98a430ec0e 100644 --- a/public/language/tr/category.json +++ b/public/language/tr/category.json @@ -10,10 +10,10 @@ "share_this_category": "Bu kategoriyi paylaş", "watch": "İzle", "ignore": "Yoksay", - "watching": "Watching", - "ignoring": "Ignoring", - "watching.description": "Show topics in unread", - "ignoring.description": "Do not show topics in unread", + "watching": "İzleniyor", + "ignoring": "Yoksayılıyor", + "watching.description": "Okunmamış başlıkları göster", + "ignoring.description": "Okunmamış başlıkları gösterme", "watch.message": "Şuan bu kategorideki güncellemeleri izliyorsunuz", "ignore.message": "Şuan bu kategoriden güncellemeleri gizliyorsunuz", "watched-categories": "Takip edilen kategoriler" diff --git a/public/language/tr/error.json b/public/language/tr/error.json index 5c1adb7ae9..6fad971610 100644 --- a/public/language/tr/error.json +++ b/public/language/tr/error.json @@ -71,7 +71,7 @@ "already-unfavourited": "Bu iletiyi zaten yer imlerinizden çıkardınız", "cant-ban-other-admins": "Başka yöneticileri yasaklayamazsınız!", "cant-remove-last-admin": "Tek yönetici sizsiniz. Kendinizi adminlikten çıkarmadan önce başka bir kullanıcıyı admin olarak ekleyiniz", - "cant-delete-admin": "Remove administrator privileges from this account before attempting to delete it.", + "cant-delete-admin": "Öncelikle yönetici izinlerini kaldırman gerekiyor.", "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", @@ -119,5 +119,6 @@ "not-in-room": "Odada kullanıcı yok", "no-users-in-room": "Bu odada kullanıcı yok", "cant-kick-self": "Kendinizi gruptan atamazsınız.", - "no-users-selected": "Seçili kullanıcı(s) bulunamadı" + "no-users-selected": "Seçili kullanıcı(s) bulunamadı", + "invalid-home-page-route": "Geçersiz anasayfa yolu" } \ No newline at end of file diff --git a/public/language/tr/global.json b/public/language/tr/global.json index 3aafe7ce99..29be110719 100644 --- a/public/language/tr/global.json +++ b/public/language/tr/global.json @@ -50,9 +50,9 @@ "topics": "Başlık", "posts": "İleti", "best": "En İyi", - "upvoters": "Upvoters", + "upvoters": "Artı", "upvoted": "Artı", - "downvoters": "Downvoters", + "downvoters": "Eksi", "downvoted": "Eksi", "views": "Görünüm", "reputation": "Saygınlık", diff --git a/public/language/tr/pages.json b/public/language/tr/pages.json index e801393479..f3377dd1b4 100644 --- a/public/language/tr/pages.json +++ b/public/language/tr/pages.json @@ -29,7 +29,7 @@ "account/edit/password": "\"%1\" parolayı düzenliyor", "account/edit/username": "\"%1\" kullanıcı adını düzenliyor", "account/edit/email": "\"%1\" email adresini düzenliyor", - "account/info": "Account Info", + "account/info": "Hesap Hakkında", "account/following": "%1 tarafından takip edilenler", "account/followers": "%1 takip edenler", "account/posts": "%1 tarafından gönderilen iletiler", diff --git a/public/language/tr/register.json b/public/language/tr/register.json index 36e7b75786..a11bf78747 100644 --- a/public/language/tr/register.json +++ b/public/language/tr/register.json @@ -1,6 +1,6 @@ { "register": "Kayıt Ol", - "cancel_registration": "Cancel Registration", + "cancel_registration": "Kaydı İptal Et", "help.email": "E-posta adresiniz varsayılan olarak topluluktan gizlidir.", "help.username_restrictions": "%1 ve %2 karakter arası bir kullanıcı ismi. Başkaları sizden @isim kullanarak bahsedebilir.", "help.minimum_password_length": "Şifreniz en az %1 karakter olmalı", @@ -16,8 +16,8 @@ "alternative_registration": "Alternatif Kayıt", "terms_of_use": "Kullanım Şartları", "agree_to_terms_of_use": "Kullanım Şartlarını kabul ediyorum", - "terms_of_use_error": "You must agree to the Terms of Use", + "terms_of_use_error": "Kullanım şartlarını kabul etmen gerekiyor", "registration-added-to-queue": "Kayıt olma isteğiniz kabul listesine eklenmiştir. Yönetici tarafından kabul edildiğinizde mail alacaksınız.", - "interstitial.intro": "We require some additional information before we can create your account.", - "interstitial.errors-found": "We could not complete your registration:" + "interstitial.intro": "Hesabınızı yaratmadan önce bazı ekstra bilgiler gerekiyor.", + "interstitial.errors-found": "Kaydınınız tamamlanmadı:" } \ No newline at end of file diff --git a/public/language/tr/topic.json b/public/language/tr/topic.json index 2bfdee81c0..9c37c91f52 100644 --- a/public/language/tr/topic.json +++ b/public/language/tr/topic.json @@ -31,7 +31,7 @@ "flag_success": "Bu ileti yöneticilere bildirildi.", "deleted_message": "Bu başlık silindi. Sadece başlık düzenleme yetkisi olan kullanıcılar görebilir.", "following_topic.message": "Artık bir kullanıcı bu başlığa ileti gönderdiğinde bildirim alacaksınız.", - "not_following_topic.message": "You will see this topic in the unread topics list, but you will not receive notifications when somebody posts to this topic.", + "not_following_topic.message": "Bu başlığı okunmamışlarda göreceksiniz ama biri bir şey yazdığında bildirim gelmeyecek.", "ignoring_topic.message": "Bu başlığı okunmamış başlıklar alanında görmeyeceksin. Eğer bir iletide bahsedilirsen veya iletin oylanırsa bildiri alacaksın.", "login_to_subscribe": "Lütfen bu iletiyi başlığa üye olmak için giriş yapın.", "markAsUnreadForAll.success": "Başlık herkes için okunmadı olarak işaretlendi.", diff --git a/public/language/vi/error.json b/public/language/vi/error.json index 737ca3da9f..38a0ab9a69 100644 --- a/public/language/vi/error.json +++ b/public/language/vi/error.json @@ -119,5 +119,6 @@ "not-in-room": "Thành viên không có trong phòng", "no-users-in-room": "Không có ai trong phòng này", "cant-kick-self": "Bạn không thể kick chính bạn ra khỏi nhóm", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/zh_CN/error.json b/public/language/zh_CN/error.json index fb79c05f2d..51ce4481b2 100644 --- a/public/language/zh_CN/error.json +++ b/public/language/zh_CN/error.json @@ -119,5 +119,6 @@ "not-in-room": "用户已不在聊天室中", "no-users-in-room": "这个聊天室中没有用户", "cant-kick-self": "你不能把自己踢出群组", - "no-users-selected": "尚未选择用户" + "no-users-selected": "尚未选择用户", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/language/zh_TW/error.json b/public/language/zh_TW/error.json index 64e29efa7e..617902283b 100644 --- a/public/language/zh_TW/error.json +++ b/public/language/zh_TW/error.json @@ -119,5 +119,6 @@ "not-in-room": "使用者沒有在聊天室中", "no-users-in-room": "沒有使用者在聊天室中", "cant-kick-self": "你不能把自己從群組中踢出", - "no-users-selected": "No user(s) selected" + "no-users-selected": "No user(s) selected", + "invalid-home-page-route": "Invalid home page route" } \ No newline at end of file diff --git a/public/less/admin/header.less b/public/less/admin/header.less index 8ccf535c30..5dd2c21aa1 100644 --- a/public/less/admin/header.less +++ b/public/less/admin/header.less @@ -29,6 +29,11 @@ } } + .fa-home { + margin-top: 12px; + font-size: 25px; + } + #user_dropdown { font-size: 25px; color: #eee; diff --git a/public/src/admin/admin.js b/public/src/admin/admin.js index 62234df1e9..8a26b91409 100644 --- a/public/src/admin/admin.js +++ b/public/src/admin/admin.js @@ -2,9 +2,23 @@ /*global config, translator, componentHandler, define, socket, app, ajaxify, utils, bootbox, Slideout, NProgress, RELATIVE_PATH*/ (function() { + var logoutTimer = 0; + function startLogoutTimer() { + if (logoutTimer) { + clearTimeout(logoutTimer); + } + + logoutTimer = setTimeout(function() { + app.alert({ + message: '[[login:logged-out-due-to-inactivity]]' + }); + setTimeout(app.logout, 5000); + }, 3600000); + } $(window).on('action:ajaxify.end', function() { showCorrectNavTab(); + startLogoutTimer(); }); function showCorrectNavTab() { diff --git a/public/src/admin/appearance/customise.js b/public/src/admin/appearance/customise.js index a135a240fe..a85fd663ad 100644 --- a/public/src/admin/appearance/customise.js +++ b/public/src/admin/appearance/customise.js @@ -16,6 +16,8 @@ define('admin/appearance/customise', ['admin/settings'], function(Settings) { customCSS.getSession().setMode("ace/mode/css"); customCSS.on('change', function(e) { + app.flags = app.flags || {}; + app.flags._unsaved = true; $('#customCSS-holder').val(customCSS.getValue()); }); @@ -23,6 +25,8 @@ define('admin/appearance/customise', ['admin/settings'], function(Settings) { customHTML.getSession().setMode("ace/mode/html"); customHTML.on('change', function(e) { + app.flags = app.flags || {}; + app.flags._unsaved = true; $('#customHTML-holder').val(customHTML.getValue()); }); }); diff --git a/public/src/admin/general/dashboard.js b/public/src/admin/general/dashboard.js index a26ed9ad5e..1071c76b0d 100644 --- a/public/src/admin/general/dashboard.js +++ b/public/src/admin/general/dashboard.js @@ -2,18 +2,18 @@ /*global define, ajaxify, app, socket, utils, bootbox, RELATIVE_PATH*/ define('admin/general/dashboard', ['semver', 'Chart'], function(semver, Chart) { - var Admin = {}, - intervals = { + var Admin = {}; + var intervals = { rooms: false, graphs: false - }, - isMobile = false, - isPrerelease = /^v?\d+\.\d+\.\d+-.+$/, - graphData = { + }; + var isMobile = false; + var isPrerelease = /^v?\d+\.\d+\.\d+-.+$/; + var graphData = { rooms: {}, traffic: {} - }, - currentGraph = { + }; + var currentGraph = { units: 'hours', until: undefined }; @@ -121,8 +121,8 @@ define('admin/general/dashboard', ['semver', 'Chart'], function(semver, Chart) { topics: null }; - var topicColors = ["#bf616a","#5B90BF","#d08770","#ebcb8b","#a3be8c","#96b5b4","#8fa1b3","#b48ead","#ab7967","#46BFBD"], - usedTopicColors = []; + var topicColors = ["#bf616a","#5B90BF","#d08770","#ebcb8b","#a3be8c","#96b5b4","#8fa1b3","#b48ead","#ab7967","#46BFBD"]; + var usedTopicColors = []; // from chartjs.org function lighten(col, amt) { @@ -170,92 +170,103 @@ define('admin/general/dashboard', ['semver', 'Chart'], function(semver, Chart) { } var data = { - labels: trafficLabels, - datasets: [ - { - label: "Page Views", - fillColor: "rgba(220,220,220,0.2)", - strokeColor: "rgba(220,220,220,1)", - pointColor: "rgba(220,220,220,1)", - pointStrokeColor: "#fff", - pointHighlightFill: "#fff", - pointHighlightStroke: "rgba(220,220,220,1)", - data: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] - }, - { - label: "Unique Visitors", - fillColor: "rgba(151,187,205,0.2)", - strokeColor: "rgba(151,187,205,1)", - pointColor: "rgba(151,187,205,1)", - pointStrokeColor: "#fff", - pointHighlightFill: "#fff", - pointHighlightStroke: "rgba(151,187,205,1)", - data: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] - } - ] - }; + labels: trafficLabels, + datasets: [ + { + label: "Page Views", + backgroundColor: "rgba(220,220,220,0.2)", + borderColor: "rgba(220,220,220,1)", + pointBackgroundColor: "rgba(220,220,220,1)", + pointHoverBackgroundColor: "#fff", + pointBorderColor: "#fff", + pointHoverBorderColor: "rgba(220,220,220,1)", + data: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] + }, + { + label: "Unique Visitors", + backgroundColor: "rgba(151,187,205,0.2)", + borderColor: "rgba(151,187,205,1)", + pointBackgroundColor: "rgba(151,187,205,1)", + pointHoverBackgroundColor: "#fff", + pointBorderColor: "#fff", + pointHoverBorderColor: "rgba(151,187,205,1)", + data: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] + } + ] + }; trafficCanvas.width = $(trafficCanvas).parent().width(); - graphs.traffic = new Chart(trafficCtx).Line(data, { - responsive: true + graphs.traffic = new Chart(trafficCtx, { + type: 'line', + data: data, + options: { + responsive: true, + legend: { + display: false + }, + scales: { + yAxes: [{ + ticks: { + beginAtZero: true + } + }] + } + } + }); + + graphs.registered = new Chart(registeredCtx, { + type: 'doughnut', + data: { + labels: ["Registered Users", "Anonymous Users"], + datasets: [{ + data: [1, 1], + backgroundColor: ["#F7464A", "#46BFBD"], + hoverBackgroundColor: ["#FF5A5E", "#5AD3D1"] + }] + }, + options: { + responsive: true, + legend: { + display: false + } + } }); - graphs.registered = new Chart(registeredCtx).Doughnut([{ - value: 1, - color:"#F7464A", - highlight: "#FF5A5E", - label: "Registered Users" + graphs.presence = new Chart(presenceCtx, { + type: 'doughnut', + data: { + labels: ["On categories list", "Reading posts", "Browsing topics", "Recent", "Unread"], + datasets: [{ + data: [1, 1, 1, 1, 1], + backgroundColor: ["#F7464A", "#46BFBD", "#FDB45C", "#949FB1", "#9FB194"], + hoverBackgroundColor: ["#FF5A5E", "#5AD3D1", "#FFC870", "#A8B3C5", "#A8B3C5"] + }] + }, + options: { + responsive: true, + legend: { + display: false + } + } + }); + + graphs.topics = new Chart(topicsCtx, { + type: 'doughnut', + data: { + labels: [], + datasets: [{ + data: [], + backgroundColor: [], + hoverBackgroundColor: [] + }] }, - { - value: 1, - color: "#46BFBD", - highlight: "#5AD3D1", - label: "Anonymous Users" - }], { - responsive: true - }); - - graphs.presence = new Chart(presenceCtx).Doughnut([{ - value: 1, - color:"#F7464A", - highlight: "#FF5A5E", - label: "On categories list" - }, - { - value: 1, - color: "#46BFBD", - highlight: "#5AD3D1", - label: "Reading posts" - }, - { - value: 1, - color: "#FDB45C", - highlight: "#FFC870", - label: "Browsing topics" - }, - { - value: 1, - color: "#949FB1", - highlight: "#A8B3C5", - label: "Recent" - }, - { - value: 1, - color: "#9FB194", - highlight: "#A8B3C5", - label: "Unread" - } - ], { - responsive: true - }); - - graphs.topics = new Chart(topicsCtx).Doughnut([], {responsive: true}); - topicsCanvas.onclick = function(evt){ - var obj = graphs.topics.getSegmentsAtEvent(evt); - if (obj && obj[0]) { - window.open(RELATIVE_PATH + '/topic/' + obj[0].tid); - } - }; + options: { + responsive: true, + legend: { + display: false + } + } + }); updateTrafficGraph(); @@ -302,15 +313,10 @@ define('admin/general/dashboard', ['semver', 'Chart'], function(semver, Chart) { graphData.traffic = data; - // If new data set contains fewer points than currently shown, truncate - while(graphs.traffic.datasets[0].points.length > data.pageviews.length) { - graphs.traffic.removeData(); - } - if (units === 'days') { - graphs.traffic.scale.xLabels = utils.getDaysArray(until); + graphs.traffic.data.xLabels = utils.getDaysArray(until); } else { - graphs.traffic.scale.xLabels = utils.getHoursArray(); + graphs.traffic.data.xLabels = utils.getHoursArray(); $('#pageViewsThisMonth').html(data.monthlyPageViews.thisMonth); $('#pageViewsLastMonth').html(data.monthlyPageViews.lastMonth); @@ -320,17 +326,9 @@ define('admin/general/dashboard', ['semver', 'Chart'], function(semver, Chart) { utils.addCommasToNumbers($('#pageViewsPastDay')); } - for (var i = 0, ii = data.pageviews.length; i < ii; i++) { - if (graphs.traffic.datasets[0].points[i]) { - graphs.traffic.datasets[0].points[i].value = data.pageviews[i]; - graphs.traffic.datasets[0].points[i].label = graphs.traffic.scale.xLabels[i]; - graphs.traffic.datasets[1].points[i].value = data.uniqueVisitors[i]; - graphs.traffic.datasets[1].points[i].label = graphs.traffic.scale.xLabels[i]; - } else { - // No points to replace? Add data. - graphs.traffic.addData([data.pageviews[i], data.uniqueVisitors[i]], graphs.traffic.scale.xLabels[i]); - } - } + graphs.traffic.data.datasets[0].data = data.pageviews; + graphs.traffic.data.datasets[1].data = data.uniqueVisitors; + graphs.traffic.data.labels = graphs.traffic.data.xLabels; graphs.traffic.update(); currentGraph.units = units; @@ -339,22 +337,21 @@ define('admin/general/dashboard', ['semver', 'Chart'], function(semver, Chart) { } function updateRegisteredGraph(registered, anonymous) { - graphs.registered.segments[0].value = registered; - graphs.registered.segments[1].value = anonymous; + graphs.registered.data.datasets[0].data[0] = registered; + graphs.registered.data.datasets[0].data[1] = anonymous; graphs.registered.update(); } function updatePresenceGraph(users) { - graphs.presence.segments[0].value = users.categories; - graphs.presence.segments[1].value = users.topics; - graphs.presence.segments[2].value = users.category; - graphs.presence.segments[3].value = users.recent; - graphs.presence.segments[4].value = users.unread; - + graphs.presence.data.datasets[0].data[0] = users.categories; + graphs.presence.data.datasets[0].data[1] = users.topics; + graphs.presence.data.datasets[0].data[2] = users.category; + graphs.presence.data.datasets[0].data[3] = users.recent; + graphs.presence.data.datasets[0].data[4] = users.unread; graphs.presence.update(); } - + function updateTopicsGraph(topics) { if (!Object.keys(topics).length) { topics = {"0": { @@ -363,88 +360,36 @@ define('admin/general/dashboard', ['semver', 'Chart'], function(semver, Chart) { }}; } - var tids = Object.keys(topics), - segments = graphs.topics.segments; - - function reassignExistingTopics() { - for (var i = segments.length - 1; i >= 0; i--) { - if (!segments[i]) { - continue; - } - - var tid = segments[i].tid; - - if ($.inArray(tid, tids) === -1) { - usedTopicColors.splice($.inArray(segments[i].fillColor, usedTopicColors), 1); - graphs.topics.removeData(i); - } else { - graphs.topics.segments[i].value = topics[tid].value; - delete topics[tid]; - } - } + var tids = Object.keys(topics); + + graphs.topics.data.labels = []; + graphs.topics.data.datasets[0].data = []; + graphs.topics.data.datasets[0].backgroundColor = []; + graphs.topics.data.datasets[0].hoverBackgroundColor = []; + + for (var i = 0, ii = tids.length; i < ii; i++) { + graphs.topics.data.labels.push(topics[tids[i]].title); + graphs.topics.data.datasets[0].data.push(topics[tids[i]].value); + graphs.topics.data.datasets[0].backgroundColor.push(topicColors[i]); + graphs.topics.data.datasets[0].hoverBackgroundColor.push(lighten(topicColors[i], 10)); } - - function assignNewTopics() { - while (segments.length < 10 && tids.length > 0) { - var tid = tids.pop(), - data = topics[tid], - color = null; - - if (!data) { - continue; - } - - if (tid === '0') { - color = '#4D5360'; - } else { - do { - for (var i = 0, ii = topicColors.length; i < ii; i++) { - var chosenColor = topicColors[i]; - - if ($.inArray(chosenColor, usedTopicColors) === -1) { - color = chosenColor; - usedTopicColors.push(color); - break; - } - } - } while (color === null && usedTopicColors.length < topicColors.length); - } - - if (color) { - graphs.topics.addData({ - value: data.value, - color: color, - highlight: lighten(color, 10), - label: data.title - }); - - segments[segments.length - 1].tid = tid; - } - } - } - + function buildTopicsLegend() { var legend = $('#topics-legend').html(''); - segments.sort(function(a, b) { - return b.value - a.value; - }); - for (var i = 0, ii = segments.length; i < ii; i++) { - var topic = segments[i], - label = topic.tid === '0' ? topic.label : ' ' + topic.label + ''; - + for (var i = 0, ii = tids.length; i < ii; i++) { + var topic = topics[tids[i]]; + var label = topic.value === '0' ? topic.title : ' ' + topic.title + ''; + legend.append( '
  • ' + - '
    ' + - '' + label + '' + + '
    ' + + '' + label + '' + '
  • '); } } - reassignExistingTopics(); - assignNewTopics(); buildTopicsLegend(); - graphs.topics.update(); } diff --git a/public/src/admin/manage/category-analytics.js b/public/src/admin/manage/category-analytics.js index cf78cfa5e3..5f4db6922f 100644 --- a/public/src/admin/manage/category-analytics.js +++ b/public/src/admin/manage/category-analytics.js @@ -1,23 +1,23 @@ "use strict"; -/*global config, define, app, socket, ajaxify, bootbox, templates, utils */ +/*global define, ajaxify, utils */ define('admin/manage/category-analytics', ['Chart'], function(Chart) { var CategoryAnalytics = {}; CategoryAnalytics.init = function() { - var hourlyCanvas = document.getElementById('pageviews:hourly'), - dailyCanvas = document.getElementById('pageviews:daily'), - topicsCanvas = document.getElementById('topics:daily'), - postsCanvas = document.getElementById('posts:daily'), - hourlyLabels = utils.getHoursArray().map(function(text, idx) { + var hourlyCanvas = document.getElementById('pageviews:hourly'); + var dailyCanvas = document.getElementById('pageviews:daily'); + var topicsCanvas = document.getElementById('topics:daily'); + var postsCanvas = document.getElementById('posts:daily'); + var hourlyLabels = utils.getHoursArray().map(function(text, idx) { return idx % 3 ? '' : text; - }), - dailyLabels = utils.getDaysArray().map(function(text, idx) { + }); + var dailyLabels = utils.getDaysArray().map(function(text, idx) { return idx % 3 ? '' : text; }); if (utils.isMobile()) { - Chart.defaults.global.showTooltips = false; + Chart.defaults.global.tooltips.enabled = false; } var data = { @@ -26,12 +26,12 @@ define('admin/manage/category-analytics', ['Chart'], function(Chart) { datasets: [ { label: "", - fillColor: "rgba(186,139,175,0.2)", - strokeColor: "rgba(186,139,175,1)", - pointColor: "rgba(186,139,175,1)", - pointStrokeColor: "#fff", - pointHighlightFill: "#fff", - pointHighlightStroke: "rgba(186,139,175,1)", + backgroundColor: "rgba(186,139,175,0.2)", + borderColor: "rgba(186,139,175,1)", + pointBackgroundColor: "rgba(186,139,175,1)", + pointHoverBackgroundColor: "#fff", + pointBorderColor: "#fff", + pointHoverBorderColor: "rgba(186,139,175,1)", data: ajaxify.data.analytics['pageviews:hourly'] } ] @@ -41,12 +41,12 @@ define('admin/manage/category-analytics', ['Chart'], function(Chart) { datasets: [ { label: "", - fillColor: "rgba(151,187,205,0.2)", - strokeColor: "rgba(151,187,205,1)", - pointColor: "rgba(151,187,205,1)", - pointStrokeColor: "#fff", - pointHighlightFill: "#fff", - pointHighlightStroke: "rgba(151,187,205,1)", + backgroundColor: "rgba(151,187,205,0.2)", + borderColor: "rgba(151,187,205,1)", + pointBackgroundColor: "rgba(151,187,205,1)", + pointHoverBackgroundColor: "#fff", + pointBorderColor: "#fff", + pointHoverBorderColor: "rgba(151,187,205,1)", data: ajaxify.data.analytics['pageviews:daily'] } ] @@ -56,12 +56,12 @@ define('admin/manage/category-analytics', ['Chart'], function(Chart) { datasets: [ { label: "", - fillColor: "rgba(171,70,66,0.2)", - strokeColor: "rgba(171,70,66,1)", - pointColor: "rgba(171,70,66,1)", - pointStrokeColor: "#fff", - pointHighlightFill: "#fff", - pointHighlightStroke: "rgba(171,70,66,1)", + backgroundColor: "rgba(171,70,66,0.2)", + borderColor: "rgba(171,70,66,1)", + pointBackgroundColor: "rgba(171,70,66,1)", + pointHoverBackgroundColor: "#fff", + pointBorderColor: "#fff", + pointHoverBorderColor: "rgba(171,70,66,1)", data: ajaxify.data.analytics['topics:daily'] } ] @@ -71,12 +71,12 @@ define('admin/manage/category-analytics', ['Chart'], function(Chart) { datasets: [ { label: "", - fillColor: "rgba(161,181,108,0.2)", - strokeColor: "rgba(161,181,108,1)", - pointColor: "rgba(161,181,108,1)", - pointStrokeColor: "#fff", - pointHighlightFill: "#fff", - pointHighlightStroke: "rgba(161,181,108,1)", + backgroundColor: "rgba(161,181,108,0.2)", + borderColor: "rgba(161,181,108,1)", + pointBackgroundColor: "rgba(161,181,108,1)", + pointHoverBackgroundColor: "#fff", + pointBorderColor: "#fff", + pointHoverBorderColor: "rgba(161,181,108,1)", data: ajaxify.data.analytics['posts:daily'] } ] @@ -87,21 +87,81 @@ define('admin/manage/category-analytics', ['Chart'], function(Chart) { dailyCanvas.width = $(dailyCanvas).parent().width(); topicsCanvas.width = $(topicsCanvas).parent().width(); postsCanvas.width = $(postsCanvas).parent().width(); - new Chart(hourlyCanvas.getContext('2d')).Line(data['pageviews:hourly'], { - responsive: true, - animation: false + + new Chart(hourlyCanvas.getContext('2d'), { + type: 'line', + data: data['pageviews:hourly'], + options: { + responsive: true, + animation: false, + legend: { + display: false + }, + scales: { + yAxes: [{ + ticks: { + beginAtZero: true + } + }] + } + } }); - new Chart(dailyCanvas.getContext('2d')).Line(data['pageviews:daily'], { - responsive: true, - animation: false + + new Chart(dailyCanvas.getContext('2d'), { + type: 'line', + data: data['pageviews:daily'], + options: { + responsive: true, + animation: false, + legend: { + display: false + }, + scales: { + yAxes: [{ + ticks: { + beginAtZero: true + } + }] + } + } }); - new Chart(topicsCanvas.getContext('2d')).Line(data['topics:daily'], { - responsive: true, - animation: false + + new Chart(topicsCanvas.getContext('2d'), { + type: 'line', + data: data['topics:daily'], + options: { + responsive: true, + animation: false, + legend: { + display: false + }, + scales: { + yAxes: [{ + ticks: { + beginAtZero: true + } + }] + } + } }); - new Chart(postsCanvas.getContext('2d')).Line(data['posts:daily'], { - responsive: true, - animation: false + + new Chart(postsCanvas.getContext('2d'), { + type: 'line', + data: data['posts:daily'], + options: { + responsive: true, + animation: false, + legend: { + display: false + }, + scales: { + yAxes: [{ + ticks: { + beginAtZero: true + } + }] + } + } }); }; diff --git a/public/src/admin/manage/category.js b/public/src/admin/manage/category.js index b58d3295b9..8288610e73 100644 --- a/public/src/admin/manage/category.js +++ b/public/src/admin/manage/category.js @@ -18,6 +18,9 @@ define('admin/manage/category', [ if (cid) { modified_categories[cid] = modified_categories[cid] || {}; modified_categories[cid][$(el).attr('data-name')] = $(el).val(); + + app.flags = app.flags || {}; + app.flags._unsaved = true; } } @@ -31,6 +34,7 @@ define('admin/manage/category', [ } if (result && result.length) { + app.flags._unsaved = false; app.alert({ title: 'Updated Categories', message: 'Category IDs ' + result.join(', ') + ' was successfully updated.', diff --git a/public/src/admin/manage/flags.js b/public/src/admin/manage/flags.js index e1ccde9c00..7a89c24845 100644 --- a/public/src/admin/manage/flags.js +++ b/public/src/admin/manage/flags.js @@ -110,7 +110,7 @@ define('admin/manage/flags', [ }); if (utils.isMobile()) { - Chart.defaults.global.showTooltips = false; + Chart.defaults.global.tooltips.enabled = false; } var data = { 'flags:daily': { @@ -118,26 +118,37 @@ define('admin/manage/flags', [ datasets: [ { label: "", - fillColor: "rgba(151,187,205,0.2)", - strokeColor: "rgba(151,187,205,1)", - pointColor: "rgba(151,187,205,1)", - pointStrokeColor: "#fff", - pointHighlightFill: "#fff", - pointHighlightStroke: "rgba(151,187,205,1)", + backgroundColor: "rgba(151,187,205,0.2)", + borderColor: "rgba(151,187,205,1)", + pointBackgroundColor: "rgba(151,187,205,1)", + pointHoverBackgroundColor: "#fff", + pointBorderColor: "#fff", + pointHoverBorderColor: "rgba(151,187,205,1)", data: ajaxify.data.analytics } ] } }; - - dailyCanvas.width = $(dailyCanvas).parent().width(); - new Chart(dailyCanvas.getContext('2d')).Line(data['flags:daily'], { - responsive: true, - animation: false + new Chart(dailyCanvas.getContext('2d'), { + type: 'line', + data: data['flags:daily'], + options: { + responsive: true, + animation: false, + legend: { + display: false + }, + scales: { + yAxes: [{ + ticks: { + beginAtZero: true + } + }] + } + } }); - } return Flags; diff --git a/public/src/admin/settings.js b/public/src/admin/settings.js index 0c341151e1..9e616cfe80 100644 --- a/public/src/admin/settings.js +++ b/public/src/admin/settings.js @@ -27,6 +27,12 @@ define('admin/settings', ['uploader'], function(uploader) { revertBtn = $('#revert'), x, key, inputType, field; + // Handle unsaved changes + $(fields).on('change', function() { + app.flags = app.flags || {}; + app.flags._unsaved = true; + }); + for (x = 0; x < numFields; x++) { field = fields.eq(x); key = field.attr('data-field'); @@ -77,6 +83,9 @@ define('admin/settings', ['uploader'], function(uploader) { type: 'danger' }); } + + app.flags._unsaved = false; + app.alert({ alert_id: 'config_status', timeout: 2500, diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index 211cbb15e4..7280de01ed 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -1,11 +1,9 @@ "use strict"; +/*global app, bootbox, templates, socket, config, RELATIVE_PATH*/ var ajaxify = ajaxify || {}; $(document).ready(function() { - - /*global app, templates, socket, config, RELATIVE_PATH*/ - var location = document.location || window.location; var rootUrl = location.protocol + '//' + (location.hostname || location.host) + (location.port ? ':' + location.port : ''); var apiXHR = null; @@ -295,6 +293,32 @@ $(document).ready(function() { // Enhancing all anchors to ajaxify... $(document.body).on('click', 'a', function (e) { + var _self = this; + var process = function() { + if (!e.ctrlKey && !e.shiftKey && !e.metaKey && e.which === 1) { + if (internalLink) { + var pathname = this.href.replace(rootUrl + RELATIVE_PATH + '/', ''); + + // Special handling for urls with hashes + if (window.location.pathname === this.pathname && this.hash.length) { + window.location.hash = this.hash; + } else { + if (ajaxify.go(pathname)) { + e.preventDefault(); + } + } + } else if (window.location.pathname !== '/outgoing') { + if (config.openOutgoingLinksInNewTab) { + window.open(this.href, '_blank'); + e.preventDefault(); + } else if (config.useOutgoingLinksPage) { + ajaxify.go('outgoing?url=' + encodeURIComponent(this.href)); + e.preventDefault(); + } + } + } + }; + if (this.target !== '' || (this.protocol !== 'http:' && this.protocol !== 'https:')) { return; } @@ -311,6 +335,7 @@ $(document).ready(function() { } } + // Default behaviour for rss feeds if (internalLink && $(this).attr('href').endsWith('.rss')) { return; } @@ -319,28 +344,19 @@ $(document).ready(function() { return e.preventDefault(); } - if (!e.ctrlKey && !e.shiftKey && !e.metaKey && e.which === 1) { - if (internalLink) { - var pathname = this.href.replace(rootUrl + RELATIVE_PATH + '/', ''); - - // Special handling for urls with hashes - if (window.location.pathname === this.pathname && this.hash.length) { - window.location.hash = this.hash; - } else { - if (ajaxify.go(pathname)) { - e.preventDefault(); + if (app.flags && app.flags.hasOwnProperty('_unsaved') && app.flags._unsaved === true) { + translator.translate('[[global:unsaved-changes]]', function(text) { + bootbox.confirm(text, function(navigate) { + if (navigate) { + app.flags._unsaved = false; + process.call(_self); } - } - } else if (window.location.pathname !== '/outgoing') { - if (config.openOutgoingLinksInNewTab) { - window.open(this.href, '_blank'); - e.preventDefault(); - } else if (config.useOutgoingLinksPage) { - ajaxify.go('outgoing?url=' + encodeURIComponent(this.href)); - e.preventDefault(); - } - } + }); + }); + return e.preventDefault(); } + + process.call(_self); }); } diff --git a/public/src/app.js b/public/src/app.js index 8875bd2d64..e9b52ef0bf 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -473,33 +473,35 @@ app.cacheBuster = null; if (!config.requireEmailConfirmation || !app.user.uid) { return; } + var msg = { + alert_id: 'email_confirm', + type: 'warning', + timeout: 0 + }; + if (!app.user.email) { - app.alert({ - alert_id: 'email_confirm', - message: '[[error:no-email-to-confirm]]', - type: 'warning', - timeout: 0, - clickfn: function() { - app.removeAlert('email_confirm'); - ajaxify.go('user/' + app.user.userslug + '/edit'); - } - }); - } else if (!app.user['email:confirmed']) { - app.alert({ - alert_id: 'email_confirm', - message: err ? err.message : '[[error:email-not-confirmed]]', - type: 'warning', - timeout: 0, - clickfn: function() { - app.removeAlert('email_confirm'); - socket.emit('user.emailConfirm', {}, function(err) { - if (err) { - return app.alertError(err.message); - } - app.alertSuccess('[[notifications:email-confirm-sent]]'); - }); - } - }); + msg.message = '[[error:no-email-to-confirm]]'; + msg.clickfn = function() { + app.removeAlert('email_confirm'); + ajaxify.go('user/' + app.user.userslug + '/edit'); + }; + app.alert(msg); + } else if (!app.user['email:confirmed'] && !app.user.isEmailConfirmSent) { + msg.message = err ? err.message : '[[error:email-not-confirmed]]'; + msg.clickfn = function() { + app.removeAlert('email_confirm'); + socket.emit('user.emailConfirm', {}, function(err) { + if (err) { + return app.alertError(err.message); + } + app.alertSuccess('[[notifications:email-confirm-sent]]'); + }); + }; + + app.alert(msg); + } else if (!app.user['email:confirmed'] && app.user.isEmailConfirmSent) { + msg.message = '[[error:email-not-confirmed-email-sent]]'; + app.alert(msg); } }; diff --git a/public/src/client/category.js b/public/src/client/category.js index fc4e7e21f3..98d0063b4b 100644 --- a/public/src/client/category.js +++ b/public/src/client/category.js @@ -196,7 +196,8 @@ define('forum/category', [ templates.parse('category', 'topics', { privileges: {editable: editable}, showSelect: editable, - topics: [topic] + topics: [topic], + template: {category: true} }, function(html) { translator.translate(html, function(translatedHTML) { var topic = $(translatedHTML), @@ -347,4 +348,4 @@ define('forum/category', [ }; return Category; -}); \ No newline at end of file +}); diff --git a/public/src/modules/settings.js b/public/src/modules/settings.js index 652398f539..79c150854d 100644 --- a/public/src/modules/settings.js +++ b/public/src/modules/settings.js @@ -473,6 +473,12 @@ define('settings', function () { }); $(window).trigger('action:admin.settingsLoaded'); + // Handle unsaved changes + $(formEl).on('change', 'input, select, textarea', function() { + app.flags = app.flags || {}; + app.flags._unsaved = true; + }); + callback(null, values); }); }, @@ -498,6 +504,9 @@ define('settings', function () { hash: hash, values: values }, function (err) { + // Remove unsaved flag to re-enable ajaxify + app.flags._unsaved = false; + if (typeof callback === 'function') { callback(); } else { diff --git a/public/vendor/autosize.js b/public/vendor/autosize.js deleted file mode 100644 index aa51c70572..0000000000 --- a/public/vendor/autosize.js +++ /dev/null @@ -1,254 +0,0 @@ -/*! - Autosize 3.0.15 - license: MIT - http://www.jacklmoore.com/autosize -*/ -(function (global, factory) { - if (typeof define === 'function' && define.amd) { - define('autosize', ['exports', 'module'], factory); - } else if (typeof exports !== 'undefined' && typeof module !== 'undefined') { - factory(exports, module); - } else { - var mod = { - exports: {} - }; - factory(mod.exports, mod); - global.autosize = mod.exports; - } -})(this, function (exports, module) { - 'use strict'; - - var set = typeof Set === 'function' ? new Set() : (function () { - var list = []; - - return { - has: function has(key) { - return Boolean(list.indexOf(key) > -1); - }, - add: function add(key) { - list.push(key); - }, - 'delete': function _delete(key) { - list.splice(list.indexOf(key), 1); - } }; - })(); - - var createEvent = function createEvent(name) { - return new Event(name); - }; - try { - new Event('test'); - } catch (e) { - // IE does not support `new Event()` - createEvent = function (name) { - var evt = document.createEvent('Event'); - evt.initEvent(name, true, false); - return evt; - }; - } - - function assign(ta) { - var _ref = arguments[1] === undefined ? {} : arguments[1]; - - var _ref$setOverflowX = _ref.setOverflowX; - var setOverflowX = _ref$setOverflowX === undefined ? true : _ref$setOverflowX; - var _ref$setOverflowY = _ref.setOverflowY; - var setOverflowY = _ref$setOverflowY === undefined ? true : _ref$setOverflowY; - - if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || set.has(ta)) return; - - var heightOffset = null; - var overflowY = null; - var clientWidth = ta.clientWidth; - - function init() { - var style = window.getComputedStyle(ta, null); - - overflowY = style.overflowY; - - if (style.resize === 'vertical') { - ta.style.resize = 'none'; - } else if (style.resize === 'both') { - ta.style.resize = 'horizontal'; - } - - if (style.boxSizing === 'content-box') { - heightOffset = -(parseFloat(style.paddingTop) + parseFloat(style.paddingBottom)); - } else { - heightOffset = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth); - } - // Fix when a textarea is not on document body and heightOffset is Not a Number - if (isNaN(heightOffset)) { - heightOffset = 0; - } - - update(); - } - - function changeOverflow(value) { - { - // Chrome/Safari-specific fix: - // When the textarea y-overflow is hidden, Chrome/Safari do not reflow the text to account for the space - // made available by removing the scrollbar. The following forces the necessary text reflow. - var width = ta.style.width; - ta.style.width = '0px'; - // Force reflow: - /* jshint ignore:start */ - ta.offsetWidth; - /* jshint ignore:end */ - ta.style.width = width; - } - - overflowY = value; - - if (setOverflowY) { - ta.style.overflowY = value; - } - - resize(); - } - - function resize() { - var htmlTop = window.pageYOffset; - var bodyTop = document.body.scrollTop; - var originalHeight = ta.style.height; - - ta.style.height = 'auto'; - - var endHeight = ta.scrollHeight + heightOffset; - - if (ta.scrollHeight === 0) { - // If the scrollHeight is 0, then the element probably has display:none or is detached from the DOM. - ta.style.height = originalHeight; - return; - } - - ta.style.height = endHeight + 'px'; - - // used to check if an update is actually necessary on window.resize - clientWidth = ta.clientWidth; - - // prevents scroll-position jumping - document.documentElement.scrollTop = htmlTop; - document.body.scrollTop = bodyTop; - } - - function update() { - var startHeight = ta.style.height; - - resize(); - - var style = window.getComputedStyle(ta, null); - - if (style.height !== ta.style.height) { - if (overflowY !== 'visible') { - changeOverflow('visible'); - } - } else { - if (overflowY !== 'hidden') { - changeOverflow('hidden'); - } - } - - if (startHeight !== ta.style.height) { - var evt = createEvent('autosize:resized'); - ta.dispatchEvent(evt); - } - } - - var pageResize = function pageResize() { - if (ta.clientWidth !== clientWidth) { - update(); - } - }; - - var destroy = (function (style) { - window.removeEventListener('resize', pageResize, false); - ta.removeEventListener('input', update, false); - ta.removeEventListener('keyup', update, false); - ta.removeEventListener('autosize:destroy', destroy, false); - ta.removeEventListener('autosize:update', update, false); - set['delete'](ta); - - Object.keys(style).forEach(function (key) { - ta.style[key] = style[key]; - }); - }).bind(ta, { - height: ta.style.height, - resize: ta.style.resize, - overflowY: ta.style.overflowY, - overflowX: ta.style.overflowX, - wordWrap: ta.style.wordWrap }); - - ta.addEventListener('autosize:destroy', destroy, false); - - // IE9 does not fire onpropertychange or oninput for deletions, - // so binding to onkeyup to catch most of those events. - // There is no way that I know of to detect something like 'cut' in IE9. - if ('onpropertychange' in ta && 'oninput' in ta) { - ta.addEventListener('keyup', update, false); - } - - window.addEventListener('resize', pageResize, false); - ta.addEventListener('input', update, false); - ta.addEventListener('autosize:update', update, false); - set.add(ta); - - if (setOverflowX) { - ta.style.overflowX = 'hidden'; - ta.style.wordWrap = 'break-word'; - } - - init(); - } - - function destroy(ta) { - if (!(ta && ta.nodeName && ta.nodeName === 'TEXTAREA')) return; - var evt = createEvent('autosize:destroy'); - ta.dispatchEvent(evt); - } - - function update(ta) { - if (!(ta && ta.nodeName && ta.nodeName === 'TEXTAREA')) return; - var evt = createEvent('autosize:update'); - ta.dispatchEvent(evt); - } - - var autosize = null; - - // Do nothing in Node.js environment and IE8 (or lower) - if (typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') { - autosize = function (el) { - return el; - }; - autosize.destroy = function (el) { - return el; - }; - autosize.update = function (el) { - return el; - }; - } else { - autosize = function (el, options) { - if (el) { - Array.prototype.forEach.call(el.length ? el : [el], function (x) { - return assign(x, options); - }); - } - return el; - }; - autosize.destroy = function (el) { - if (el) { - Array.prototype.forEach.call(el.length ? el : [el], destroy); - } - return el; - }; - autosize.update = function (el) { - if (el) { - Array.prototype.forEach.call(el.length ? el : [el], update); - } - return el; - }; - } - - module.exports = autosize; -}); \ No newline at end of file diff --git a/src/messaging/notifications.js b/src/messaging/notifications.js index e58c3dc660..6747192b6e 100644 --- a/src/messaging/notifications.js +++ b/src/messaging/notifications.js @@ -91,7 +91,7 @@ module.exports = function(Messaging) { userData: function(next) { user.getUsersFields(uids, ['uid', 'username', 'userslug'], next); }, - settings: function(next) { + userSettings: function(next) { user.getMultipleUserSettings(uids, next); } }, function(err, results) { diff --git a/src/meta/js.js b/src/meta/js.js index 7a0a57c86b..e25ac45ba0 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -30,7 +30,6 @@ module.exports = function(Meta) { 'public/vendor/tinycon/tinycon.js', 'public/vendor/xregexp/xregexp.js', 'public/vendor/xregexp/unicode/unicode-base.js', - 'public/vendor/autosize.js', './node_modules/templates.js/lib/templates.js', 'public/src/utils.js', 'public/src/sockets.js', @@ -79,7 +78,7 @@ module.exports = function(Meta) { // modules listed below are routed through express (/src/modules) so they can be defined anonymously modules: { - "Chart.js": './node_modules/chart.js/Chart.js', + "Chart.js": './node_modules/chart.js/dist/Chart.min.js', "mousetrap.js": './node_modules/mousetrap/mousetrap.js', "buzz.js": 'public/vendor/buzz/buzz.js' diff --git a/src/middleware/header.js b/src/middleware/header.js index ef73610b92..4f25a55ca5 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -3,6 +3,7 @@ var async = require('async'); var nconf = require('nconf'); +var db = require('../database'); var user = require('../user'); var meta = require('../meta'); var plugins = require('../plugins'); @@ -94,6 +95,12 @@ module.exports = function(app, middleware) { next(null, userData); } }, + isEmailConfirmSent: function(next) { + if (!meta.config.requireEmailConfirmation || !req.uid) { + return next(null, false); + } + db.get('uid:' + req.uid + ':confirm:email:sent', next); + }, navigation: async.apply(navigation.get), tags: async.apply(meta.tags.parse, res.locals.metaTags, res.locals.linkTags) }, function(err, results) { @@ -110,6 +117,7 @@ module.exports = function(app, middleware) { results.user.isGlobalMod = results.isGlobalMod; results.user.uid = parseInt(results.user.uid, 10); results.user['email:confirmed'] = parseInt(results.user['email:confirmed'], 10) === 1; + results.user.isEmailConfirmSent = !!results.isEmailConfirmSent; if (parseInt(meta.config.disableCustomUserSkins, 10) !== 1 && res.locals.config.bootswatchSkin !== 'default') { templateValues.bootswatchCSS = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + res.locals.config.bootswatchSkin + '/bootstrap.min.css'; diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index a1c98b0c3d..afae4957ea 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -206,6 +206,7 @@ middleware.isAdmin = function(req, res, next) { var loginTime = req.session.meta ? req.session.meta.datetime : 0; if (loginTime && parseInt(loginTime, 10) > Date.now() - 3600000) { + req.session.meta.datetime += 300000; return next(); } diff --git a/src/plugins.js b/src/plugins.js index da7f25eefe..6eba5de2ce 100644 --- a/src/plugins.js +++ b/src/plugins.js @@ -425,28 +425,4 @@ var middleware; ], next); }; - // function addLanguages(params, callback) { - // Plugins.customLanguages.forEach(function(lang) { - // console.log('route for', '/language/' + lang.route); - // params.router.get('/language' + lang.route, function(req, res, next) { - // res.json(lang.file); - // }); - - // var components = lang.route.split('/'), - // language = components[1], - // filename = components[2].replace('.json', ''); - - // translator.addTranslation(language, filename, lang.file); - // }); - - // for(var resource in Plugins.customLanguageFallbacks) { - // params.router.get('/language/:lang/' + resource + '.json', function(req, res, next) { - // winston.verbose('[translator] No resource file found for ' + req.params.lang + '/' + path.basename(req.path, '.json') + ', using provided fallback language file'); - // res.sendFile(Plugins.customLanguageFallbacks[path.basename(req.path, '.json')]); - // }); - // } - - // callback(null); - // } - }(exports)); diff --git a/src/plugins/hooks.js b/src/plugins/hooks.js index c2dde7b92a..3e0bdcc848 100644 --- a/src/plugins/hooks.js +++ b/src/plugins/hooks.js @@ -14,13 +14,17 @@ module.exports = function(Plugins) { `data.priority`, the relative priority of the method when it is eventually called (default: 10) */ Plugins.registerHook = function(id, data, callback) { + callback = callback || function() {}; function register() { Plugins.loadedHooks[data.hook] = Plugins.loadedHooks[data.hook] || []; Plugins.loadedHooks[data.hook].push(data); - if (typeof callback === 'function') { - callback(); - } + callback(); + } + + if (!data.hook) { + winston.warn('[plugins/' + id + '] registerHook called with invalid data.hook', data); + return callback(); } var method; @@ -65,6 +69,7 @@ module.exports = function(Plugins) { register(); } else { winston.warn('[plugins/' + id + '] Hook method mismatch: ' + data.hook + ' => ' + data.method); + return callback(); } } }; diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 772166687a..cebbfe1a24 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -1,6 +1,6 @@ "use strict"; -var SocketIO = require('socket.io'); +var SocketIO = require('socket.io'); var socketioWildcard = require('socketio-wildcard')(); var async = require('async'); var nconf = require('nconf'); diff --git a/src/user/email.js b/src/user/email.js index 4b98cc72aa..902d2ead73 100644 --- a/src/user/email.js +++ b/src/user/email.js @@ -28,8 +28,8 @@ var emailer = require('../emailer'); UserEmail.sendValidationEmail = function(uid, email, callback) { callback = callback || function() {}; - var confirm_code = utils.generateUUID(), - confirm_link = nconf.get('url') + '/confirm/' + confirm_code; + var confirm_code = utils.generateUUID(); + var confirm_link = nconf.get('url') + '/confirm/' + confirm_code; var emailInterval = meta.config.hasOwnProperty('emailConfirmInterval') ? parseInt(meta.config.emailConfirmInterval, 10) : 10; @@ -97,6 +97,7 @@ var emailer = require('../emailer'); async.series([ async.apply(user.setUserField, confirmObj.uid, 'email:confirmed', 1), async.apply(db.delete, 'confirm:' + code), + async.apply(db.delete, 'uid:' + confirmObj.uid + ':confirm:email:sent'), function(next) { db.sortedSetRemove('users:notvalidated', confirmObj.uid, next); } diff --git a/src/user/picture.js b/src/user/picture.js index 385050fcf2..38d013cab0 100644 --- a/src/user/picture.js +++ b/src/user/picture.js @@ -25,6 +25,7 @@ module.exports = function(User) { var updateUid = uid; var imageDimension = parseInt(meta.config.profileImageDimension, 10) || 128; var convertToPNG = parseInt(meta.config['profile:convertProfileImageToPNG'], 10) === 1; + var keepAllVersions = parseInt(meta.config['profile:keepAllUserImages'], 10) === 1; var uploadedImage; async.waterfall([ @@ -42,7 +43,7 @@ module.exports = function(User) { return plugins.fireHook('filter:uploadImage', {image: picture, uid: updateUid}, next); } - var filename = updateUid + '-profileimg' + (convertToPNG ? '.png' : extension); + var filename = updateUid + '-profileimg' + (keepAllVersions ? '-' + Date.now() : '') + (convertToPNG ? '.png' : extension); async.waterfall([ function(next) { @@ -68,21 +69,7 @@ module.exports = function(User) { }); }, function(next) { - User.getUserField(updateUid, 'uploadedpicture', next); - }, - function(oldpicture, next) { - if (!oldpicture) { - return file.saveFileToLocal(filename, 'profile', picture.path, next); - } - var oldpicturePath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), 'profile', path.basename(oldpicture)); - - fs.unlink(oldpicturePath, function (err) { - if (err) { - winston.error(err); - } - - file.saveFileToLocal(filename, 'profile', picture.path, next); - }); + file.saveFileToLocal(filename, 'profile', picture.path, next); }, ], next); }, @@ -134,6 +121,7 @@ module.exports = function(User) { }; User.updateCoverPicture = function(data, callback) { + var keepAllVersions = parseInt(meta.config['profile:keepAllUserImages'], 10) === 1; var url, md5sum; if (!data.imageData && data.position) { @@ -181,7 +169,7 @@ module.exports = function(User) { return plugins.fireHook('filter:uploadImage', {image: image, uid: data.uid}, next); } - var filename = data.uid + '-profilecover'; + var filename = data.uid + '-profilecover' + (keepAllVersions ? '-' + Date.now() : ''); async.waterfall([ function (next) { file.isFileTypeAllowed(data.file.path, next); diff --git a/src/views/admin/header.tpl b/src/views/admin/header.tpl index 03cab80005..3d797ed5be 100644 --- a/src/views/admin/header.tpl +++ b/src/views/admin/header.tpl @@ -15,7 +15,8 @@ var app = { template: "{template.name}", user: JSON.parse('{{userJSON}}'), - config: JSON.parse(decodeURIComponent("{{adminConfigJSON}}")) + config: JSON.parse(decodeURIComponent("{{adminConfigJSON}}")), + flags: {} }; diff --git a/src/views/admin/partials/menu.tpl b/src/views/admin/partials/menu.tpl index 38c5e5eeb8..e35b41847c 100644 --- a/src/views/admin/partials/menu.tpl +++ b/src/views/admin/partials/menu.tpl @@ -118,15 +118,9 @@