diff --git a/package.json b/package.json
index 35a00723e9..8a7041d5a5 100644
--- a/package.json
+++ b/package.json
@@ -55,7 +55,7 @@
"morgan": "^1.3.2",
"mousetrap": "^1.5.3",
"nconf": "~0.8.2",
- "nodebb-plugin-composer-default": "4.4.17",
+ "nodebb-plugin-composer-default": "4.4.18",
"nodebb-plugin-dbsearch": "2.0.4",
"nodebb-plugin-emoji-extended": "1.1.1",
"nodebb-plugin-emoji-one": "1.2.1",
diff --git a/public/language/he/admin/admin.json b/public/language/he/admin/admin.json
index c3f8841544..073a6b28be 100644
--- a/public/language/he/admin/admin.json
+++ b/public/language/he/admin/admin.json
@@ -2,6 +2,6 @@
"alert.confirm-reload": "האם אתה בטוח שאתה רוצה לטעון מחדש את NodeBB?",
"alert.confirm-restart": "האם אתה בטוח שאתה רוצה לאתחל מחדש את NodeBB?",
- "acp-title": "1% | לוח בקרה לאדמין NodeBB",
+ "acp-title": "%1 | לוח בקרה לאדמין NodeBB",
"settings-header-contents": "תוכן"
}
\ No newline at end of file
diff --git a/public/language/it/notifications.json b/public/language/it/notifications.json
index 70b794bdc6..11c9941543 100644
--- a/public/language/it/notifications.json
+++ b/public/language/it/notifications.json
@@ -10,15 +10,15 @@
"return_to": "Ritorna a %1",
"new_notification": "Nuova Notifica",
"you_have_unread_notifications": "Hai notifiche non lette.",
- "all": "All",
+ "all": "Tutte",
"topics": "Topics",
- "replies": "Replies",
- "chat": "Chats",
- "follows": "Follows",
- "upvote": "Upvotes",
- "new-flags": "New Flags",
- "my-flags": "Flags assigned to me",
- "bans": "Bans",
+ "replies": "Risposte",
+ "chat": "Chat",
+ "follows": "Segui",
+ "upvote": "Voti",
+ "new-flags": "Nuove segnalazioni",
+ "my-flags": "Segnalazioni assegnate a me",
+ "bans": "Espulsioni",
"new_message_from": "Nuovo messaggio da %1",
"upvoted_your_post_in": "%1 ha votato positivamente il tuo post in %2.",
"upvoted_your_post_in_dual": "%1 e %2 hanno apprezzato il tuo post in %3.",
diff --git a/public/language/pl/notifications.json b/public/language/pl/notifications.json
index 77a4567ff8..9d1d4d7882 100644
--- a/public/language/pl/notifications.json
+++ b/public/language/pl/notifications.json
@@ -10,15 +10,15 @@
"return_to": "Wróć do %1",
"new_notification": "Nowe powiadomienie",
"you_have_unread_notifications": "Masz nieprzeczytane powiadomienia.",
- "all": "All",
+ "all": "Wszystko",
"topics": "Topics",
- "replies": "Replies",
- "chat": "Chats",
- "follows": "Follows",
- "upvote": "Upvotes",
- "new-flags": "New Flags",
- "my-flags": "Flags assigned to me",
- "bans": "Bans",
+ "replies": "Odpowiedzi",
+ "chat": "Rozmowy",
+ "follows": "Obserwowani",
+ "upvote": "Głosy na tak",
+ "new-flags": "Nowe flagi",
+ "my-flags": "Flagi przypisane mnie",
+ "bans": "Bany",
"new_message_from": "Nowa wiadomość od %1",
"upvoted_your_post_in": "%1 zagłosował na Twój post w %2",
"upvoted_your_post_in_dual": "%1 oraz%2 za na twój post w %3.",
diff --git a/public/language/pt-PT/notifications.json b/public/language/pt-PT/notifications.json
index 6e7c61c457..cc059ae2b4 100644
--- a/public/language/pt-PT/notifications.json
+++ b/public/language/pt-PT/notifications.json
@@ -10,15 +10,15 @@
"return_to": "Voltar a %1",
"new_notification": "Nova notificação",
"you_have_unread_notifications": "Tens notificações por ler.",
- "all": "All",
+ "all": "Tudo",
"topics": "Topics",
- "replies": "Replies",
- "chat": "Chats",
- "follows": "Follows",
- "upvote": "Upvotes",
- "new-flags": "New Flags",
- "my-flags": "Flags assigned to me",
- "bans": "Bans",
+ "replies": "Respostas",
+ "chat": "Chat",
+ "follows": "Novos seguidores",
+ "upvote": "Votos positivos",
+ "new-flags": "Novas sinalizações",
+ "my-flags": "Sinalizações para mim",
+ "bans": "Banimentos",
"new_message_from": "Nova mensagem de %1",
"upvoted_your_post_in": "%1 votou de forma favorável na tua publicação em %2.",
"upvoted_your_post_in_dual": "%1 e %2 votaram favoravelmente à tua publicação em %3.",
diff --git a/public/language/ru/notifications.json b/public/language/ru/notifications.json
index 0e16058163..81f06dd3b1 100644
--- a/public/language/ru/notifications.json
+++ b/public/language/ru/notifications.json
@@ -10,15 +10,15 @@
"return_to": "Вернуться к %1",
"new_notification": "Новое уведомление",
"you_have_unread_notifications": "У вас есть непрочитанные уведомления.",
- "all": "All",
+ "all": "Все",
"topics": "Topics",
- "replies": "Replies",
- "chat": "Chats",
- "follows": "Follows",
- "upvote": "Upvotes",
- "new-flags": "New Flags",
- "my-flags": "Flags assigned to me",
- "bans": "Bans",
+ "replies": "Ответы",
+ "chat": "Чаты",
+ "follows": "Следят",
+ "upvote": "Понравилось",
+ "new-flags": "Новые закладки",
+ "my-flags": "Закладки для меня",
+ "bans": "Блокировки",
"new_message_from": "Новое сообщение от участника %1",
"upvoted_your_post_in": "Участник %1 проголосовал за ваше сообщение в %2.",
"upvoted_your_post_in_dual": "Участники %1 и %2 проголосовали за ваше сообщение в %3.",
diff --git a/public/language/sr/admin/admin.json b/public/language/sr/admin/admin.json
index 9c01f56006..c571378ac3 100644
--- a/public/language/sr/admin/admin.json
+++ b/public/language/sr/admin/admin.json
@@ -1,7 +1,7 @@
{
- "alert.confirm-reload": "Are you sure you wish to reload NodeBB?",
- "alert.confirm-restart": "Are you sure you wish to restart NodeBB?",
+ "alert.confirm-reload": "Da li želite da ponovo učitate NodeBB?",
+ "alert.confirm-restart": "Da li želite da restartujete NodeBB?",
- "acp-title": "%1 | NodeBB Admin Control Panel",
- "settings-header-contents": "Contents"
+ "acp-title": "%1 | NodeBB Administratorski panel",
+ "settings-header-contents": "Sadržaj"
}
\ No newline at end of file
diff --git a/public/language/sr/admin/advanced/database.json b/public/language/sr/admin/advanced/database.json
index b88ca6fc82..dfcd8313bd 100644
--- a/public/language/sr/admin/advanced/database.json
+++ b/public/language/sr/admin/advanced/database.json
@@ -1,36 +1,36 @@
{
"x-b": "%1 b",
- "x-mb": "%1 mb",
- "x-gb": "%1 gb",
+ "x-mb": "%1 megabajt",
+ "x-gb": "%1 gigabajt",
"uptime-seconds": "Uptime in Seconds",
"uptime-days": "Uptime in Days",
"mongo": "Mongo",
- "mongo.version": "MongoDB Version",
+ "mongo.version": "MongoDB verzija",
"mongo.storage-engine": "Storage Engine",
- "mongo.collections": "Collections",
- "mongo.objects": "Objects",
- "mongo.avg-object-size": "Avg. Object Size",
- "mongo.data-size": "Data Size",
+ "mongo.collections": "Kolekcije",
+ "mongo.objects": "Objekti",
+ "mongo.avg-object-size": "Prosečna veličina objekta",
+ "mongo.data-size": "Veličina podatka",
"mongo.storage-size": "Storage Size",
- "mongo.index-size": "Index Size",
- "mongo.file-size": "File Size",
+ "mongo.index-size": "Veličina Index-a",
+ "mongo.file-size": "Veličina Fajla",
"mongo.resident-memory": "Resident Memory",
- "mongo.virtual-memory": "Virtual Memory",
- "mongo.mapped-memory": "Mapped Memory",
- "mongo.raw-info": "MongoDB Raw Info",
+ "mongo.virtual-memory": "Virtuelna memorija",
+ "mongo.mapped-memory": "Mapirana Memorija",
+ "mongo.raw-info": "Sirove informacije o MongoDB",
"redis": "Redis",
- "redis.version": "Redis Version",
- "redis.connected-clients": "Connected Clients",
- "redis.connected-slaves": "Connected Slaves",
- "redis.blocked-clients": "Blocked Clients",
- "redis.used-memory": "Used Memory",
- "redis.memory-frag-ratio": "Memory Fragmentation Ratio",
- "redis.total-connections-recieved": "Total Connections Received",
- "redis.total-commands-processed": "Total Commands Processed",
- "redis.iops": "Instantaneous Ops. Per Second",
+ "redis.version": "Redis verzija",
+ "redis.connected-clients": "Klijenata povezano",
+ "redis.connected-slaves": "Povezano \"robova\"",
+ "redis.blocked-clients": "Klijenata blokirano",
+ "redis.used-memory": "Iskorišćena memorija",
+ "redis.memory-frag-ratio": "Odnos fragmentisane memorije",
+ "redis.total-connections-recieved": "Ukupno primljeno konekcija",
+ "redis.total-commands-processed": "Ukupno komandi procesuirano",
+ "redis.iops": "Trenutno operacija po sekundi",
"redis.keyspace-hits": "Keyspace Hits",
"redis.keyspace-misses": "Keyspace Misses",
- "redis.raw-info": "Redis Raw Info"
+ "redis.raw-info": "Sirove informacije o Redis-u"
}
\ No newline at end of file
diff --git a/public/language/sr/admin/menu.json b/public/language/sr/admin/menu.json
index 985c540e8a..f56feb43d7 100644
--- a/public/language/sr/admin/menu.json
+++ b/public/language/sr/admin/menu.json
@@ -1,74 +1,74 @@
{
- "section-general": "General",
- "general/dashboard": "Dashboard",
- "general/homepage": "Home Page",
- "general/navigation": "Navigation",
- "general/languages": "Languages",
- "general/sounds": "Sounds",
- "general/social": "Social",
+ "section-general": "Uopšteno",
+ "general/dashboard": "Komandna tabla",
+ "general/homepage": "Home stranica",
+ "general/navigation": "Navigacija",
+ "general/languages": "Jezici",
+ "general/sounds": "Zvukovi",
+ "general/social": "Društveno",
- "section-manage": "Manage",
- "manage/categories": "Categories",
- "manage/tags": "Tags",
- "manage/users": "Users",
- "manage/registration": "Registration Queue",
- "manage/groups": "Groups",
- "manage/ip-blacklist": "IP Blacklist",
+ "section-manage": "Menadžment",
+ "manage/categories": "Kategorije",
+ "manage/tags": "Tagovi",
+ "manage/users": "Korisnici",
+ "manage/registration": "Lista Registracija",
+ "manage/groups": "Grupe",
+ "manage/ip-blacklist": "Crna Lista IP adresa",
- "section-settings": "Settings",
- "settings/general": "General",
- "settings/reputation": "Reputation",
+ "section-settings": "Podešavanja",
+ "settings/general": "Uopšteno",
+ "settings/reputation": "Reputacija",
"settings/email": "Email",
- "settings/user": "User",
- "settings/group": "Group",
- "settings/guest": "Guests",
- "settings/uploads": "Uploads",
- "settings/post": "Post",
- "settings/chat": "Chat",
+ "settings/user": "Korisnik",
+ "settings/group": "Grupa",
+ "settings/guest": "Gosti",
+ "settings/uploads": "Otpremljene datoteke",
+ "settings/post": "Poruka",
+ "settings/chat": "Ćaskanje",
"settings/pagination": "Pagination",
"settings/tags": "Tags",
- "settings/notifications": "Notifications",
+ "settings/notifications": "Notifikacije",
"settings/cookies": "Cookies",
"settings/web-crawler": "Web Crawler",
"settings/sockets": "Sockets",
- "settings/advanced": "Advanced",
+ "settings/advanced": "Napredno",
- "settings.page-title": "%1 Settings",
+ "settings.page-title": "%1 Podešavanja",
- "section-appearance": "Appearance",
- "appearance/themes": "Themes",
+ "section-appearance": "Izgled",
+ "appearance/themes": "Teme",
"appearance/skins": "Skins",
"appearance/customise": "Custom HTML & CSS",
- "section-extend": "Extend",
- "extend/plugins": "Plugins",
- "extend/widgets": "Widgets",
- "extend/rewards": "Rewards",
+ "section-extend": "Proširiti",
+ "extend/plugins": "Plaginovi",
+ "extend/widgets": "Vidžeti",
+ "extend/rewards": "Nagrade",
- "section-social-auth": "Social Authentication",
+ "section-social-auth": "Auntentifikacija sa društvenih mreža",
"section-plugins": "Plugins",
- "extend/plugins.install": "Install Plugins",
+ "extend/plugins.install": "Instaliraj plaginove",
- "section-advanced": "Advanced",
- "advanced/database": "Database",
- "advanced/events": "Events",
- "advanced/logs": "Logs",
- "advanced/errors": "Errors",
+ "section-advanced": "Napredno",
+ "advanced/database": "Baza podataka",
+ "advanced/events": "Događaji",
+ "advanced/logs": "Izveštaji",
+ "advanced/errors": "Greške",
"advanced/cache": "Cache",
- "development/logger": "Logger",
+ "development/logger": "Loger",
"development/info": "Info",
- "reload-forum": "Reload Forum",
- "restart-forum": "Restart Forum",
- "logout": "Log out",
- "view-forum": "View Forum",
+ "reload-forum": "Ponovo učitaj podešavanja Foruma",
+ "restart-forum": "Ponovo učitaj forum",
+ "logout": "Izloguj se",
+ "view-forum": "Pogledaj Forum",
- "search.placeholder": "Search...",
- "search.no-results": "No results...",
- "search.search-forum": "Search the forum for ",
- "search.keep-typing": "Type more to see results...",
- "search.start-typing": "Start typing to see results...",
+ "search.placeholder": "Pretraga...",
+ "search.no-results": "Nema rezultata...",
+ "search.search-forum": "Pretraži forum za ",
+ "search.keep-typing": "Ukucaj više da vidiš rezultate",
+ "search.start-typing": "Počni da kucaš da vidiš rezultate...",
- "connection-lost": "Connection to %1 has been lost, attempting to reconnect..."
+ "connection-lost": "Konekcija ka %1 je izgubljena, pokušavam ponovo da se konektujem..."
}
\ No newline at end of file
diff --git a/public/language/sr/admin/settings/user.json b/public/language/sr/admin/settings/user.json
index fa8049c3a9..a9e9709e24 100644
--- a/public/language/sr/admin/settings/user.json
+++ b/public/language/sr/admin/settings/user.json
@@ -1,62 +1,62 @@
{
- "authentication": "Authentication",
- "allow-local-login": "Allow local login",
- "require-email-confirmation": "Require Email Confirmation",
- "email-confirm-interval": "User may not resend a confirmation email until",
- "email-confirm-email2": "minutes have elapsed",
- "allow-login-with": "Allow login with",
- "allow-login-with.username-email": "Username or Email",
- "allow-login-with.username": "Username Only",
- "allow-login-with.email": "Email Only",
- "account-settings": "Account Settings",
- "disable-username-changes": "Disable username changes",
- "disable-email-changes": "Disable email changes",
- "disable-password-changes": "Disable password changes",
- "allow-account-deletion": "Allow account deletion",
- "user-info-private": "Make user info private",
- "themes": "Themes",
- "disable-user-skins": "Prevent users from choosing a custom skin",
- "account-protection": "Account Protection",
- "login-attempts": "Login attempts per hour",
- "login-attempts-help": "If login attempts to a user's account exceeds this threshold, that account will be locked for a pre-configured amount of time",
- "lockout-duration": "Account Lockout Duration (minutes)",
+ "authentication": "Auntentifikacija",
+ "allow-local-login": "Dozvoli lokalni login",
+ "require-email-confirmation": "Zahteva Email konfirmaciju",
+ "email-confirm-interval": "Korisnik možda neće moći da ponovo pošalje email konfirmaciju sve dok",
+ "email-confirm-email2": "minuta je prošlo",
+ "allow-login-with": "Dozvoli login sa",
+ "allow-login-with.username-email": "Korisničko ime ili Email",
+ "allow-login-with.username": "Samo korisničko ime",
+ "allow-login-with.email": "Samo Email",
+ "account-settings": "Podešavanje naloga",
+ "disable-username-changes": "Onemogući promenu korisničkog imena",
+ "disable-email-changes": "Onemogući promenu email-a",
+ "disable-password-changes": "Onemogući promenu šifre",
+ "allow-account-deletion": "Dozvoli brisanje naloga",
+ "user-info-private": "Načini korisničke podatke privatnim",
+ "themes": "Teme",
+ "disable-user-skins": "Onemogući korisnike da izaberu određenu temu",
+ "account-protection": "Začtita naloga",
+ "login-attempts": "Dozvoljeno logovanje po satu",
+ "login-attempts-help": "Ako broj logovanja prema user's predje određenu granicu, taj nalog može biti zaključan na određeno prekonfigurisano vreme",
+ "lockout-duration": "Trajanje dok se nalog ne otključa (minuta)",
"login-days": "Days to remember user login sessions",
- "password-expiry-days": "Force password reset after a set number of days",
- "registration": "User Registration",
- "registration-type": "Registration Type",
- "registration-type.normal": "Normal",
- "registration-type.admin-approval": "Admin Approval",
- "registration-type.admin-approval-ip": "Admin Approval for IPs",
- "registration-type.invite-only": "Invite Only",
- "registration-type.admin-invite-only": "Admin Invite Only",
- "registration-type.disabled": "No registration",
- "registration-type.help": "Normal - Users can register from the /register page.
\nAdmin Approval - User registrations are placed in an approval queue for administrators.
\nAdmin Approval for IPs - Normal for new users, Admin Approval for IP addresses that already have an account.
\nInvite Only - Users can invite others from the users page.
\nAdmin Invite Only - Only administrators can invite others from users and admin/manage/users pages.
\nNo registration - No user registration.
",
- "registration.max-invites": "Maximum Invitations per User",
+ "password-expiry-days": "Forsiraj resetovanje lozinke nakon odredjenog broja dana",
+ "registration": "Registracija korisnika",
+ "registration-type": "Tip registracije",
+ "registration-type.normal": "Normalno",
+ "registration-type.admin-approval": "Administratorsko odobravanje",
+ "registration-type.admin-approval-ip": "Administratosko odobravanje za IP",
+ "registration-type.invite-only": "Samo pozivnica",
+ "registration-type.admin-invite-only": "Samo administratorsko pozivanje",
+ "registration-type.disabled": "Nema registracije",
+ "registration-type.help": "Regularno - Korisnici mogu da se registruju na /register stranici.
\nAdministratorsko odobravanje - Registracije korisnika su plasirane u red za odobravanje za administratore.
\n\nAdministratorsko odobravanje za IP - Regularno za nove korisnike, administratorsko odobravanje za IP adrese koje već imaju nalog.
\nSamo pozivnica - Korisnici mogu da pozovu druge sa korisničke stranice.
\nSamo administratorsko pozivanje - samo administratori mogu pozvati druge sa korisničke i admin/manage/users stranice.
\nBez registracije - nema registracije novih korisnika.
",
+ "registration.max-invites": "Maksimum poziva po korisniku.",
"max-invites": "Maximum Invitations per User",
- "max-invites-help": "0 for no restriction. Admins get infinite invitations
Only applicable for \"Invite Only\"",
- "invite-expiration": "Invite expiration",
- "invite-expiration-help": "# of days invitations expire in.",
- "min-username-length": "Minimum Username Length",
- "max-username-length": "Maximum Username Length",
- "min-password-length": "Minimum Password Length",
- "min-password-strength": "Minimum Password Strength",
- "max-about-me-length": "Maximum About Me Length",
- "terms-of-use": "Forum Terms of Use (Leave blank to disable)",
- "user-search": "User Search",
- "user-search-results-per-page": "Number of results to display",
- "default-user-settings": "Default User Settings",
- "show-email": "Show email",
- "show-fullname": "Show fullname",
- "restrict-chat": "Only allow chat messages from users I follow",
- "outgoing-new-tab": "Open outgoing links in new tab",
- "topic-search": "Enable In-Topic Searching",
- "digest-freq": "Subscribe to Digest",
- "digest-freq.off": "Off",
- "digest-freq.daily": "Daily",
- "digest-freq.weekly": "Weekly",
- "digest-freq.monthly": "Monthly",
- "email-chat-notifs": "Send an email if a new chat message arrives and I am not online",
- "email-post-notif": "Send an email when replies are made to topics I am subscribed to",
- "follow-created-topics": "Follow topics you create",
- "follow-replied-topics": "Follow topics that you reply to"
+ "max-invites-help": "0 za bez restrikcija. Administratori dobijaju bezgranično pozivnica
Samo određeni za \"Samo pozivnica\"",
+ "invite-expiration": "Isticanje pozivnice",
+ "invite-expiration-help": "# dana kada ističe pozivnica.",
+ "min-username-length": "Minimum karaktera u korisničkom imenu",
+ "max-username-length": "Maksimum karaktera u korisničkom imenu",
+ "min-password-length": "Minimum karaktera u lozinci",
+ "min-password-strength": "Minimalna jačina lozinke",
+ "max-about-me-length": "Maksimum karaktera O Meni",
+ "terms-of-use": "Uslovi upotrebe foruma (Ostavite prazno da onemogućite)",
+ "user-search": "Pretraga Korisnika",
+ "user-search-results-per-page": "Broj rezultata po prikazu",
+ "default-user-settings": "Uobičajne Postavke Korisnika",
+ "show-email": "Prikaži email",
+ "show-fullname": "Prikaži puno ime",
+ "restrict-chat": "Samo dozvoli chat poruke korisnika koje ja pratim",
+ "outgoing-new-tab": "Otvori odlazeće linove u novom tabu",
+ "topic-search": "Omogući pretraživanje u temi",
+ "digest-freq": "Pretplatite se na Digest",
+ "digest-freq.off": "Isključeno",
+ "digest-freq.daily": "Dnevno",
+ "digest-freq.weekly": "Nedeljno",
+ "digest-freq.monthly": "Mesečno",
+ "email-chat-notifs": "Pošalji email ako nova chat poruka stigne dok nisam online",
+ "email-post-notif": "Pošalji email kada odgovori su načinjeni u temu u kojoj sam ja pretplaćen",
+ "follow-created-topics": "Prati teme koje si ti napravio",
+ "follow-replied-topics": "Prati teme na koje si ti odgovorio"
}
\ No newline at end of file
diff --git a/public/language/sr/admin/settings/web-crawler.json b/public/language/sr/admin/settings/web-crawler.json
index 2e0d31d12b..3b03ea1a92 100644
--- a/public/language/sr/admin/settings/web-crawler.json
+++ b/public/language/sr/admin/settings/web-crawler.json
@@ -1,10 +1,10 @@
{
- "crawlability-settings": "Crawlability Settings",
- "robots-txt": "Custom Robots.txt Leave blank for default",
- "sitemap-feed-settings": "Sitemap & Feed Settings",
- "disable-rss-feeds": "Disable RSS Feeds",
- "disable-sitemap-xml": "Disable Sitemap.xml",
- "sitemap-topics": "Number of Topics to display in the Sitemap",
- "clear-sitemap-cache": "Clear Sitemap Cache",
- "view-sitemap": "View Sitemap"
+ "crawlability-settings": "Podešsavanje crawl-ovanja",
+ "robots-txt": "Napredni Robots.txt Ostavite prazno za uobičajena podešavanja",
+ "sitemap-feed-settings": "Mapa sajta i podešavanje Feed-a",
+ "disable-rss-feeds": "Onemogući RSS Feed",
+ "disable-sitemap-xml": "Onemogući Sitemap.xml",
+ "sitemap-topics": "Broj Tema za prikaz u Mapi sajta",
+ "clear-sitemap-cache": "Obroši cache Mape sajta",
+ "view-sitemap": "Pogledaj Mapu sajta"
}
\ No newline at end of file
diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js
index 26c9950254..9ca9052dc9 100644
--- a/public/src/ajaxify.js
+++ b/public/src/ajaxify.js
@@ -293,11 +293,21 @@ $(document).ready(function () {
headers: {
'X-Return-To': app.previousUrl,
},
- success: function (data) {
+ success: function (data, textStatus, xhr) {
if (!data) {
return;
}
+ if (xhr.getResponseHeader('X-Redirect')) {
+ return callback({
+ data: {
+ status: 302,
+ responseJSON: data,
+ },
+ textStatus: 'error',
+ });
+ }
+
ajaxify.data = data;
data.config = config;
diff --git a/public/src/modules/scrollStop.js b/public/src/modules/scrollStop.js
index 82d2df4de8..0e07641cc3 100644
--- a/public/src/modules/scrollStop.js
+++ b/public/src/modules/scrollStop.js
@@ -16,7 +16,7 @@ define('scrollStop', function () {
$(element).on('mousewheel', function (e) {
var scrollTop = this.scrollTop;
var scrollHeight = this.scrollHeight;
- var elementHeight = this.getBoundingClientRect().height;
+ var elementHeight = Math.round(this.getBoundingClientRect().height);
if (
(e.originalEvent.deltaY < 0 && scrollTop === 0) || // scroll up
diff --git a/src/controllers/errors.js b/src/controllers/errors.js
index 99614f9e87..177bcaa769 100644
--- a/src/controllers/errors.js
+++ b/src/controllers/errors.js
@@ -45,7 +45,7 @@ exports.handleErrors = function (err, req, res, next) { // eslint-disable-line n
var status = parseInt(err.status, 10);
if ((status === 302 || status === 308) && err.path) {
- return res.locals.isAPI ? res.status(status).json(err.path) : res.redirect(err.path);
+ return res.locals.isAPI ? res.set('X-Redirect', err.path).status(200).json(err.path) : res.redirect(err.path);
}
winston.error(req.path + '\n', err.stack);
diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js
index 761faafa82..8db1d6266f 100644
--- a/src/controllers/helpers.js
+++ b/src/controllers/helpers.js
@@ -53,7 +53,7 @@ helpers.notAllowed = function (req, res, error) {
helpers.redirect = function (res, url) {
if (res.locals.isAPI) {
- res.status(308).json(url);
+ res.set('X-Redirect', url).status(200).json(url);
} else {
res.redirect(nconf.get('relative_path') + encodeURI(url));
}
diff --git a/src/flags.js b/src/flags.js
index ebc3e37ba6..9e3059d2a4 100644
--- a/src/flags.js
+++ b/src/flags.js
@@ -3,6 +3,7 @@
var async = require('async');
var _ = require('lodash');
var S = require('string');
+var winston = require('winston');
var db = require('./database');
var user = require('./user');
@@ -18,6 +19,60 @@ var utils = require('../public/src/utils');
var Flags = module.exports;
+Flags.init = function (callback) {
+ // Query plugins for custom filter strategies and merge into core filter strategies
+ var prepareSets = function (sets, orSets, prefix, value) {
+ if (!Array.isArray(value)) {
+ sets.push(prefix + value);
+ } else if (value.length) {
+ value.forEach(function (x) {
+ orSets.push(prefix + x);
+ });
+ }
+ };
+
+ plugins.fireHook('filter:flags.getFilters', {
+ filters: {
+ type: function (sets, orSets, key) {
+ prepareSets(sets, orSets, 'flags:byType:', key);
+ },
+ state: function (sets, orSets, key) {
+ prepareSets(sets, orSets, 'flags:byState:', key);
+ },
+ reporterId: function (sets, orSets, key) {
+ prepareSets(sets, orSets, 'flags:byReporter:', key);
+ },
+ assignee: function (sets, orSets, key) {
+ prepareSets(sets, orSets, 'flags:byAssignee:', key);
+ },
+ targetUid: function (sets, orSets, key) {
+ prepareSets(sets, orSets, 'flags:byTargetUid:', key);
+ },
+ cid: function (sets, orSets, key) {
+ prepareSets(sets, orSets, 'flags:byCid:', key);
+ },
+ quick: function (sets, orSets, key, uid) {
+ switch (key) {
+ case 'mine':
+ sets.push('flags:byAssignee:' + uid);
+ break;
+ }
+ },
+ },
+ helpers: {
+ prepareSets: prepareSets,
+ },
+ }, function (err, data) {
+ if (err) {
+ winston.error('[flags/init] Could not retrieve filters (error: ' + err.message + ')');
+ data.filters = {};
+ }
+
+ Flags._filters = data.filters;
+ callback();
+ });
+};
+
Flags.get = function (flagId, callback) {
async.waterfall([
// First stage
@@ -64,51 +119,14 @@ Flags.list = function (filters, uid, callback) {
var sets = [];
var orSets = [];
- var prepareSets = function (setPrefix, value) {
- if (!Array.isArray(value)) {
- sets.push(setPrefix + value);
- } else if (value.length) {
- value.forEach(function (x) {
- orSets.push(setPrefix + x);
- });
- }
- };
if (Object.keys(filters).length > 0) {
for (var type in filters) {
if (filters.hasOwnProperty(type)) {
- switch (type) {
- case 'type':
- prepareSets('flags:byType:', filters[type]);
- break;
-
- case 'state':
- prepareSets('flags:byState:', filters[type]);
- break;
-
- case 'reporterId':
- prepareSets('flags:byReporter:', filters[type]);
- break;
-
- case 'assignee':
- prepareSets('flags:byAssignee:', filters[type]);
- break;
-
- case 'targetUid':
- prepareSets('flags:byTargetUid:', filters[type]);
- break;
-
- case 'cid':
- prepareSets('flags:byCid:', filters[type]);
- break;
-
- case 'quick':
- switch (filters.quick) {
- case 'mine':
- sets.push('flags:byAssignee:' + uid);
- break;
- }
- break;
+ if (Flags._filters.hasOwnProperty(type)) {
+ Flags._filters[type](sets, orSets, filters[type], uid);
+ } else {
+ winston.warn('[flags/list] No flag filter type found: ' + type);
}
}
}
diff --git a/src/webserver.js b/src/webserver.js
index 867a2c8826..a7dc0a8182 100644
--- a/src/webserver.js
+++ b/src/webserver.js
@@ -24,6 +24,7 @@ var meta = require('./meta');
var languages = require('./languages');
var logger = require('./logger');
var plugins = require('./plugins');
+var flags = require('./flags');
var routes = require('./routes');
var auth = require('./routes/authentication');
var templates = require('templates.js');
@@ -106,6 +107,7 @@ function initializeNodeBB(callback) {
meta.sounds.addUploads,
languages.init,
meta.blacklist.load,
+ flags.init,
], next);
},
], function (err) {
diff --git a/test/controllers.js b/test/controllers.js
index 1f73d4a7ae..34c515fc7a 100644
--- a/test/controllers.js
+++ b/test/controllers.js
@@ -808,7 +808,8 @@ describe('Controllers', function () {
it('should redirect to account page with logged in user', function (done) {
request(nconf.get('url') + '/api/login', { jar: jar, json: true }, function (err, res, body) {
assert.ifError(err);
- assert.equal(res.statusCode, 308);
+ assert.equal(res.statusCode, 200);
+ assert.equal(res.headers['x-redirect'], '/user/foo');
assert.equal(body, '/user/foo');
done();
});
@@ -825,7 +826,8 @@ describe('Controllers', function () {
it('should redirect to userslug', function (done) {
request(nconf.get('url') + '/api/uid/' + fooUid, { json: true }, function (err, res, body) {
assert.ifError(err);
- assert.equal(res.statusCode, 308);
+ assert.equal(res.statusCode, 200);
+ assert.equal(res.headers['x-redirect'], '/user/foo');
assert.equal(body, '/user/foo');
done();
});
@@ -1238,10 +1240,11 @@ describe('Controllers', function () {
});
it('should return correct post path', function (done) {
- request(nconf.get('url') + '/api/post/' + pid, function (err, res, body) {
+ request(nconf.get('url') + '/api/post/' + pid, { json: true }, function (err, res, body) {
assert.ifError(err);
- assert.equal(res.statusCode, 308);
- assert.equal(body, '"/topic/1/test-topic-title/1"');
+ assert.equal(res.statusCode, 200);
+ assert.equal(res.headers['x-redirect'], '/topic/1/test-topic-title/1');
+ assert.equal(body, '/topic/1/test-topic-title/1');
done();
});
});
@@ -1411,7 +1414,8 @@ describe('Controllers', function () {
request(nconf.get('url') + '/api/users', { json: true }, function (err, res, body) {
assert.ifError(err);
- assert.equal(res.statusCode, 308);
+ assert.equal(res.statusCode, 200);
+ assert.equal(res.headers['x-redirect'], '/api/popular');
assert(body, '/api/popular');
done();
});
@@ -1521,7 +1525,8 @@ describe('Controllers', function () {
it('should redirect if topic index is negative', function (done) {
request(nconf.get('url') + '/api/category/' + category.slug + '/-10', function (err, res) {
assert.ifError(err);
- assert.equal(res.statusCode, 308);
+ assert.equal(res.statusCode, 200);
+ assert.ok(res.headers['x-redirect']);
done();
});
});
@@ -1627,7 +1632,8 @@ describe('Controllers', function () {
function (category, next) {
request(nconf.get('url') + '/api/category/' + category.slug, { jar: jar, json: true }, function (err, res, body) {
assert.ifError(err);
- assert.equal(res.statusCode, 308);
+ assert.equal(res.statusCode, 200);
+ assert.equal(res.headers['x-redirect'], 'https://nodebb.org');
assert.equal(body, 'https://nodebb.org');
next();
});
@@ -1754,10 +1760,11 @@ describe('Controllers', function () {
});
it('should redirect if page is out of bounds', function (done) {
- request(nconf.get('url') + '/api/unread?page=-1', { jar: jar }, function (err, res, body) {
+ request(nconf.get('url') + '/api/unread?page=-1', { jar: jar, json: true }, function (err, res, body) {
assert.ifError(err);
- assert.equal(res.statusCode, 308);
- assert.equal(body, '"/unread?page=1"');
+ assert.equal(res.statusCode, 200);
+ assert.equal(res.headers['x-redirect'], '/unread?page=1');
+ assert.equal(body, '/unread?page=1');
done();
});
});
diff --git a/test/messaging.js b/test/messaging.js
index d6426812fe..cc8283698b 100644
--- a/test/messaging.js
+++ b/test/messaging.js
@@ -591,10 +591,11 @@ describe('Messaging Library', function () {
});
it('should redirect to chats page', function (done) {
- request(nconf.get('url') + '/api/chats', { jar: jar }, function (err, response, body) {
+ request(nconf.get('url') + '/api/chats', { jar: jar, json: true }, function (err, res, body) {
assert.ifError(err);
- assert.equal(body, '"/user/herp/chats"');
- assert.equal(response.statusCode, 308);
+ assert.equal(res.statusCode, 200);
+ assert.equal(res.headers['x-redirect'], '/user/herp/chats');
+ assert.equal(body, '/user/herp/chats');
done();
});
});
diff --git a/test/topics.js b/test/topics.js
index 541000dd52..a1d0bda512 100644
--- a/test/topics.js
+++ b/test/topics.js
@@ -791,10 +791,11 @@ describe('Topic\'s', function () {
});
it('should redirect if post index is out of range', function (done) {
- request(nconf.get('url') + '/api/topic/' + topicData.slug + '/-1', function (err, response, body) {
+ request(nconf.get('url') + '/api/topic/' + topicData.slug + '/-1', { json: true }, function (err, res, body) {
assert.ifError(err);
- assert.equal(response.statusCode, 308);
- assert.equal(body, '"/topic/13/topic-for-controller-test"');
+ assert.equal(res.statusCode, 200);
+ assert.equal(res.headers['x-redirect'], '/topic/13/topic-for-controller-test');
+ assert.equal(body, '/topic/13/topic-for-controller-test');
done();
});
});