mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-06-22 19:00:03 +02:00
Merge branch 'master' into v0.5.x
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# <img alt="NodeBB" src="http://i.imgur.com/mYxPPtB.png" />
|
||||
[](https://travis-ci.org/nodebb/nodebb)
|
||||
[](https://travis-ci.org/NodeBB/NodeBB)
|
||||
[](https://david-dm.org/nodebb/nodebb)
|
||||
[](https://codeclimate.com/github/designcreateplay/NodeBB)
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
"validator": "~3.16.1",
|
||||
"winston": "~0.7.2",
|
||||
"xregexp": "~2.0.0",
|
||||
"templates.js": "0.0.8"
|
||||
"templates.js": "0.0.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "~1.13.0"
|
||||
@@ -67,7 +67,7 @@
|
||||
"url": "https://github.com/NodeBB/NodeBB/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
"node": ">=0.10"
|
||||
},
|
||||
"maintainers": [
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"chat.pop-out": "Pop out chat",
|
||||
"chat.maximize": "Maximize",
|
||||
|
||||
"composer.user_said_in": "%1 said in %2:\n",
|
||||
"composer.user_said": "%1 said:\n",
|
||||
"composer.user_said_in": "%1 said in %2:",
|
||||
"composer.user_said": "%1 said:",
|
||||
"composer.discard": "Are you sure you wish to discard this post?"
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
"recent": "Recent Topics",
|
||||
"users": "Registered Users",
|
||||
"notifications": "Notifications",
|
||||
"tags": "Topics tagged under \"%1\"",
|
||||
"user.edit": "Editing \"%1\"",
|
||||
"user.following": "People %1 Follows",
|
||||
"user.followers": "People who Follow %1",
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
"more_users_and_guests": "%1 more user(s) and %2 guest(s)",
|
||||
"more_users": "%1 more user(s)",
|
||||
"more_guests": "%1 more guest(s)",
|
||||
"users_and_others": "%1 and %2 others",
|
||||
|
||||
"sort_by": "Sort by",
|
||||
"oldest_to_newest": "Oldest to Newest",
|
||||
|
||||
@@ -50,9 +50,9 @@
|
||||
"read_more": "loe veel",
|
||||
"posted_ago_by_guest": "postitatud %1 tagasi külalise poolt",
|
||||
"posted_ago_by": "postitatud %1 tagasi kasutaja %2 poolt",
|
||||
"posted_ago": "postitatud %1 tagasi",
|
||||
"posted_ago": "postitatud %1",
|
||||
"posted_in_ago_by_guest": "postitatud kategooriasse %1 %2 tagasi külalise poolt",
|
||||
"posted_in_ago_by": "postitatud kategooriasse %1 %2 aega tagasi kasutaja %3 poolt",
|
||||
"posted_in_ago_by": "postitatud kategooriasse %1 %2 kasutaja %3 poolt",
|
||||
"posted_in_ago": "postitatud kategooriasse %1 %2 tagasi",
|
||||
"replied_ago": "vastas %1",
|
||||
"user_posted_ago": "kasutaja %1 postitas %2 tagasi",
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"thread_tools.pin": "Tõsta esile teema",
|
||||
"thread_tools.unpin": "Märgista teema",
|
||||
"thread_tools.lock": "Lukusta teema",
|
||||
"thread_tools.unlock": "Eemalda märgistatud teema",
|
||||
"thread_tools.unlock": "Taasava teema",
|
||||
"thread_tools.move": "Liiguta teema",
|
||||
"thread_tools.move_all": "Liiguta kõik",
|
||||
"thread_tools.fork": "Fork Topic",
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
{
|
||||
"password-reset-requested": "Password Reset Requested - %1!",
|
||||
"welcome-to": "Welcome to %1",
|
||||
"greeting_no_name": "Hello",
|
||||
"greeting_with_name": "Hello %1",
|
||||
"welcome.text1": "Thank you for registering with %1!",
|
||||
"password-reset-requested": "Recuperação de Senha Solicitada - %1!",
|
||||
"welcome-to": "Bem vindo a %1",
|
||||
"greeting_no_name": "Olá",
|
||||
"greeting_with_name": "Olà %1",
|
||||
"welcome.text1": "Obrigado por se registrar com %1!",
|
||||
"welcome.text2": "To fully activate your account, we need to verify that you own the email address you registered with.",
|
||||
"welcome.cta": "Click here to confirm your email address",
|
||||
"welcome.cta": "Clique aqui para confirmar seu endereço de email",
|
||||
"reset.text1": "We received a request to reset your password, possibly because you have forgotten it. If this is not the case, please ignore this email.",
|
||||
"reset.text2": "To continue with the password reset, please click on the following link:",
|
||||
"reset.cta": "Click here to reset your password",
|
||||
"reset.cta": "Clique aqui para recuperar sua senha",
|
||||
"digest.notifications": "You have some unread notifications from %1:",
|
||||
"digest.latest_topics": "Latest topics from %1",
|
||||
"digest.cta": "Click here to visit %1",
|
||||
"digest.latest_topics": "Últimos tópicos de %1",
|
||||
"digest.cta": "Clique aqui para visitar %1",
|
||||
"digest.unsub.info": "This digest was sent to you due to your subscription settings.",
|
||||
"digest.unsub.cta": "Click here to alter those settings",
|
||||
"digest.unsub.cta": "Clique aqui para alterar suas configurações",
|
||||
"digest.daily.no_topics": "There have been no active topics in the past day",
|
||||
"test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.",
|
||||
"closing": "Thanks!"
|
||||
"closing": "Obrigado!"
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
"no-user": "Usuário não existe",
|
||||
"no-teaser": "Chamada não existe",
|
||||
"no-privileges": "Você não possui permissões para esta ação.",
|
||||
"no-emailers-configured": "No email plugins were loaded, so a test email could not be sent",
|
||||
"no-emailers-configured": "Nenhum plugin de email foi carregado, por isso o email de teste não pode ser enviado",
|
||||
"category-disabled": "Categoria desativada",
|
||||
"topic-locked": "Tópico trancado",
|
||||
"still-uploading": "Aguarde a conclusão dos uploads.",
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
"see_all": "Visualizar todas as notificações",
|
||||
"back_to_home": "Voltar para %1",
|
||||
"outgoing_link": "Link Externo",
|
||||
"outgoing_link_message": "You are now leaving %1.",
|
||||
"continue_to": "Continue to %1",
|
||||
"return_to": "Return to %1",
|
||||
"outgoing_link_message": "Você deixou de seguir %1.",
|
||||
"continue_to": "Continuar para %1",
|
||||
"return_to": "Voltar para %1",
|
||||
"new_notification": "Nova notificação",
|
||||
"you_have_unread_notifications": "Você possui notificações não lidas.",
|
||||
"new_message_from": "Nova mensagem de <strong>%1</strong>",
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
{
|
||||
"password-reset-requested": "Password Reset Requested - %1!",
|
||||
"welcome-to": "Welcome to %1",
|
||||
"greeting_no_name": "Hello",
|
||||
"greeting_with_name": "Hello %1",
|
||||
"welcome.text1": "Thank you for registering with %1!",
|
||||
"welcome.text2": "To fully activate your account, we need to verify that you own the email address you registered with.",
|
||||
"welcome.cta": "Click here to confirm your email address",
|
||||
"reset.text1": "We received a request to reset your password, possibly because you have forgotten it. If this is not the case, please ignore this email.",
|
||||
"reset.text2": "To continue with the password reset, please click on the following link:",
|
||||
"reset.cta": "Click here to reset your password",
|
||||
"digest.notifications": "You have some unread notifications from %1:",
|
||||
"digest.latest_topics": "Latest topics from %1",
|
||||
"digest.cta": "Click here to visit %1",
|
||||
"digest.unsub.info": "This digest was sent to you due to your subscription settings.",
|
||||
"digest.unsub.cta": "Click here to alter those settings",
|
||||
"digest.daily.no_topics": "There have been no active topics in the past day",
|
||||
"test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.",
|
||||
"closing": "Thanks!"
|
||||
"password-reset-requested": "Пароль сброшен - %1!",
|
||||
"welcome-to": "Добро пожаловать на %1",
|
||||
"greeting_no_name": "Здравствуйте!",
|
||||
"greeting_with_name": "Здравствуйте, %1!",
|
||||
"welcome.text1": "Благодарим за регистрацию %1! ",
|
||||
"welcome.text2": "Для активации вашей учетной записи мы должны убедиться, что вы указали верный email адрес.",
|
||||
"welcome.cta": "Перейдите по ссылке для подтверждения вашего email",
|
||||
"reset.text1": "Мы получили запрос на изменение вашего пароля. Если не подавали запрос на изменение пароля, пожалуйста, проигнорируйте это сообщение.",
|
||||
"reset.text2": "Для продолжения процедуры изменения пароля, пожалуйста, перейдите по ссылке:",
|
||||
"reset.cta": "Кликните здесь для изменения пароля",
|
||||
"digest.notifications": "У вас есть непрочитанные уведомления от %1:",
|
||||
"digest.latest_topics": "Последние темы %1",
|
||||
"digest.cta": "Кликните здесь для просмотра %1",
|
||||
"digest.unsub.info": "Вам была выслана сводка новостей в соответствии с вашими настройками.",
|
||||
"digest.unsub.cta": "Кликните здесь для изменения ваших настроек.",
|
||||
"digest.daily.no_topics": "За минувший день новых тем нет.",
|
||||
"test.text1": "Это тестовое сообщение для проверки почтового сервиса NodeBB.",
|
||||
"closing": "Спасибо!"
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
"invalid-pagination-value": "Неверное значение пагинации",
|
||||
"username-taken": "Имя пользователя занято",
|
||||
"email-taken": "Email занят",
|
||||
"email-not-confirmed": "Your email is not confirmed, please click here to confirm your email.",
|
||||
"email-not-confirmed": "Ваш email не подтвержден, нажмите для подтверждения.",
|
||||
"username-too-short": "Слишком короткое имя пользователя",
|
||||
"user-banned": "Пользователь заблокирован",
|
||||
"no-category": "Несуществующая категория",
|
||||
@@ -25,10 +25,10 @@
|
||||
"no-user": "Несуществующий пользователь",
|
||||
"no-teaser": "Несуществующее превью",
|
||||
"no-privileges": "У вас недостаточно прав, чтобы совершить данное действие.",
|
||||
"no-emailers-configured": "No email plugins were loaded, so a test email could not be sent",
|
||||
"no-emailers-configured": "Не подключен ни один плагин для отправки почты, поэтому тестовый email не может быть отправлен",
|
||||
"category-disabled": "Категория отключена",
|
||||
"topic-locked": "Тема закрыт",
|
||||
"still-uploading": "Пожалуйста, подождите завершения загрузки",
|
||||
"topic-locked": "Тема закрыта",
|
||||
"still-uploading": "Пожалуйста, подождите завершения загрузки.",
|
||||
"content-too-short": "Пост должен содержать минимум %1 символов.",
|
||||
"title-too-short": "Заголовок должен содержать минимум %1 символов.",
|
||||
"title-too-long": "Заголовок не может быть длиннее %1 символов.",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"please_log_in": "Пожалуйста, войдите под своим аккаунтом",
|
||||
"logout": "Выйти",
|
||||
"posting_restriction_info": "Сообщения могут оставлять только зарегистрированные пользователи, нажмите сюда, чтобы войти",
|
||||
"welcome_back": "Welcome Back",
|
||||
"welcome_back": "С возвращением",
|
||||
"you_have_successfully_logged_in": "Вы вышли из аккаунта",
|
||||
"save_changes": "Сохранить изменения",
|
||||
"close": "Закрыть",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"view_group": "View Group",
|
||||
"details.title": "Group Details",
|
||||
"details.members": "Member List",
|
||||
"details.has_no_posts": "This group's members have not made any posts.",
|
||||
"details.latest_posts": "Latest Posts"
|
||||
"view_group": "Просмотр группы",
|
||||
"details.title": "Информация о группе",
|
||||
"details.members": "Список пользователей",
|
||||
"details.has_no_posts": "Пользователями этой группы не публиковали никаких записей",
|
||||
"details.latest_posts": "Последние записи"
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"chat.chatting_with": "Чат с <span id=\"chat-with-name\"></span>",
|
||||
"chat.placeholder": "Type chat message here, press enter to send",
|
||||
"chat.placeholder": "Введите сообщение, нажмите ENTER для отправки",
|
||||
"chat.send": "Отправить",
|
||||
"chat.no_active": "У вас нет активных чатов.",
|
||||
"chat.user_typing": "%1 печатает ...",
|
||||
"chat.user_has_messaged_you": "%1 отправил вам сообщение.",
|
||||
"chat.see_all": "Просмотр всех диалогов",
|
||||
"chat.no-messages": "Please select a recipient to view chat message history",
|
||||
"chat.recent-chats": "Recent Chats",
|
||||
"chat.contacts": "Contacts",
|
||||
"chat.message-history": "Message History",
|
||||
"chat.pop-out": "Pop out chat",
|
||||
"chat.maximize": "Maximize",
|
||||
"composer.user_said_in": "%1 said in %2:",
|
||||
"composer.user_said": "%1 said:",
|
||||
"chat.no-messages": "Пожалуйста, выберите собеседника для просмотра истории сообщений",
|
||||
"chat.recent-chats": "Последние переписки",
|
||||
"chat.contacts": "Контакты",
|
||||
"chat.message-history": "История сообщений",
|
||||
"chat.pop-out": "Покинуть диалог",
|
||||
"chat.maximize": "Развернуть",
|
||||
"composer.user_said_in": "%1 сказал %2:",
|
||||
"composer.user_said": "%1 сказал:",
|
||||
"composer.discard": "Вы уверены, что хотите отказаться от этого поста?"
|
||||
}
|
||||
@@ -4,9 +4,9 @@
|
||||
"see_all": "Просмотреть все уведомления",
|
||||
"back_to_home": "Назад к %1",
|
||||
"outgoing_link": "Внешняя ссылка",
|
||||
"outgoing_link_message": "You are now leaving %1.",
|
||||
"continue_to": "Continue to %1",
|
||||
"return_to": "Return to %1",
|
||||
"outgoing_link_message": "Вы покидаете %1.",
|
||||
"continue_to": "Перейти на %1",
|
||||
"return_to": "Вернуться к %1",
|
||||
"new_notification": "Новое Уведомление",
|
||||
"you_have_unread_notifications": "У вас есть непрочитанные уведомления",
|
||||
"new_message_from": "Новое сообщение от <strong>%1</strong>",
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
"day": "День",
|
||||
"week": "Неделя",
|
||||
"month": "Месяц",
|
||||
"year": "Year",
|
||||
"year": "Год",
|
||||
"no_recent_topics": "Нет свежих тем."
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"results_matching": "%1 result(s) matching \"%2\", (%3 seconds)"
|
||||
"results_matching": "%1 результатов по фразе \"%2\", (%3 секунды) "
|
||||
}
|
||||
@@ -54,7 +54,7 @@
|
||||
"topic_move_success": "Эта тема успешно перемещена в %1",
|
||||
"post_delete_confirm": "Вы уверены, что хотите удалить этот пост?",
|
||||
"post_restore_confirm": "Вы уверены, что хотите восстановить этот пост?",
|
||||
"post_purge_confirm": "Are you sure you want to purge this post?",
|
||||
"post_purge_confirm": "Вы уверены, что хотите очистить эту запись?",
|
||||
"load_categories": "Загружаем Категории",
|
||||
"disabled_categories_note": "Отключенные категории затемненны",
|
||||
"confirm_move": "Перенести",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"offline": "Не в сети",
|
||||
"username": "Имя пользователя",
|
||||
"email": "Email",
|
||||
"confirm_email": "Confirm Email",
|
||||
"confirm_email": "Подтвердить Email",
|
||||
"fullname": "Полное имя",
|
||||
"website": "Сайт",
|
||||
"location": "Откуда",
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"enter_username": "Введите имя пользователя для поиска",
|
||||
"load_more": "Загрузить еще",
|
||||
"user-not-found": "Пользователь не найден!",
|
||||
"users-found-search-took": "Нашел %1 пользователя(ей)! Поиск занял %2 ms."
|
||||
"users-found-search-took": "Нашел %1 пользователя(ей)! Поиск занял %2 мс."
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
{
|
||||
"password-reset-requested": "Password Reset Requested - %1!",
|
||||
"welcome-to": "Welcome to %1",
|
||||
"greeting_no_name": "Hello",
|
||||
"greeting_with_name": "Hello %1",
|
||||
"welcome.text1": "Thank you for registering with %1!",
|
||||
"welcome.text2": "To fully activate your account, we need to verify that you own the email address you registered with.",
|
||||
"welcome.cta": "Click here to confirm your email address",
|
||||
"reset.text1": "We received a request to reset your password, possibly because you have forgotten it. If this is not the case, please ignore this email.",
|
||||
"reset.text2": "To continue with the password reset, please click on the following link:",
|
||||
"reset.cta": "Click here to reset your password",
|
||||
"digest.notifications": "You have some unread notifications from %1:",
|
||||
"digest.latest_topics": "Latest topics from %1",
|
||||
"digest.cta": "Click here to visit %1",
|
||||
"digest.unsub.info": "This digest was sent to you due to your subscription settings.",
|
||||
"digest.unsub.cta": "Click here to alter those settings",
|
||||
"digest.daily.no_topics": "There have been no active topics in the past day",
|
||||
"test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.",
|
||||
"closing": "Thanks!"
|
||||
"password-reset-requested": "Parola Değiştirme İsteği Gönderildi",
|
||||
"welcome-to": "Hoşgeldiniz",
|
||||
"greeting_no_name": "Merhaba",
|
||||
"greeting_with_name": "Merhaba %1",
|
||||
"welcome.text1": "Kaydolduğunuz için teşekkürler!",
|
||||
"welcome.text2": "Hesabınızı aktif hale getirmek için, kaydolduğunuz e-posta adresinin size ait olduğunu onaylamamız gerekiyor.",
|
||||
"welcome.cta": "E-posta adresinizi onaylamak için buraya tıklayın",
|
||||
"reset.text1": "Şifrenizi değiştirmek istediğinize dair bir ileti aldık. Eğer böyle bir istek göndermediyseniz, lütfen bu e-postayı görmezden gelin.",
|
||||
"reset.text2": "Parola değiştirme işlemine devam etmek için aşağıdaki bağlantıya tıklayın:",
|
||||
"reset.cta": "Parolanızı değiştirmek için buraya tıklayın",
|
||||
"digest.notifications": "Okunmamış bazı bildirimleriniz var",
|
||||
"digest.latest_topics": "En güncel konular",
|
||||
"digest.cta": "Ziyaret etmek için buraya tıklayın",
|
||||
"digest.unsub.info": "Bu e-posta seçtiğiniz ayarlar nedeniyle gönderildi.",
|
||||
"digest.unsub.cta": "Bu ayarları değiştirmek için buraya tıklayın",
|
||||
"digest.daily.no_topics": "Geçtiğimiz gün içinde aktif bir konu yok.",
|
||||
"test.text1": "Bu ileti NodeBB e-posta ayarlarınızın doğru çalışıp çalışmadığını kontrol etmek için gönderildi.",
|
||||
"closing": "Teşekkürler!"
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"view_group": "View Group",
|
||||
"details.title": "Group Details",
|
||||
"details.members": "Member List",
|
||||
"details.has_no_posts": "This group's members have not made any posts.",
|
||||
"details.latest_posts": "Latest Posts"
|
||||
"view_group": "Grubu Gör",
|
||||
"details.title": "Grup Detayları",
|
||||
"details.members": "Üye Listesi",
|
||||
"details.has_no_posts": "Bu grubun üyeleri henüz bir ileti göndermedi.",
|
||||
"details.latest_posts": "En son iletiler"
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"results_matching": "%1 result(s) matching \"%2\", (%3 seconds)"
|
||||
"results_matching": "%1 tane “%2“ bulundu (%3 saniye)"
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
{
|
||||
"password-reset-requested": "Password Reset Requested - %1!",
|
||||
"welcome-to": "Welcome to %1",
|
||||
"greeting_no_name": "Hello",
|
||||
"greeting_with_name": "Hello %1",
|
||||
"welcome.text1": "Thank you for registering with %1!",
|
||||
"welcome.text2": "To fully activate your account, we need to verify that you own the email address you registered with.",
|
||||
"welcome.cta": "Click here to confirm your email address",
|
||||
"reset.text1": "We received a request to reset your password, possibly because you have forgotten it. If this is not the case, please ignore this email.",
|
||||
"reset.text2": "To continue with the password reset, please click on the following link:",
|
||||
"reset.cta": "Click here to reset your password",
|
||||
"digest.notifications": "You have some unread notifications from %1:",
|
||||
"digest.latest_topics": "Latest topics from %1",
|
||||
"digest.cta": "Click here to visit %1",
|
||||
"digest.unsub.info": "This digest was sent to you due to your subscription settings.",
|
||||
"digest.unsub.cta": "Click here to alter those settings",
|
||||
"digest.daily.no_topics": "There have been no active topics in the past day",
|
||||
"test.text1": "This is a test email to verify that the emailer is set up correctly for your NodeBB.",
|
||||
"closing": "Thanks!"
|
||||
"password-reset-requested": "密码重设申请 - %1!",
|
||||
"welcome-to": "欢迎来到 %1",
|
||||
"greeting_no_name": "您好",
|
||||
"greeting_with_name": "%1,您好",
|
||||
"welcome.text1": "谢谢您使用 %1 注册帐户!",
|
||||
"welcome.text2": "如需完全激活您的帐户,我们需要校验您注册的电子邮箱地址。",
|
||||
"welcome.cta": "点击这里确认您的电子邮箱地址",
|
||||
"reset.text1": "我们收到了重置您密码的申请,可能因为您忘记了密码。如果不是,请忽略这封邮件。",
|
||||
"reset.text2": "如需继续重置密码,请点击下面的链接:",
|
||||
"reset.cta": "点击这里重设您的密码",
|
||||
"digest.notifications": "您有一些来自 %1 的未读通知:",
|
||||
"digest.latest_topics": "来自 %1 的最新主题",
|
||||
"digest.cta": "点击这里访问 %1",
|
||||
"digest.unsub.info": "根据您的订阅设置,为您发送此摘要。",
|
||||
"digest.unsub.cta": "点击这里修改这些设置",
|
||||
"digest.daily.no_topics": "最近几天有一些未激活的主题",
|
||||
"test.text1": "这是一封测试邮件,用来验证 NodeBB 的邮件配置是否设置正确。",
|
||||
"closing": "谢谢!"
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"view_group": "View Group",
|
||||
"details.title": "Group Details",
|
||||
"details.members": "Member List",
|
||||
"details.has_no_posts": "This group's members have not made any posts.",
|
||||
"details.latest_posts": "Latest Posts"
|
||||
"view_group": "查看用户组",
|
||||
"details.title": "用户组详情",
|
||||
"details.members": "会员列表",
|
||||
"details.has_no_posts": "此用户组的会员尚未发表任何帖子。",
|
||||
"details.latest_posts": "最新帖子"
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"results_matching": "%1 result(s) matching \"%2\", (%3 seconds)"
|
||||
"results_matching": "%1 条结果,匹配 \"%2\",(耗时 %3 秒)"
|
||||
}
|
||||
@@ -96,12 +96,13 @@ var ajaxify = ajaxify || {};
|
||||
translator.translate(template, function(translatedTemplate) {
|
||||
setTimeout(function() {
|
||||
$('#content').html(translatedTemplate);
|
||||
|
||||
ajaxify.variables.parse();
|
||||
|
||||
ajaxify.widgets.render(tpl_url, url, function() {
|
||||
$(window).trigger('action:ajaxify.end', {url: url});
|
||||
});
|
||||
|
||||
ajaxify.variables.parse();
|
||||
|
||||
$(window).trigger('action:ajaxify.contentLoaded', {url: url});
|
||||
|
||||
ajaxify.loadScript(tpl_url);
|
||||
@@ -203,7 +204,6 @@ var ajaxify = ajaxify || {};
|
||||
}
|
||||
|
||||
var location = document.location || window.location,
|
||||
api_url = (url === '' || url === '/') ? 'home' : url,
|
||||
tpl_url = ajaxify.getCustomTemplateMapping(url.split('?')[0]);
|
||||
|
||||
if (!tpl_url) {
|
||||
@@ -211,7 +211,7 @@ var ajaxify = ajaxify || {};
|
||||
}
|
||||
|
||||
apiXHR = $.ajax({
|
||||
url: RELATIVE_PATH + '/api/' + api_url,
|
||||
url: RELATIVE_PATH + '/api/' + url,
|
||||
cache: false,
|
||||
success: function(data) {
|
||||
if (!data) {
|
||||
@@ -329,9 +329,9 @@ var ajaxify = ajaxify || {};
|
||||
|
||||
templates.registerLoader(ajaxify.loadTemplate);
|
||||
|
||||
$.when($.getJSON(RELATIVE_PATH + '/templates/config.json'), $.getJSON(RELATIVE_PATH + '/api/get_templates_listing')).done(function (config_data, templates_data) {
|
||||
templatesConfig = config_data[0];
|
||||
availableTemplates = templates_data[0];
|
||||
$.getJSON(RELATIVE_PATH + '/api/get_templates_listing', function (data) {
|
||||
templatesConfig = data.templatesConfig;
|
||||
availableTemplates = data.availableTemplates;
|
||||
|
||||
app.load();
|
||||
});
|
||||
|
||||
@@ -413,8 +413,8 @@ var socket,
|
||||
if (utils.findBootstrapEnvironment() === 'xs') {
|
||||
return;
|
||||
}
|
||||
$('#header-menu li i[title]').each(function() {
|
||||
$(this).parents('a').tooltip({
|
||||
$('#header-menu li [title]').each(function() {
|
||||
$(this).tooltip({
|
||||
placement: 'bottom',
|
||||
title: $(this).attr('title')
|
||||
});
|
||||
@@ -441,7 +441,7 @@ var socket,
|
||||
searchButton.show();
|
||||
}
|
||||
|
||||
searchButton.off().on('click', function(e) {
|
||||
searchButton.on('click', function(e) {
|
||||
if (!config.loggedIn && !config.allowGuestSearching) {
|
||||
app.alert({
|
||||
message:'[[error:search-requires-login]]',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"use strict";
|
||||
/*global define, socket, app, bootbox, tabIndent, config, RELATIVE_PATH*/
|
||||
/*global define, socket, app, bootbox, tabIndent, config, RELATIVE_PATH, templates */
|
||||
|
||||
define('forum/admin/themes', ['forum/admin/settings'], function(Settings) {
|
||||
var Themes = {};
|
||||
@@ -71,17 +71,17 @@ define('forum/admin/themes', ['forum/admin/settings'], function(Settings) {
|
||||
if (confirm) {
|
||||
socket.emit('admin.themes.set', {
|
||||
type: 'local',
|
||||
id: 'nodebb-theme-cerulean'
|
||||
id: 'nodebb-theme-vanilla'
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
highlightSelectedTheme('nodebb-theme-cerulean');
|
||||
highlightSelectedTheme('nodebb-theme-vanilla');
|
||||
app.alert({
|
||||
alert_id: 'admin:theme',
|
||||
type: 'success',
|
||||
title: 'Theme Changed',
|
||||
message: 'You have successfully reverted your NodeBB back to it\'s default theme. Restarting your NodeBB <i class="fa fa-refresh fa-spin"></i>',
|
||||
message: 'You have successfully reverted your NodeBB back to it\'s default theme.',
|
||||
timeout: 3500
|
||||
});
|
||||
});
|
||||
@@ -95,34 +95,19 @@ define('forum/admin/themes', ['forum/admin/settings'], function(Settings) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
|
||||
var instListEl = $('#installed_themes').empty(), liEl;
|
||||
var instListEl = $('#installed_themes');
|
||||
|
||||
if (!themes.length) {
|
||||
instListEl.append($('<li/ >').addClass('no-themes').html('No installed themes found'));
|
||||
return;
|
||||
} else {
|
||||
templates.parse('partials/admin/theme_list', {
|
||||
themes: themes
|
||||
}, function(html) {
|
||||
instListEl.html(html);
|
||||
highlightSelectedTheme(config['theme:id']);
|
||||
});
|
||||
}
|
||||
|
||||
for (var x = 0, numThemes = themes.length; x < numThemes; x++) {
|
||||
liEl = $('<li/ >').attr({
|
||||
'data-type': 'local',
|
||||
'data-theme': themes[x].id
|
||||
}).html('<img src="' + (themes[x].screenshot ? RELATIVE_PATH + '/css/previews/' + themes[x].id : RELATIVE_PATH + '/images/themes/default.png') + '" />' +
|
||||
'<div>' +
|
||||
'<div class="pull-right">' +
|
||||
'<button class="btn btn-primary" data-action="use">Use</button> ' +
|
||||
'</div>' +
|
||||
'<h4>' + themes[x].name + '</h4>' +
|
||||
'<p>' +
|
||||
themes[x].description +
|
||||
(themes[x].url ? ' (<a href="' + themes[x].url + '">Homepage</a>)' : '') +
|
||||
'</p>' +
|
||||
'</div>' +
|
||||
'<div class="clear">');
|
||||
|
||||
instListEl.append(liEl);
|
||||
}
|
||||
|
||||
highlightSelectedTheme(config['theme:id']);
|
||||
});
|
||||
|
||||
// Proper tabbing for "Custom CSS" field
|
||||
@@ -138,26 +123,23 @@ define('forum/admin/themes', ['forum/admin/settings'], function(Settings) {
|
||||
};
|
||||
|
||||
Themes.render = function(bootswatch) {
|
||||
var themeContainer = $('#bootstrap_themes').empty(),
|
||||
numThemes = bootswatch.themes.length, themeEl, theme;
|
||||
var themeContainer = $('#bootstrap_themes');
|
||||
|
||||
for (var x = 0; x < numThemes; x++) {
|
||||
theme = bootswatch.themes[x];
|
||||
themeEl = $('<li />').attr({
|
||||
'data-type': 'bootswatch',
|
||||
'data-css': theme.cssCdn,
|
||||
'data-theme': theme.name
|
||||
}).html('<img src="' + theme.thumbnail + '" />' +
|
||||
'<div>' +
|
||||
'<div class="pull-right">' +
|
||||
'<button class="btn btn-primary" data-action="use">Use</button> ' +
|
||||
'</div>' +
|
||||
'<h4>' + theme.name + '</h4>' +
|
||||
'<p>' + theme.description + '</p>' +
|
||||
'</div>' +
|
||||
'<div class="clear">');
|
||||
themeContainer.append(themeEl);
|
||||
}
|
||||
templates.parse('partials/admin/theme_list', {
|
||||
themes: bootswatch.themes.map(function(theme) {
|
||||
return {
|
||||
type: 'bootswatch',
|
||||
id: theme.name,
|
||||
name: theme.name,
|
||||
description: theme.description,
|
||||
screenshot_url: theme.thumbnail,
|
||||
url: theme.preview,
|
||||
css: theme.cssCdn
|
||||
};
|
||||
})
|
||||
}, function(html) {
|
||||
themeContainer.html(html);
|
||||
});
|
||||
};
|
||||
|
||||
Themes.prepareWidgets = function() {
|
||||
|
||||
@@ -28,7 +28,7 @@ define('forum/login', function() {
|
||||
if (previousUrl) {
|
||||
app.previousUrl = previousUrl;
|
||||
} else if (!app.previousUrl) {
|
||||
app.previousUrl = '/';
|
||||
app.previousUrl = RELATIVE_PATH || '/';
|
||||
}
|
||||
|
||||
if(app.previousUrl.indexOf('/reset/') !== -1) {
|
||||
|
||||
@@ -72,13 +72,11 @@ define('forum/topic', dependencies, function(pagination, infinitescroll, threadT
|
||||
|
||||
$(window).trigger('action:topic.loaded');
|
||||
|
||||
socket.emit('topics.markAsRead', tid);
|
||||
socket.emit('topics.markTopicNotificationsRead', tid);
|
||||
socket.emit('topics.increaseViewCount', tid);
|
||||
socket.emit('topics.enter', tid);
|
||||
};
|
||||
|
||||
Topic.toTop = function() {
|
||||
navigator.scrollTop(1);
|
||||
navigator.scrollTop(0);
|
||||
};
|
||||
|
||||
Topic.toBottom = function() {
|
||||
@@ -353,7 +351,10 @@ define('forum/topic', dependencies, function(pagination, infinitescroll, threadT
|
||||
utils.makeNumbersHumanReadable(element.find('.human-readable-number'));
|
||||
element.find('span.timeago').timeago();
|
||||
element.find('.post-content img:not(.emoji)').addClass('img-responsive').each(function() {
|
||||
$(this).wrap('<a href="' + $(this).attr('src') + '" target="_blank">');
|
||||
var $this = $(this);
|
||||
if (!$this.parent().is('a')) {
|
||||
$this.wrap('<a href="' + $this.attr('src') + '" target="_blank">');
|
||||
}
|
||||
});
|
||||
postTools.updatePostCount();
|
||||
showBottomPostBar();
|
||||
@@ -364,7 +365,7 @@ define('forum/topic', dependencies, function(pagination, infinitescroll, threadT
|
||||
postHtml.find('.move').toggleClass('none', !privileges.move);
|
||||
postHtml.find('.reply, .quote').toggleClass('none', !$('.post_reply').length);
|
||||
var isSelfPost = parseInt(postHtml.attr('data-uid'), 10) === parseInt(app.uid, 10);
|
||||
postHtml.find('.chat, .flag').toggleClass('none', isSelfPost);
|
||||
postHtml.find('.chat, .flag').toggleClass('none', isSelfPost || !app.uid);
|
||||
}
|
||||
|
||||
function loadMorePosts(direction) {
|
||||
|
||||
@@ -36,13 +36,26 @@ define('forum/topic/postTools', ['composer', 'share', 'navigator'], function(com
|
||||
};
|
||||
|
||||
function addFavouriteHandler() {
|
||||
$('#post-container').on('mouseenter', '.favourite-tooltip', function(e) {
|
||||
$('#post-container').on('mouseenter', '.favourite-tooltip', function() {
|
||||
if (!$(this).data('users-loaded')) {
|
||||
$(this).data('users-loaded', "true");
|
||||
$(this).data('users-loaded', 'true');
|
||||
var pid = $(this).parents('.post-row').attr('data-pid');
|
||||
var el = $(this).attr('title', "Loading...");
|
||||
socket.emit('posts.getFavouritedUsers', pid, function(err, usernames) {
|
||||
el.attr('title', usernames).tooltip('show');
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
if (usernames.length > 6) {
|
||||
var otherCount = usernames.length - 5;
|
||||
usernames = usernames.slice(0, 5).join(', ').replace(/,/g, '|');
|
||||
translator.translate('[[topic:users_and_others, ' + usernames + ', ' + otherCount + ']]', function(translated) {
|
||||
translated = translated.replace(/\|/g, ',');
|
||||
el.attr('title', translated).tooltip('show');
|
||||
});
|
||||
} else {
|
||||
usernames = usernames.join(', ');
|
||||
el.attr('title', usernames).tooltip('show');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -137,7 +150,7 @@ define('forum/topic/postTools', ['composer', 'share', 'navigator'], function(com
|
||||
if($('.composer').length) {
|
||||
composer.addQuote(tid, ajaxify.variables.get('topic_slug'), getData(button, 'data-index'), pid, topicName, username, quoted);
|
||||
} else {
|
||||
composer.newReply(tid, pid, topicName, '[[modules:composer.user_said, ' + username + ']]' + quoted);
|
||||
composer.newReply(tid, pid, topicName, '[[modules:composer.user_said, ' + username + ']]\n' + quoted);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
property = tag.property ? 'property="' + tag.property + '" ' : '',
|
||||
content = tag.content ? 'content="' + tag.content.replace(/\n/g, ' ') + '" ' : '';
|
||||
|
||||
return '<meta ' + name + property + content + ' />';
|
||||
return '<meta ' + name + property + content + '/>';
|
||||
};
|
||||
|
||||
if ('undefined' !== typeof window) {
|
||||
|
||||
@@ -249,7 +249,7 @@ define('chat', ['taskbar', 'string', 'sounds', 'forum/chats'], function(taskbar,
|
||||
|
||||
module.center = function(chatModal) {
|
||||
chatModal.css("left", Math.max(0, (($(window).width() - $(chatModal).outerWidth()) / 2) + $(window).scrollLeft()) + "px");
|
||||
chatModal.css("top", $(window).height() / 4 - $(chatModal).outerHeight() / 2);
|
||||
chatModal.css("top", Math.max(0, $(window).height() / 4 - $(chatModal).outerHeight() / 2));
|
||||
chatModal.css("zIndex", 2000);
|
||||
chatModal.find('#chat-message-input').focus();
|
||||
return chatModal;
|
||||
|
||||
@@ -119,7 +119,7 @@ define('composer', dependencies, function(taskbar, controls, uploads, formatting
|
||||
var uuid = composer.active;
|
||||
|
||||
if (uuid === undefined) {
|
||||
composer.newReply(tid, pid, title, '[[modules:composer.user_said, ' + username + ']]' + text);
|
||||
composer.newReply(tid, pid, title, '[[modules:composer.user_said, ' + username + ']]\n' + text);
|
||||
return;
|
||||
}
|
||||
var postContainer = $('#cmp-uuid-' + uuid);
|
||||
@@ -127,9 +127,9 @@ define('composer', dependencies, function(taskbar, controls, uploads, formatting
|
||||
var prevText = bodyEl.val();
|
||||
if (parseInt(tid, 10) !== parseInt(composer.posts[uuid].tid, 10)) {
|
||||
var link = '[' + title + '](/topic/' + topicSlug + '/' + (parseInt(postIndex, 10) + 1) + ')';
|
||||
translator.translate('[[modules:composer.user_said_in, ' + username + ', ' + link + ']]', onTranslated);
|
||||
translator.translate('[[modules:composer.user_said_in, ' + username + ', ' + link + ']]\n', onTranslated);
|
||||
} else {
|
||||
translator.translate('[[modules:composer.user_said, ' + username + ']]', onTranslated);
|
||||
translator.translate('[[modules:composer.user_said, ' + username + ']]\n', onTranslated);
|
||||
}
|
||||
|
||||
function onTranslated(translated) {
|
||||
@@ -360,33 +360,45 @@ define('composer', dependencies, function(taskbar, controls, uploads, formatting
|
||||
return composerAlert('[[error:content-too-short, ' + config.minimumPostLength + ']]');
|
||||
}
|
||||
|
||||
var composerData = {}, action;
|
||||
|
||||
if (parseInt(postData.cid, 10) > 0) {
|
||||
socket.emit('topics.post', {
|
||||
composerData = {
|
||||
title: titleEl.val(),
|
||||
content: bodyEl.val(),
|
||||
topic_thumb: thumbEl.val() || '',
|
||||
category_id: postData.cid,
|
||||
tags: tags.getTags(post_uuid)
|
||||
}, function(err, topic) {
|
||||
};
|
||||
|
||||
action = 'topics.post';
|
||||
socket.emit(action, composerData, function(err, topic) {
|
||||
done(err);
|
||||
|
||||
if (!err) {
|
||||
ajaxify.go('topic/' + topic.slug);
|
||||
}
|
||||
});
|
||||
} else if (parseInt(postData.tid, 10) > 0) {
|
||||
socket.emit('posts.reply', {
|
||||
composerData = {
|
||||
tid: postData.tid,
|
||||
content: bodyEl.val(),
|
||||
toPid: postData.toPid
|
||||
}, done);
|
||||
};
|
||||
|
||||
action = 'posts.reply';
|
||||
socket.emit(action, composerData, done);
|
||||
} else if (parseInt(postData.pid, 10) > 0) {
|
||||
socket.emit('posts.edit', {
|
||||
composerData = {
|
||||
pid: postData.pid,
|
||||
content: bodyEl.val(),
|
||||
title: titleEl.val(),
|
||||
topic_thumb: thumbEl.val() || '',
|
||||
tags: tags.getTags(post_uuid)
|
||||
}, done);
|
||||
};
|
||||
|
||||
action = 'posts.edit';
|
||||
socket.emit(action, composerData, done);
|
||||
}
|
||||
|
||||
function done(err) {
|
||||
@@ -401,6 +413,8 @@ define('composer', dependencies, function(taskbar, controls, uploads, formatting
|
||||
|
||||
discard(post_uuid);
|
||||
drafts.removeDraft(postData.save_id);
|
||||
|
||||
$(window).trigger('action:composer.' + action, composerData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ define('navigator', ['forum/pagination'], function(pagination) {
|
||||
|
||||
navigator.scrollTop = function(index) {
|
||||
if ($('li[data-index="' + index + '"]').length) {
|
||||
navigator.scrollUp();
|
||||
navigator.scrollToPost(index, true);
|
||||
} else {
|
||||
ajaxify.go(generateUrl());
|
||||
}
|
||||
@@ -118,7 +118,7 @@ define('navigator', ['forum/pagination'], function(pagination) {
|
||||
|
||||
navigator.scrollBottom = function(index) {
|
||||
if ($('li[data-index="' + index + '"]').length) {
|
||||
navigator.scrollDown();
|
||||
navigator.scrollToPost(index, true);
|
||||
} else {
|
||||
index = parseInt(index, 10) + 1;
|
||||
ajaxify.go(generateUrl(index));
|
||||
|
||||
@@ -36,7 +36,7 @@ Please use the npm module instead - require('templates.js')
|
||||
}
|
||||
|
||||
callback(parse(loaded, obj, bind));
|
||||
});
|
||||
});
|
||||
} else {
|
||||
callback(parse(templates.cache[template], obj, bind));
|
||||
}
|
||||
@@ -58,14 +58,15 @@ Please use the npm module instead - require('templates.js')
|
||||
};
|
||||
|
||||
templates.getBlock = function(template, block) {
|
||||
return template.replace(new RegExp('[\\s\\S]*(<!--[\\s]*BEGIN ' + block + '[\\s]*-->[\r\n]*[\\s\\S]*?[\r\n]*<!--[\\s]*END ' + block + '[\\s]*-->)[\\s\\S]*', 'g'), '$1');
|
||||
return template.replace(new RegExp('[\\s\\S]*(<!--[\\s]*BEGIN ' + block + '[\\s]*-->[\\s\\S]*?<!--[\\s]*END ' + block + '[\\s]*-->)[\\s\\S]*', 'g'), '$1');
|
||||
};
|
||||
|
||||
function express(filename, options, fn) {
|
||||
console.log(filename, options, fn);
|
||||
var fs = require('fs'),
|
||||
tpl = filename.replace(options.settings.views + '/', '');
|
||||
|
||||
options['_locals'] = null;
|
||||
|
||||
if (!templates.cache[tpl]) {
|
||||
fs.readFile(filename, function(err, html) {
|
||||
templates.cache[tpl] = (html || '').toString();
|
||||
@@ -82,11 +83,11 @@ Please use the npm module instead - require('templates.js')
|
||||
}
|
||||
|
||||
function makeRegex(block) {
|
||||
return new RegExp('<!--[\\s]*BEGIN ' + block + '[\\s]*-->[\\s\\S]*?<!--[\\s]*END ' + block + '[\\s]*-->');
|
||||
return new RegExp('[\\t ]*<!--[\\s]*BEGIN ' + block + '[\\s]*-->[\\s\\S]*?<!--[\\s]*END ' + block + '[\\s]*-->');
|
||||
}
|
||||
|
||||
function makeBlockRegex(block) {
|
||||
return new RegExp('([\\n]?<!--[\\s]*BEGIN ' + block + '[\\s]*-->[\\n]?)|([\\n]?<!--[\\s]*END ' + block + '[\\s]*-->[\\n]?)', 'g');
|
||||
return new RegExp('([\\t ]*<!--[\\s]*BEGIN ' + block + '[\\s]*-->[\\r\\n?|\\n]?)|(<!--[\\s]*END ' + block + '[\\s]*-->)', 'g');
|
||||
}
|
||||
|
||||
function makeConditionalRegex(block) {
|
||||
@@ -94,7 +95,7 @@ Please use the npm module instead - require('templates.js')
|
||||
}
|
||||
|
||||
function makeStatementRegex(key) {
|
||||
return new RegExp('([\\s]*<!--[\\s]*IF ' + key + '[\\s]*-->)|(<!--[\\s]*ENDIF ' + key + '[\\s]*-->[\\s]*)', 'gi');
|
||||
return new RegExp('(<!--[\\s]*IF ' + key + '[\\s]*-->)|(<!--[\\s]*ENDIF ' + key + '[\\s]*-->)', 'g');
|
||||
}
|
||||
|
||||
function registerGlobals(obj) {
|
||||
@@ -113,23 +114,23 @@ Please use the npm module instead - require('templates.js')
|
||||
if (matches !== null) {
|
||||
for (var i = 0, ii = matches.length; i < ii; i++) {
|
||||
var statement = makeStatementRegex(key),
|
||||
nestedConditionals = matches[i].match(/[\s|\S]<!-- IF[\s\S]*ENDIF[\s\S]*-->[\s|\S]/),
|
||||
match = matches[i].replace(statement, '').replace(/[\s|\S]<!-- IF[\s\S]*ENDIF[\s\S]*-->[\s|\S]/gi, '<!-- NESTED -->'),
|
||||
conditionalBlock = match.split(/\s*<!-- ELSE -->\s*/);
|
||||
nestedConditionals = matches[i].match(/(?!^)<!-- IF([\s\S]*?)ENDIF[ a-zA-Z0-9\._:]*-->(?!$)/gi),
|
||||
match = matches[i].replace(statement, '').replace(/(?!^)<!-- IF([\s\S]*?)ENDIF[ a-zA-Z0-9\._:]*-->(?!$)/gi, '<!-- NESTED -->'),
|
||||
conditionalBlock = match.split(/[\r\n?\n]*?<!-- ELSE -->[\r\n?\n]*?/);
|
||||
|
||||
if (conditionalBlock[1]) {
|
||||
// there is an else statement
|
||||
if (!value) {
|
||||
template = template.replace(matches[i], conditionalBlock[1].replace(/(^[\r\n\t]*)|([\r\n\t]*$)/gi, ''));
|
||||
if (!value) { // todo check second line break conditional, doesn't match.
|
||||
template = template.replace(matches[i], conditionalBlock[1].replace(/(^[\r\n?|\n]*)|([\r\n\t]*$)/gi, ''));
|
||||
} else {
|
||||
template = template.replace(matches[i], conditionalBlock[0].replace(/(^[\r\n\t]*)|([\r\n\t]*$)/gi, ''));
|
||||
template = template.replace(matches[i], conditionalBlock[0].replace(/(^[\r\n?|\n]*)|([\r\n\t]*$)/gi, ''));
|
||||
}
|
||||
} else {
|
||||
// regular if statement
|
||||
if (!value) {
|
||||
template = template.replace(matches[i], '');
|
||||
} else {
|
||||
template = template.replace(matches[i], match.replace(/(^[\r\n\t]*)|([\r\n\t]*$)/gi, ''));
|
||||
template = template.replace(matches[i], match.replace(/(^[\r\n?|\n]*)|([\r\n\t]*$)/gi, ''));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,40 +182,43 @@ Please use the npm module instead - require('templates.js')
|
||||
var regex = makeRegex(key), block;
|
||||
|
||||
if (!array[key].length) {
|
||||
return template;
|
||||
return template.replace(regex, '');
|
||||
}
|
||||
|
||||
while (block = template.match(regex)) {
|
||||
block = block[0].replace(makeBlockRegex(key), '');
|
||||
|
||||
|
||||
var numblocks = array[key].length - 1,
|
||||
iterator = 0,
|
||||
result = '',
|
||||
parsedBlock;
|
||||
|
||||
do {
|
||||
parsedBlock = parse(block, array[key][iterator], bind, namespace, {iterator: iterator, total: numblocks}) + ((iterator < numblocks) ? '\r\n':'');
|
||||
|
||||
parsedBlock = parse(block, array[key][iterator], bind, namespace, {iterator: iterator, total: numblocks});
|
||||
|
||||
result += (!bind) ? parsedBlock : setBindContainer(parsedBlock, bind + namespace + iterator);
|
||||
result = parseFunctions(block, result, {
|
||||
data: array[key][iterator],
|
||||
iterator: iterator,
|
||||
numblocks: numblocks
|
||||
});
|
||||
|
||||
result = checkConditional(result, '@first', iterator === 0);
|
||||
result = checkConditional(result, '!@first', iterator !== 0);
|
||||
result = checkConditional(result, '@last', iterator === numblocks);
|
||||
result = checkConditional(result, '!@last', iterator !== numblocks);
|
||||
|
||||
result = result.replace(/^[\r\n?|\n|\t]*?|[\r\n?|\n|\t]*?$/g, '');
|
||||
|
||||
result = parseFunctions(block, result, {
|
||||
data: array[key][iterator],
|
||||
iterator: iterator,
|
||||
numblocks: numblocks
|
||||
});
|
||||
|
||||
if (bind) {
|
||||
array[key][iterator].__template = block;
|
||||
}
|
||||
} while (iterator++ < numblocks);
|
||||
|
||||
template = template.replace(regex, result);
|
||||
template = template.replace(regex, result.replace(/^[\r\n?|\n]|[\r\n?|\n]$/g, ''));
|
||||
}
|
||||
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
@@ -248,14 +252,14 @@ Please use the npm module instead - require('templates.js')
|
||||
this['__' + key] = value;
|
||||
|
||||
var els = document.querySelectorAll('[data-binding="' + (this.__iterator !== false ? (bind + this.__namespace + this.__iterator) : bind) + '"]');
|
||||
|
||||
|
||||
for (var el in els) {
|
||||
if (els.hasOwnProperty(el)) {
|
||||
if (this.__parent) {
|
||||
var parent = this.__parent();
|
||||
els[el].innerHTML = parse(parent.template, parent.data, false);
|
||||
} else {
|
||||
els[el].innerHTML = parse(this.__template, obj, false, this.__namespace);
|
||||
els[el].innerHTML = parse(this.__template, obj, false, this.__namespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,7 +299,7 @@ Please use the npm module instead - require('templates.js')
|
||||
template = parse(template, obj[key], bind, namespace + key + '.');
|
||||
} else {
|
||||
template = parseValue(template, namespace + key, obj[key]);
|
||||
|
||||
|
||||
if (bind && obj[key]) {
|
||||
setupBindings({
|
||||
obj: obj,
|
||||
|
||||
@@ -93,7 +93,7 @@ var db = require('./database'),
|
||||
category.pageCount = results.pageCount;
|
||||
category.topic_row_size = 'col-md-9';
|
||||
|
||||
callback(null, category);
|
||||
plugins.fireHook('filter:category.get', category, uid, callback);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -237,10 +237,9 @@ var db = require('./database'),
|
||||
};
|
||||
|
||||
Categories.markAsUnreadForAll = function(cid, callback) {
|
||||
callback = callback || function() {};
|
||||
db.delete('cid:' + cid + ':read_by_uid', function(err) {
|
||||
if(typeof callback === 'function') {
|
||||
callback(err);
|
||||
}
|
||||
callback(err);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -295,6 +294,13 @@ var db = require('./database'),
|
||||
db.getObjectField('category:' + cid, field, callback);
|
||||
};
|
||||
|
||||
Categories.getMultipleCategoryFields = function(cids, fields, callback) {
|
||||
var keys = cids.map(function(cid) {
|
||||
return 'category:' + cid;
|
||||
});
|
||||
db.getObjectsFields(keys, fields, callback);
|
||||
};
|
||||
|
||||
Categories.getCategoryFields = function(cid, fields, callback) {
|
||||
db.getObjectFields('category:' + cid, fields, callback);
|
||||
};
|
||||
|
||||
@@ -365,6 +365,8 @@ accountsController.accountSettings = function(req, res, next) {
|
||||
userData.settings = results.settings;
|
||||
userData.languages = results.languages;
|
||||
|
||||
userData.disableEmailSubscriptions = meta.config.disableEmailSubscriptions !== undefined && parseInt(meta.config.disableEmailSubscriptions, 10) === 1;
|
||||
|
||||
res.render('account/settings', userData);
|
||||
});
|
||||
});
|
||||
@@ -402,7 +404,11 @@ accountsController.uploadPicture = function (req, res, next) {
|
||||
image.resizeImage(req.files.userPhoto.path, extension, 128, 128, next);
|
||||
},
|
||||
function(next) {
|
||||
image.convertImageToPng(req.files.userPhoto.path, extension, next);
|
||||
if (parseInt(meta.config['profile:convertProfileImageToPNG'], 10) === 1) {
|
||||
image.convertImageToPng(req.files.userPhoto.path, extension, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
function(next) {
|
||||
user.getUidByUserslug(req.params.userslug, next);
|
||||
@@ -448,7 +454,7 @@ accountsController.uploadPicture = function (req, res, next) {
|
||||
return plugins.fireHook('filter:uploadImage', req.files.userPhoto, done);
|
||||
}
|
||||
|
||||
var convertToPNG = parseInt(meta.config['profile:convertProfileImageToPNG'], 10);
|
||||
var convertToPNG = parseInt(meta.config['profile:convertProfileImageToPNG'], 10) === 1;
|
||||
var filename = updateUid + '-profileimg' + (convertToPNG ? '.png' : extension);
|
||||
|
||||
user.getUserField(updateUid, 'uploadedpicture', function (err, oldpicture) {
|
||||
|
||||
@@ -8,7 +8,8 @@ var categoriesController = {},
|
||||
user = require('./../user'),
|
||||
categories = require('./../categories'),
|
||||
topics = require('./../topics'),
|
||||
meta = require('./../meta');
|
||||
meta = require('./../meta'),
|
||||
plugins = require('./../plugins');
|
||||
|
||||
categoriesController.recent = function(req, res, next) {
|
||||
var uid = req.user ? req.user.uid : 0;
|
||||
@@ -19,7 +20,9 @@ categoriesController.recent = function(req, res, next) {
|
||||
|
||||
data['feeds:disableRSS'] = meta.config['feeds:disableRSS'] === '1' ? true : false;
|
||||
|
||||
res.render('recent', data);
|
||||
plugins.fireHook('filter:category.get', data, uid, function(err, data) {
|
||||
res.render('recent', data);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -35,7 +38,9 @@ categoriesController.popular = function(req, res, next) {
|
||||
|
||||
data['feeds:disableRSS'] = meta.config['feeds:disableRSS'] === '1' ? true : false;
|
||||
|
||||
res.render('popular', {topics: data});
|
||||
plugins.fireHook('filter:category.get', {topics: data}, uid, function(err, data) {
|
||||
res.render('popular', data);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -47,7 +52,9 @@ categoriesController.unread = function(req, res, next) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.render('unread', data);
|
||||
plugins.fireHook('filter:category.get', data, uid, function(err, data) {
|
||||
res.render('unread', data);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -74,22 +81,16 @@ categoriesController.get = function(req, res, next) {
|
||||
},
|
||||
function(disabled, next) {
|
||||
if (parseInt(disabled, 10) === 1) {
|
||||
return next(new Error('category-disabled'));
|
||||
return next(new Error('[[error:category-disabled]]'));
|
||||
}
|
||||
|
||||
privileges.categories.get(cid, uid, function(err, categoryPrivileges) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (!categoryPrivileges.read) {
|
||||
return next(new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
|
||||
next(null, categoryPrivileges);
|
||||
});
|
||||
privileges.categories.get(cid, uid, next);
|
||||
},
|
||||
function (privileges, next) {
|
||||
if (!privileges.read) {
|
||||
return next(new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
|
||||
user.getSettings(uid, function(err, settings) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
@@ -111,12 +112,6 @@ categoriesController.get = function(req, res, next) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (categoryData) {
|
||||
if (parseInt(categoryData.disabled, 10) === 1) {
|
||||
return next(new Error('[[error:category-disabled]]'));
|
||||
}
|
||||
}
|
||||
|
||||
categoryData.privileges = privileges;
|
||||
next(err, categoryData);
|
||||
});
|
||||
@@ -189,7 +184,6 @@ categoriesController.get = function(req, res, next) {
|
||||
active: x === parseInt(page, 10)
|
||||
});
|
||||
}
|
||||
|
||||
res.render('category', data);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
var tagsController = {},
|
||||
async = require('async'),
|
||||
nconf = require('nconf'),
|
||||
topics = require('./../topics');
|
||||
|
||||
tagsController.getTag = function(req, res, next) {
|
||||
@@ -13,7 +14,7 @@ tagsController.getTag = function(req, res, next) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (!tids || !tids.length) {
|
||||
if (Array.isArray(tids) && !tids.length) {
|
||||
topics.deleteTag(tag);
|
||||
return res.render('tag', {topics: [], tag:tag});
|
||||
}
|
||||
@@ -22,6 +23,22 @@ tagsController.getTag = function(req, res, next) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.locals.metaTags = [
|
||||
{
|
||||
name: "title",
|
||||
content: tag
|
||||
},
|
||||
{
|
||||
property: 'og:title',
|
||||
content: tag
|
||||
},
|
||||
{
|
||||
property: "og:url",
|
||||
content: nconf.get('url') + '/tags/' + tag
|
||||
}
|
||||
];
|
||||
|
||||
data.tag = tag;
|
||||
res.render('tag', data);
|
||||
});
|
||||
|
||||
@@ -51,6 +51,10 @@ module.exports = function(db, module) {
|
||||
});
|
||||
};
|
||||
|
||||
module.getSetsMembers = function(keys, callback) {
|
||||
throw new Error('not-implemented');
|
||||
};
|
||||
|
||||
module.setCount = function(key, callback) {
|
||||
module.getListRange(key, 0, -1, function(err, set) {
|
||||
callback(err, set.length);
|
||||
|
||||
@@ -91,6 +91,24 @@ module.exports = function(db, module) {
|
||||
});
|
||||
};
|
||||
|
||||
module.getSetsMembers = function(keys, callback) {
|
||||
db.collection('objects').find({_key: {$in: keys}}, {_key: 1, members: 1}).toArray(function(err, data) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
var sets = {};
|
||||
data.forEach(function(set) {
|
||||
sets[set._key] = set.members || [];
|
||||
});
|
||||
|
||||
var returnData = new Array(keys.length);
|
||||
for(var i=0; i<keys.length; ++i) {
|
||||
returnData[i] = sets[keys[i]] || [];
|
||||
}
|
||||
callback(null, returnData);
|
||||
});
|
||||
};
|
||||
|
||||
module.setCount = function(key, callback) {
|
||||
db.collection('objects').findOne({_key:key}, function(err, data) {
|
||||
return callback(err, data ? data.members.length : 0);
|
||||
|
||||
@@ -151,13 +151,15 @@ module.exports = function(db, module) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
results = results.map(function(item) {
|
||||
return item.value;
|
||||
});
|
||||
|
||||
values = values.map(function(value) {
|
||||
return results.indexOf(value) !== -1;
|
||||
});
|
||||
callback(err, results);
|
||||
callback(null, values);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ module.exports = function(redisClient, module) {
|
||||
};
|
||||
|
||||
module.setObjectField = function(key, field, value, callback) {
|
||||
callback = callback || function() {};
|
||||
redisClient.hset(key, field, value, callback);
|
||||
};
|
||||
|
||||
|
||||
@@ -54,6 +54,14 @@ module.exports = function(redisClient, module) {
|
||||
redisClient.smembers(key, callback);
|
||||
};
|
||||
|
||||
module.getSetsMembers = function(keys, callback) {
|
||||
var multi = redisClient.multi();
|
||||
for (var i=0; i<keys.length; ++i) {
|
||||
multi.smembers(keys[i]);
|
||||
}
|
||||
multi.exec(callback);
|
||||
};
|
||||
|
||||
module.setCount = function(key, callback) {
|
||||
redisClient.scard(key, callback);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
module.exports = function(redisClient, module) {
|
||||
module.sortedSetAdd = function(key, score, value, callback) {
|
||||
callback = callback || function() {};
|
||||
redisClient.zadd(key, score, value, callback);
|
||||
};
|
||||
|
||||
|
||||
@@ -247,9 +247,10 @@ var async = require('async'),
|
||||
};
|
||||
|
||||
Favourites.getFavouritedUidsByPids = function(pids, callback) {
|
||||
async.map(pids, function(pid, next) {
|
||||
db.getSetMembers('pid:' + pid + ':users_favourited', next);
|
||||
}, callback);
|
||||
var sets = pids.map(function(pid) {
|
||||
return 'pid:' + pid + ':users_favourited';
|
||||
});
|
||||
db.getSetsMembers(sets, callback);
|
||||
};
|
||||
|
||||
}(exports));
|
||||
|
||||
@@ -156,6 +156,10 @@
|
||||
db.isSetMember('group:' + groupName + ':members', uid, callback);
|
||||
};
|
||||
|
||||
Groups.isMembers = function(uids, groupName, callback) {
|
||||
db.isSetMembers('group:' + groupName + ':members', uids, callback);
|
||||
};
|
||||
|
||||
Groups.isMemberOfGroups = function(uid, groups, callback) {
|
||||
groups = groups.map(function(groupName) {
|
||||
return 'group:' + groupName + ':members';
|
||||
@@ -434,7 +438,7 @@
|
||||
});
|
||||
};
|
||||
|
||||
Groups.getUserGroups = function(uid, callback) {
|
||||
Groups.getUserGroups = function(uids, callback) {
|
||||
var ignoredGroups = ['registered-users'];
|
||||
|
||||
db.getSetMembers('groups', function(err, groupNames) {
|
||||
@@ -462,19 +466,22 @@
|
||||
return 'group:' + group.name + ':members';
|
||||
});
|
||||
|
||||
db.isMemberOfSets(groupSets, uid, function(err, isMembers) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
for(var i=isMembers.length - 1; i>=0; --i) {
|
||||
if (!isMembers[i]) {
|
||||
groupData.splice(i, 1);
|
||||
async.map(uids, function(uid, next) {
|
||||
db.isMemberOfSets(groupSets, uid, function(err, isMembers) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
}
|
||||
|
||||
callback(null, groupData);
|
||||
});
|
||||
var memberOf = [];
|
||||
isMembers.forEach(function(isMember, index) {
|
||||
if (isMember) {
|
||||
memberOf.push(groupData[index]);
|
||||
}
|
||||
});
|
||||
|
||||
next(null, memberOf);
|
||||
});
|
||||
}, callback);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs'),
|
||||
gm = require('gm').subClass({imageMagick: true}),
|
||||
meta = require('./meta');
|
||||
gm = require('gm').subClass({imageMagick: true});
|
||||
|
||||
var image = {};
|
||||
|
||||
@@ -28,8 +27,7 @@ image.resizeImage = function(path, extension, width, height, callback) {
|
||||
};
|
||||
|
||||
image.convertImageToPng = function(path, extension, callback) {
|
||||
var convertToPNG = parseInt(meta.config['profile:convertProfileImageToPNG'], 10);
|
||||
if(convertToPNG && extension !== '.png') {
|
||||
if(extension !== '.png') {
|
||||
gm(path).toBuffer('png', function(err, buffer) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
|
||||
@@ -266,12 +266,19 @@ function setOnEmpty(key1, key2) {
|
||||
|
||||
function enableDefaultTheme(next) {
|
||||
var meta = require('./meta');
|
||||
winston.info('Enabling default theme: Lavender');
|
||||
|
||||
meta.themes.set({
|
||||
type: 'local',
|
||||
id: 'nodebb-theme-lavender'
|
||||
}, next);
|
||||
meta.configs.get('theme:id', function(err, id) {
|
||||
if (err || id) {
|
||||
winston.info('Previous theme detected, skipping enabling default theme');
|
||||
return next(err);
|
||||
}
|
||||
|
||||
winston.info('Enabling default theme: Lavender');
|
||||
meta.themes.set({
|
||||
type: 'local',
|
||||
id: 'nodebb-theme-lavender'
|
||||
}, next);
|
||||
});
|
||||
}
|
||||
|
||||
function createAdministrator(next) {
|
||||
|
||||
@@ -6,6 +6,7 @@ var db = require('./database'),
|
||||
user = require('./user'),
|
||||
plugins = require('./plugins'),
|
||||
meta = require('./meta'),
|
||||
utils = require('../public/src/utils'),
|
||||
notifications = require('./notifications'),
|
||||
userNotifications = require('./user/notifications');
|
||||
|
||||
@@ -128,7 +129,7 @@ var db = require('./database'),
|
||||
var self = parseInt(message.fromuid, 10) === parseInt(fromuid, 10);
|
||||
message.fromUser = self ? userData[0] : userData[1];
|
||||
message.toUser = self ? userData[1] : userData[0];
|
||||
message.timestampISO = new Date(parseInt(message.timestamp, 10)).toISOString();
|
||||
message.timestampISO = utils.toISOString(message.timestamp);
|
||||
message.self = self ? 1 : 0;
|
||||
message.newSet = false;
|
||||
|
||||
@@ -193,7 +194,11 @@ var db = require('./database'),
|
||||
}
|
||||
},
|
||||
function(mids, next) {
|
||||
db.getObjects(['message:' + mids[0], 'message:' + mids[1]], next);
|
||||
if (typeof mids !== 'boolean') {
|
||||
db.getObjects(['message:' + mids[0], 'message:' + mids[1]], next);
|
||||
} else {
|
||||
next(null, mids);
|
||||
}
|
||||
},
|
||||
function(messages, next) {
|
||||
if (typeof messages !== 'boolean') {
|
||||
@@ -216,32 +221,27 @@ var db = require('./database'),
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
async.parallel({
|
||||
unreadUids: async.apply(db.isSortedSetMembers, 'uid:' + uid + ':chats:unread', uids),
|
||||
users: async.apply(user.getMultipleUserFields, uids, ['username', 'picture', 'uid'])
|
||||
}, function(err, results) {
|
||||
db.isSortedSetMembers('uid:' + uid + ':chats:unread', uids, function(err, unreadUids) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
var users = results.users;
|
||||
|
||||
for (var i=0; i<users.length; ++i) {
|
||||
users[i].unread = results.unreadUids[i];
|
||||
}
|
||||
user.isOnline(uids, function(err, users) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
users = users.filter(function(user, index) {
|
||||
return !!user.uid;
|
||||
});
|
||||
|
||||
async.map(users, function(userData, next) {
|
||||
user.isOnline(userData.uid, function(err, data) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
users.forEach(function(user, index) {
|
||||
if (user) {
|
||||
user.unread = unreadUids[index];
|
||||
}
|
||||
userData.status = data.status;
|
||||
next(null, userData);
|
||||
});
|
||||
}, callback);
|
||||
|
||||
users = users.filter(function(user) {
|
||||
return !!user.uid;
|
||||
});
|
||||
callback(null, users);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -258,7 +258,9 @@ var db = require('./database'),
|
||||
db.sortedSetAdd('uid:' + uid + ':chats:unread', Date.now(), toUid, callback);
|
||||
};
|
||||
|
||||
// todo #1798 -- this utility method creates a room name given an array of uids.
|
||||
/*
|
||||
todo #1798 -- this utility method creates a room name given an array of uids.
|
||||
|
||||
Messaging.uidsToRoom = function(uids, callback) {
|
||||
uid = parseInt(uid, 10);
|
||||
if (typeof uid === 'number' && Array.isArray(roomUids)) {
|
||||
@@ -274,7 +276,7 @@ var db = require('./database'),
|
||||
} else {
|
||||
callback(new Error('invalid-uid-or-participant-uids'));
|
||||
}
|
||||
};
|
||||
};*/
|
||||
|
||||
Messaging.verifySpammer = function(uid, callback) {
|
||||
var messagesToCompare = 10;
|
||||
|
||||
@@ -39,6 +39,15 @@ module.exports = function(Meta) {
|
||||
return next();
|
||||
} else {
|
||||
var configObj = JSON.parse(file.toString());
|
||||
|
||||
// Minor adjustments for API output
|
||||
configObj.type = 'local';
|
||||
if (configObj.screenshot) {
|
||||
configObj.screenshot_url = nconf.get('relative_path') + '/css/previews/' + configObj.id
|
||||
} else {
|
||||
configObj.screenshot_url = nconf.get('relative_path') + '/images/themes/default.png';
|
||||
}
|
||||
|
||||
next(err, configObj);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ module.exports = function(Meta) {
|
||||
var tests = {
|
||||
isCategory: /^category\/\d+\/?/,
|
||||
isTopic: /^topic\/\d+\/?/,
|
||||
isTag: /^tags\/[\s\S]+\/?/,
|
||||
isUserPage: /^user\/[^\/]+(\/[\w]+)?/
|
||||
};
|
||||
|
||||
@@ -42,6 +43,12 @@ module.exports = function(Meta) {
|
||||
var tid = urlFragment.match(/topic\/(\d+)/)[1];
|
||||
|
||||
require('../topics').getTopicField(tid, 'title', callback);
|
||||
} else if (tests.isTag.test(urlFragment)) {
|
||||
var tag = urlFragment.match(/tags\/([\s\S]+)/)[1];
|
||||
|
||||
translator.translate('[[pages:tags, ' + tag + ']]', language, function(translated) {
|
||||
callback(null, translated);
|
||||
});
|
||||
} else if (tests.isUserPage.test(urlFragment)) {
|
||||
var matches = urlFragment.match(/user\/([^\/]+)\/?([\w]+)?/),
|
||||
userslug = matches[1],
|
||||
|
||||
@@ -16,7 +16,7 @@ var app,
|
||||
|
||||
middleware.isAdmin = function(req, res, next) {
|
||||
if (!req.user) {
|
||||
return res.redirect('/login?next=admin');
|
||||
return res.redirect(nconf.get('relative_path') + '/login?next=admin');
|
||||
}
|
||||
|
||||
user.isAdministrator((req.user && req.user.uid) ? req.user.uid : 0, function (err, isAdmin) {
|
||||
|
||||
@@ -41,7 +41,7 @@ function routeThemeScreenshots(app, themes) {
|
||||
(function(id, path) {
|
||||
fs.exists(path, function(exists) {
|
||||
if (exists) {
|
||||
app.get('/css/previews/' + id, function(req, res) {
|
||||
app.get(relativePath + '/css/previews/' + id, function(req, res) {
|
||||
res.sendfile(path);
|
||||
});
|
||||
}
|
||||
@@ -173,7 +173,7 @@ module.exports = function(app, data) {
|
||||
if(meta.config.cookieDomain) {
|
||||
cookie.domain = meta.config.cookieDomain;
|
||||
}
|
||||
|
||||
|
||||
app.use(session({
|
||||
store: db.sessionStore,
|
||||
secret: nconf.get('secret'),
|
||||
|
||||
@@ -6,7 +6,6 @@ var app,
|
||||
path = require('path'),
|
||||
winston = require('winston'),
|
||||
validator = require('validator'),
|
||||
fs = require('fs'),
|
||||
nconf = require('nconf'),
|
||||
plugins = require('./../plugins'),
|
||||
meta = require('./../meta'),
|
||||
@@ -209,6 +208,7 @@ middleware.renderHeader = function(req, res, callback) {
|
||||
var uid = req.user ? parseInt(req.user.uid, 10) : 0;
|
||||
|
||||
var custom_header = {
|
||||
uid: uid,
|
||||
'navigation': []
|
||||
};
|
||||
|
||||
|
||||
@@ -285,12 +285,22 @@ var async = require('async'),
|
||||
return winston.error(err.message);
|
||||
}
|
||||
|
||||
async.filter(nids, function(nid, next) {
|
||||
db.getObjectField('notifications:' + nid, 'datetime', function(err, datetime) {
|
||||
next(!err && parseInt(datetime, 10) < cutoffTime);
|
||||
var keys = nids.map(function(nid) {
|
||||
return 'notifications:' + nid;
|
||||
});
|
||||
|
||||
db.getObjectsFields(keys, ['nid', 'datetime'], function(err, notifs) {
|
||||
if (err) {
|
||||
return winston.error(err.message);
|
||||
}
|
||||
|
||||
var expiredNids = notifs.filter(function(notif) {
|
||||
return notif && parseInt(notif.datetime, 10) < cutoffTime;
|
||||
}).map(function(notif) {
|
||||
return notif.nid;
|
||||
});
|
||||
}, function(expiredNids) {
|
||||
async.each(expiredNids, function(nid, next) {
|
||||
|
||||
async.eachLimit(expiredNids, 50, function(nid, next) {
|
||||
async.parallel([
|
||||
function(next) {
|
||||
db.setRemove('notifications', nid, next);
|
||||
@@ -303,15 +313,15 @@ var async = require('async'),
|
||||
next(err);
|
||||
});
|
||||
}, function(err) {
|
||||
if (!err) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
winston.info('[notifications.prune] Notification pruning completed. ' + numPruned + ' expired notification' + (numPruned !== 1 ? 's' : '') + ' removed.');
|
||||
}
|
||||
var diff = process.hrtime(start);
|
||||
events.log('Pruning notifications took : ' + (diff[0] * 1e3 + diff[1] / 1e6) + ' ms');
|
||||
} else {
|
||||
winston.error('Encountered error pruning notifications: ' + err.message);
|
||||
if (err) {
|
||||
return winston.error('Encountered error pruning notifications: ' + err.message);
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
winston.info('[notifications.prune] Notification pruning completed. ' + numPruned + ' expired notification' + (numPruned !== 1 ? 's' : '') + ' removed.');
|
||||
}
|
||||
var diff = process.hrtime(start);
|
||||
events.log('Pruning '+ numPruned + ' notifications took : ' + (diff[0] * 1e3 + diff[1] / 1e6) + ' ms');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
88
src/posts.js
88
src/posts.js
@@ -93,7 +93,7 @@ var async = require('async'),
|
||||
};
|
||||
|
||||
Posts.getPostsByTid = function(tid, set, start, end, reverse, callback) {
|
||||
db[reverse ? 'getSortedSetRevRange' : 'getSortedSetRange'](set, start, end, function(err, pids) {
|
||||
Posts.getPidsFromSet(set, start, end, reverse, function(err, pids) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
@@ -102,31 +102,15 @@ var async = require('async'),
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
Posts.getPostsByPids(pids, function(err, posts) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if(!Array.isArray(posts) || !posts.length) {
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
plugins.fireHook('filter:post.getPosts', {tid: tid, posts: posts}, function(err, data) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if(!data || !Array.isArray(data.posts)) {
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
callback(null, data.posts);
|
||||
});
|
||||
});
|
||||
Posts.getPostsByPids(pids, tid, callback);
|
||||
});
|
||||
};
|
||||
|
||||
Posts.getPostsByPids = function(pids, callback) {
|
||||
Posts.getPidsFromSet = function(set, start, end, reverse, callback) {
|
||||
db[reverse ? 'getSortedSetRevRange' : 'getSortedSetRange'](set, start, end, callback);
|
||||
};
|
||||
|
||||
Posts.getPostsByPids = function(pids, tid, callback) {
|
||||
var keys = [];
|
||||
|
||||
for(var x=0, numPids=pids.length; x<numPids; ++x) {
|
||||
@@ -155,7 +139,23 @@ var async = require('async'),
|
||||
next(null, postData);
|
||||
});
|
||||
|
||||
}, callback);
|
||||
}, function(err, posts) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
plugins.fireHook('filter:post.getPosts', {tid: tid, posts: posts}, function(err, data) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!data || !Array.isArray(data.posts)) {
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
callback(null, data.posts);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -222,21 +222,31 @@ var async = require('async'),
|
||||
};
|
||||
|
||||
Posts.getUserInfoForPosts = function(uids, callback) {
|
||||
user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'reputation', 'postcount', 'picture', 'signature', 'banned'], function(err, userData) {
|
||||
async.parallel({
|
||||
groups: function(next) {
|
||||
groups.getUserGroups(uids, next);
|
||||
},
|
||||
userData: function(next) {
|
||||
user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'reputation', 'postcount', 'picture', 'signature', 'banned'], next);
|
||||
}
|
||||
}, function(err, results) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var userData = results.userData;
|
||||
for(var i=0; i<userData.length; ++i) {
|
||||
userData[i].groups = results.groups[i];
|
||||
}
|
||||
|
||||
async.map(userData, function(userData, next) {
|
||||
var userInfo = {
|
||||
uid: userData.uid || 0,
|
||||
username: userData.username || '[[global:guest]]',
|
||||
userslug: userData.userslug || '',
|
||||
reputation: userData.reputation || 0,
|
||||
postcount: userData.postcount || 0,
|
||||
banned: parseInt(userData.banned, 10) === 1,
|
||||
picture: userData.picture || user.createGravatarURLFromEmail('')
|
||||
};
|
||||
userData.uid = userData.uid || 0;
|
||||
userData.username = userData.username || '[[global:guest]]';
|
||||
userData.userslug = userData.userslug || '';
|
||||
userData.reputation = userData.reputation || 0;
|
||||
userData.postcount = userData.postcount || 0;
|
||||
userData.banned = parseInt(userData.banned, 10) === 1;
|
||||
userData.picture = userData.picture || user.createGravatarURLFromEmail('');
|
||||
|
||||
async.parallel({
|
||||
signature: function(next) {
|
||||
@@ -247,18 +257,14 @@ var async = require('async'),
|
||||
},
|
||||
customProfileInfo: function(next) {
|
||||
plugins.fireHook('filter:posts.custom_profile_info', {profile: [], uid: userData.uid}, next);
|
||||
},
|
||||
groups: function(next) {
|
||||
groups.getUserGroups(userData.uid, next);
|
||||
}
|
||||
}, function(err, results) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
userInfo.signature = results.signature;
|
||||
userInfo.custom_profile_info = results.customProfileInfo.profile;
|
||||
userInfo.groups = results.groups;
|
||||
next(null, userInfo);
|
||||
userData.signature = results.signature;
|
||||
userData.custom_profile_info = results.customProfileInfo.profile;
|
||||
next(null, userData);
|
||||
});
|
||||
}, callback);
|
||||
});
|
||||
|
||||
@@ -104,6 +104,28 @@ module.exports = function(privileges) {
|
||||
});
|
||||
};
|
||||
|
||||
privileges.categories.isAdminOrMod = function(cids, uid, callback) {
|
||||
async.parallel({
|
||||
isModerators: function(next) {
|
||||
user.isModerator(uid, cids, next);
|
||||
},
|
||||
isAdmin: function(next) {
|
||||
user.isAdministrator(uid, next);
|
||||
}
|
||||
}, function(err, results) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var returnData = new Array(cids.length);
|
||||
for (var i=0; i<cids.length; ++i) {
|
||||
returnData[i] = results.isAdmin || results.isModerators[i];
|
||||
}
|
||||
|
||||
callback(null, returnData);
|
||||
});
|
||||
};
|
||||
|
||||
privileges.categories.canMoveAllTopics = function(currentCid, targetCid, uid, callback) {
|
||||
async.parallel({
|
||||
isAdministrator: function(next) {
|
||||
|
||||
@@ -135,10 +135,10 @@ function getModerators(req, res, next) {
|
||||
});
|
||||
}
|
||||
|
||||
var templatesListingCache = [];
|
||||
var templatesListingCache = {};
|
||||
|
||||
function getTemplatesListing(req, res, next) {
|
||||
if (templatesListingCache.length) {
|
||||
if (templatesListingCache.availableTemplates && templatesListingCache.templatesConfig) {
|
||||
return res.json(templatesListingCache);
|
||||
}
|
||||
|
||||
@@ -148,11 +148,18 @@ function getTemplatesListing(req, res, next) {
|
||||
},
|
||||
extended: function(next) {
|
||||
plugins.fireHook('filter:templates.get_virtual', [], next);
|
||||
}
|
||||
},
|
||||
config: function(next) {
|
||||
fs.readFile(path.join(nconf.get('views_dir'), 'config.json'), function(err, config) {
|
||||
config = JSON.parse(config.toString());
|
||||
plugins.fireHook('filter:templates.get_config', config, next);
|
||||
});
|
||||
},
|
||||
}, function(err, results) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var data = [];
|
||||
data = results.views.filter(function(value, index, self) {
|
||||
return self.indexOf(value) === index;
|
||||
@@ -161,8 +168,13 @@ function getTemplatesListing(req, res, next) {
|
||||
});
|
||||
|
||||
data = data.concat(results.extended);
|
||||
templatesListingCache = data;
|
||||
res.json(data);
|
||||
|
||||
templatesListingCache = {
|
||||
availableTemplates: data,
|
||||
templatesConfig: results.config
|
||||
};
|
||||
|
||||
res.json(templatesListingCache);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -97,9 +97,6 @@
|
||||
return res.redirect(nconf.get('relative_path') + '/register' + (err.message ? '?error=' + err.message : ''));
|
||||
}
|
||||
|
||||
delete userData['password-confirm'];
|
||||
delete userData['_csrf'];
|
||||
|
||||
user.create(userData, function(err, uid) {
|
||||
if (err || !uid) {
|
||||
return res.redirect(nconf.get('relative_path') + '/register');
|
||||
|
||||
@@ -19,7 +19,7 @@ var nconf = require('nconf'),
|
||||
|
||||
function mainRoutes(app, middleware, controllers) {
|
||||
app.get('/', middleware.buildHeader, controllers.home);
|
||||
app.get('/api/home', controllers.home);
|
||||
app.get('/api', controllers.home);
|
||||
|
||||
app.get('/login', middleware.redirectToAccountIfLoggedIn, middleware.buildHeader, controllers.login);
|
||||
app.get('/api/login', middleware.redirectToAccountIfLoggedIn, controllers.login);
|
||||
@@ -160,7 +160,7 @@ module.exports = function(app, middleware) {
|
||||
app.render.apply(app, arguments);
|
||||
};
|
||||
|
||||
app.all(relativePath + '/api/*', middleware.updateLastOnlineTime, middleware.prepareAPI);
|
||||
app.all(relativePath + '/api/?*', middleware.updateLastOnlineTime, middleware.prepareAPI);
|
||||
app.all(relativePath + '/api/admin/*', middleware.admin.isAdmin, middleware.prepareAPI);
|
||||
app.all(relativePath + '/admin/*', middleware.admin.isAdmin);
|
||||
app.get(relativePath + '/admin', middleware.admin.isAdmin);
|
||||
@@ -247,4 +247,4 @@ function catch404(req, res, next) {
|
||||
} else {
|
||||
res.type('txt').send('Not found');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,10 +76,8 @@ var path = require('path'),
|
||||
},
|
||||
render: function(callback) {
|
||||
if (sitemap.obj !== undefined && sitemap.obj.cache.length) {
|
||||
console.log('using sitemap from cache!');
|
||||
sitemap.obj.toXML(callback);
|
||||
} else {
|
||||
console.log('generating new sitemap!', sitemap.obj);
|
||||
async.parallel([sitemap.getStaticUrls, sitemap.getDynamicUrls], function(err, urls) {
|
||||
urls = urls[0].concat(urls[1]);
|
||||
sitemap.obj = sm.createSitemap({
|
||||
|
||||
@@ -4,8 +4,8 @@ var async = require('async'),
|
||||
db = require('../database'),
|
||||
categories = require('../categories'),
|
||||
privileges = require('../privileges'),
|
||||
meta = require('../meta'),
|
||||
user = require('../user'),
|
||||
websockets = require('./index'),
|
||||
|
||||
SocketCategories = {};
|
||||
|
||||
@@ -44,6 +44,10 @@ SocketCategories.loadMore = function(socket, data, callback) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!results.privileges.read) {
|
||||
return callback(new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
|
||||
var start = parseInt(data.after, 10),
|
||||
end = start + results.settings.topicsPerPage - 1;
|
||||
|
||||
@@ -70,4 +74,9 @@ SocketCategories.lastTopicIndex = function(socket, cid, callback) {
|
||||
db.sortedSetCard('categories:' + cid + ':tid', callback);
|
||||
};
|
||||
|
||||
SocketCategories.getUsersInCategory = function(socket, cid, callback) {
|
||||
var uids = websockets.getUidsInRoom('category_' + cid);
|
||||
user.getMultipleUserFields(uids, ['uid', 'userslug', 'username', 'picture'], callback);
|
||||
};
|
||||
|
||||
module.exports = SocketCategories;
|
||||
|
||||
@@ -287,17 +287,6 @@ function updateRoomBrowsingText(roomName) {
|
||||
return;
|
||||
}
|
||||
|
||||
function getUidsInRoom() {
|
||||
var uids = [];
|
||||
var clients = io.sockets.clients(roomName);
|
||||
for(var i=0; i<clients.length; ++i) {
|
||||
if (uids.indexOf(clients[i].uid) === -1 && clients[i].uid !== 0) {
|
||||
uids.push(clients[i].uid);
|
||||
}
|
||||
}
|
||||
return uids;
|
||||
}
|
||||
|
||||
function getAnonymousCount() {
|
||||
var clients = io.sockets.clients(roomName);
|
||||
var anonCount = 0;
|
||||
@@ -310,7 +299,7 @@ function updateRoomBrowsingText(roomName) {
|
||||
return anonCount;
|
||||
}
|
||||
|
||||
var uids = getUidsInRoom(),
|
||||
var uids = Sockets.getUidsInRoom(roomName),
|
||||
anonymousCount = getAnonymousCount();
|
||||
|
||||
user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture', 'status'], function(err, users) {
|
||||
@@ -328,6 +317,18 @@ function updateRoomBrowsingText(roomName) {
|
||||
});
|
||||
}
|
||||
|
||||
Sockets.getUidsInRoom = function(roomName) {
|
||||
var uids = [];
|
||||
var clients = io.sockets.clients(roomName);
|
||||
for(var i=0; i<clients.length; ++i) {
|
||||
if (uids.indexOf(clients[i].uid) === -1 && clients[i].uid !== 0) {
|
||||
uids.push(clients[i].uid);
|
||||
}
|
||||
}
|
||||
return uids;
|
||||
};
|
||||
|
||||
|
||||
Sockets.emitTopicPostStats = emitTopicPostStats;
|
||||
function emitTopicPostStats(callback) {
|
||||
db.getObjectFields('global', ['topicCount', 'postCount'], function(err, data) {
|
||||
|
||||
@@ -161,6 +161,8 @@ SocketPosts.edit = function(socket, data, callback) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
} else if (!data.title || data.title.length < parseInt(meta.config.minimumTitleLength, 10)) {
|
||||
return callback(new Error('[[error:title-too-short, ' + meta.config.minimumTitleLength + ']]'));
|
||||
} else if (data.title.length > parseInt(meta.config.maximumTitleLength, 10)) {
|
||||
return callback(new Error('[[error:title-too-long, ' + meta.config.maximumTitleLength + ']]'));
|
||||
} else if (!data.content || data.content.length < parseInt(meta.config.minimumPostLength, 10)) {
|
||||
return callback(new Error('[[error:content-too-short, ' + meta.config.minimumPostLength + ']]'));
|
||||
}
|
||||
@@ -241,37 +243,18 @@ SocketPosts.getPrivileges = function(socket, pid, callback) {
|
||||
};
|
||||
|
||||
SocketPosts.getFavouritedUsers = function(socket, pid, callback) {
|
||||
|
||||
favourites.getFavouritedUidsByPids([pid], function(err, data) {
|
||||
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if(!Array.isArray(data) || !data.length) {
|
||||
callback(null, "");
|
||||
callback(null, []);
|
||||
}
|
||||
|
||||
var max = 5; //hardcoded
|
||||
var finalText = "";
|
||||
|
||||
var pid_uids = data[0];
|
||||
var rest_amount = 0;
|
||||
|
||||
if (pid_uids.length > max) {
|
||||
rest_amount = pid_uids.length - max;
|
||||
pid_uids = pid_uids.slice(0, max);
|
||||
}
|
||||
|
||||
user.getUsernamesByUids(pid_uids, function(err, usernames) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
finalText = usernames.join(', ') + (rest_amount > 0 ?
|
||||
(" and " + rest_amount + (rest_amount > 1 ? " others" : " other")) : "");
|
||||
callback(null, finalText);
|
||||
});
|
||||
user.getUsernamesByUids(pid_uids, callback);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -54,6 +54,15 @@ SocketTopics.post = function(socket, data, callback) {
|
||||
});
|
||||
};
|
||||
|
||||
SocketTopics.enter = function(socket, tid, callback) {
|
||||
if (!tid || !socket.uid) {
|
||||
return;
|
||||
}
|
||||
SocketTopics.markAsRead(socket, tid);
|
||||
topics.markTopicNotificationsRead(tid, socket.uid);
|
||||
topics.increaseViewCount(tid);
|
||||
};
|
||||
|
||||
SocketTopics.postcount = function(socket, tid, callback) {
|
||||
topics.getTopicField(tid, 'postcount', callback);
|
||||
};
|
||||
@@ -141,25 +150,58 @@ SocketTopics.markCategoryTopicsRead = function(socket, cid, callback) {
|
||||
};
|
||||
|
||||
SocketTopics.markAsUnreadForAll = function(socket, tids, callback) {
|
||||
if(!Array.isArray(tids)) {
|
||||
if (!Array.isArray(tids)) {
|
||||
return callback(new Error('[[error:invalid-tid]]'));
|
||||
}
|
||||
|
||||
async.each(tids, function(tid, next) {
|
||||
topics.markAsUnreadForAll(tid, function(err) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
if (!socket.uid) {
|
||||
return callback(new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
|
||||
db.sortedSetAdd('topics:recent', Date.now(), tid, function(err) {
|
||||
if(err) {
|
||||
user.isAdministrator(socket.uid, function(err, isAdmin) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
async.each(tids, function(tid, next) {
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
threadTools.exists(tid, next);
|
||||
},
|
||||
function(exists, next) {
|
||||
if (!exists) {
|
||||
return next(new Error('[[error:invalid-tid]]'));
|
||||
}
|
||||
topics.getTopicField(tid, 'cid', next);
|
||||
},
|
||||
function(cid, next) {
|
||||
user.isModerator(socket.uid, cid, next);
|
||||
}
|
||||
], function(err, isMod) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
topics.pushUnreadCount();
|
||||
next();
|
||||
|
||||
if (!isAdmin && !isMod) {
|
||||
return next(new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
|
||||
topics.markAsUnreadForAll(tid, function(err) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
db.sortedSetAdd('topics:recent', Date.now(), tid, function(err) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
topics.pushUnreadCount();
|
||||
next();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}, callback);
|
||||
}, callback);
|
||||
});
|
||||
};
|
||||
|
||||
SocketTopics.delete = function(socket, data, callback) {
|
||||
@@ -333,20 +375,31 @@ SocketTopics.loadMore = function(socket, data, callback) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
}
|
||||
|
||||
user.getSettings(socket.uid, function(err, settings) {
|
||||
if(err) {
|
||||
async.parallel({
|
||||
settings: function(next) {
|
||||
user.getSettings(socket.uid, next);
|
||||
},
|
||||
privileges: function(next) {
|
||||
privileges.topics.get(data.tid, socket.uid, next);
|
||||
}
|
||||
}, function(err, results) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!results.privileges.read) {
|
||||
return callback(new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
|
||||
var start = Math.max(parseInt(data.after, 10) - 1, 0),
|
||||
end = start + settings.postsPerPage - 1;
|
||||
end = start + results.settings.postsPerPage - 1;
|
||||
|
||||
var set = 'tid:' + data.tid + ':posts',
|
||||
reverse = false;
|
||||
|
||||
if (settings.topicPostSort === 'newest_to_oldest') {
|
||||
if (results.settings.topicPostSort === 'newest_to_oldest') {
|
||||
reverse = true;
|
||||
} else if (settings.topicPostSort === 'most_votes') {
|
||||
} else if (results.settings.topicPostSort === 'most_votes') {
|
||||
reverse = true;
|
||||
set = 'tid:' + data.tid + ':posts:votes';
|
||||
}
|
||||
@@ -356,7 +409,7 @@ SocketTopics.loadMore = function(socket, data, callback) {
|
||||
topics.getTopicPosts(data.tid, set, start, end, socket.uid, reverse, next);
|
||||
},
|
||||
privileges: function(next) {
|
||||
privileges.topics.get(data.tid, socket.uid, next);
|
||||
next(null, results.privileges);
|
||||
},
|
||||
'reputation:disabled': function(next) {
|
||||
next(null, parseInt(meta.config['reputation:disabled'], 10) === 1);
|
||||
|
||||
@@ -5,6 +5,7 @@ var async = require('async'),
|
||||
groups = require('../groups'),
|
||||
topics = require('../topics'),
|
||||
messaging = require('../messaging'),
|
||||
plugins = require('../plugins'),
|
||||
utils = require('./../../public/src/utils'),
|
||||
meta = require('../meta'),
|
||||
SocketUser = {};
|
||||
@@ -79,7 +80,9 @@ SocketUser.reset.commit = function(socket, data, callback) {
|
||||
};
|
||||
|
||||
SocketUser.isOnline = function(socket, uid, callback) {
|
||||
user.isOnline(uid, callback);
|
||||
user.isOnline([uid], function(err, data) {
|
||||
callback(err, Array.isArray(data) ? data[0] : null);
|
||||
});
|
||||
};
|
||||
|
||||
SocketUser.changePassword = function(socket, data, callback) {
|
||||
@@ -159,16 +162,30 @@ SocketUser.changePicture = function(socket, data, callback) {
|
||||
|
||||
SocketUser.follow = function(socket, data, callback) {
|
||||
if (socket.uid && data) {
|
||||
user.follow(socket.uid, data.uid, callback);
|
||||
toggleFollow('follow', socket.uid, data.uid, callback);
|
||||
}
|
||||
};
|
||||
|
||||
SocketUser.unfollow = function(socket, data, callback) {
|
||||
if (socket.uid && data) {
|
||||
user.unfollow(socket.uid, data.uid, callback);
|
||||
toggleFollow('unfollow', socket.uid, data.uid, callback);
|
||||
}
|
||||
};
|
||||
|
||||
function toggleFollow(method, uid, theiruid, callback) {
|
||||
user[method](uid, theiruid, function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
plugins.fireHook('action:user.' + method, {
|
||||
fromUid: uid,
|
||||
toUid: theiruid
|
||||
});
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
SocketUser.getSettings = function(socket, data, callback) {
|
||||
if (socket.uid) {
|
||||
if (socket.uid === parseInt(data.uid, 10)) {
|
||||
@@ -217,24 +234,23 @@ SocketUser.setTopicSort = function(socket, sort, callback) {
|
||||
}
|
||||
};
|
||||
|
||||
SocketUser.getOnlineUsers = function(socket, data, callback) {
|
||||
SocketUser.getOnlineUsers = function(socket, uids, callback) {
|
||||
var returnData = {};
|
||||
if(!data) {
|
||||
if (!uids) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
}
|
||||
|
||||
function getUserStatus(uid, next) {
|
||||
SocketUser.isOnline(socket, uid, function(err, data) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
returnData[uid] = data;
|
||||
next();
|
||||
});
|
||||
}
|
||||
user.isOnline(uids, function(err, userData) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
async.each(data, getUserStatus, function(err) {
|
||||
callback(err, returnData);
|
||||
userData.forEach(function(user) {
|
||||
if (user) {
|
||||
returnData[user.uid] = user;
|
||||
}
|
||||
});
|
||||
callback(null, returnData);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -120,8 +120,7 @@ var winston = require('winston'),
|
||||
plugins.fireHook('action:topic.lock', {
|
||||
tid: tid,
|
||||
isLocked: lock,
|
||||
uid: uid,
|
||||
timestamp: Date.now()
|
||||
uid: uid
|
||||
});
|
||||
|
||||
emitTo('topic_' + tid);
|
||||
@@ -165,8 +164,7 @@ var winston = require('winston'),
|
||||
plugins.fireHook('action:topic.pin', {
|
||||
tid: tid,
|
||||
isPinned: pin,
|
||||
uid: uid,
|
||||
timestamp: Date.now()
|
||||
uid: uid
|
||||
});
|
||||
|
||||
emitTo('topic_' + tid);
|
||||
@@ -214,8 +212,7 @@ var winston = require('winston'),
|
||||
tid: tid,
|
||||
fromCid: oldCid,
|
||||
toCid: cid,
|
||||
uid: uid,
|
||||
timestamp: Date.now()
|
||||
uid: uid
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
260
src/topics.js
260
src/topics.js
@@ -1,12 +1,11 @@
|
||||
"use strict";
|
||||
|
||||
var async = require('async'),
|
||||
gravatar = require('gravatar'),
|
||||
validator = require('validator'),
|
||||
|
||||
db = require('./database'),
|
||||
posts = require('./posts'),
|
||||
utils = require('./../public/src/utils'),
|
||||
utils = require('../public/src/utils'),
|
||||
plugins = require('./plugins'),
|
||||
user = require('./user'),
|
||||
categories = require('./categories'),
|
||||
@@ -26,11 +25,7 @@ var async = require('async'),
|
||||
|
||||
Topics.getTopicData = function(tid, callback) {
|
||||
Topics.getTopicsData([tid], function(err, topics) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback(null, topics ? topics[0] : null);
|
||||
callback(err, Array.isArray(topics) && topics.length ? topics[0] : null);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -128,7 +123,7 @@ var async = require('async'),
|
||||
nextStart: 0
|
||||
};
|
||||
|
||||
if (!tids || !tids.length) {
|
||||
if (!Array.isArray(tids) || !tids.length) {
|
||||
return callback(null, returnTopics);
|
||||
}
|
||||
|
||||
@@ -170,96 +165,83 @@ var async = require('async'),
|
||||
};
|
||||
|
||||
Topics.getTopicsByTids = function(tids, uid, callback) {
|
||||
if (!Array.isArray(tids) || tids.length === 0) {
|
||||
if (!Array.isArray(tids) || !tids.length) {
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
var categoryCache = {},
|
||||
privilegeCache = {},
|
||||
userCache = {};
|
||||
|
||||
|
||||
function loadTopicInfo(topicData, next) {
|
||||
if (!topicData) {
|
||||
return next(null, null);
|
||||
}
|
||||
|
||||
function isTopicVisible(topicData, topicInfo) {
|
||||
if (parseInt(topicInfo.categoryData.disabled, 10) === 1) {
|
||||
return false;
|
||||
}
|
||||
var deleted = parseInt(topicData.deleted, 10) !== 0;
|
||||
return !deleted || (deleted && topicInfo.privileges.view_deleted) || parseInt(topicData.uid, 10) === parseInt(uid, 10);
|
||||
}
|
||||
|
||||
async.parallel({
|
||||
hasread: function(next) {
|
||||
Topics.hasReadTopic(topicData.tid, uid, next);
|
||||
},
|
||||
teaser: function(next) {
|
||||
Topics.getTeaser(topicData.tid, next);
|
||||
},
|
||||
privileges: function(next) {
|
||||
if (privilegeCache[topicData.cid]) {
|
||||
return next(null, privilegeCache[topicData.cid]);
|
||||
}
|
||||
privileges.categories.get(topicData.cid, uid, next);
|
||||
},
|
||||
categoryData: function(next) {
|
||||
if (categoryCache[topicData.cid]) {
|
||||
return next(null, categoryCache[topicData.cid]);
|
||||
}
|
||||
categories.getCategoryFields(topicData.cid, ['name', 'slug', 'icon', 'bgColor', 'color', 'disabled'], next);
|
||||
},
|
||||
user: function(next) {
|
||||
if (userCache[topicData.uid]) {
|
||||
return next(null, userCache[topicData.uid]);
|
||||
}
|
||||
user.getUserFields(topicData.uid, ['username', 'userslug', 'picture'], next);
|
||||
},
|
||||
tags: function(next) {
|
||||
Topics.getTopicTagsObjects(topicData.tid, next);
|
||||
}
|
||||
}, function(err, topicInfo) {
|
||||
if(err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
privilegeCache[topicData.cid] = topicInfo.privileges;
|
||||
categoryCache[topicData.cid] = topicInfo.categoryData;
|
||||
userCache[topicData.uid] = topicInfo.user;
|
||||
|
||||
if (!isTopicVisible(topicData, topicInfo)) {
|
||||
return next(null, null);
|
||||
}
|
||||
|
||||
topicData.pinned = parseInt(topicData.pinned, 10) === 1;
|
||||
topicData.locked = parseInt(topicData.locked, 10) === 1;
|
||||
topicData.deleted = parseInt(topicData.deleted, 10) === 1;
|
||||
topicData.unread = !(topicInfo.hasread && parseInt(uid, 10) !== 0);
|
||||
topicData.unreplied = parseInt(topicData.postcount, 10) <= 1;
|
||||
|
||||
topicData.category = topicInfo.categoryData;
|
||||
topicData.teaser = topicInfo.teaser;
|
||||
topicData.user = topicInfo.user;
|
||||
topicData.tags = topicInfo.tags;
|
||||
|
||||
next(null, topicData);
|
||||
});
|
||||
}
|
||||
|
||||
Topics.getTopicsData(tids, function(err, topics) {
|
||||
function mapFilter(array, field) {
|
||||
return array.map(function(topic) {
|
||||
return topic[field];
|
||||
}).filter(function(value, index, array) {
|
||||
return array.indexOf(value) === index;
|
||||
});
|
||||
}
|
||||
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
async.mapSeries(topics, loadTopicInfo, function(err, topics) {
|
||||
if(err) {
|
||||
var uids = mapFilter(topics, 'uid');
|
||||
var cids = mapFilter(topics, 'cid');
|
||||
|
||||
async.parallel({
|
||||
users: function(next) {
|
||||
user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture'], next);
|
||||
},
|
||||
categories: function(next) {
|
||||
categories.getMultipleCategoryFields(cids, ['cid', 'name', 'slug', 'icon', 'bgColor', 'color', 'disabled'], next);
|
||||
},
|
||||
hasRead: function(next) {
|
||||
Topics.hasReadTopics(tids, uid, next);
|
||||
},
|
||||
isAdminOrMod: function(next) {
|
||||
privileges.categories.isAdminOrMod(cids, uid, next);
|
||||
},
|
||||
teasers: function(next) {
|
||||
Topics.getTeasers(tids, next);
|
||||
},
|
||||
tags: function(next) {
|
||||
Topics.getTopicsTagsObjects(tids, next);
|
||||
}
|
||||
}, function(err, results) {
|
||||
function arrayToObject(array, field) {
|
||||
var obj = {};
|
||||
for (var i=0; i<array.length; ++i) {
|
||||
obj[array[i][field]] = array[i];
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var users = arrayToObject(results.users, 'uid');
|
||||
var categories = arrayToObject(results.categories, 'cid');
|
||||
var isAdminOrMod = {};
|
||||
cids.forEach(function(cid, index) {
|
||||
isAdminOrMod[cid] = results.isAdminOrMod[index];
|
||||
});
|
||||
|
||||
for (var i=0; i<topics.length; ++i) {
|
||||
topics[i].category = categories[topics[i].cid];
|
||||
topics[i].category.disabled = parseInt(topics[i].category.disabled, 10) === 1;
|
||||
topics[i].user = users[topics[i].uid];
|
||||
topics[i].teaser = results.teasers[i];
|
||||
topics[i].tags = results.tags[i];
|
||||
|
||||
topics[i].pinned = parseInt(topics[i].pinned, 10) === 1;
|
||||
topics[i].locked = parseInt(topics[i].locked, 10) === 1;
|
||||
topics[i].deleted = parseInt(topics[i].deleted, 10) === 1;
|
||||
topics[i].unread = !(results.hasRead[i] && parseInt(uid, 10) !== 0);
|
||||
topics[i].unreplied = parseInt(topics[i].postcount, 10) <= 1;
|
||||
}
|
||||
|
||||
topics = topics.filter(function(topic) {
|
||||
return !!topic;
|
||||
return !topic.category.disabled &&
|
||||
(!topic.deleted || (topic.deleted && isAdminOrMod[topic.cid]) ||
|
||||
parseInt(topic.uid, 10) === parseInt(uid, 10));
|
||||
});
|
||||
|
||||
plugins.fireHook('filter:topics.get', topics, callback);
|
||||
@@ -274,12 +256,29 @@ var async = require('async'),
|
||||
}
|
||||
|
||||
async.parallel({
|
||||
mainPost: function(next) {
|
||||
Topics.getMainPost(tid, uid, next);
|
||||
},
|
||||
posts: function(next) {
|
||||
Topics.getTopicPosts(tid, set, start, end, uid, reverse, next);
|
||||
},
|
||||
posts: function(next) {
|
||||
posts.getPidsFromSet(set, start, end, reverse, function(err, pids) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
pids = topicData.mainPid ? [topicData.mainPid].concat(pids) : pids;
|
||||
if (!pids.length) {
|
||||
return next(null, []);
|
||||
}
|
||||
posts.getPostsByPids(pids, tid, function(err, posts) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
start = parseInt(start, 10);
|
||||
for(var i=0; i<posts.length; ++i) {
|
||||
posts[i].index = start + i;
|
||||
}
|
||||
posts[0].index = 0;
|
||||
Topics.addPostData(posts, uid, next);
|
||||
});
|
||||
});
|
||||
},
|
||||
category: function(next) {
|
||||
Topics.getCategoryData(tid, next);
|
||||
},
|
||||
@@ -298,7 +297,7 @@ var async = require('async'),
|
||||
}
|
||||
|
||||
topicData.category = results.category;
|
||||
topicData.posts = results.mainPost.concat(results.posts);
|
||||
topicData.posts = results.posts;
|
||||
topicData.tags = results.tags;
|
||||
topicData.thread_tools = results.threadTools;
|
||||
topicData.pageCount = results.pageCount;
|
||||
@@ -307,9 +306,7 @@ var async = require('async'),
|
||||
topicData.locked = parseInt(topicData.locked, 10) === 1;
|
||||
topicData.pinned = parseInt(topicData.pinned, 10) === 1;
|
||||
|
||||
plugins.fireHook('filter:topic.get', topicData, function(err, topicData) {
|
||||
callback(null, topicData);
|
||||
});
|
||||
plugins.fireHook('filter:topic.get', topicData, callback);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -322,7 +319,7 @@ var async = require('async'),
|
||||
if (!parseInt(mainPid, 10)) {
|
||||
return callback(null, []);
|
||||
}
|
||||
posts.getPostsByPids([mainPid], function(err, postData) {
|
||||
posts.getPostsByPids([mainPid], tid, function(err, postData) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
@@ -336,12 +333,71 @@ var async = require('async'),
|
||||
};
|
||||
|
||||
Topics.getTeasers = function(tids, callback) {
|
||||
|
||||
if(!Array.isArray(tids)) {
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
async.map(tids, Topics.getTeaser, callback);
|
||||
async.map(tids, function(tid, next) {
|
||||
db.getSortedSetRevRange('tid:' + tid + ':posts', 0, 0, function(err, data) {
|
||||
next(err, Array.isArray(data) && data.length ? data[0] : null);
|
||||
});
|
||||
}, function(err, pids) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var postKeys = pids.map(function(pid) {
|
||||
return 'post:' + pid;
|
||||
});
|
||||
|
||||
async.parallel({
|
||||
indices: function(next) {
|
||||
var sets = tids.map(function(tid) {
|
||||
return 'tid:' + tid + ':posts';
|
||||
});
|
||||
db.sortedSetsRanks(sets, pids, next);
|
||||
},
|
||||
posts: function(next) {
|
||||
db.getObjectsFields(postKeys, ['pid', 'uid', 'timestamp'], next);
|
||||
}
|
||||
}, function(err, results) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var indices = results.indices.map(function(index) {
|
||||
if (!utils.isNumber(index)) {
|
||||
return 1;
|
||||
}
|
||||
return parseInt(index, 10) + 2;
|
||||
});
|
||||
|
||||
var uids = results.posts.map(function(post) {
|
||||
return post.uid;
|
||||
}).filter(function(uid, index, array) {
|
||||
return array.indexOf(uid) === index;
|
||||
});
|
||||
|
||||
user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture'], function(err, userData) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var users = {};
|
||||
userData.forEach(function(user) {
|
||||
users[user.uid] = user;
|
||||
});
|
||||
|
||||
results.posts.forEach(function(post, index) {
|
||||
post.user = users[post.uid];
|
||||
post.index = indices[index];
|
||||
post.timestamp = utils.toISOString(post.timestamp);
|
||||
});
|
||||
|
||||
callback(err, results.posts);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Topics.getTeaser = function(tid, callback) {
|
||||
@@ -413,8 +469,12 @@ var async = require('async'),
|
||||
};
|
||||
|
||||
Topics.isOwner = function(tid, uid, callback) {
|
||||
uid = parseInt(uid, 10);
|
||||
if (uid === 0) {
|
||||
return callback(null, false);
|
||||
}
|
||||
Topics.getTopicField(tid, 'uid', function(err, author) {
|
||||
callback(err, parseInt(author, 10) === parseInt(uid, 10));
|
||||
callback(err, parseInt(author, 10) === uid);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ module.exports = function(Topics) {
|
||||
return '';
|
||||
}
|
||||
tag = tag.trim().toLowerCase();
|
||||
tag = tag.replace(/&/g, '&');
|
||||
tag = tag.replace(/[,\/#!$%\^\*;:{}=_`<>'"~()?\|]/g, '');
|
||||
tag = tag.substr(0, meta.config.maximumTagLength || 15);
|
||||
var matches = tag.match(/^[.-]*(.+?)[.-]*$/);
|
||||
@@ -93,6 +92,25 @@ module.exports = function(Topics) {
|
||||
});
|
||||
};
|
||||
|
||||
Topics.getTopicsTagsObjects = function(tids, callback) {
|
||||
var sets = tids.map(function(tid) {
|
||||
return 'topic:' + tid + ':tags';
|
||||
});
|
||||
|
||||
db.getSetsMembers(sets, function(err, members) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
members.forEach(function(tags, index) {
|
||||
if (Array.isArray(tags)) {
|
||||
members[index] = mapToObject(tags);
|
||||
}
|
||||
})
|
||||
callback(null, members);
|
||||
});
|
||||
};
|
||||
|
||||
function mapToObject(tags) {
|
||||
if (!tags) {
|
||||
return tags;
|
||||
|
||||
@@ -23,26 +23,26 @@ module.exports = function(Topics) {
|
||||
done = false;
|
||||
|
||||
uid = parseInt(uid, 10);
|
||||
if(uid === 0) {
|
||||
if (uid === 0) {
|
||||
return callback(null, unreadTids);
|
||||
}
|
||||
|
||||
async.whilst(function() {
|
||||
return unreadTids.length < 21 && !done;
|
||||
}, function(callback) {
|
||||
}, function(next) {
|
||||
Topics.getLatestTids(start, stop, 'month', function(err, tids) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (tids && !tids.length) {
|
||||
done = true;
|
||||
return callback();
|
||||
return next();
|
||||
}
|
||||
|
||||
Topics.hasReadTopics(tids, uid, function(err, read) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var newtids = tids.filter(function(tid, index) {
|
||||
@@ -50,16 +50,15 @@ module.exports = function(Topics) {
|
||||
});
|
||||
|
||||
privileges.topics.filter('read', newtids, uid, function(err, newtids) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
unreadTids.push.apply(unreadTids, newtids);
|
||||
|
||||
start = stop + 1;
|
||||
stop = start + 19;
|
||||
|
||||
callback();
|
||||
next();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
94
src/user.js
94
src/user.js
@@ -68,8 +68,9 @@ var bcrypt = require('bcryptjs'),
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback(null, modifyUserData(users, fieldsToRemove));
|
||||
plugins.fireHook('filter:user.removeFields', fieldsToRemove, function(err, fields) {
|
||||
callback(err, modifyUserData(users, fields));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -94,7 +95,9 @@ var bcrypt = require('bcryptjs'),
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback(null, modifyUserData(users, []));
|
||||
plugins.fireHook('filter:user.removeFields', [], function(err, fields) {
|
||||
callback(err, modifyUserData(users, fields));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -239,36 +242,32 @@ var bcrypt = require('bcryptjs'),
|
||||
};
|
||||
|
||||
User.getUsers = function(uids, callback) {
|
||||
function loadUserInfo(user, callback) {
|
||||
if (!user) {
|
||||
return callback(null, user);
|
||||
async.parallel({
|
||||
userData: function(next) {
|
||||
User.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture', 'status', 'banned', 'postcount', 'reputation'], next);
|
||||
},
|
||||
isAdmin: function(next) {
|
||||
User.isAdministrator(uids, next);
|
||||
},
|
||||
isOnline: function(next) {
|
||||
db.isSortedSetMembers('users:online', uids, next);
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
User.isAdministrator(user.uid, next);
|
||||
},
|
||||
function(isAdmin, next) {
|
||||
user.status = !user.status ? 'online' : user.status;
|
||||
user.administrator = isAdmin;
|
||||
user.banned = parseInt(user.banned, 10) === 1;
|
||||
db.isSortedSetMember('users:online', user.uid, next);
|
||||
},
|
||||
function(isMember, next) {
|
||||
if (!isMember) {
|
||||
user.status = 'offline';
|
||||
}
|
||||
next(null, user);
|
||||
}
|
||||
], callback);
|
||||
}
|
||||
|
||||
User.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture', 'status', 'banned', 'postcount', 'reputation'], function(err, usersData) {
|
||||
}, function(err, results) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
async.map(usersData, loadUserInfo, callback);
|
||||
results.userData.forEach(function(user, index) {
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
user.status = !user.status ? 'online' : user.status;
|
||||
user.status = !results.isOnline[index] ? 'offline' : user.status;
|
||||
user.administrator = results.isAdmin[index];
|
||||
user.banned = parseInt(user.banned, 10) === 1;
|
||||
});
|
||||
|
||||
callback(err, results.userData);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -410,29 +409,40 @@ var bcrypt = require('bcryptjs'),
|
||||
};
|
||||
|
||||
User.isAdministrator = function(uid, callback) {
|
||||
groups.isMember(uid, 'administrators', callback);
|
||||
if (Array.isArray(uid)) {
|
||||
groups.isMembers(uid, 'administrators', callback);
|
||||
} else {
|
||||
groups.isMember(uid, 'administrators', callback);
|
||||
}
|
||||
};
|
||||
|
||||
User.isOnline = function(uid, callback) {
|
||||
User.getUserFields(uid, ['username', 'userslug', 'picture', 'status', 'reputation', 'postcount'] , function(err, data) {
|
||||
if(err) {
|
||||
User.isOnline = function(uids, callback) {
|
||||
if (!Array.isArray(uids)) {
|
||||
uids = [uids];
|
||||
}
|
||||
|
||||
User.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture', 'status', 'reputation', 'postcount'] , function(err, userData) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var websockets = require('./socket.io');
|
||||
var online = websockets.isUserOnline(uid);
|
||||
|
||||
data.status = online ? (data.status || 'online') : 'offline';
|
||||
userData = userData.map(function(user) {
|
||||
var online = websockets.isUserOnline(user.uid);
|
||||
user.status = online ? (user.status || 'online') : 'offline';
|
||||
|
||||
if(data.status === 'offline') {
|
||||
online = false;
|
||||
}
|
||||
if (user.status === 'offline') {
|
||||
online = false;
|
||||
}
|
||||
|
||||
data.online = online;
|
||||
data.uid = uid;
|
||||
data.timestamp = Date.now();
|
||||
data.rooms = websockets.getUserRooms(uid);
|
||||
user.online = online;
|
||||
user.timestamp = Date.now();
|
||||
user.rooms = websockets.getUserRooms(user.uid);
|
||||
return user;
|
||||
});
|
||||
|
||||
callback(null, data);
|
||||
callback(null, userData);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -12,19 +12,38 @@ var async = require('async'),
|
||||
|
||||
module.exports = function(User) {
|
||||
|
||||
User.create = function(userData, callback) {
|
||||
userData = userData || {};
|
||||
User.create = function(data, callback) {
|
||||
var gravatar = User.createGravatarURLFromEmail(data.email);
|
||||
var timestamp = Date.now();
|
||||
var password = data.password;
|
||||
|
||||
var userData = {
|
||||
'username': data.username.trim(),
|
||||
'email': data.email,
|
||||
'joindate': timestamp,
|
||||
'picture': gravatar,
|
||||
'gravatarpicture': gravatar,
|
||||
'fullname': '',
|
||||
'location': '',
|
||||
'birthday': '',
|
||||
'website': '',
|
||||
'signature': '',
|
||||
'uploadedpicture': '',
|
||||
'profileviews': 0,
|
||||
'reputation': 0,
|
||||
'postcount': 0,
|
||||
'lastposttime': 0,
|
||||
'banned': 0,
|
||||
'status': 'online'
|
||||
};
|
||||
|
||||
userData.userslug = utils.slugify(userData.username);
|
||||
|
||||
userData.username = userData.username.trim();
|
||||
if (userData.email !== undefined) {
|
||||
userData.email = userData.email.trim();
|
||||
userData.email = validator.escape(userData.email);
|
||||
}
|
||||
|
||||
var password = userData.password;
|
||||
userData.password = null;
|
||||
|
||||
async.parallel({
|
||||
emailValid: function(next) {
|
||||
if (userData.email) {
|
||||
@@ -84,7 +103,7 @@ module.exports = function(User) {
|
||||
}
|
||||
},
|
||||
customFields: function(next) {
|
||||
plugins.fireHook('filter:user.custom_fields', userData, next);
|
||||
plugins.fireHook('filter:user.custom_fields', [], next);
|
||||
},
|
||||
userData: function(next) {
|
||||
plugins.fireHook('filter:user.create', userData, next);
|
||||
@@ -94,7 +113,14 @@ module.exports = function(User) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
userData = utils.merge(results.userData, results.customFields);
|
||||
var customData = {};
|
||||
results.customFields.forEach(function(customField) {
|
||||
if (data[customField]) {
|
||||
customData[customField] = data[customField];
|
||||
}
|
||||
});
|
||||
|
||||
userData = utils.merge(results.userData, customData);
|
||||
|
||||
var userNameChanged = !!results.renamedUsername;
|
||||
|
||||
@@ -104,37 +130,14 @@ module.exports = function(User) {
|
||||
}
|
||||
|
||||
db.incrObjectField('global', 'nextUid', function(err, uid) {
|
||||
if(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var gravatar = User.createGravatarURLFromEmail(userData.email);
|
||||
var timestamp = Date.now();
|
||||
|
||||
userData = utils.merge({
|
||||
'uid': uid,
|
||||
'username': userData.username,
|
||||
'userslug': userData.userslug,
|
||||
'fullname': '',
|
||||
'location': '',
|
||||
'birthday': '',
|
||||
'website': '',
|
||||
'email': userData.email || '',
|
||||
'signature': '',
|
||||
'joindate': timestamp,
|
||||
'picture': gravatar,
|
||||
'gravatarpicture': gravatar,
|
||||
'uploadedpicture': '',
|
||||
'profileviews': 0,
|
||||
'reputation': 0,
|
||||
'postcount': 0,
|
||||
'lastposttime': 0,
|
||||
'banned': 0,
|
||||
'status': 'online'
|
||||
}, userData);
|
||||
userData.uid = uid;
|
||||
|
||||
db.setObject('user:' + uid, userData, function(err) {
|
||||
if(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,14 +18,11 @@ module.exports = function(User) {
|
||||
},
|
||||
function(next) {
|
||||
deleteTopics(uid, next);
|
||||
},
|
||||
function(next) {
|
||||
deleteAccount(uid, next);
|
||||
}
|
||||
], function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
deleteAccount(uid, callback);
|
||||
});
|
||||
], callback);
|
||||
};
|
||||
|
||||
function deletePosts(uid, callback) {
|
||||
|
||||
@@ -22,6 +22,11 @@ module.exports = function(User) {
|
||||
};
|
||||
|
||||
User.sendDailyDigests = function() {
|
||||
var digestsDisabled = meta.config.disableEmailSubscriptions !== undefined && parseInt(meta.config.disableEmailSubscriptions, 10) === 1;
|
||||
if (digestsDisabled) {
|
||||
return winston.log('[user/jobs] Did not send daily digests because subscription system is disabled.');
|
||||
}
|
||||
|
||||
async.parallel({
|
||||
recent: function(next) {
|
||||
topics.getLatestTopics(0, 0, 10, 'day', next);
|
||||
|
||||
@@ -55,6 +55,7 @@ var async = require('async'),
|
||||
}
|
||||
|
||||
db.sortedSetRemove(set, nidsToUniqueIds[nid]);
|
||||
db.deleteObjectField('uid:' + uid + ':notifications:uniqueId:nid', nidsToUniqueIds[nid]);
|
||||
return next();
|
||||
}
|
||||
|
||||
@@ -141,7 +142,7 @@ var async = require('async'),
|
||||
}
|
||||
|
||||
notifs = notifs.filter(function(notif) {
|
||||
return notif !== null;
|
||||
return !!notif;
|
||||
}).sort(function(a, b) {
|
||||
return parseInt(b.datetime, 10) - parseInt(a.datetime, 10);
|
||||
}).map(function(notif) {
|
||||
|
||||
Reference in New Issue
Block a user