diff --git a/.tx/config b/.tx/config index eb73e33399..3df5e9b1b8 100644 --- a/.tx/config +++ b/.tx/config @@ -19,6 +19,7 @@ trans.he = public/language/he/category.json trans.hu = public/language/hu/category.json trans.it = public/language/it/category.json trans.ja = public/language/ja/category.json +trans.ko = public/language/ko/category.json trans.lt = public/language/lt/category.json trans.ms = public/language/ms/category.json trans.nb = public/language/nb/category.json @@ -54,6 +55,7 @@ trans.he = public/language/he/login.json trans.hu = public/language/hu/login.json trans.it = public/language/it/login.json trans.ja = public/language/ja/login.json +trans.ko = public/language/ko/login.json trans.lt = public/language/lt/login.json trans.ms = public/language/ms/login.json trans.nb = public/language/nb/login.json @@ -88,6 +90,7 @@ trans.he = public/language/he/recent.json trans.hu = public/language/hu/recent.json trans.it = public/language/it/recent.json trans.ja = public/language/ja/recent.json +trans.ko = public/language/ko/recent.json trans.lt = public/language/lt/recent.json trans.ms = public/language/ms/recent.json trans.nb = public/language/nb/recent.json @@ -122,6 +125,7 @@ trans.he = public/language/he/unread.json trans.hu = public/language/hu/unread.json trans.it = public/language/it/unread.json trans.ja = public/language/ja/unread.json +trans.ko = public/language/ko/unread.json trans.lt = public/language/lt/unread.json trans.ms = public/language/ms/unread.json trans.nb = public/language/nb/unread.json @@ -156,6 +160,7 @@ trans.he = public/language/he/modules.json trans.hu = public/language/hu/modules.json trans.it = public/language/it/modules.json trans.ja = public/language/ja/modules.json +trans.ko = public/language/ko/modules.json trans.lt = public/language/lt/modules.json trans.ms = public/language/ms/modules.json trans.nb = public/language/nb/modules.json @@ -190,6 +195,7 @@ trans.he = public/language/he/register.json trans.hu = public/language/hu/register.json trans.it = public/language/it/register.json trans.ja = public/language/ja/register.json +trans.ko = public/language/ko/register.json trans.lt = public/language/lt/register.json trans.ms = public/language/ms/register.json trans.nb = public/language/nb/register.json @@ -224,6 +230,7 @@ trans.he = public/language/he/user.json trans.hu = public/language/hu/user.json trans.it = public/language/it/user.json trans.ja = public/language/ja/user.json +trans.ko = public/language/ko/user.json trans.lt = public/language/lt/user.json trans.ms = public/language/ms/user.json trans.nb = public/language/nb/user.json @@ -258,6 +265,7 @@ trans.he = public/language/he/global.json trans.hu = public/language/hu/global.json trans.it = public/language/it/global.json trans.ja = public/language/ja/global.json +trans.ko = public/language/ko/global.json trans.lt = public/language/lt/global.json trans.ms = public/language/ms/global.json trans.nb = public/language/nb/global.json @@ -292,6 +300,7 @@ trans.he = public/language/he/notifications.json trans.hu = public/language/hu/notifications.json trans.it = public/language/it/notifications.json trans.ja = public/language/ja/notifications.json +trans.ko = public/language/ko/notifications.json trans.lt = public/language/lt/notifications.json trans.ms = public/language/ms/notifications.json trans.nb = public/language/nb/notifications.json @@ -326,6 +335,7 @@ trans.he = public/language/he/reset_password.json trans.hu = public/language/hu/reset_password.json trans.it = public/language/it/reset_password.json trans.ja = public/language/ja/reset_password.json +trans.ko = public/language/ko/reset_password.json trans.lt = public/language/lt/reset_password.json trans.ms = public/language/ms/reset_password.json trans.nb = public/language/nb/reset_password.json @@ -360,6 +370,7 @@ trans.he = public/language/he/users.json trans.hu = public/language/hu/users.json trans.it = public/language/it/users.json trans.ja = public/language/ja/users.json +trans.ko = public/language/ko/users.json trans.lt = public/language/lt/users.json trans.ms = public/language/ms/users.json trans.nb = public/language/nb/users.json @@ -394,6 +405,7 @@ trans.he = public/language/he/language.json trans.hu = public/language/hu/language.json trans.it = public/language/it/language.json trans.ja = public/language/ja/language.json +trans.ko = public/language/ko/language.json trans.lt = public/language/lt/language.json trans.ms = public/language/ms/language.json trans.nb = public/language/nb/language.json @@ -428,6 +440,7 @@ trans.he = public/language/he/pages.json trans.hu = public/language/hu/pages.json trans.it = public/language/it/pages.json trans.ja = public/language/ja/pages.json +trans.ko = public/language/ko/pages.json trans.lt = public/language/lt/pages.json trans.ms = public/language/ms/pages.json trans.nb = public/language/nb/pages.json @@ -462,6 +475,7 @@ trans.he = public/language/he/topic.json trans.hu = public/language/hu/topic.json trans.it = public/language/it/topic.json trans.ja = public/language/ja/topic.json +trans.ko = public/language/ko/topic.json trans.lt = public/language/lt/topic.json trans.ms = public/language/ms/topic.json trans.nb = public/language/nb/topic.json @@ -496,6 +510,7 @@ trans.he = public/language/he/success.json trans.hu = public/language/hu/success.json trans.it = public/language/it/success.json trans.ja = public/language/ja/success.json +trans.ko = public/language/ko/success.json trans.lt = public/language/lt/success.json trans.ms = public/language/ms/success.json trans.nb = public/language/nb/success.json @@ -530,6 +545,7 @@ trans.he = public/language/he/error.json trans.hu = public/language/hu/error.json trans.it = public/language/it/error.json trans.ja = public/language/ja/error.json +trans.ko = public/language/ko/error.json trans.lt = public/language/lt/error.json trans.ms = public/language/ms/error.json trans.nb = public/language/nb/error.json diff --git a/docs/upgrading/index.rst b/docs/upgrading/index.rst index 00b5d36d50..be5ce29edf 100644 --- a/docs/upgrading/index.rst +++ b/docs/upgrading/index.rst @@ -108,16 +108,15 @@ Navigate to your NodeBB: ``$ cd /path/to/nodebb``. If you are upgrading from a lower branch to a higher branch, switch branches as necessary. ***Make sure you are completely up-to-date on your current branch!***. -For example, if upgrading from ``v0.1.4`` to ``v0.2.0``: +For example, if upgrading from ``v0.3.2`` to ``v0.4.3``: .. code:: bash - $ git fetch # Grab the latest code from your current branch - $ git checkout v0.2.x + $ git fetch # Grab the latest code from the NodeBB Repository + $ git checkout v0.4.x # Type this as-is! Not v0.4.2 or v0.4.3, but "v0.4.x"! + $ git merge origin/v0.4.x -If not upgrading between branches, skip the commands above. - -Then, grab the latest code: +If not upgrading between branches, just run the following command: .. code:: bash @@ -145,4 +144,4 @@ This script will install any missing dependencies, upgrade any plugins or themes 6. Start up NodeBB & Test! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -You should now be running the latest version of NodeBB. \ No newline at end of file +You should now be running the latest version of NodeBB. diff --git a/package.json b/package.json index cd1fa88b4c..8226b165c2 100644 --- a/package.json +++ b/package.json @@ -47,8 +47,8 @@ "nodebb-plugin-mentions": "~0.4.0", "nodebb-plugin-markdown": "~0.4.1", "nodebb-widget-essentials": "~0.0.21", - "nodebb-theme-vanilla": "<0.0.21", - "nodebb-theme-lavender": "<0.0.27", + "nodebb-theme-vanilla": "~0.0.21", + "nodebb-theme-lavender": "~0.0.26", "nodebb-plugin-soundpack-default": "~0.1.1", "nodebb-plugin-dbsearch": "0.0.9" }, diff --git a/public/language/ko/category.json b/public/language/ko/category.json new file mode 100644 index 0000000000..bbd85848a8 --- /dev/null +++ b/public/language/ko/category.json @@ -0,0 +1,7 @@ +{ + "new_topic_button": "새 주제 생성", + "no_topics": "이 카테고리에는 생성된 주제가 없습니다.
먼저 주제를 생성해 보세요.", + "browsing": "이 주제를 읽고 있는 사용자", + "no_replies": "답글이 없습니다.", + "share_this_category": "이 카테고리를 공유" +} diff --git a/public/language/ko/error.json b/public/language/ko/error.json new file mode 100644 index 0000000000..12a15dc868 --- /dev/null +++ b/public/language/ko/error.json @@ -0,0 +1,50 @@ +{ + "invalid-data": "올바르지 않은 정보입니다.", + "not-logged-in": "로그인하지 않았습니다.", + "account-locked": "임시로 잠긴 계정입니다.", + "invalid-cid": "올바르지 않은 카테고리 ID입니다.", + "invalid-tid": "올바르지 않은 주제 ID입니다.", + "invalid-pid": "올바르지 않은 게시물 ID입니다.", + "invalid-uid": "올바르지 않은 사용자 ID입니다.", + "invalid-username": "올바르지 않은 사용자 이름입니다.", + "invalid-email": "올바르지 않은 이메일입니다.", + "invalid-title": "올바르지 않은 제목입니다.", + "invalid-user-data": "올바르지 않은 사용자 정보입니다.", + "invalid-password": "올바르지 않은 비밀번호입니다.", + "invalid-pagination-value": "올바르지 않은 페이지입니다.", + "username-taken": "이미 사용 중인 사용자 이름입니다.", + "email-taken": "이미 사용 중인 이메일입니다.", + "user-banned": "차단된 사용자입니다.", + "no-category": "존재하지 않는 카테고리입니다.", + "no-topic": "존재하지 않는 주제입니다.", + "no-post": "존재하지 않는 게시물입니다.", + "no-group": "존재하지 않는 그룹입니다.", + "no-user": "존재하지 않는 사용자입니다.", + "no-teaser": "존재하지 않는 미리보기입니다.", + "no-privileges": "이 작업을 할 수 있는 권한이 없습니다.", + "category-disabled": "비활성화된 카테고리입니다.", + "topic-locked": "잠긴 주제입니다.", + "still-uploading": "업로드가 끝날 때까지 기다려 주세요.", + "content-too-short": "게시물의 내용은 최소 %1자로 제한됩니다.", + "title-too-short": "제목은 최소 %1자로 제한됩니다.", + "title-too-long": "제목은 최대 %1자로 제한됩니다.", + "too-many-posts": "새 게시물 작성은 %1초 간격으로 제한됩니다", + "file-too-big": "파일의 크기는 최대 %1KB로 제한됩니다.", + "cant-vote-self-post": "자신의 게시물은 추천할 수 없습니다.", + "already-favourited": "이미 이 게시물을 좋아하는 중입니다.", + "already-unfavourited": "이미 이 게시물을 좋아하고 있지 않습니다.", + "cant-ban-other-admins": "다른 관리자를 차단할 수 없습니다.", + "invalid-image-type": "올바르지 않은 이미지입니다.", + "group-name-too-short": "그룹 이름이 너무 짧습니다.", + "group-already-exists": "이미 존재하는 그룹입니다.", + "group-name-change-not-allowed": "금지된 그룹 이름입니다.", + "post-already-deleted": "이미 삭제된 게시물입니다.", + "post-already-restored": "이미 복원된 게시물입니다.", + "topic-already-deleted": "이미 삭제된 주제입니다.", + "topic-already-restored": "이미 복원된 주제입니다.", + "topic-thumbnails-are-disabled": "주제 섬네일이 이미 해제되었습니다.", + "invalid-file": "올바르지 않은 파일입니다.", + "uploads-are-disabled": "업로드는 비활성화되어 있습니다.", + "signature-too-long": "서명은 최대 %1자로 제한됩니다.", + "cant-chat-with-yourself": "자신과는 채팅할 수 없습니다." +} \ No newline at end of file diff --git a/public/language/ko/footer.json b/public/language/ko/footer.json new file mode 100644 index 0000000000..ef75c4211d --- /dev/null +++ b/public/language/ko/footer.json @@ -0,0 +1,7 @@ +{ + "stats.online": "온라인", + "stats.users": "사용자", + "stats.topics": "주제", + "stats.posts": "게시물", + "success": "성공" +} diff --git a/public/language/ko/global.json b/public/language/ko/global.json new file mode 100644 index 0000000000..6b5bb9a2b2 --- /dev/null +++ b/public/language/ko/global.json @@ -0,0 +1,71 @@ +{ + "home": "홈", + "search": "검색", + "buttons.close": "닫기", + "403.title": "접근이 거부되었습니다.", + "403.message": "접근 권한이 없는 페이지에 접근하였습니다. 로그인되어 있는지 확인해 주세요.", + "404.title": "페이지를 찾을 수 없습니다.", + "404.message": "존재하지 않는 페이지에 접근하였습니다. 홈으로 돌아갈 수 있습니다.", + "500.title": "내부 오류가 발생했습니다.", + "500.message": "알 수 없는 오류가 발생했습니다.", + "register": "회원가입", + "login": "로그인", + "please_log_in": "로그인해 주세요.", + "logout": "로그아웃", + "posting_restriction_info": "게시물 작성은 현재 회원에게만 제한되고 있습니다. 여기를 누르면 로그인 페이지로 이동합니다.", + "welcome_back": "환영합니다 : ", + "you_have_successfully_logged_in": "성공적으로 로그인했습니다.", + "save_changes": "저장", + "close": "닫기", + "pagination": "페이지", + "pagination.out_of": "%1 out of %2", + "header.admin": "관리자", + "header.recent": "최근 주제", + "header.unread": "읽지 않은 주제", + "header.popular": "인기 주제", + "header.users": "사용자", + "header.chats": "채팅", + "header.notifications": "알림", + "header.search": "검색", + "header.profile": "프로필", + "notifications.loading": "알림을 로딩중입니다.", + "chats.loading": "채팅을 로딩중입니다.", + "motd.welcome": "NodeBB에 오신 것을 환경합니다.", + "previouspage": "이전 페이지", + "nextpage": "다음 페이지", + "alert.success": "성공", + "alert.error": "오류", + "alert.banned": "차단됨", + "alert.banned.message": "이 계정은 차단되었습니다. 지금 로그아웃됩니다.", + "alert.unfollow": "더 이상 %1님을 팔로우하지 않습니다.", + "alert.follow": "%1님을 팔로우합니다.", + "online": "온라인", + "users": "사용자", + "topics": "주제", + "posts": "게시물", + "views": "조회 수", + "reputation": "인기도", + "read_more": "전체 보기", + "posted_ago_by_guest": "익명 사용자가 %1에 작성했습니다.", + "posted_ago_by": "%2님이 %1에 작성했습니다.", + "posted_ago": "%1에 작성되었습니다.", + "posted_in_ago_by_guest": "%2, 익명 사용자가 %1에 작성했습니다.", + "posted_in_ago_by": "%3님이 %2 %1에 작성했습니다.", + "posted_in_ago": "%2 %1에 작성되었습니다. ", + "replied_ago": "%1에 답글이 작성되었습니다.", + "user_posted_ago": "%1님이 %2에 작성했습니다.", + "guest_posted_ago": "익명 사용자가 %1에 작성했습니다.", + "last_edited_by_ago": "%1님이 %2에 마지막으로 수정했습니다.", + "norecentposts": "최근 작성된 게시물이 없습니다.", + "norecenttopics": "최근 생성된 생성된 주제가 없습니다.", + "recentposts": "최근 게시물", + "recentips": "최근 로그인 IP", + "away": "자리 비움", + "dnd": "다른 용무 중", + "invisible": "오프라인으로 표시", + "offline": "오프라인", + "email": "이메일", + "language": "언어", + "guest": "익명 사용자", + "guests": "익명 사용자" +} \ No newline at end of file diff --git a/public/language/ko/language.json b/public/language/ko/language.json new file mode 100644 index 0000000000..9a19e01124 --- /dev/null +++ b/public/language/ko/language.json @@ -0,0 +1,5 @@ +{ + "name": "한국어", + "code": "ko", + "dir": "ltr" +} \ No newline at end of file diff --git a/public/language/ko/login.json b/public/language/ko/login.json new file mode 100644 index 0000000000..c967c46230 --- /dev/null +++ b/public/language/ko/login.json @@ -0,0 +1,8 @@ +{ + "username": "사용자 이름 / 이메일", + "remember_me": "로그인 유지", + "forgot_password": "비밀번호 초기화", + "alternative_logins": "다른 방법으로 로그인", + "failed_login_attempt": "로그인에 실패했습니다.", + "login_successful": "성공적으로 로그인했습니다." +} diff --git a/public/language/ko/modules.json b/public/language/ko/modules.json new file mode 100644 index 0000000000..395536cb8a --- /dev/null +++ b/public/language/ko/modules.json @@ -0,0 +1,8 @@ +{ + "chat.chatting_with": "님과의 채팅", + "chat.placeholder": "여기에 메시지를 입력하세요.", + "chat.send": "전송", + "chat.no_active": "활성화된 채팅이 없습니다.", + "chat.user_typing": "%1님이 입력 중입니다.", + "chat.user_has_messaged_you": "%1님이 메시지를 보냈습니다." +} \ No newline at end of file diff --git a/public/language/ko/notifications.json b/public/language/ko/notifications.json new file mode 100644 index 0000000000..b2e9d7e4ce --- /dev/null +++ b/public/language/ko/notifications.json @@ -0,0 +1,18 @@ +{ + "title": "알림", + "no_notifs": "새 알림이 없습니다.", + "see_all": "모든 알림 보기", + "back_to_home": "홈으로 돌아가기", + "outgoing_link": "외부 링크", + "outgoing_link_message": "다른 사이트로 이동합니다.", + "continue_to": "계속", + "return_to": "돌아가기 : ", + "new_notification": "새 알림", + "you_have_unread_notifications": "읽지 않은 알림이 있습니다.", + "user_made_post": "%1님이 새 게시물을 작성했습니다.", + "new_message_from": "%1님이 메시지를 보냈습니다.", + "upvoted_your_post": "%1님이 내 게시물을 추천했습니다.", + "favourited_your_post": "%1님이 내 게시물을 관심글로 등록했습니다.", + "user_flagged_post": "%1님이 게시물을 신고했습니다.", + "user_posted_to": "%1님이 %2님의 게시물에 답글을 작성했습니다." +} \ No newline at end of file diff --git a/public/language/ko/pages.json b/public/language/ko/pages.json new file mode 100644 index 0000000000..230a012757 --- /dev/null +++ b/public/language/ko/pages.json @@ -0,0 +1,15 @@ +{ + "home": "홈", + "unread": "읽지 않은 주제", + "popular": "인기 주제", + "recent": "최근 주제", + "users": "사용자", + "notifications": "알림", + "user.edit": "%1님의 프로필 수정", + "user.following": "%1님이 팔로우하는 사용자", + "user.followers": "%1님을 팔로우하는 사용자", + "user.posts": "%1님이 작성한 게시물", + "user.topics": "%1님이 생성한 주제", + "user.favourites": "%1님이 좋아하는 게시물", + "user.settings": "설정" +} \ No newline at end of file diff --git a/public/language/ko/recent.json b/public/language/ko/recent.json new file mode 100644 index 0000000000..0280acfaab --- /dev/null +++ b/public/language/ko/recent.json @@ -0,0 +1,7 @@ +{ + "title": "최근 주제", + "day": "일간", + "week": "주간", + "month": "월간", + "no_recent_topics": "최근 생성된 주제가 없습니다." +} \ No newline at end of file diff --git a/public/language/ko/register.json b/public/language/ko/register.json new file mode 100644 index 0000000000..4d531f7cb4 --- /dev/null +++ b/public/language/ko/register.json @@ -0,0 +1,18 @@ +{ + "register": "회원가입", + "help.email": "입력하신 이메일 주소는 공개되지 않으며, 설정을 통해 공개하실 수 있습니다.", + "help.username_restrictions": "%1자 이상 %2자 이하의 고유한 이름을 입력하세요. @username 같은 방식으로 다른 사람들을 언급할 수 있습니다.", + "help.minimum_password_length": "비밀번호는 최소 %1자로 제한됩니다.", + "email_address": "이메일", + "email_address_placeholder": "여기에 이메일 주소를 입력하세요.", + "username": "사용자 이름", + "username_placeholder": "여기에 사용자 이름을 입력하세요.", + "password": "비밀번호", + "password_placeholder": "여기에 비밀번호를 입력하세요.", + "confirm_password": "비밀번호 재입력", + "confirm_password_placeholder": "여기에 비밀번호를 재입력하세요.", + "register_now_button": "회원가입", + "alternative_registration": "다른 방법으로 회원가입", + "terms_of_use": "이용약관", + "agree_to_terms_of_use": "이용약관에 동의합니다." +} \ No newline at end of file diff --git a/public/language/ko/reset_password.json b/public/language/ko/reset_password.json new file mode 100644 index 0000000000..d229a5a931 --- /dev/null +++ b/public/language/ko/reset_password.json @@ -0,0 +1,14 @@ +{ + "reset_password": "비밀번호 초기화", + "update_password": "비밀번호 변경", + "password_changed.title": "비밀번호가 변경되었습니다.", + "password_changed.message": "

성공적으로 비밀번호가 초기화되었습니다. 다시 로그인해 주세요.", + "wrong_reset_code.title": "올바르지 않은 초기화 코드입니다.", + "wrong_reset_code.message": "올바르지 않은 초기화 코드입니다. 다시 시도하거나 새로운 초기화 코드를 요청하세요.", + "new_password": "새 비밀번호", + "repeat_password": "비밀번호 재입력", + "enter_email": "이메일 주소를 입력하면 비밀번호를 초기화하는 방법을 메일로 알려드립니다.", + "enter_email_address": "여기에 이메일 주소를 입력하세요.", + "password_reset_sent": "이메일이 발송되었습니다.", + "invalid_email": "올바르지 않거나 가입되지 않은 이메일입니다." +} \ No newline at end of file diff --git a/public/language/ko/success.json b/public/language/ko/success.json new file mode 100644 index 0000000000..e7b911bb2d --- /dev/null +++ b/public/language/ko/success.json @@ -0,0 +1,6 @@ +{ + "success": "성공", + "topic-post": "성공적으로 게시물을 작성했습니다.", + "authentication-successful": "인증에 성공했습니다.", + "settings-saved": "성공적으로 설정을 저장했습니다." +} \ No newline at end of file diff --git a/public/language/ko/topic.json b/public/language/ko/topic.json new file mode 100644 index 0000000000..3432b0ae87 --- /dev/null +++ b/public/language/ko/topic.json @@ -0,0 +1,95 @@ +{ + "topic": "주제", + "topic_id": "주제 ID", + "topic_id_placeholder": "여기에 주제 ID를 입력하세요.", + "no_topics_found": "주제를 찾을 수 없습니다.", + "no_posts_found": "게시물을 찾을 수 없습니다.", + "post_is_deleted": "이 게시물은 삭제되었습니다.", + "profile": "프로필", + "posted_by": "%1님이 작성했습니다.", + "posted_by_guest": "익명 사용자가 작성했습니다.", + "chat": "채팅", + "notify_me": "이 주제의 새 답글 알리기", + "quote": "인용", + "reply": "답글", + "edit": "수정", + "delete": "삭제", + "restore": "복원", + "move": "이동", + "fork": "복제", + "banned": "차단됨", + "link": "링크", + "share": "공유", + "tools": "도구", + "flag": "신고", + "bookmark_instructions": "여기를 누르면 마지막에 읽던 위치로 이동합니다.", + "flag_title": "이 게시물을 신고", + "flag_confirm": "정말로 신고하시겠습니까?", + "flag_success": "이 게시물은 신고되었습니다.", + "deleted_message": "이 주제는 삭제되었습니다. 게시물 관리 권한이 있는 사용자만 접근할 수 있습니다.", + "following_topic.message": "이제 이 주제에 새 답글이 달리면 알림을 받습니다.", + "not_following_topic.message": "더 이상 이 주제의 새 답글을 알리지 않습니다.", + "login_to_subscribe": "이 주제의 알림을 받기 위해서는 로그인해야 합니다.", + "markAsUnreadForAll.success": "모든 사용자에 대해 읽지 않음으로 표시했습니다.", + "watch": "관심 주제", + "watch.title": "이 주제의 새 답글 알리기", + "share_this_post": "이 게시물 공유", + "thread_tools.title": "주제 도구", + "thread_tools.markAsUnreadForAll": "모두에게 읽지 않음으로 표시", + "thread_tools.pin": "상단 고정", + "thread_tools.unpin": "상단 고정 해제", + "thread_tools.lock": "잠금", + "thread_tools.unlock": "잠금 해제", + "thread_tools.move": "이동", + "thread_tools.move_all": "모두 이동", + "thread_tools.fork": "복제", + "thread_tools.delete": "삭제", + "thread_tools.delete_confirm": "이 주제를 삭제하시겠습니까?", + "thread_tools.restore": "복원", + "thread_tools.restore_confirm": "이 주제를 복원하시겠습니까?", + "topic_lock_success": "성공적으로 주제를 잠갔습니다.", + "topic_unlock_success": "성공적으로 주제가 잠금 해제되었습니다.", + "topic_pin_success": "성공적으로 주제가 상단에 고정되었습니다.", + "topic_unpin_success": "성공적으로 주제가 상단 고정 해제되었습니다.", + "topic_move_success": "성공적으로 이 주제를 %1로 이동했습니다.", + "post_delete_confirm": "이 게시물을 삭제하시겠습니까?", + "post_restore_confirm": "이 게시물을 복원하시겠습니까?", + "post_delete_error": "이 게시물을 삭제하는 데 실패했습니다.", + "post_restore_error": "이 게시물을 복원하는 데 실패했습니다.", + "load_categories": "카테고리 로딩 중", + "disabled_categories_note": "비활성화된 카테고리는 회색으로 표시됩니다.", + "confirm_move": "이동", + "confirm_fork": "복제", + "favourite": "좋아요", + "favourites": "좋아요", + "favourites.has_no_favourites": "좋아하는 게시물이 없습니다.", + "loading_more_posts": "게시물을 로딩 중", + "move_topic": "주제 이동", + "move_topics": "주제 이동", + "move_post": "게시물 이동", + "post_moved": "게시물이 이동되었습니다.", + "fork_topic": "주제 복제", + "topic_will_be_moved_to": "이 주제를 지정한 카테고리로 이동합니다.", + "fork_topic_instruction": "복제할 게시물을 선택하세요.", + "fork_no_pids": "게시물이 선택되지 않았습니다.", + "fork_success": "성공적으로 주제를 복제했습니다.", + "composer.title_placeholder": "여기에 제목을 입력하세요.", + "composer.write": "작성", + "composer.preview": "미리보기", + "composer.help": "도움말", + "composer.discard": "취소", + "composer.submit": "등록", + "composer.replying_to": "%1님께 답글>", + "composer.new_topic": "새 주제 생성", + "composer.uploading": "업로드 중", + "composer.thumb_url_label": "섬네일 URL", + "composer.thumb_title": "이 주제에 섬네일 추가", + "composer.thumb_url_placeholder": "http://example.com/thumb.png", + "composer.thumb_file_label": "혹은 파일 업로드", + "composer.thumb_remove": "섬네일 삭제", + "composer.drag_and_drop_images": "이미지를 여기에 드래그&드롭하세요.", + "composer.upload_instructions": "이미지를 드래그&드롭해서 업로드", + "more_users_and_guests": "%1명 이상의 회원과 %2명의 익명 사용자", + "more_users": "%1명 이상의 회원", + "more_guests": "%1명 이상의 익명 사용자" +} \ No newline at end of file diff --git a/public/language/ko/unread.json b/public/language/ko/unread.json new file mode 100644 index 0000000000..b7ffd0ad4e --- /dev/null +++ b/public/language/ko/unread.json @@ -0,0 +1,9 @@ +{ + "title": "읽지 않은 주제", + "no_unread_topics": "읽지 않은 주제가 없습니다.", + "load_more": "더 보기", + "mark_as_read": "읽음으로 표시", + "selected": "선택된 주제", + "all": "전체", + "topics_marked_as_read.success": "성공적으로 읽음으로 표시했습니다." +} \ No newline at end of file diff --git a/public/language/ko/user.json b/public/language/ko/user.json new file mode 100644 index 0000000000..d341173148 --- /dev/null +++ b/public/language/ko/user.json @@ -0,0 +1,64 @@ +{ + "banned": "차단됨", + "offline": "오프라인", + "username": "사용자 이름", + "email": "이메일", + "fullname": "이름", + "website": "웹 사이트", + "location": "거주지", + "age": "나이", + "joined": "가입일", + "lastonline": "최근 로그인", + "profile": "프로필", + "profile_views": "프로필 조회 수", + "reputation": "인기도", + "favourites": "좋아요", + "followers": "이 사용자를 팔로우", + "following": "이 사용자가 팔로우", + "signature": "서명", + "gravatar": "Gravatar", + "birthday": "생일", + "chat": "채팅", + "follow": "팔로우", + "unfollow": "팔로우 취소", + "profile_update_success": "성공적으로 프로필을 저장했습니다.", + "change_picture": "사진 변경", + "edit": "프로필 수정", + "uploaded_picture": "사진 업로드", + "upload_new_picture": "새 사진 업로드", + "current_password": "현재 비밀번호", + "change_password": "비밀번호 변경", + "change_password_error": "유효하지 않은 비밀번호", + "change_password_error_wrong_current": "현재 비밀번호가 올바르지 않습니다.", + "change_password_error_length": "새 비밀번호가 너무 짧습니다.", + "change_password_error_match": "비밀번호 재입력이 새 비밀번호와 일치하지 않습니다.", + "change_password_error_privileges": "비밀번호를 바꿀 권한이 없습니다.", + "change_password_success": "성공적으로 비밀번호를 변경했습니다.", + "confirm_password": "비밀번호 재입력", + "password": "새 비밀번호", + "username_taken_workaround": "새 사용자 이름이 이미 존재하여 %1로 저장되었습니다.", + "upload_picture": "사진 업로드", + "upload_a_picture": "사진 업로드", + "image_spec": "PNG, JPG, GIF 파일만을 업로드할 수 있습니다.", + "max": "최대", + "settings": "설정", + "show_email": "이메일 공개", + "digest_label": "포럼 이메일 수신", + "digest_description": "지정한 주기로 이 포럼의 새 알림과 주제를 이메일로 수신합니다.", + "digest_off": "해제", + "digest_daily": "매일", + "digest_weekly": "매주", + "digest_monthly": "매월", + "has_no_follower": "아무도 이 사용자를 팔로우하지 않습니다.", + "follows_no_one": "이 사용자는 아무도 팔로우하지 않습니다.", + "has_no_posts": "이 사용자는 게시물을 작성하지 않았습니다.", + "has_no_topics": "이 사용자는 주제를 생성하지 않았습니다.", + "email_hidden": "이메일 비공개", + "hidden": "비공개", + "paginate_description": "주제와 게시물을 여러 페이지로 나누어 표시", + "topics_per_page": "페이지 당 주제 수", + "posts_per_page": "페이지 당 게시물 수", + "notification_sounds": "알림을 수신하면 알림음을 재생", + "browsing": "페이지 열기", + "open_links_in_new_tab": "외부 링크를 새 탭에서 열기" +} \ No newline at end of file diff --git a/public/language/ko/users.json b/public/language/ko/users.json new file mode 100644 index 0000000000..63040e0782 --- /dev/null +++ b/public/language/ko/users.json @@ -0,0 +1,8 @@ +{ + "latest_users": "가입일", + "top_posters": "게시물 수", + "most_reputation": "인기도", + "search": "검색", + "enter_username": "검색할 사용자 이름을 입력하세요.", + "load_more": "더 보기" +} \ No newline at end of file diff --git a/public/src/admin.js b/public/src/admin.js index 65a7504208..d504750add 100644 --- a/public/src/admin.js +++ b/public/src/admin.js @@ -1,4 +1,5 @@ "use strict"; +/*global app, socket*/ var admin = {}; @@ -17,7 +18,47 @@ var admin = {}; } } }); - }); + }); }; + $(function() { + var menuEl = $('.sidebar-nav'), + liEls = menuEl.find('li'), + parentEl, + activate = function(li) { + liEls.removeClass('active'); + li.addClass('active'); + }; + + // also on ready, check the pathname, maybe it was a page refresh and no item was clicked + liEls.each(function(i, li){ + li = $(li); + if ((li.find('a').attr('href') || '').indexOf(location.pathname) >= 0) { + activate(li); + } + }); + + // On menu click, change "active" state + menuEl.on('click', function(e) { + parentEl = $(e.target).parent(); + if (parentEl.is('li')) { + activate(parentEl); + } + }); + }); + + socket.emit('admin.config.get', function(err, config) { + if(err) { + return app.alert({ + alert_id: 'config_status', + timeout: 2500, + title: 'Error', + message: 'NodeBB encountered a problem getting config', + type: 'danger' + }); + } + + // move this to admin.config + app.config = config; + }); }()); \ No newline at end of file diff --git a/public/src/forum/admin/categories.js b/public/src/forum/admin/categories.js index 78e173997a..6a6f2f9373 100644 --- a/public/src/forum/admin/categories.js +++ b/public/src/forum/admin/categories.js @@ -308,9 +308,11 @@ define(['uploader'], function(uploader) { uid: uid, privilege: privilege, set: !anchorEl.hasClass('active') - }, function(err, privileges) { - anchorEl.toggleClass('active', privileges[privilege]); - + }, function(err) { + if (err) { + return app.alertError(err.message); + } + anchorEl.toggleClass('active', !anchorEl.hasClass('active')); Categories.refreshPrivilegeList(cid); }); }); diff --git a/public/src/forum/admin/footer.js b/public/src/forum/admin/footer.js deleted file mode 100644 index a7a040a0f4..0000000000 --- a/public/src/forum/admin/footer.js +++ /dev/null @@ -1,41 +0,0 @@ -"use strict"; -/*global app, socket*/ - -$(function() { - var menuEl = $('.sidebar-nav'), - liEls = menuEl.find('li'), - parentEl, - activate = function(li) { - liEls.removeClass('active'); - li.addClass('active'); - }; - - // also on ready, check the pathname, maybe it was a page refresh and no item was clicked - liEls.each(function(i, li){ - li = $(li); - if ((li.find('a').attr('href') || '').indexOf(location.pathname) >= 0) { - activate(li); - } - }); - - // On menu click, change "active" state - menuEl.on('click', function(e) { - parentEl = $(e.target).parent(); - if (parentEl.is('li')) { - activate(parentEl); - } - }); -}); - -socket.emit('admin.config.get', function(err, config) { - if(err) { - return app.alert({ - alert_id: 'config_status', - timeout: 2500, - title: 'Error', - message: 'NodeBB encountered a problem getting config', - type: 'danger' - }); - } - app.config = config; -}); diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js index 460bfa43b6..3d4775b774 100644 --- a/public/src/forum/topic.js +++ b/public/src/forum/topic.js @@ -278,7 +278,7 @@ define(['forum/pagination', 'forum/topic/threadTools', 'forum/topic/postTools', pagination.recreatePaginationLinks(newPageCount); - if(pagination.currentPage === pagination.pageCount) { + if (pagination.currentPage === pagination.pageCount) { createNewPosts(data); } else if(data.posts && data.posts.length && parseInt(data.posts[0].uid, 10) === parseInt(app.uid, 10)) { pagination.loadPage(pagination.pageCount); @@ -390,8 +390,11 @@ define(['forum/pagination', 'forum/topic/threadTools', 'forum/topic/postTools', } function toggleModTools(postHtml, privileges) { - postHtml.find('.edit, .delete').toggleClass('none', !privileges.editable); - postHtml.find('.move').toggleClass('none', !privileges.move); + postHtml.find('.edit, .delete').toggleClass('none', !privileges.meta.editable); + postHtml.find('.move').toggleClass('none', !privileges.meta.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); } function loadMorePosts(tid, after, callback) { diff --git a/public/src/forum/topic/threadTools.js b/public/src/forum/topic/threadTools.js index 35dbf38a42..7f6fe6a4b7 100644 --- a/public/src/forum/topic/threadTools.js +++ b/public/src/forum/topic/threadTools.js @@ -21,53 +21,48 @@ define(['forum/topic/fork', 'forum/topic/move'], function(fork, move) { ThreadTools.setPinnedState({tid: tid, isPinned: true}); } - if (ajaxify.variables.get('expose_tools') === '1') { + $('.delete_thread').on('click', function(e) { + var command = threadState.deleted !== '1' ? 'delete' : 'restore'; - $('.thread-tools').removeClass('hide'); - - $('.delete_thread').on('click', function(e) { - var command = threadState.deleted !== '1' ? 'delete' : 'restore'; - - translator.translate('[[topic:thread_tools.' + command + '_confirm]]', function(msg) { - bootbox.confirm(msg, function(confirm) { - if (confirm) { - socket.emit('topics.' + command, [tid]); - } - }); + translator.translate('[[topic:thread_tools.' + command + '_confirm]]', function(msg) { + bootbox.confirm(msg, function(confirm) { + if (confirm) { + socket.emit('topics.' + command, [tid]); + } }); - - return false; }); - $('.lock_thread').on('click', function(e) { - socket.emit(threadState.locked !== '1' ? 'topics.lock' : 'topics.unlock', [tid]); - return false; - }); + return false; + }); - $('.pin_thread').on('click', function(e) { - socket.emit(threadState.pinned !== '1' ? 'topics.pin' : 'topics.unpin', [tid]); - return false; - }); + $('.lock_thread').on('click', function(e) { + socket.emit(threadState.locked !== '1' ? 'topics.lock' : 'topics.unlock', [tid]); + return false; + }); - $('.markAsUnreadForAll').on('click', function() { - var btn = $(this); - socket.emit('topics.markAsUnreadForAll', [tid], function(err) { - if(err) { - return app.alertError(err.message); - } - app.alertSuccess('[[topic:markAsUnreadForAll.success]]'); - btn.parents('.thread-tools.open').find('.dropdown-toggle').trigger('click'); - }); - return false; - }); + $('.pin_thread').on('click', function(e) { + socket.emit(threadState.pinned !== '1' ? 'topics.pin' : 'topics.unpin', [tid]); + return false; + }); - $('.move_thread').on('click', function(e) { - move.init([tid], ajaxify.variables.get('category_id')); - return false; + $('.markAsUnreadForAll').on('click', function() { + var btn = $(this); + socket.emit('topics.markAsUnreadForAll', [tid], function(err) { + if(err) { + return app.alertError(err.message); + } + app.alertSuccess('[[topic:markAsUnreadForAll.success]]'); + btn.parents('.thread-tools.open').find('.dropdown-toggle').trigger('click'); }); + return false; + }); - fork.init(); - } + $('.move_thread').on('click', function(e) { + move.init([tid], ajaxify.variables.get('category_id')); + return false; + }); + + fork.init(); socket.emit('topics.followCheck', tid, function(err, state) { setFollowState(state); diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index e68130519f..4d99a202f3 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -323,10 +323,11 @@ define(['taskbar', 'string', 'sounds'], function(taskbar, S, sounds) { message.append(userPicture) .append(userName) - .append('
'); + .append('
') + .prepend(time); } - message.append(S(data.content + time).stripTags('p').s); + message.append(S(data.content).stripTags('p').s); message.toggleClass('chat-message-them', !isYou); message.find('img:not(".chat-user-image")').addClass('img-responsive'); diff --git a/src/categories.js b/src/categories.js index 0b0e2dbe76..06b96c5c84 100644 --- a/src/categories.js +++ b/src/categories.js @@ -8,7 +8,6 @@ var db = require('./database'), Groups = require('./groups'), topics = require('./topics'), plugins = require('./plugins'), - CategoryTools = require('./categoryTools'), meta = require('./meta'), emitter = require('./emitter'), validator = require('validator'), @@ -60,6 +59,10 @@ var db = require('./database'), }); }; + Categories.exists = function(cid, callback) { + db.isSortedSetMember('categories:cid', cid, callback); + }; + Categories.getCategoryById = function(cid, start, end, uid, callback) { Categories.getCategoryData(cid, function(err, category) { if(err || !category) { diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js index 925e83e42e..8142a06c24 100644 --- a/src/categories/recentreplies.js +++ b/src/categories/recentreplies.js @@ -6,8 +6,7 @@ var async = require('async'), db = require('./../database'), posts = require('./../posts'), - topics = require('./../topics'), - CategoryTools = require('./../categoryTools'); + topics = require('./../topics'); module.exports = function(Categories) { Categories.getRecentReplies = function(cid, uid, count, callback) { diff --git a/src/categoryTools.js b/src/categoryTools.js deleted file mode 100644 index c965b9b17c..0000000000 --- a/src/categoryTools.js +++ /dev/null @@ -1,135 +0,0 @@ -"use strict"; - -var Groups = require('./groups'), - User = require('./user'), - categories = require('./categories'), - - async = require('async'), - db = require('./database'); - -var internals = { - isMember: function(key, candidate, next){ - Groups.exists(key, function(err, exists) { - if (exists) { - Groups.isMember(candidate, key, next); - } else { - next(null, null); - } - }); - }, - - isMemberOfGroupList: function(key, candidate, next){ - Groups.exists(key, function(err, exists) { - if (exists) { - Groups.isMemberOfGroupList(candidate, key, next); - } else { - next(null, null); - } - }); - } -}; - -var CategoryTools = {}; - -CategoryTools.exists = function(cid, callback) { - db.isSortedSetMember('categories:cid', cid, callback); -}; - -CategoryTools.privileges = function(cid, uid, callback) { - async.parallel({ - "disabled": function(next) { - categories.getCategoryField(cid, 'disabled', next); - }, - read: function(next) { - internals.isMember('cid:' + cid + ':privileges:read', uid, next); - }, - "topics:create": function(next) { - internals.isMember('cid:' + cid + ':privileges:topics:create', uid, next); - }, - "topics:reply": function(next) { - internals.isMember('cid:' + cid + ':privileges:topics:reply', uid, next); - }, - "groups:read": function(next) { - internals.isMemberOfGroupList('cid:' + cid + ':privileges:groups:read', uid, next); - }, - "groups:topics:create": function(next) { - internals.isMemberOfGroupList('cid:' + cid + ':privileges:groups:topics:create', uid, next); - }, - "groups:topics:reply": function(next) { - internals.isMemberOfGroupList('cid:' + cid + ':privileges:groups:topics:reply', uid, next); - }, - mods: function(next) { - User.isModerator(uid, cid, next); - }, - admin: function(next) { - User.isAdministrator(uid, next); - } - }, function(err, privileges) { - if (privileges) { - privileges.meta = { - read: ( - ( - parseInt(privileges.disabled, 10) !== 1 && - ( - (privileges['read'] === null && privileges['groups:read'] === null) || - privileges['read'] || privileges['groups:read'] - ) - ) || - privileges.mods || - privileges.admin - ), - "topics:create": ( - ( - parseInt(privileges.disabled, 10) !== 1 && - ( - (privileges['topics:create'] === null && privileges['groups:topics:create'] === null) || - privileges['topics:create'] || privileges['groups:topics:create'] - ) - ) || - privileges.mods || - privileges.admin - ), - "topics:reply": ( - ( - parseInt(privileges.disabled, 10) !== 1 && - ( - (privileges['topics:reply'] === null && privileges['groups:topics:reply'] === null) || - privileges['topics:reply'] || privileges['groups:topics:reply'] - ) - ) || - privileges.mods || - privileges.admin - ), - editable: privileges.mods || privileges.admin, - view_deleted: privileges.mods || privileges.admin - }; - } - - // console.log(privileges, cid, uid); - callback(err, privileges || null); - }); -}; - -CategoryTools.groupPrivileges = function(cid, groupName, callback) { - async.parallel({ - "groups:read":function(next) { - internals.isMember('cid:' + cid + ':privileges:groups:read', groupName, function(err, isMember){ - next(err, !!isMember); - }); - }, - "groups:topics:create":function(next) { - internals.isMember('cid:' + cid + ':privileges:groups:topics:create', groupName, function(err, isMember){ - next(err, !!isMember); - }); - }, - "groups:topics:reply":function(next) { - internals.isMember('cid:' + cid + ':privileges:groups:topics:reply', groupName, function(err, isMember){ - next(err, !!isMember); - }); - } - }, function(err, privileges) { - callback(err, privileges || null); - }); -}; - -module.exports = CategoryTools; diff --git a/src/controllers/categories.js b/src/controllers/categories.js index 58fd5e80d5..12d5f8346f 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -4,7 +4,7 @@ var categoriesController = {}, async = require('async'), qs = require('querystring'), nconf = require('nconf'), - categoryTools = require('./../categoryTools'), + privileges = require('../privileges'), user = require('./../user'), categories = require('./../categories'), topics = require('./../topics'); @@ -72,16 +72,16 @@ categoriesController.get = function(req, res, next) { }); }, function(next) { - categoryTools.privileges(cid, uid, function(err, categoryPrivileges) { + privileges.categories.get(cid, uid, function(err, categoryPrivileges) { if (err) { return next(err); } if (!categoryPrivileges.meta.read) { - next(new Error('[[error:no-privileges]]')); - } else { - next(null, categoryPrivileges); + return next(new Error('[[error:no-privileges]]')); } + + next(null, categoryPrivileges); }); }, function (privileges, next) { @@ -152,14 +152,14 @@ categoriesController.get = function(req, res, next) { } ], function (err, data) { if (err) { - if (err.message === 'not-enough-privileges') { - return res.redirect('403'); + if (err.message === '[[error:no-privileges]]') { + return res.locals.isAPI ? res.json(403, err.message) : res.redirect('403'); } else { - return res.redirect('404'); + return res.locals.isAPI ? res.json(404, 'not-found') : res.redirect('404'); } } - if(data.link) { + if (data.link) { return res.redirect(data.link); } diff --git a/src/controllers/index.js b/src/controllers/index.js index 4bb46ed943..9d3254fca1 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -17,7 +17,7 @@ var topicsController = require('./topics'), topics = require('./../topics'), plugins = require('./../plugins'), categories = require('./../categories'), - categoryTools = require('./../categoryTools'); + privileges = require('../privileges'); @@ -69,12 +69,6 @@ Controllers.home = function(req, res, next) { return !category.disabled; }); - function canSee(category, next) { - categoryTools.privileges(category.cid, ((req.user) ? req.user.uid || 0 : 0), function(err, privileges) { - next(!err && privileges.meta.read); - }); - } - function getRecentReplies(category, callback) { categories.getRecentReplies(category.cid, uid, parseInt(category.numRecentReplies, 10), function (err, posts) { category.posts = posts; @@ -83,7 +77,11 @@ Controllers.home = function(req, res, next) { }); } - async.filter(data.categories, canSee, function(visibleCategories) { + async.filter(data.categories, function (category, next) { + privileges.categories.canRead(category.cid, uid, function(err, canRead) { + next(!err && canRead); + }); + }, function(visibleCategories) { data.categories = visibleCategories; async.each(data.categories, getRecentReplies, function (err) { diff --git a/src/controllers/topics.js b/src/controllers/topics.js index b16541afc7..d407c07168 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -10,27 +10,27 @@ var topicsController = {}, meta = require('./../meta'), topics = require('./../topics'), posts = require('../posts'), - threadTools = require('./../threadTools'), + privileges = require('../privileges'), utils = require('./../../public/src/utils'); topicsController.get = function(req, res, next) { var tid = req.params.topic_id, page = req.query.page || 1, uid = req.user ? req.user.uid : 0, - privileges; + userPrivileges; async.waterfall([ function(next) { - threadTools.privileges(tid, uid, function(err, userPrivileges) { + privileges.topics.get(tid, uid, function(err, privileges) { if (err) { return next(err); } - if (!userPrivileges.meta.read) { + if (!privileges.meta.read) { return next(new Error('[[error:no-privileges]]')); } - privileges = userPrivileges; + userPrivileges = privileges; next(); }); }, @@ -45,7 +45,7 @@ topicsController.get = function(req, res, next) { topics.getTopicWithPosts(tid, uid, start, end, function (err, topicData) { if (topicData) { - if (parseInt(topicData.deleted, 10) === 1 && parseInt(topicData.expose_tools, 10) === 0) { + if (parseInt(topicData.deleted, 10) === 1 && !userPrivileges.view_deleted) { return next(new Error('[[error:no-topic]]')); } topicData.currentPage = page; @@ -147,14 +147,14 @@ topicsController.get = function(req, res, next) { } ], function (err, data) { if (err) { - if (err.message === 'not-enough-privileges') { + if (err.message === '[[error:no-privileges]]') { return res.locals.isAPI ? res.json(403, err.message) : res.redirect('403'); } else { return res.locals.isAPI ? res.json(404, 'not-found') : res.redirect('404'); } } - data.privileges = privileges; + data.privileges = userPrivileges; var topic_url = tid + (req.params.slug ? '/' + req.params.slug : ''); var queryString = qs.stringify(req.query); diff --git a/src/privileges.js b/src/privileges.js index 275db8b724..e93584b727 100644 --- a/src/privileges.js +++ b/src/privileges.js @@ -2,7 +2,8 @@ var privileges = {}; +require('./privileges/categories')(privileges); +require('./privileges/topics')(privileges); require('./privileges/posts')(privileges); - module.exports = privileges; \ No newline at end of file diff --git a/src/privileges/categories.js b/src/privileges/categories.js new file mode 100644 index 0000000000..af2f50cdaf --- /dev/null +++ b/src/privileges/categories.js @@ -0,0 +1,118 @@ + +'use strict'; + +var async = require('async'), + + user = require('../user'), + groups = require('../groups'), + helpers = require('./helpers'); + + +module.exports = function(privileges) { + + privileges.categories = {}; + + privileges.categories.get = function(cid, uid, callback) { + async.parallel({ + 'topics:create': function(next) { + helpers.allowedTo('topics:create', uid, cid, next); + }, + read: function(next) { + helpers.allowedTo('read', uid, cid, next); + }, + isAdministrator: function(next) { + user.isAdministrator(uid, next); + }, + isModerator: function(next) { + user.isModerator(uid, cid, next); + } + }, function(err, results) { + if(err) { + return callback(err); + } + + var editable = results.isAdministrator || results.isModerator; + + callback(null, { + meta: { + 'topics:create': results['topics:create'], + editable: editable, + view_deleted: editable, + read: results.read + } + }); + }); + }; + + privileges.categories.canRead = function(cid, uid, callback) { + helpers.some([ + function(next) { + helpers.allowedTo('read', uid, cid, next); + }, + function(next) { + user.isModerator(uid, cid, next); + }, + function(next) { + user.isAdministrator(uid, next); + } + ], callback); + }; + + privileges.categories.canMoveAllTopics = function(currentCid, targetCid, uid, callback) { + async.parallel({ + isAdministrator: function(next) { + user.isAdministrator(uid, next); + }, + moderatorOfCurrent: function(next) { + user.isModerator(uid, currentCid, next); + }, + moderatorOfTarget: function(next) { + user.isModerator(uid, targetCid, next); + } + }, function(err, results) { + if (err) { + return callback(err); + } + + callback(null, results.isAdministrator || (results.moderatorOfCurrent && results.moderatorOfTarget)); + }); + }; + + privileges.categories.userPrivileges = function(cid, uid, callback) { + async.parallel({ + read: function(next) { + helpers.isMember(groups.isMember, 'cid:' + cid + ':privileges:read', uid, next); + }, + 'topics:create': function(next) { + helpers.isMember(groups.isMember, 'cid:' + cid + ':privileges:topics:create', uid, next); + }, + 'topics:reply': function(next) { + helpers.isMember(groups.isMember, 'cid:' + cid + ':privileges:topics:reply', uid, next); + }, + mods: function(next) { + user.isModerator(uid, cid, next); + } + }, callback); + }; + + privileges.categories.groupPrivileges = function(cid, groupName, callback) { + async.parallel({ + 'groups:read': function(next) { + helpers.isMember(groups.isMember, 'cid:' + cid + ':privileges:groups:read', groupName, function(err, isMember){ + next(err, !!isMember); + }); + }, + 'groups:topics:create': function(next) { + helpers.isMember(groups.isMember, 'cid:' + cid + ':privileges:groups:topics:create', groupName, function(err, isMember){ + next(err, !!isMember); + }); + }, + 'groups:topics:reply': function(next) { + helpers.isMember(groups.isMember, 'cid:' + cid + ':privileges:groups:topics:reply', groupName, function(err, isMember){ + next(err, !!isMember); + }); + } + }, callback); + }; + +}; diff --git a/src/privileges/helpers.js b/src/privileges/helpers.js index f57fcc73fd..2bf2f49226 100644 --- a/src/privileges/helpers.js +++ b/src/privileges/helpers.js @@ -10,6 +10,16 @@ var async = require('async'), var helpers = {}; +helpers.some = function(tasks, callback) { + async.some(tasks, function(task, next) { + task(function(err, result) { + next(!err && result); + }); + }, function(result) { + callback(null, result); + }); +}; + helpers.allowedTo = function(privilege, uid, cid, callback) { categories.getCategoryField(cid, 'disabled', function(err, disabled) { if (err) { @@ -22,31 +32,32 @@ helpers.allowedTo = function(privilege, uid, cid, callback) { async.parallel({ hasUserPrivilege: function(next) { - hasPrivilege(groups.isMember, 'cid:' + cid + ':privileges:' + privilege, uid, next); + helpers.isMember(groups.isMember, 'cid:' + cid + ':privileges:' + privilege, uid, next); }, hasGroupPrivilege: function(next) { - hasPrivilege(groups.isMemberOfGroupList, 'cid:' + cid + ':privileges:groups:' + privilege, uid, next); + helpers.isMember(groups.isMemberOfGroupList, 'cid:' + cid + ':privileges:groups:' + privilege, uid, next); }, }, function(err, results) { if (err) { return callback(err); } - callback(null, results.hasUserPrivilege && results.hasGroupPrivilege); + + callback(null, (results.hasUserPrivilege === null && results.hasGroupPrivilege === null) || results.hasUserPrivilege || results.hasGroupPrivilege); }); }); }; -function hasPrivilege(method, group, uid, callback) { +helpers.isMember = function(method, group, uid, callback) { groups.exists(group, function(err, exists) { if (err) { return callback(err); } if (!exists) { - return callback(null, true); + return callback(null, null); } - method(group, uid, callback); + method(uid, group, callback); }); } diff --git a/src/privileges/posts.js b/src/privileges/posts.js index 8894dd1509..d3501f218c 100644 --- a/src/privileges/posts.js +++ b/src/privileges/posts.js @@ -59,28 +59,12 @@ module.exports = function(privileges) { return callback(err); } - async.some([ - function(next) { - helpers.allowedTo('read', uid, cid, next); - }, - function(next) { - user.isModerator(uid, cid, next); - }, - function(next) { - user.isAdministrator(uid, next); - } - ], function(task, next) { - task(function(err, result) { - next(!err && result); - }); - }, function(result) { - callback(null, result); - }); + privileges.categories.canRead(cid, uid, callback); }); }; privileges.posts.canEdit = function(pid, uid, callback) { - async.some([ + helpers.some([ function(next) { posts.isOwner(pid, uid, next); }, @@ -101,12 +85,22 @@ module.exports = function(privileges) { function(next) { user.isAdministrator(uid, next); } - ], function(task, next) { - task(function(err, result) { - next(!err && result); - }); - }, function(result) { - callback(null, result); - }); + ], callback); + }; + + privileges.posts.canMove = function(pid, uid, callback) { + helpers.some([ + function(next) { + posts.getCidByPid(pid, function(err, cid) { + if (err) { + return next(err); + } + user.isModerator(uid, cid, next); + }); + }, + function(next) { + user.isAdministrator(uid, next); + } + ], callback); }; }; diff --git a/src/privileges/topics.js b/src/privileges/topics.js new file mode 100644 index 0000000000..7c82452f93 --- /dev/null +++ b/src/privileges/topics.js @@ -0,0 +1,136 @@ + +'use strict'; + +var async = require('async'), + + topics = require('../topics'), + user = require('../user'), + helpers = require('./helpers'), + groups = require('../groups'), + categories = require('../categories'); + +module.exports = function(privileges) { + + privileges.topics = {}; + + privileges.topics.get = function(tid, uid, callback) { + + topics.getTopicField(tid, 'cid', function(err, cid) { + if (err) { + return callback(err); + } + + async.parallel({ + 'topics:reply': function(next) { + helpers.allowedTo('topics:reply', uid, cid, next); + }, + read: function(next) { + helpers.allowedTo('read', uid, cid, next); + }, + manage_topic: function(next) { + helpers.hasEnoughReputationFor('privileges:manage_topic', uid, next); + }, + isAdministrator: function(next) { + user.isAdministrator(uid, next); + }, + isModerator: function(next) { + user.isModerator(uid, cid, next); + } + }, function(err, results) { + if(err) { + return callback(err); + } + + var editable = results.isAdministrator || results.isModerator || results.manage_topic; + + callback(null, { + meta: { + 'topics:reply': results['topics:reply'], + editable: editable, + view_deleted: editable, + read: results.read + } + }); + }); + }); + }; + + privileges.topics.canRead = function(tid, uid, callback) { + topics.getTopicField(tid, 'cid', function(err, cid) { + if (err) { + return callback(err); + } + + privileges.categories.canRead(cid, uid, callback); + }); + }; + + privileges.topics.canCreate = function(cid, uid, callback) { + helpers.some([ + function(next) { + helpers.allowedTo('topics:create', uid, cid, next); + }, + function(next) { + user.isModerator(uid, cid, next); + }, + function(next) { + user.isAdministrator(uid, next); + } + ], callback); + }; + + privileges.topics.canReply = function(tid, uid, callback) { + topics.getTopicField(tid, 'cid', function(err, cid) { + if (err) { + return callback(err); + } + + helpers.some([ + function(next) { + helpers.allowedTo('topics:reply', uid, cid, next); + }, + function(next) { + user.isModerator(uid, cid, next); + }, + function(next) { + user.isAdministrator(uid, next); + } + ], callback); + }); + }; + + privileges.topics.canEdit = function(tid, uid, callback) { + helpers.some([ + function(next) { + helpers.hasEnoughReputationFor('privileges:manage_topic', uid, next); + }, + function(next) { + topics.getTopicField(tid, 'cid', function(err, cid) { + if (err) { + return next(err); + } + user.isModerator(uid, cid, next); + }); + }, + function(next) { + user.isAdministrator(uid, next); + } + ], callback); + }; + + privileges.topics.canMove = function(tid, uid, callback) { + helpers.some([ + function(next) { + topics.getTopicField(tid, 'cid', function(err, cid) { + if (err) { + return next(err); + } + user.isModerator(uid, cid, next); + }); + }, + function(next) { + user.isAdministrator(uid, next); + } + ], callback); + }; +}; diff --git a/src/routes/debug.js b/src/routes/debug.js index 6d6641615f..2534576d20 100644 --- a/src/routes/debug.js +++ b/src/routes/debug.js @@ -54,11 +54,10 @@ module.exports = function(app, middleware, controllers) { }); app.get('/test', function(req, res) { - var tools = require('../categoryTools'); - tools.privileges(1, 2, function() { - console.log(arguments); - }) - res.send(200); + var privileges = require('../privileges'); + privileges.topics.get(1299, 1, function(err, result) { + res.json(result); + }); }); }); }; diff --git a/src/routes/feeds.js b/src/routes/feeds.js index b42db1b3b3..3b1fc298ef 100644 --- a/src/routes/feeds.js +++ b/src/routes/feeds.js @@ -7,30 +7,29 @@ var posts = require('./../posts'), rss = require('rss'), nconf = require('nconf'), - ThreadTools = require('./../threadTools'), - CategoryTools = require('./../categoryTools'); + privileges = require('../privileges'); function hasTopicPrivileges(req, res, next) { var tid = req.params.topic_id; - hasPrivileges(ThreadTools, tid, req, res, next); + hasPrivileges(privileges.topics.canRead, tid, req, res, next); } function hasCategoryPrivileges(req, res, next) { var cid = req.params.category_id; - hasPrivileges(CategoryTools, cid, req, res, next); + hasPrivileges(privileges.categories.canRead, cid, req, res, next); } -function hasPrivileges(module, id, req, res, next) { +function hasPrivileges(method, id, req, res, next) { var uid = req.user ? req.user.uid || 0 : 0; - module.privileges(id, uid, function(err, privileges) { - if(err) { + method(id, uid, function(err, canRead) { + if (err) { return next(err); } - if(!privileges.read) { + if (!canRead) { return res.redirect('403'); } @@ -66,7 +65,7 @@ function generateForTopic(req, res, next) { } topicData.posts.forEach(function(postData) { - if (parseInt(postData.deleted, 10) === 0) { + if (!postData.deleted) { dateStamp = new Date(parseInt(parseInt(postData.edited, 10) === 0 ? postData.timestamp : postData.edited, 10)).toUTCString(); feed.item({ diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js index 9a03319baa..aa653d516f 100644 --- a/src/socket.io/admin.js +++ b/src/socket.io/admin.js @@ -7,7 +7,6 @@ var groups = require('../groups'), user = require('../user'), topics = require('../topics'), categories = require('../categories'), - CategoryTools = require('../categoryTools'), logger = require('../logger'), events = require('../events'), db = require('../database'), diff --git a/src/socket.io/admin/categories.js b/src/socket.io/admin/categories.js index dba2efca97..4c70599e14 100644 --- a/src/socket.io/admin/categories.js +++ b/src/socket.io/admin/categories.js @@ -3,7 +3,7 @@ var groups = require('../../groups'), user = require('../../user'), categories = require('../../categories'), - categoryTools = require('../../categoryTools'), + privileges = require('../../privileges'), async = require('async'), Categories = {}; @@ -32,8 +32,12 @@ Categories.search = function(socket, data, callback) { cid = data.cid; user.search(username, function(err, data) { + if (err) { + return callback(err); + } + async.map(data.users, function(userObj, next) { - categoryTools.privileges(cid, userObj.uid, function(err, privileges) { + privileges.categories.userPrivileges(cid, userObj.uid, function(err, privileges) { if(err) { return next(err); } @@ -50,22 +54,7 @@ Categories.setPrivilege = function(socket, data, callback) { return callback(new Error('[[error:invalid-data]]')); } - var cid = data.cid, - uid = data.uid, - privilege = data.privilege, - set = data.set, - cb = function(err) { - if(err) { - return callback(err); - } - categoryTools.privileges(cid, uid, callback); - }; - - if (set) { - groups.join('cid:' + cid + ':privileges:' + privilege, uid, cb); - } else { - groups.leave('cid:' + cid + ':privileges:' + privilege, uid, cb); - } + groups[data.set ? 'join' : 'leave']('cid:' + data.cid + ':privileges:' + data.privilege, data.uid, callback); }; Categories.getPrivilegeSettings = function(socket, cid, callback) { @@ -119,7 +108,7 @@ Categories.groupsList = function(socket, cid, callback) { } async.map(data, function(groupObj, next) { - categoryTools.groupPrivileges(cid, groupObj.name, function(err, privileges) { + privileges.categories.groupPrivileges(cid, groupObj.name, function(err, privileges) { if(err) { return next(err); } diff --git a/src/socket.io/categories.js b/src/socket.io/categories.js index a3ca8abb37..117640793d 100644 --- a/src/socket.io/categories.js +++ b/src/socket.io/categories.js @@ -2,19 +2,19 @@ var async = require('async'), categories = require('../categories'), - categoryTools = require('../categoryTools'), + privileges = require('../privileges'), meta = require('./../meta'), user = require('./../user'), SocketCategories = {}; SocketCategories.getRecentReplies = function(socket, cid, callback) { - categoryTools.privileges(cid, socket.uid, function(err, privileges) { + privileges.categories.canRead(cid, socket.uid, function(err, canRead) { if (err) { return callback(err); } - if (!privileges || !privileges.meta.read) { + if (!canRead) { return callback(null, []); } @@ -33,7 +33,7 @@ SocketCategories.loadMore = function(socket, data, callback) { async.parallel({ privileges: function(next) { - categoryTools.privileges(data.cid, socket.uid, next); + privileges.categories.get(data.cid, socket.uid, next); }, settings: function(next) { user.getSettings(socket.uid, next); diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index 278a1ab2d3..1d240140f6 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -37,8 +37,15 @@ SocketPosts.reply = function(socket, data, callback) { } if (postData) { + var privileges = { + meta : { + 'topics:reply': true + } + }; + websockets.server.sockets.emit('event:new_post', { - posts: [postData] + posts: [postData], + privileges: privileges }); module.parent.exports.emitTopicPostStats(); diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index 7971635410..d0cf8e716e 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -3,8 +3,8 @@ var topics = require('../topics'), categories = require('../categories'), + privileges = require('../privileges'), threadTools = require('../threadTools'), - categoryTools = require('../categoryTools'), websockets = require('./index'), user = require('../user'), db = require('./../database'), @@ -181,12 +181,12 @@ function doTopicAction(action, socket, tids, callback) { } async.each(tids, function(tid, next) { - threadTools.privileges(tid, socket.uid, function(err, privileges) { + privileges.topics.canEdit(tid, socket.uid, function(err, canEdit) { if(err) { return next(err); } - if(!privileges || !privileges.meta.editable) { + if(!canEdit) { return next(new Error('[[error:no-privileges]]')); } @@ -210,21 +210,17 @@ SocketTopics.createTopicFromPosts = function(socket, data, callback) { }; SocketTopics.movePost = function(socket, data, callback) { - if(!socket.uid) { + if (!socket.uid) { return callback(new Error('[[error:not-logged-in]]')); } - if(!data || !data.pid || !data.tid) { + if (!data || !data.pid || !data.tid) { return callback(new Error('[[error:invalid-data]]')); } - threadTools.privileges(data.tid, socket.uid, function(err, privileges) { - if(err) { - return callback(err); - } - - if(!(privileges.admin || privileges.moderator)) { - return callback(new Error('[[error:no-privileges]]')); + privileges.posts.canMove(data.tid, socket.uid, function(err, canMove) { + if (err || !canMove) { + return callback(err || new Error('[[error:no-privileges]]')); } topics.movePostToTopic(data.pid, data.tid, callback); @@ -240,10 +236,10 @@ SocketTopics.move = function(socket, data, callback) { var oldCid; async.waterfall([ function(next) { - threadTools.privileges(tid, socket.uid, next); + privileges.topics.canMove(tid, socket.uid, next); }, - function(privileges, next) { - if(!(privileges.admin || privileges.moderator)) { + function(canMove, next) { + if (!canMove) { return next(new Error('[[error:no-privileges]]')); } next(); @@ -278,20 +274,9 @@ SocketTopics.moveAll = function(socket, data, callback) { return callback(new Error('[[error:invalid-data]]')); } - async.parallel({ - from: function(next) { - categoryTools.privileges(data.currentCid, socket.uid, next); - }, - to: function(next) { - categoryTools.privileges(data.cid, socket.uid, next); - } - }, function(err, results) { - if (err) { - return callback(err); - } - - if (!results.from.admin && (!results.from.mods || !results.to.mods)) { - return callback(new Error('[[error:no-privileges]]')); + privileges.categories.canMoveAllTopics(data.currentCid, data.cid, data.uid, function(err, canMove) { + if (err || canMove) { + return callback(err || new Error('[[error:no-privileges]]')); } categories.getTopicIds(data.currentCid, 0, -1, function(err, tids) { @@ -336,7 +321,7 @@ SocketTopics.loadMore = function(socket, data, callback) { topics.getTopicPosts(data.tid, start, end, socket.uid, false, next); }, privileges: function(next) { - threadTools.privileges(data.tid, socket.uid, next); + privileges.topics.get(data.tid, socket.uid, next); } }, callback); }); diff --git a/src/threadTools.js b/src/threadTools.js index 72d5a5d13e..c476e461f5 100644 --- a/src/threadTools.js +++ b/src/threadTools.js @@ -7,7 +7,6 @@ var winston = require('winston'), db = require('./database'), topics = require('./topics'), categories = require('./categories'), - CategoryTools = require('./categoryTools'), user = require('./user'), notifications = require('./notifications'), posts = require('./posts'), @@ -23,38 +22,6 @@ var winston = require('winston'), db.isSortedSetMember('topics:tid', tid, callback); }; - ThreadTools.privileges = function(tid, uid, callback) { - async.parallel({ - categoryPrivs: function(next) { - topics.getTopicField(tid, 'cid', function(err, cid) { - CategoryTools.privileges(cid, uid, next); - }); - }, - hasEnoughRep: function(next) { - if (parseInt(meta.config['privileges:disabled'], 10)) { - return next(null, false); - } else { - user.getUserField(uid, 'reputation', function(err, reputation) { - if (err) { - return next(null, false); - } - next(null, parseInt(reputation, 10) >= parseInt(meta.config['privileges:manage_topic'], 10)); - }); - } - } - }, function(err, results) { - if (err) { - return callback(err); - } - - var privileges = results.categoryPrivs; - privileges.meta.editable = privileges.meta.editable || results.hasEnoughRep; - privileges.meta.view_deleted = privileges.meta.view_deleted || results.hasEnoughRep; - - callback(null, privileges); - }); - }; - ThreadTools.delete = function(tid, uid, callback) { toggleDelete(tid, uid, true, callback); }; diff --git a/src/topics.js b/src/topics.js index 6f31c817ca..0763a13a0c 100644 --- a/src/topics.js +++ b/src/topics.js @@ -10,8 +10,7 @@ var async = require('async'), plugins = require('./plugins'), user = require('./user'), categories = require('./categories'), - categoryTools = require('./categoryTools'), - threadTools = require('./threadTools'); + privileges = require('./privileges'); (function(Topics) { @@ -22,7 +21,6 @@ var async = require('async'), require('./topics/posts')(Topics); require('./topics/follow')(Topics); - Topics.getTopicData = function(tid, callback) { Topics.getTopicsData([tid], function(err, topics) { if (err) { @@ -132,8 +130,8 @@ var async = require('async'), } async.filter(tids, function(tid, next) { - threadTools.privileges(tid, uid, function(err, privileges) { - next(!err && privileges.meta.read); + privileges.topics.canRead(tid, uid, function(err, canRead) { + next(!err && canRead); }); }, function(tids) { Topics.getTopicsByTids(tids, uid, function(err, topicData) { @@ -199,7 +197,7 @@ var async = require('async'), if (privilegeCache[topicData.cid]) { return next(null, privilegeCache[topicData.cid]); } - categoryTools.privileges(topicData.cid, uid, next); + privileges.categories.get(topicData.cid, uid, next); }, categoryData: function(next) { if (categoryCache[topicData.cid]) { @@ -269,9 +267,6 @@ var async = require('async'), posts: function(next) { Topics.getTopicPosts(tid, start, end, uid, false, next); }, - privileges: function(next) { - threadTools.privileges(tid, uid, next); - }, category: function(next) { Topics.getCategoryData(tid, next); }, @@ -291,7 +286,6 @@ var async = require('async'), topicData.thread_tools = results.threadTools; topicData.pageCount = results.pageCount; topicData.unreplied = parseInt(topicData.postcount, 10) === 1; - topicData.expose_tools = results.privileges.meta.editable ? 1 : 0; callback(null, topicData); }); diff --git a/src/topics/create.js b/src/topics/create.js index 454ea14d28..e47216de08 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -2,14 +2,15 @@ 'use strict'; var async = require('async'), - db = require('./../database'), - utils = require('./../../public/src/utils'), - plugins = require('./../plugins'), - user = require('./../user'), - meta = require('./../meta'), - posts = require('./../posts'), - threadTools = require('./../threadTools'), - categoryTools = require('./../categoryTools'); + db = require('../database'), + utils = require('../../public/src/utils'), + plugins = require('../plugins'), + user = require('../user'), + meta = require('../meta'), + posts = require('../posts'), + threadTools = require('../threadTools'), + privileges = require('../privileges'), + categories = require('../categories'); module.exports = function(Topics) { @@ -92,16 +93,16 @@ module.exports = function(Topics) { }); }, function(next) { - categoryTools.exists(cid, next); + categories.exists(cid, next); }, function(categoryExists, next) { if (!categoryExists) { return next(new Error('[[error:no-category]]')); } - categoryTools.privileges(cid, uid, next); + privileges.topics.canCreate(cid, uid, next); }, - function(privileges, next) { - if(!privileges.meta['topics:create']) { + function(canCreate, next) { + if(!canCreate) { return next(new Error('[[error:no-privileges]]')); } next(); @@ -144,7 +145,6 @@ module.exports = function(Topics) { uid = data.uid, toPid = data.toPid, content = data.content, - privileges, postData; async.waterfall([ @@ -173,11 +173,10 @@ module.exports = function(Topics) { return next(new Error('[[error:topic-locked]]')); } - threadTools.privileges(tid, uid, next); + privileges.topics.canReply(tid, uid, next); }, - function(privilegesData, next) { - privileges = privilegesData; - if (!privileges.meta['topics:reply']) { + function(canReply, next) { + if (!canReply) { return next(new Error('[[error:no-privileges]]')); } next(); @@ -232,7 +231,8 @@ module.exports = function(Topics) { postData.favourited = false; postData.votes = 0; postData.display_moderator_tools = true; - postData.display_move_tools = privileges.admin || privileges.moderator; + postData.display_move_tools = true; + postData.selfPost = false; postData.relativeTime = utils.toISOString(postData.timestamp); next(null, postData); diff --git a/src/topics/unread.js b/src/topics/unread.js index 375d921921..4f153cf227 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -8,7 +8,7 @@ var async = require('async'), user = require('./../user'), notifications = require('./../notifications'), categories = require('./../categories'), - threadTools = require('./../threadTools'); + privileges = require('../privileges'); module.exports = function(Topics) { @@ -49,8 +49,8 @@ module.exports = function(Topics) { }); async.filter(newtids, function(tid, next) { - threadTools.privileges(tid, uid, function(err, privileges) { - next(!err && privileges.meta.read); + privileges.topics.canRead(tid, uid, function(err, canRead) { + next(!err && canRead); }); }, function(newtids) { unreadTids.push.apply(unreadTids, newtids);