From 3de22a82bf1c3c0a3b593be4075cf42a3ec9291e Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Fri, 27 Oct 2017 08:04:44 -0700 Subject: Refactor initial state: reduce_motion and auto_play_gif (#5501) --- app/views/layouts/application.html.haml | 1 - 1 file changed, 1 deletion(-) (limited to 'app/views') diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 831858bcf..ee995c987 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -27,7 +27,6 @@ = yield :header_tags - body_classes ||= @body_classes || '' - - body_classes += ' reduce-motion' if current_account&.user&.setting_reduce_motion - body_classes += ' system-font' if current_account&.user&.setting_system_font_ui %body{ class: add_rtl_body_class(body_classes) } -- cgit From 29609fbb6a2bdfb8937077fac9f1aa280f730633 Mon Sep 17 00:00:00 2001 From: SerCom_KC Date: Mon, 30 Oct 2017 11:34:58 +0800 Subject: Updating Chinese (Simplified) translations (#5508) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * i18n: (zh-CN) fix punctuations and spaces Spaces are fixed according to https://github.com/sparanoid/chinese-copywriting-guidelines * i18n: (zh-CN) fix punctuation * i18n: (zh-CN) Adapt official translation of Discourse Privacy Policy from GitHub, with minor fixes https://github.com/discourse/discourse/blob/master/config/locales/server.zh_CN.yml#L2677 * i18n: (zh-CN) Update missing translations * i18n: (zh-CN) Fixing errors * i18n: (zh-CN) Fix indent error * i18n: (zh-CN) Fix language tag * i18n: (zh-CN) Remove quotes * i18n: (zh-CN) Update translation (#5485) * i18n: (zh-CN) Remove whitespaces, x -> × * i18n: (zh-CN) Rewording on time distance * i18n: (zh-CN) Overall improvements * i18n: (zh-CN) i18n-tasks normalization * i18n: (zh-CN) Add missing translation --- app/javascript/mastodon/locales/zh-CN.json | 208 ++++---- .../confirmation_instructions.zh-cn.html.erb | 13 +- .../confirmation_instructions.zh-cn.text.erb | 12 +- .../user_mailer/password_change.zh-cn.html.erb | 4 +- .../user_mailer/password_change.zh-cn.text.erb | 4 +- .../reset_password_instructions.zh-cn.html.erb | 9 +- .../reset_password_instructions.zh-cn.text.erb | 9 +- config/locales/activerecord.zh-CN.yml | 13 + config/locales/simple_form.zh-CN.yml | 59 ++- config/locales/zh-CN.yml | 560 ++++++++++++++++----- 10 files changed, 602 insertions(+), 289 deletions(-) create mode 100644 config/locales/activerecord.zh-CN.yml (limited to 'app/views') diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 827c815cf..f0f23959a 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -1,25 +1,25 @@ { "account.block": "屏蔽 @{name}", "account.block_domain": "隐藏一切来自 {domain} 的嘟文", - "account.disclaimer_full": "下列资料不一定完整。", + "account.disclaimer_full": "此处显示的信息可能不是全部内容。", "account.edit_profile": "修改个人资料", "account.follow": "关注", "account.followers": "关注者", - "account.follows": "正关注", - "account.follows_you": "关注你", + "account.follows": "正在关注", + "account.follows_you": "关注了你", "account.media": "媒体", "account.mention": "提及 @{name}", - "account.mute": "将 @{name} 静音", + "account.mute": "静音 @{name}", "account.posts": "嘟文", "account.report": "举报 @{name}", - "account.requested": "等待审批", - "account.share": "分享 @{name}的个人资料", - "account.unblock": "解除对 @{name} 的屏蔽", + "account.requested": "正在等待对方同意。点击以取消发送关注请求", + "account.share": "分享 @{name} 的个人资料", + "account.unblock": "不再屏蔽 @{name}", "account.unblock_domain": "不再隐藏 {domain}", "account.unfollow": "取消关注", - "account.unmute": "取消 @{name} 的静音", + "account.unmute": "不再静音 @{name}", "account.view_full_profile": "查看完整资料", - "boost_modal.combo": "如你想在下次路过时显示,请按{combo},", + "boost_modal.combo": "下次按住 {combo} 即可跳过此提示", "bundle_column_error.body": "载入组件出错。", "bundle_column_error.retry": "重试", "bundle_column_error.title": "网络错误", @@ -37,72 +37,72 @@ "column.public": "跨站公共时间轴", "column_back_button.label": "返回", "column_header.hide_settings": "隐藏设置", - "column_header.moveLeft_settings": "将栏左移", - "column_header.moveRight_settings": "将栏右移", + "column_header.moveLeft_settings": "将此栏左移", + "column_header.moveRight_settings": "将此栏右移", "column_header.pin": "固定", "column_header.show_settings": "显示设置", - "column_header.unpin": "取下", + "column_header.unpin": "取消固定", "column_subheading.navigation": "导航", "column_subheading.settings": "设置", - "compose_form.lock_disclaimer": "你的帐户没 {locked}. 任何人可以通过关注你来查看只有关注者可见的嘟文.", + "compose_form.lock_disclaimer": "你的帐户没有{locked}。任何人都可以通过关注你来查看仅关注者可见的嘟文。", "compose_form.lock_disclaimer.lock": "被保护", "compose_form.placeholder": "在想啥?", "compose_form.publish": "嘟嘟", "compose_form.publish_loud": "{publish}!", - "compose_form.sensitive": "将媒体文件标示为“敏感内容”", - "compose_form.spoiler": "将部分文本藏于警告消息之后", - "compose_form.spoiler_placeholder": "敏感内容的警告消息", + "compose_form.sensitive": "将媒体文件标记为“敏感内容”", + "compose_form.spoiler": "将部分文字隐藏于警告消息之后", + "compose_form.spoiler_placeholder": "隐藏文字的警告消息", "confirmation_modal.cancel": "取消", "confirmations.block.confirm": "屏蔽", - "confirmations.block.message": "想好了,真的要屏蔽 {name}?", + "confirmations.block.message": "想好了,真的要屏蔽 {name}?", "confirmations.delete.confirm": "删除", - "confirmations.delete.message": "想好了,真的要删除这条嘟文?", + "confirmations.delete.message": "想好了,真的要删除这条嘟文?", "confirmations.domain_block.confirm": "隐藏整个网站", - "confirmations.domain_block.message": "你真的真的确定要隐藏整个 {domain} ?多数情况下,封锁或静音几个特定目标就好。", + "confirmations.domain_block.message": "你真的真的确定要隐藏整个 {domain}?多数情况下,屏蔽或静音几个特定的用户就应该能满足你的需要了。", "confirmations.mute.confirm": "静音", - "confirmations.mute.message": "想好了,真的要静音 {name}?", + "confirmations.mute.message": "想好了,真的要静音 {name}?", "confirmations.unfollow.confirm": "取消关注", - "confirmations.unfollow.message": "确定要取消关注 {name}吗?", - "embed.instructions": "要内嵌此嘟文,请将以下代码贴进你的网站。", - "embed.preview": "到时大概长这样:", + "confirmations.unfollow.message": "确定要取消关注 {name} 吗?", + "embed.instructions": "要在你的网站上嵌入这条嘟文,请复制以下代码。", + "embed.preview": "它会像这样显示出来:", "emoji_button.activity": "活动", - "emoji_button.custom": "Custom", + "emoji_button.custom": "自定义", "emoji_button.flags": "旗帜", "emoji_button.food": "食物和饮料", "emoji_button.label": "加入表情符号", "emoji_button.nature": "自然", - "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻", + "emoji_button.not_found": "木有这个表情符号!(╯°□°)╯︵ ┻━┻", "emoji_button.objects": "物体", "emoji_button.people": "人物", - "emoji_button.recent": "Frequently used", + "emoji_button.recent": "常用", "emoji_button.search": "搜索…", - "emoji_button.search_results": "Search results", + "emoji_button.search_results": "搜索结果", "emoji_button.symbols": "符号", - "emoji_button.travel": "旅途和地点", - "empty_column.community": "本站时间轴暂时未有内容,快嘟几个来抢头香啊!", - "empty_column.hashtag": "这个标签暂时未有内容。", + "emoji_button.travel": "旅行和地点", + "empty_column.community": "本站时间轴暂时没有内容,快嘟几个来抢头香啊!", + "empty_column.hashtag": "这个话题标签下暂时没有内容。", "empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。", "empty_column.home.public_timeline": "公共时间轴", - "empty_column.notifications": "你没有任何通知纪录,快向其他用户搭讪吧。", - "empty_column.public": "跨站公共时间轴暂时没有内容!快写一些公共的嘟文,或者关注另一些服务器实例的用户吧!你和本站、友站的交流,将决定这里出现的内容。", - "follow_request.authorize": "批准", + "empty_column.notifications": "你还没有收到过通知信息,快向其他用户搭讪吧。", + "empty_column.public": "这里神马都没有!写一些公开的嘟文,或者关注其他实例的用户,这里就会有嘟文出现了哦!", + "follow_request.authorize": "同意", "follow_request.reject": "拒绝", - "getting_started.appsshort": "Apps", - "getting_started.faq": "FAQ", + "getting_started.appsshort": "应用", + "getting_started.faq": "常见问题", "getting_started.heading": "开始使用", - "getting_started.open_source_notice": "Mastodon 是一个开放源码的软件。你可以在官方 GitHub ({github}) 贡献或者回报问题。", + "getting_started.open_source_notice": "Mastodon 是一个开放源码的软件。你可以在官方 GitHub({github})贡献或者回报问题。", "getting_started.userguide": "用户指南", - "home.column_settings.advanced": "高端", - "home.column_settings.basic": "基本", - "home.column_settings.filter_regex": "使用正则表达式 (regex) 过滤", - "home.column_settings.show_reblogs": "显示被转的嘟文", - "home.column_settings.show_replies": "显示回应嘟文", - "home.settings": "字段设置", + "home.column_settings.advanced": "高级设置", + "home.column_settings.basic": "基本设置", + "home.column_settings.filter_regex": "使用正则表达式(regex)过滤", + "home.column_settings.show_reblogs": "显示转嘟", + "home.column_settings.show_replies": "显示回复", + "home.settings": "栏目设置", "lightbox.close": "关闭", "lightbox.next": "下一步", "lightbox.previous": "上一步", "loading_indicator.label": "加载中……", - "media_gallery.toggle_visible": "打开或关上", + "media_gallery.toggle_visible": "切换显示/隐藏", "missing_indicator.label": "找不到内容", "navigation_bar.blocks": "被屏蔽的用户", "navigation_bar.community_timeline": "本站时间轴", @@ -119,9 +119,9 @@ "notification.follow": "{name} 开始关注你", "notification.mention": "{name} 提及你", "notification.reblog": "{name} 转嘟了你的嘟文", - "notifications.clear": "清空通知纪录", - "notifications.clear_confirmation": "你确定要清空通知纪录吗?", - "notifications.column_settings.alert": "显示桌面通知", + "notifications.clear": "清空通知列表", + "notifications.clear_confirmation": "你确定要清空通知列表吗?", + "notifications.column_settings.alert": "桌面通知", "notifications.column_settings.favourite": "你的嘟文被收藏:", "notifications.column_settings.follow": "关注你:", "notifications.column_settings.mention": "提及你:", @@ -132,90 +132,90 @@ "notifications.column_settings.sound": "播放音效", "onboarding.done": "出发!", "onboarding.next": "下一步", - "onboarding.page_five.public_timelines": "本站时间轴显示来自 {domain} 的所有人的公共嘟文。 跨站公共时间轴显示 {domain} 上的各位关注的来自所有Mastodon服务器实例上的人发表的公共嘟文。这些就是寻人好去处的公共时间轴啦。", - "onboarding.page_four.home": "你的主时间轴上是你关注的用户的嘟文.", - "onboarding.page_four.notifications": "如果你和他人产生了互动,便会出现在通知列上啦~", - "onboarding.page_one.federation": "Mastodon是由一系列独立的服务器共同打造的强大的社交网络,我们将这些独立但又相互连接的服务器叫做服务器实例。", - "onboarding.page_one.handle": "你在 {domain}, {handle} 就是你的完整帐户名称。", - "onboarding.page_one.welcome": "欢迎来到 Mastodon!", + "onboarding.page_five.public_timelines": "本站时间轴显示的是由本站({domain})用户发布的所有公开嘟文。跨站公共时间轴显示的的是由本站用户关注对象所发布的所有公开嘟文。这些就是寻人好去处的公共时间轴啦。", + "onboarding.page_four.home": "你的主页上的时间轴上显示的是你关注对象的嘟文。", + "onboarding.page_four.notifications": "如果有人与你互动,便会出现在通知栏中哦~", + "onboarding.page_one.federation": "Mastodon 是由一系列独立的服务器共同打造的强大的社交网络,我们将这些各自独立但又相互连接的服务器叫做实例。", + "onboarding.page_one.handle": "你在 {domain},{handle} 就是你的完整帐户名称。", + "onboarding.page_one.welcome": "欢迎来到 Mastodon!", "onboarding.page_six.admin": "{admin} 是你所在服务器实例的管理员.", - "onboarding.page_six.almost_done": "差不多了…", - "onboarding.page_six.appetoot": "嗷呜~", - "onboarding.page_six.apps_available": "也有适用于 iOS, Android 和其它平台的 {apps} 咯~", - "onboarding.page_six.github": "Mastodon 是自由的开放源代码软件。欢迎来 {github} 报告问题,提交功能请求,或者贡献代码 :-)", + "onboarding.page_six.almost_done": "差不多了……", + "onboarding.page_six.appetoot": "嗷呜~", + "onboarding.page_six.apps_available": "我们还有适用于 iOS、Android 和其它平台的{apps}哦~", + "onboarding.page_six.github": "Mastodon 是自由的开源软件。欢迎前往 {github} 反馈问题、提出对新功能的建议或贡献代码 :-)", "onboarding.page_six.guidelines": "社区指南", - "onboarding.page_six.read_guidelines": "别忘了看看 {domain} 的 {guidelines}!", - "onboarding.page_six.various_app": "移动应用程序", - "onboarding.page_three.profile": "修改你的个人资料,比如头像、简介、和昵称等等。在那还可以找到其它首选项。", - "onboarding.page_three.search": "用搜索来找人和标签吧,比如 {illustration} 或者 {introductions}。想找其它服务器实例上的人,用完整帐户名称(用户名@域名)啦。", - "onboarding.page_two.compose": "从这里开始嘟!上面的按钮提供了上传图片,修改隐私设置和提示敏感内容等多种功能。.", - "onboarding.skip": "好啦好啦我知道啦", - "privacy.change": "调整隐私设置", - "privacy.direct.long": "只有提及的用户能看到", - "privacy.direct.short": "私人消息", - "privacy.private.long": "只有关注你用户能看到", - "privacy.private.short": "关注者", - "privacy.public.long": "在公共时间轴显示", - "privacy.public.short": "公共", - "privacy.unlisted.long": "公开,但不在公共时间轴显示", - "privacy.unlisted.short": "公开", - "relative_time.days": "{number}d", - "relative_time.hours": "{number}h", - "relative_time.just_now": "now", - "relative_time.minutes": "{number}m", - "relative_time.seconds": "{number}s", + "onboarding.page_six.read_guidelines": "别忘了看看 {domain} 的{guidelines}!", + "onboarding.page_six.various_app": "移动设备应用", + "onboarding.page_three.profile": "你可以修改你的个人资料,比如头像、简介和昵称等偏好设置。", + "onboarding.page_three.search": "你可以通过搜索功能寻找用户和话题标签,比如{illustration}或者{introductions}。如果你想搜索其他实例上的用户,就需要输入完整帐户名称(用户名@域名)哦。", + "onboarding.page_two.compose": "在撰写栏中开始嘟嘟吧!下方的按钮分别用来上传图片,修改嘟文可见范围,以及添加警告信息。", + "onboarding.skip": "跳过", + "privacy.change": "设置嘟文可见范围", + "privacy.direct.long": "只有被提及的用户能看到", + "privacy.direct.short": "私信", + "privacy.private.long": "只有关注你的用户能看到", + "privacy.private.short": "仅关注者", + "privacy.public.long": "所有人可见,并会出现在公共时间轴上", + "privacy.public.short": "公开", + "privacy.unlisted.long": "所有人可见,但不会出现在公共时间轴上", + "privacy.unlisted.short": "不公开", + "relative_time.days": "{number} 天", + "relative_time.hours": "{number} 时", + "relative_time.just_now": "刚刚", + "relative_time.minutes": "{number} 分", + "relative_time.seconds": "{number} 秒", "reply_indicator.cancel": "取消", - "report.placeholder": "额外消息", + "report.placeholder": "附言", "report.submit": "提交", - "report.target": "Reporting", + "report.target": "举报 {target}", "search.placeholder": "搜索", - "search_popout.search_format": "Advanced search format", - "search_popout.tips.hashtag": "hashtag", - "search_popout.tips.status": "status", - "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", - "search_popout.tips.user": "user", - "search_results.total": "{count, number} {count, plural, one {result} other {results}}", + "search_popout.search_format": "高级搜索格式", + "search_popout.tips.hashtag": "话题标签", + "search_popout.tips.status": "嘟文", + "search_popout.tips.text": "使用普通字符进行搜索将会返回昵称、用户名和话题标签", + "search_popout.tips.user": "用户", + "search_results.total": "共 {count, number} 个结果", "standalone.public_title": "大家都在干啥?", - "status.cannot_reblog": "没法转嘟这条嘟文啦……", + "status.cannot_reblog": "无法转嘟这条嘟文", "status.delete": "删除", "status.embed": "嵌入", "status.favourite": "收藏", "status.load_more": "加载更多", "status.media_hidden": "隐藏媒体内容", "status.mention": "提及 @{name}", - "status.more": "More", - "status.mute_conversation": "静音对话", + "status.more": "更多", + "status.mute_conversation": "静音此对话", "status.open": "展开嘟文", - "status.pin": "置顶到资料", + "status.pin": "在个人资料页面置顶", "status.reblog": "转嘟", - "status.reblogged_by": "{name} 转嘟", - "status.reply": "回应", - "status.replyAll": "回应整串", + "status.reblogged_by": "{name} 转嘟了", + "status.reply": "回复", + "status.replyAll": "回复所有人", "status.report": "举报 @{name}", "status.sensitive_toggle": "点击显示", "status.sensitive_warning": "敏感内容", - "status.share": "Share", - "status.show_less": "减少显示", - "status.show_more": "显示更多", - "status.unmute_conversation": "解禁对话", - "status.unpin": "解除置顶", + "status.share": "分享", + "status.show_less": "隐藏内容", + "status.show_more": "显示内容", + "status.unmute_conversation": "不再静音此对话", + "status.unpin": "在个人资料页面取消置顶", "tabs_bar.compose": "撰写", "tabs_bar.federated_timeline": "跨站", "tabs_bar.home": "主页", "tabs_bar.local_timeline": "本站", "tabs_bar.notifications": "通知", - "upload_area.title": "将文件拖放至此上传", + "upload_area.title": "将文件拖放到此处开始上传", "upload_button.label": "上传媒体文件", - "upload_form.description": "Describe for the visually impaired", - "upload_form.undo": "还原", - "upload_progress.label": "上传中……", - "video.close": "关闭影片", + "upload_form.description": "为视觉障碍人士添加文字说明", + "upload_form.undo": "取消上传", + "upload_progress.label": "上传中…", + "video.close": "关闭视频", "video.exit_fullscreen": "退出全屏", - "video.expand": "展开影片", + "video.expand": "展开视频", "video.fullscreen": "全屏", - "video.hide": "隐藏影片", + "video.hide": "隐藏视频", "video.mute": "静音", "video.pause": "暂停", "video.play": "播放", - "video.unmute": "解除静音" + "video.unmute": "取消静音" } diff --git a/app/views/user_mailer/confirmation_instructions.zh-cn.html.erb b/app/views/user_mailer/confirmation_instructions.zh-cn.html.erb index de2f8b6e0..8a676498a 100644 --- a/app/views/user_mailer/confirmation_instructions.zh-cn.html.erb +++ b/app/views/user_mailer/confirmation_instructions.zh-cn.html.erb @@ -1,10 +1,13 @@ -

<%= @resource.email %> ,嗨呀!

+

<%= @resource.email %>,你好呀!

-

你刚刚在 <%= @instance %> 创建了帐号。

+

你刚刚在 <%= @instance %> 创建了一个帐户呢。

-

点击下面的链接来完成注册啦 :
+

点击下面的链接来完成注册啦:
<%= link_to '确认帐户', confirmation_url(@resource, confirmation_token: @token) %> -

别忘了看看 <%= link_to '使用条款', terms_url %>。

+

上面的链接按不动?把下面的链接复制到地址栏再试试:
+<%= confirmation_url(@resource, confirmation_token: @token) %> -

<%= @instance %> 敬上

\ No newline at end of file +

记得读一读我们的<%= link_to '使用条款', terms_url %>哦。

+ +

来自 <%= @instance %> 管理团队

diff --git a/app/views/user_mailer/confirmation_instructions.zh-cn.text.erb b/app/views/user_mailer/confirmation_instructions.zh-cn.text.erb index d7d4b4b23..25d901f16 100644 --- a/app/views/user_mailer/confirmation_instructions.zh-cn.text.erb +++ b/app/views/user_mailer/confirmation_instructions.zh-cn.text.erb @@ -1,10 +1,10 @@ -<%= @resource.email %> ,嗨呀! +<%= @resource.email %>,你好呀! -你刚刚在 <%= @instance %> 创建了帐号。 +你刚刚在 <%= @instance %> 创建了一个帐户呢。 -点击下面的链接来完成注册啦 :
-<%= link_to '确认帐户', confirmation_url(@resource, confirmation_token: @token) %> +点击下面的链接来完成注册啦: +<%= confirmation_url(@resource, confirmation_token: @token) %> -别忘了看看 <%= link_to 'terms and conditions', terms_url %>。 +记得读一读我们的使用条款哦:<%= terms_url %> -<%= @instance %> 敬上 \ No newline at end of file +来自 <%= @instance %> 管理团队 \ No newline at end of file diff --git a/app/views/user_mailer/password_change.zh-cn.html.erb b/app/views/user_mailer/password_change.zh-cn.html.erb index 115030af4..64e8b6b2f 100644 --- a/app/views/user_mailer/password_change.zh-cn.html.erb +++ b/app/views/user_mailer/password_change.zh-cn.html.erb @@ -1,3 +1,3 @@ -

<%= @resource.email %>,嗨呀!

+

<%= @resource.email %>,你好呀!

-

这只是一封用来通知你的密码已经被修改的邮件。_(:3」∠)_

+

提醒一下,你在 <%= @instance %> 上的密码被更改了哦。

diff --git a/app/views/user_mailer/password_change.zh-cn.text.erb b/app/views/user_mailer/password_change.zh-cn.text.erb index 5a989d324..dbc065173 100644 --- a/app/views/user_mailer/password_change.zh-cn.text.erb +++ b/app/views/user_mailer/password_change.zh-cn.text.erb @@ -1,3 +1,3 @@ -<%= @resource.email %>,嗨呀! +<%= @resource.email %>,你好呀! -这只是一封用来通知你的密码已经被修改的邮件。_(:3」∠)_ +提醒一下,你在 <%= @instance %> 上的密码被更改了哦。 diff --git a/app/views/user_mailer/reset_password_instructions.zh-cn.html.erb b/app/views/user_mailer/reset_password_instructions.zh-cn.html.erb index 51e3073f1..124305675 100644 --- a/app/views/user_mailer/reset_password_instructions.zh-cn.html.erb +++ b/app/views/user_mailer/reset_password_instructions.zh-cn.html.erb @@ -1,7 +1,8 @@ -

<%= @resource.email %> ,嗨呀!!

+

<%= @resource.email %>,你好呀!

-

有人(但愿是你)请求更改你Mastodon帐户的密码。如果是你的话,请点击下面的链接:

+

有人想修改你在 <%= @instance %> 上的密码呢。如果你确实想修改密码的话,点击下面的链接吧:

-

<%= link_to '更改密码', edit_password_url(@resource, reset_password_token: @token) %>

+

<%= link_to '修改密码', edit_password_url(@resource, reset_password_token: @token) %>

-

如果不是的话,忘了它吧。只有你本人通过上面的链接设置新的密码以后你的新密码才会生效。

+

如果你不想修改密码的话,还请忽略这封邮件哦。

+

在你点击上面的链接并修改密码前,你的密码是不会改变的。

diff --git a/app/views/user_mailer/reset_password_instructions.zh-cn.text.erb b/app/views/user_mailer/reset_password_instructions.zh-cn.text.erb index 7df590f78..f7cd88847 100644 --- a/app/views/user_mailer/reset_password_instructions.zh-cn.text.erb +++ b/app/views/user_mailer/reset_password_instructions.zh-cn.text.erb @@ -1,7 +1,8 @@ -<%= @resource.email %> ,嗨呀!! +<%= @resource.email %>,你好呀! -有人(但愿是你)请求更改你Mastodon帐户的密码。如果是你的话,请点击下面的链接: +有人想修改你在 <%= @instance %> 上的密码呢。如果你确实想修改密码的话,点击下面的链接吧: -<%= link_to '更改密码', edit_password_url(@resource, reset_password_token: @token) %> +<%= edit_password_url(@resource, reset_password_token: @token) %> -如果不是的话,忘了它吧。只有你本人通过上面的链接设置新的密码以后你的新密码才会生效。 +如果你不想修改密码的话,还请忽略这封邮件哦。 +在你点击上面的链接并修改密码前,你的密码是不会改变的。 diff --git a/config/locales/activerecord.zh-CN.yml b/config/locales/activerecord.zh-CN.yml new file mode 100644 index 000000000..8628d6677 --- /dev/null +++ b/config/locales/activerecord.zh-CN.yml @@ -0,0 +1,13 @@ +--- +zh-CN: + activerecord: + errors: + models: + account: + attributes: + username: + invalid: 只能使用字母、数字和下划线 + status: + attributes: + reblog: + taken: 已经被转嘟过 diff --git a/config/locales/simple_form.zh-CN.yml b/config/locales/simple_form.zh-CN.yml index eafaa972e..8bd3b576c 100644 --- a/config/locales/simple_form.zh-CN.yml +++ b/config/locales/simple_form.zh-CN.yml @@ -3,49 +3,60 @@ zh-CN: simple_form: hints: defaults: - avatar: 最大 2MB,限 PNG, GIF 或 JPG 格式,将缩到 120x120px - display_name: 不起过 30 个字符 - header: 最大 2MB,限 PNG, GIF 或 JPG 格式,将缩到 700x335px - locked: 默认仅向粉丝公开嘟文,需要手工批准粉丝关注请求。 - note: 最多 160 个字符 + avatar: 文件大小限制 2MB,只支持 PNG、GIF 或 JPG 格式。图片分辨率将会压缩至 120×120px + digest: 在你长时间未登录的情况下,我们会向你发送一份含有提及你的嘟文的摘要邮件 + display_name: 还能输入 %{count} 个字符 + header: 文件大小限制 2MB,只支持 PNG、GIF 或 JPG 格式。图片分辨率将会压缩至 700×335px + locked: 你需要手动审核所有关注请求 + note: 还能输入 %{count} 个字符 + setting_noindex: 此设置会影响到你的公开个人资料以及嘟文页面 + setting_theme: 此设置会影响到你从任意设备登录时 Mastodon 的显示样式 imports: - data: 从其他服务器节点导出的 CSV 文件 + data: 请上传从其他 Mastodon 实例导出的 CSV 文件 sessions: - otp: 输入你手机生成的两步验证码,或者恢复代码。 + otp: 输入你手机上生成的两步验证码,或者任意一个恢复代码。 user: - filtered_languages: 下列被选择的语言的嘟文将不会出现在你的公共时间轴上。 + filtered_languages: 勾选语言的嘟文将不会出现在你的公共时间轴上 labels: defaults: avatar: 头像 confirm_new_password: 确认新密码 confirm_password: 确认密码 current_password: 当前密码 - data: 数据 - display_name: 显示名 - email: 邮箱 - filtered_languages: 屏蔽下列语言的嘟文 - header: 个人页面顶部 + data: 数据文件 + display_name: 昵称 + email: 电子邮件地址 + filtered_languages: 语言过滤 + header: 个人资料页横幅图片 locale: 语言 - locked: 隐私模式(锁嘟) + locked: 保护你的帐户(锁嘟) new_password: 新密码 note: 简介 - otp_attempt: 两步认证码 + otp_attempt: 两步认证代码 password: 密码 + setting_auto_play_gif: 自动播放 GIF 动画 setting_boost_modal: 在转嘟前询问我 - setting_default_privacy: 嘟文默认隐私度 - severity: 等级 + setting_default_privacy: 嘟文默认可见范围 + setting_default_sensitive: 总是将我发送的媒体文件标记为敏感内容 + setting_delete_modal: 在删除嘟文前询问我 + setting_noindex: 禁止搜索引擎建立索引 + setting_reduce_motion: 降低过渡动画效果 + setting_system_font_ui: 使用系统默认字体 + setting_theme: 站点主题 + setting_unfollow_modal: 在取消关注前询问我 + severity: 级别 type: 导入数据类型 username: 用户名 interactions: - must_be_follower: 隐藏没有关注你的用户的通知 - must_be_following: 隐藏你不关注的用户的通知 + must_be_follower: 屏蔽来自未关注你的用户的通知 + must_be_following: 屏蔽来自你未关注的用户的通知 notification_emails: digest: 发送摘要邮件 - favourite: 当有用户赞了你的嘟文时,发电邮通知 - follow: 当有用户关注你时,发电邮通知 - follow_request: 当有用户要求关注你时,发电邮通知 - mention: 当有用户在嘟文中提及你时,发电邮通知 - reblog: 当有用户转嘟了你的嘟文时,发电邮通知 + favourite: 当有用户收藏了你的嘟文时,发送电子邮件提醒我 + follow: 当有用户关注你时,发送电子邮件提醒我 + follow_request: 当有用户向你发送关注请求时,发送电子邮件提醒我 + mention: 当有用户在嘟文中提及你时,发送电子邮件提醒我 + reblog: 当有用户转嘟了你的嘟文时,发送电子邮件提醒我 'no': 否 required: mark: "*" diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index 44d0f3803..4a71a9959 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -1,196 +1,335 @@ --- zh-CN: about: - about_mastodon_html: Mastodon(长毛象)是一个自由、开放源码的社交网站。它是一个分布式的服务,避免你的通信被单一商业机构垄断操控。请你选择一家你信任的 Mastodon 实例,在上面创建帐号,然后你就可以和任一 Mastodon 实例上的用户互通,享受无缝的社交交流。 + about_hashtag_html: 这里展示的是带有话题标签 #%{hashtag} 的公开嘟文。如果你在象毛世界中拥有一个帐户,就可以加入讨论。 + about_mastodon_html: Mastodon(长毛象)是一个基于开放式网络协议和自由、开源软件建立的社交网络,有着类似于电子邮件的分布式设计。 about_this: 关于本实例 - closed_registrations: 这个实例目前不开放注册 _(:3」∠)_ + closed_registrations: 这个实例目前没有开放注册。不过,你可以前往其他实例注册一个帐户,同样可以加入到这个网络中哦! contact: 联络 + contact_missing: 未设定 + contact_unavailable: 未公开 description_headline: 关于 %{domain} domain_count_after: 个其它实例 domain_count_before: 现已接入 - other_instances: 其它实例 + extended_description_html: | +

这里可以写一些规定

+

本站尚未设置详细介绍。

+ features: + humane_approach_body: Mastodon 从其他网络的失败经验中汲取了教训,致力于在与错误的社交媒体使用方式的斗争中做出符合伦理化设计的选择。 + humane_approach_title: 更加以人为本 + not_a_product_body: Mastodon 绝非一个商业网络。这里既没有广告,也没有数据挖掘,更没有围墙花园。中心机构在这里不复存在。 + not_a_product_title: 作为用户,你并非一件商品 + real_conversation_body: Mastodon 有着高达 500 字的字数限制,以及对内容的细化控制和媒体警告提示的支持,只为让你能够畅所欲言。 + real_conversation_title: 为真正的交流而生 + within_reach_body: 通过一个面向开发者友好的 API 生态系统,Mastodon 让你可以随时随地通过众多 iOS、Android 以及其他平台的应用与朋友们保持联系。 + within_reach_title: 始终触手可及 + find_another_instance: 寻找另一个实例 + generic_description: "%{domain} 是这个庞大网络中的一台服务器" + hosted_on: 一个在 %{domain} 上运行的 Mastodon 实例 + learn_more: 详细了解 + other_instances: 其他实例 source_code: 源码 status_count_after: 条嘟文 status_count_before: 他们共嘟出了 user_count_after: 位用户 user_count_before: 这里共注册有 + what_is_mastodon: Mastodon 是什么? accounts: follow: 关注 - followers: 粉丝 - following: 关注 - nothing_here: 神马都没有! + followers: 关注者 + following: 正在关注 + media: 媒体 + nothing_here: 这里神马都没有! people_followed_by: 正关注 people_who_follow: 粉丝 posts: 嘟文 + posts_with_replies: 嘟文和回复 remote_follow: 跨站关注 + reserved_username: 此用户名已保留 + roles: + admin: 管理员 unfollow: 取消关注 admin: + account_moderation_notes: + account: 管理员 + create: 新建 + created_at: 日期 + created_msg: 管理记录建立成功! + delete: 删除 + destroyed_msg: 管理记录删除成功! accounts: are_you_sure: 你确定吗? confirm: 确认 confirmed: 已确认 - disable_two_factor_authentication: 两步认证无效 - display_name: 显示名称 + disable_two_factor_authentication: 停用两步认证 + display_name: 昵称 domain: 域名 edit: 编辑 - email: 电邮地址 + email: 电子邮件地址 feed_url: 订阅 URL followers: 关注者 + followers_url: 关注者(Followers)URL follows: 正在关注 - ip: IP地址 + inbox_url: 收件箱(Inbox)URL + ip: IP 地址 location: all: 全部 local: 本地 remote: 远程 - title: 地点 + title: 位置 media_attachments: 媒体文件 moderation: all: 全部 - silenced: 被静音的 - suspended: 被停权的 - title: 管理操作 - most_recent_activity: 最新活动 - most_recent_ip: 最新 IP 地址 + silenced: 已静音 + suspended: 已封禁 + title: 帐户状态 + moderation_notes: 管理记录 + most_recent_activity: 最后一次活跃的时间 + most_recent_ip: 最后一次活跃的 IP 地址 not_subscribed: 未订阅 order: alphabetic: 按字母 most_recent: 按时间 title: 排序 - perform_full_suspension: 实行完全暂停 - profile_url: 个人文件 URL - public: 公共 - push_subscription_expires: 推送订阅过期 + outbox_url: 发件箱(Outbox)URL + perform_full_suspension: 永久封禁 + profile_url: 个人资料页面 URL + protocol: 协议 + public: 公开页面 + push_subscription_expires: PuSH 订阅过期时间 + redownload: 刷新头像 reset: 重置 reset_password: 重置密码 - salmon_url: Salmon 反馈 URL + resubscribe: 重新订阅 + salmon_url: Salmon URL search: 搜索 + shared_inbox_url: 公用收件箱(Shared Inbox)URL show: - created_reports: 这个帐户创建的报告 - report: 报告 - targeted_reports: 关于这个帐户的报告 + created_reports: 这个帐户提交的举报 + report: 个举报 + targeted_reports: 针对这个帐户的举报 silence: 静音 statuses: 嘟文 + subscribe: 订阅 title: 用户 undo_silenced: 解除静音 - undo_suspension: 解除停权 - username: 用户名称 - web: 用户页面 + undo_suspension: 解除封禁 + unsubscribe: 取消订阅 + username: 用户名 + web: 站内页面 + custom_emojis: + copied_msg: 成功将表情复制到本地 + copy: 复制 + copy_failed_msg: 无法将表情复制到本地 + created_msg: 表情添加成功! + delete: 删除 + destroyed_msg: 表情删除成功! + disable: 停用 + disabled_msg: 表情停用成功 + emoji: 表情 + enable: 启用 + enabled_msg: 表情启用成功 + image_hint: PNG 格式,最大 50KB + listed: 默认显示的表情 + new: + title: 添加新的自定义表情 + shortcode: 短代码 + shortcode_hint: 至少 2 个字符,只能使用字母、数字和下划线 + title: 自定义表情 + unlisted: 默认隐藏的表情 + update_failed_msg: 表情更新失败! + updated_msg: 表情更新成功! + upload: 上传 domain_blocks: add_new: 添加 - created_msg: 正处理域名阻隔 - destroyed_msg: 已撤销域名阻隔 - domain: 域名阻隔 + created_msg: 正在进行域名屏蔽 + destroyed_msg: 域名屏蔽已撤销 + domain: 域名 new: - create: 添加域名阻隔 - hint: "「域名阻隔」不会隔绝该域名用户的嘟帐户入本站数据库,但会嘟文抵达后,自动套用特定的审批操作。" + create: 添加域名屏蔽 + hint: 域名屏蔽不会阻止该域名下的帐户进入本站的数据库,但是会对来自这个域名的帐户自动进行预先设置的管理操作。 severity: - desc_html: "「自动静音」令该域名用户的嘟文,设为只对关注者显示,没有关注的人会看不到。 「自动除名」会自动将该域名用户的嘟文、媒体文件、个人资料从本服务器实例删除。" + desc_html: 选择自动静音会将该域名下帐户发送的嘟文设置为仅关注者可见;选择自动封禁会将该域名下帐户发送的嘟文、媒体文件以及个人资料数据从本实例上删除;选择可以拒绝接收来自该域名的任何媒体文件。 + noop: 无 silence: 自动静音 - suspend: 自动除名 - title: 添加域名阻隔 - reject_media: 拒绝媒体文件 - reject_media_hint: 删除本地缓存的媒体文件,再也不在未来下载这个站点的文件。和自动除名无关。 + suspend: 自动封禁 + title: 添加域名屏蔽 + reject_media: 拒绝接收媒体文件 + reject_media_hint: 删除本地已缓存的媒体文件,并且不再接收来自该域名的任何媒体文件。此选项不影响封禁 severities: + noop: 无 silence: 自动静音 - suspend: 自动除名 - severity: 阻隔程度 + suspend: 自动封禁 + severity: 封禁级别 show: - affected_accounts: - one: 数据库中有1个帐户受影响 - other: 数据库中有%{count}个帐户受影响 + affected_accounts: 将会影响到数据库中的 %{count} 个帐户 retroactive: silence: 对此域名的所有帐户取消静音 - suspend: 对此域名的所有帐户取消除名 - title: 撤销 %{domain} 的域名阻隔 + suspend: 对此域名的所有帐户取消封禁 + title: 撤销对 %{domain} 的域名屏蔽 undo: 撤销 - title: 域名阻隔 + title: 域名屏蔽 undo: 撤销 + email_domain_blocks: + add_new: 添加新条目 + created_msg: 电子邮件域名屏蔽添加成功 + delete: 删除 + destroyed_msg: 电子邮件域名屏蔽删除成功 + domain: 域名 + new: + create: 添加封禁 + title: 添加电子邮件域名屏蔽 + title: 电子邮件域名屏蔽 instances: - account_count: 已知帐号 + account_count: 已知帐户 domain_name: 域名 + reset: 重置 + search: 搜索 title: 已知实例 reports: - are_you_sure: 你确定吗? + action_taken_by: 操作执行者 + are_you_sure: 你确定吗? comment: label: 备注 none: 没有 delete: 删除 id: ID - mark_as_resolved: 标示为「已处理」 + mark_as_resolved: 标记为“已处理” nsfw: - 'false': NSFW无效 - 'true': NSFW有效 + 'false': 取消 NSFW 标记 + 'true': 添加 NSFW 标记 report: '举报 #%{id}' + report_contents: 内容 reported_account: 举报用户 - reported_by: 举报者 + reported_by: 举报人 resolved: 已处理 - silence_account: 将用户静音 + silence_account: 静音用户 status: 状态 - suspend_account: 将用户停权 - target: 对象 + suspend_account: 封禁用户 + target: 被举报人 title: 举报 unresolved: 未处理 view: 查看 settings: + bootstrap_timeline_accounts: + desc_html: 用半角逗号分隔多个用户名。只能添加来自本站且未开启保护的帐户。如果留空,则默认关注本站所有的管理员。 + title: 新用户默认关注 contact_information: - email: 输入一个公开的电邮地址 - username: 输入用户名称 + email: 输入一个公开的电子邮件地址 + username: 输入用户名 registrations: closed_message: - desc_html: 当本站暂停接受注册时,会显示这个消息。
可使用 HTML - title: 暂停注册消息 + desc_html: 本站关闭注册期间的提示信息。可以使用 HTML 标签 + title: 关闭注册时的提示消息 + deletion: + desc_html: 允许所有人删除自己的帐户 + title: 开放删除帐户权限 open: + desc_html: 允许任何人建立一个帐户 title: 开放注册 site_description: - desc_html: 在首页显示,及在 meta 标签中用作网站介绍。
你可以在此使用 HTML 标签,尤其是<a><em>。 - title: 本站介绍 + desc_html: 展示在首页以及 meta 标签中的网站简介。可以使用 HTML 标签,包括 <a><em>。 + title: 本站简介 site_description_extended: - desc_html: 本站详细信息页的內容
你可在此使用 HTML - title: 本站详细信息 + desc_html: 可以填写行为守则、规定、指南或其他本站特有的内容。可以使用 HTML 标签 + title: 本站详细介绍 + site_terms: + desc_html: 可以填写自己的隐私权政策、使用条款或其他法律文本。可以使用 HTML 标签 + title: 自定义使用条款 site_title: 本站名称 + thumbnail: + desc_html: 用于在 OpenGraph 和 API 中显示预览图。推荐分辨率 1200×630px + title: 本站缩略图 + timeline_preview: + desc_html: 在主页显示公开时间线 + title: 时间线预览 title: 网站设置 + statuses: + back_to_account: 返回帐户信息页 + batch: + delete: 删除 + nsfw_off: 取消 NSFW 标记 + nsfw_on: 添加 NSFW 标记 + execute: 执行 + failed_to_execute: 执行失败 + media: + hide: 隐藏媒体文件 + show: 显示媒体文件 + title: 媒体文件 + no_media: 不含媒体文件 + title: 帐户嘟文 + with_media: 含有媒体文件 subscriptions: callback_url: 回调 URL - confirmed: 确定 - expires_in: 期限 - last_delivery: 数据最后送抵时间 - title: WebSub 订阅 - topic: 所订阅资源 + confirmed: 已确认 + expires_in: 失效时间 + last_delivery: 最后一次接收数据的时间 + title: WebSub + topic: Topic title: 管理 + admin_mailer: + new_report: + body: "%{reporter} 举报了 %{target}" + subject: 来自 %{instance} 的新举报(#%{id}) application_mailer: - settings: 更改电邮设置︰%{link} + salutation: "%{name}," + settings: 更改电子邮件首选项:%{link} signature: 来自 %{instance} 的 Mastodon 通知 view: 查看: applications: + created: 应用创建成功 + destroyed: 应用删除成功 invalid_url: URL 无效 + regenerate_token: 重置访问令牌 + token_regenerated: 访问令牌重置成功 + warning: 一定小心,千万不要把它分享给任何人! + your_token: 你的访问令牌 auth: - change_password: 登录凭据 + agreement_html: 注册即表示你同意我们的使用条款隐私权政策。 + change_password: 帐户安全 + delete_account: 删除帐户 + delete_account_html: 如果你想删除你的帐户,请点击这里继续。你需要确认你的操作。 didnt_get_confirmation: 没有收到确认邮件? forgot_password: 忘记密码? + invalid_reset_password_token: 密码重置令牌无效或已过期。请重新发起重置密码请求。 login: 登录 logout: 登出 register: 注册 - resend_confirmation: 重发确认邮件 + resend_confirmation: 重新发送确认邮件 reset_password: 重置密码 set_new_password: 设置新密码 authorize_follow: error: 对不起,寻找这个跨站用户时出错 follow: 关注 + follow_request: 关注请求已发送给: + following: 成功!你正在关注: + post_follow: + close: 你也可以直接关闭这个窗口。 + return: 返回至个人资料页 + web: 返回本站 title: 关注 %{acct} datetime: distance_in_words: - about_x_hours: "%{count} 小时" + about_x_hours: "%{count} 时" about_x_months: "%{count} 个月" about_x_years: "%{count} 年" - almost_x_years: 接近 %{count} 年 + almost_x_years: "%{count} 年" half_a_minute: 刚刚 - less_than_x_minutes: "%{count} 分不到" + less_than_x_minutes: "%{count} 分" less_than_x_seconds: 刚刚 - over_x_years: 超过 %{count} 年 + over_x_years: "%{count} 年" x_days: "%{count} 天" x_minutes: "%{count} 分" x_months: "%{count} 个月" x_seconds: "%{count} 秒" + deletes: + bad_password_msg: 想得美,黑客!密码输入错误 + confirm_password: 输入你当前的密码来验证身份 + description_html: 继续操作将会永久地、不可撤销地删除你帐户中的内容,并冻结你的帐户。你的用户名将会被保留,以防有人冒用你的身份。 + proceed: 删除帐户 + success_msg: 你的帐户已经成功删除 + warning_html: 我们只能保证本实例上的内容已经被彻底删除。对于已经被广泛传播的内容,它们在本实例以外的某些地方可能仍然可见。此外,失去连接的服务器以及停止接收订阅的服务器上的数据亦无法删除。 + warning_title: 关于已传播的内容的警告 errors: '403': 无权查看 '404': 找不到页面 @@ -199,68 +338,70 @@ zh-CN: content: 无法确认登录信息。你是不是屏蔽了 Cookie? title: 无法确认登录信息 '429': 被限制 + '500': + content: 抱歉,我们这里出错了。 + title: 这个页面不正确 + noscript_html: 请启用 JavaScript 以便使用 Mastodon 网页版应用。你也可以选择适用于你的平台的 Mastodon 应用。 exports: - blocks: 被你封锁的用户 + blocks: 屏蔽的用户 csv: CSV - follows: 你所关注的用户 - mutes: 你所静音的用户 - storage: 媒体容量大小 + follows: 关注的用户 + mutes: 静音的用户 + storage: 媒体文件存储 followers: domain: 域名 - explanation_html: 想要保护你的嘟文的话,请慎重考虑关注你的人。你的受保护的嘟文会发送到有你的关注者的所有实例上。你也许想要复查一下关注者列表来移除那些你无法信任的关注者。 + explanation_html: 为保证你的嘟文的隐私安全,你应当时刻留意你的关注者列表。受保护的嘟文将会发送到所有关注者所在的实例上。有些实例使用的软件代码或其管理员可能不会尊重你的隐私设置,因此你应当复查一下关注者列表,并移除那些你无法信任的关注者。 followers_count: 关注者数量 - lock_link: 保护你的帐户 + lock_link: 为你的帐户开启保护 purge: 从关注者中移除 - success: 从 %{count} 个域名中移除了关注者。 - true_privacy_html: "真正的隐私只能靠端到端加密来实现!" - unlocked_warning_html: 任何人都可以关注你然后查看被保护的嘟文, %{lock_link} 可以复核和拒绝关注请求。 - unlocked_warning_title: 你的帐户没被保护 + success: 正在从 %{count} 个域名中移除关注者…… + true_privacy_html: 请始终铭记:真正的隐私只能靠端到端加密来实现! + unlocked_warning_html: 任何人都可以通过关注你来立即查看被保护的嘟文。%{lock_link},即可审核并拒绝关注请求。 + unlocked_warning_title: 你的帐户未受到保护 generic: - changes_saved_msg: 更改已被保存。 + changes_saved_msg: 更改保存成功! powered_by: 基于 %{link} 构建 - save_changes: 保存 + save_changes: 保存更改 validation_errors: - one: 出错啦!请确认以下出错的地方,修改之后再来一次: - other: 出错啦!请确认以下 %{count} 处出错的地方,修改之后再来一次: + one: 出错啦!检查一下下面出错的地方吧 + other: 出错啦!检查一下下面 %{count} 处出错的地方吧 imports: - preface: 你可以在此导入你在其他服务器实例所导出的数据文件,包括︰你所关注、封锁的用户。 - success: 你已成功上载数据文件,我们正将数据导入,请稍候 + preface: 你可以在此导入你在其他实例导出的数据,比如你所关注或屏蔽的用户列表。 + success: 数据上传成功,正在处理中 types: - blocking: 封锁名单 - following: 关注名单 - muting: 静音名单 - upload: 上载 - landing_strip_html: "%{name} 是一个在 %{link_to_root_path} 的用户。只要你是象毛世界里(Mastodon、GNU social)任一服务器实例的用户,便可以跨站关注此站用户并与其沟通。" - landing_strip_signup_html: 如果你没有这类帐户,欢迎在此处登记。 + blocking: 屏蔽列表 + following: 关注列表 + muting: 静音列表 + upload: 上传 + landing_strip_html: "%{name} 是一位来自 %{link_to_root_path} 的用户。如果你想关注这个人或者与这个人互动,你需要在任意一个 Mastodon 实例或与其兼容的网站上拥有一个帐户。" + landing_strip_signup_html: 还没有这种帐户?你可以在本站注册一个。 media_attachments: validations: - images_and_video: 无法添加视频到一个已经包含图片的嘟文中 - too_many: 最多只能添加4张图片 + images_and_video: 无法在嘟文中同时插入视频和图片 + too_many: 最多只能添加 4 张图片 notification_mailer: digest: - body: 自从你在%{since}使用%{instance}以后,错过了这些嘟嘟滴滴: - mention: "%{name} 在此提及了你︰" + body: 自从你最后一次(时间是%{since})登录 %{instance} 以来,你错过了这些嘟嘟滴滴: + mention: "%{name} 在嘟文中提到了你:" new_followers_summary: - one: 有人关注你了!耶! - other: 有 %{count} 个人关注了你!别激动! - subject: - one: "你有一个新通知 \U0001F418" - other: "%{count} 个通知太多,赶快去看看 \U0001F418" + one: 有个人关注了你!耶! + other: 有 %{count} 个人关注了你!好棒! + subject: "自从你最后一次登录以来,你错过了 %{count} 条新通知 \U0001F418" favourite: - body: "%{name} 收藏了你" - subject: "%{name} 给你点了收藏" + body: 你的嘟文被 %{name} 收藏了: + subject: "%{name} 收藏了你的嘟文" follow: - body: "%{name} 关注了你" + body: "%{name} 关注了你!" subject: "%{name} 关注了你" follow_request: - body: "%{name} 要求关注你" - subject: 等候关注你的用户︰ %{name} + body: "%{name} 请求关注你" + subject: 待审核的关注者:%{name} mention: - body: "%{name} 在文章中提及你︰" - subject: "%{name} 在文章中提及你" + body: "%{name} 在嘟文中提到了你:" + subject: "%{name} 提到了你" reblog: - body: 你的嘟文得到 %{name} 的转嘟 - subject: "%{name} 转嘟(嘟嘟滴)了你的嘟文" + body: 你的嘟文被 %{name} 转嘟了: + subject: "%{name} 转嘟了你的嘟文" number: human: decimal_units: @@ -275,54 +416,197 @@ zh-CN: pagination: next: 下一页 prev: 上一页 - truncate: "……" + truncate: "…" + preferences: + languages: 语言 + other: 其他 + publishing: 发布 + web: 站内 + push_notifications: + favourite: + title: "%{name} 收藏了你的嘟文" + follow: + title: "%{name} 关注了你" + group: + title: "%{count} 条新通知" + mention: + action_boost: 转嘟 + action_expand: 显示更多 + action_favourite: 收藏 + title: "%{name} 提到了你" + reblog: + title: "%{name} 转嘟了你的嘟文" remote_follow: - acct: 请输入你的︰用户名称@实例域名 - missing_resource: 无法找到您的帐户转接网址 - proceed: 下一步 - prompt: 你正准备关注︰ + acct: 请输入你的“用户名@实例域名” + missing_resource: 无法确定你的帐户的跳转 URL + proceed: 确认关注 + prompt: 你正准备关注: + sessions: + activity: 最后一次活跃的时间 + browser: 浏览器 + browsers: + alipay: 支付宝 + blackberry: Blackberry + chrome: Chrome + edge: Microsoft Edge + firefox: Firefox + generic: 未知浏览器 + ie: Internet Explorer + micro_messenger: 微信 + nokia: Nokia S40 Ovi 浏览器 + opera: Opera + phantom_js: PhantomJS + qq: QQ浏览器 + safari: Safari + uc_browser: UC浏览器 + weibo: 新浪微博 + current_session: 当前会话 + description: "%{platform} 上的 %{browser}" + explanation: 你的 Mastodon 帐户目前已在这些浏览器上登录。 + ip: IP 地址 + platforms: + adobe_air: Adobe Air + android: Android + blackberry: Blackberry + chrome_os: ChromeOS + firefox_os: Firefox OS + ios: iOS + linux: Linux + mac: Mac + other: 未知平台 + windows: Windows + windows_mobile: Windows Mobile + windows_phone: Windows Phone + revoke: 注销 + revoke_success: 会话注销成功 + title: 会话 settings: authorized_apps: 已授权的应用 back: 回到 Mastodon + delete: 删除帐户 + development: 开发 edit_profile: 更改个人信息 export: 导出 followers: 授权的关注者 import: 导入 + notifications: 通知 preferences: 首选项 settings: 设置 two_factor_authentication: 两步认证 + your_apps: 你的应用 statuses: - open_in_web: 打开网页 + open_in_web: 在站内打开 over_character_limit: 超过了 %{max} 字的限制 + pin_errors: + limit: 置顶的嘟文条数超出限制 + ownership: 不能置顶他人的嘟文 + private: 不能置顶非公开的嘟文 + reblog: 不能置顶转嘟 show_more: 显示更多 visibilities: - private: 限关注者 - private_long: 仅向关注者公开 + private: 仅关注者 + private_long: 只有关注你的用户能看到 public: 公开 - public_long: 向所有人公开 - unlisted: 于公共时间线中隐藏 - unlisted_long: 公开,但不显示在公共时间线中 + public_long: 所有人可见,并会出现在公共时间轴上 + unlisted: 不公开 + unlisted_long: 所有人可见,但不会出现在公共时间轴上 stream_entries: - click_to_show: 显示 + click_to_show: 点击显示 + pinned: 置顶嘟文 reblogged: 转嘟 sensitive_content: 敏感内容 + terms: + body_html: | +

隐私权政策

+ +

我们收集什么信息?

+ +

我们从你在我们站点注册开始从你那开始收集信息,并收集关于你在论坛的阅读和写作的数据,并评估分享的内容。

+ +

当在我们站点注册时,你可能被要求输入你的名字和邮件地址。然而你可以在不用注册的情况下访问站点。你的邮件地将通过一个独一无二的链接验证。如果链接被访问了,我们就知道你控制了该邮件地址。

+ +

当已注册和发帖时,我们记录发布帖子时的 IP 地址。我们也可能保留服务器日志,其中包括了每一个向我们服务器的请求。

+ +

我们如何使用你的信息?

+ +

从你那收集的任何数据将以以下方式使用:

+ + + +

我们如何保护你的信息?

+ +

我们实现了一系列的安全措施保证你输入、提交或者访问你个人信息的数据安全。

+ +

数据保存政策是什么?

+ +

我们将善意地:

+ + + +

我们使用 Cookie 吗?

+ +

是的。Cookie 是网站或它的服务商通过网页浏览器存储在你电脑硬盘上的小文件(如果你同意)。这些 Cookie 使站点能分辨你的浏览器,并且,如果你注册了一个账户,与你的注册账户关联。

+ +

我们使用 Cookie 为之后的访问和编译一小段关于站点流量和交互的数据来判断并保存你的个人设置,这样我们可以在之后提供更好的站点体验和工具。我们可能使用第三方服务商来帮助我们更好地理解我们的站点访客。这些服务商是不允许代表我们使用收集的信息,除非是在帮助我们执行和改进我们的站点。

+ +

我们会在站外提供任何信息吗?

+ +

我们绝不销售、交易或任何向外转移你个人信息的行为。这不包括帮助我们管理站点、改进站点或给你提供服务的第三方团体,这些团体需要保证对这些信息保密。当我们认为提交你的信息符合法律、我们的站点政策或保护我们或其他人的权利、知识产权或安全时,我们也可能提交你的信息。然而,非个人访问信息可能供其他团体使用,用于市场、广告或其他用途。

+ +

第三方链接

+ +

偶尔地,根据我们的判断,我们可能在我们的站点上包括或提供第三方团体的产品或服务。这些第三方站点用于独立和不同的隐私政策。因此我们对链接到的站点或活动没有责任和权利。尽管如此,我们寻求保护我们的整个站点并且欢迎你给这些站点反馈。

+ +

儿童在线隐私保护法案合规

+ +

我们的站点、产品和服务提供给 13 岁以上的人们。如果服务器位于美国,并且你小于 13 岁,根据儿童在线隐私保护法案合规,不要使用这个站点。

+ +

仅用于在线隐私政策

+ +

这个线上隐私政策只适用于通过我们站点收集到的信息,并不包括线下收集的信息。

+ + + +

你使用站点的同时,代表你同意了我们网站的隐私政策。

+ +

隐私政策的更改

+ +

如果我们决定更改我们的隐私政策,我们将在此页更新这些改变。

+ +

文档以 CC-BY-SA 发布。最后更新时间为2013年5月31日。

+ +

原文出自 Discourse 隐私权政策

+ title: "%{instance} 使用条款和隐私权政策" + themes: + default: Mastodon time: formats: - default: "%Y年%-m月%d日 %H:%M" + default: "%Y年%-m月%d日%H:%M" two_factor_authentication: - code_hint: 请输入你认证器产生的代码,以确认设置 - description_html: 当你启用两步认证后,你登录时将额外需要使用手机或其他认证器生成的代码。 + code_hint: 输入你的认证器生成的代码以确认 + description_html: 启用两步认证后,你需要输入手机认证器生成的代码才能登录 disable: 停用 enable: 启用 - enabled_success: 已成功启用两步认证 + enabled: 两步认证已启用 + enabled_success: 两步认证启用成功 generate_recovery_codes: 生成恢复代码 - instructions_html: "请用你手机的认证器应用(如 Google Authenticator、Authy),扫描这里的 QR 二维码。在两步认证启用后,你登录时将需要使用此应用程序产生的认证码。" - lost_recovery_codes: 如果你丢了手机,你可以用恢复代码重新访问你的帐户。如果你丢了恢复代码,也可以在这里重新生成一个,不过以前的恢复代码就失效了。(废话) - manual_instructions: 如果你无法扫描 QR 二维码,请手动输入这个文本密码︰ - recovery_codes_regenerated: 已成功重新生成恢复代码 - recovery_instructions_html: 如果你的手机无法使用,你可以使用下面的任何恢复代码来恢复你的帐号。请保管好你的恢复代码以防泄漏(例如你可以打印好它们并和重要文档一起保存)。 + instructions_html: "请使用 Google 身份验证器或其他类似的 TOTP 两步验证手机应用扫描此处的二维码。启用两部验证后,你需要输入该应用生成的代码来登录你的帐户。" + lost_recovery_codes: 如果你的手机不慎丢失,你可以使用恢复代码来重新获得对帐户的访问权。如果你遗失了恢复代码,可以在此处重新生成。之前使用的恢复代码将会失效。 + manual_instructions: 如果你无法扫描二维码,请手动输入下列文本: + recovery_codes: 备份恢复代码 + recovery_codes_regenerated: 恢复代码重新生成成功 + recovery_instructions_html: 如果你的手机无法使用,你可以使用下列任意一个恢复代码来重新获得对帐户的访问权。请妥善保管好你的恢复代码(例如,你可以将它们打印出来,然后和其他重要的文件放在一起)。 setup: 设置 - wrong_code: 你输入的认证码并不正确!可能服务器时间和你手机不一致,请检查你手机的时钟,或与本站管理员联系。 + wrong_code: 输入的认证码无效!请检查你设备上显示的时间是否正确,如果正确,你可能需要联系管理员以检查服务器的时间是否正确。 users: - invalid_email: 邮箱格式有误 - invalid_otp_token: 两步认证码有误 + invalid_email: 输入的电子邮件地址无效 + invalid_otp_token: 输入的两步认证代码无效 + signed_in_as: 当前登录的帐户: -- cgit From 0692991b54c660d1292ff2cea0fa135c952c608e Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Tue, 31 Oct 2017 04:25:51 -0700 Subject: Add ServiceWorker caching for static assets (#5524) --- app/views/home/index.html.haml | 2 -- config/webpack/production.js | 32 +++++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 3 deletions(-) (limited to 'app/views') diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 659295ebf..8c88d2d64 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -3,8 +3,6 @@ %link{ href: asset_pack_path('features/compose.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ %link{ href: asset_pack_path('features/home_timeline.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ %link{ href: asset_pack_path('features/notifications.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ - %link{ href: asset_pack_path('features/community_timeline.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ - %link{ href: asset_pack_path('features/public_timeline.js'), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ %meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key} %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json) diff --git a/config/webpack/production.js b/config/webpack/production.js index cd1dd91dc..6de79c811 100644 --- a/config/webpack/production.js +++ b/config/webpack/production.js @@ -48,7 +48,37 @@ module.exports = merge(sharedConfig, { }), new OfflinePlugin({ publicPath: publicPath, // sw.js must be served from the root to avoid scope issues - caches: { }, // do not cache things, we only use it for push notifications for now + caches: { + main: [':rest:'], + additional: [':externals:'], + optional: [ + '**/locale_*.js', // don't fetch every locale; the user only needs one + '**/*_polyfills-*.js', // the user may not need polyfills + '**/*.woff2', // the user may have system-fonts enabled + // images/audio can be cached on-demand + '**/*.png', + '**/*.jpg', + '**/*.jpeg', + '**/*.svg', + '**/*.mp3', + '**/*.ogg', + ], + }, + externals: [ + '/emoji/1f602.svg', // used for emoji picker dropdown + '/emoji/sheet.png', // used in emoji-mart + ], + excludes: [ + '**/*.gz', + '**/*.map', + 'stats.json', + 'report.html', + // any browser that supports ServiceWorker will support woff2 + '**/*.eot', + '**/*.ttf', + '**/*-webfont-*.svg', + '**/*.woff', + ], ServiceWorker: { entry: path.join(__dirname, '../../app/javascript/mastodon/service_worker/entry.js'), cacheName: 'mastodon', -- cgit From 38d072446be2321c82f42b6e1cf90891f935725c Mon Sep 17 00:00:00 2001 From: MitarashiDango Date: Wed, 1 Nov 2017 22:46:05 +0900 Subject: add account search condition (instance domain) (#5577) --- app/views/admin/accounts/index.html.haml | 2 +- config/locales/en.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'app/views') diff --git a/app/views/admin/accounts/index.html.haml b/app/views/admin/accounts/index.html.haml index 1b56a3a31..27a0682d8 100644 --- a/app/views/admin/accounts/index.html.haml +++ b/app/views/admin/accounts/index.html.haml @@ -42,7 +42,7 @@ - if params[key].present? = hidden_field_tag key, params[key] - - %i(username display_name email ip).each do |key| + - %i(username by_domain display_name email ip).each do |key| .input.string.optional = text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.accounts.#{key}") diff --git a/config/locales/en.yml b/config/locales/en.yml index 2d821550a..d72ec92d0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -59,6 +59,7 @@ en: destroyed_msg: Moderation note successfully destroyed! accounts: are_you_sure: Are you sure? + by_domain: Domain confirm: Confirm confirmed: Confirmed disable_two_factor_authentication: Disable 2FA -- cgit From b6e2e999bd4c603bc36b1234af484184644104e9 Mon Sep 17 00:00:00 2001 From: nullkal Date: Tue, 7 Nov 2017 22:49:32 +0900 Subject: Show the local couterpart of emoji when it exists in /admin/custom_emojis (#5467) * Show the local couterpart of emoji when it exists in admin/custom_emojis * Fix indentation * Fix error * Add class table-action-link to Overwrite link * Make it enable to overwrite emojis * Make Code Climate happy --- app/controllers/admin/custom_emojis_controller.rb | 6 +-- app/helpers/application_helper.rb | 4 ++ app/models/custom_emoji.rb | 2 + .../admin/custom_emojis/_custom_emoji.html.haml | 7 ++- config/brakeman.ignore | 56 +++++++++++----------- config/locales/en.yml | 1 + 6 files changed, 43 insertions(+), 33 deletions(-) (limited to 'app/views') diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb index cbd7abe95..daa1460fb 100644 --- a/app/controllers/admin/custom_emojis_controller.rb +++ b/app/controllers/admin/custom_emojis_controller.rb @@ -5,7 +5,7 @@ module Admin before_action :set_custom_emoji, except: [:index, :new, :create] def index - @custom_emojis = filtered_custom_emojis.page(params[:page]) + @custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page]) end def new @@ -36,9 +36,9 @@ module Admin end def copy - emoji = CustomEmoji.new(domain: nil, shortcode: @custom_emoji.shortcode, image: @custom_emoji.image) + emoji = CustomEmoji.find_or_create_by(domain: nil, shortcode: @custom_emoji.shortcode) - if emoji.save + if emoji.update(image: @custom_emoji.image) flash[:notice] = I18n.t('admin.custom_emojis.copied_msg') else flash[:alert] = I18n.t('admin.custom_emojis.copy_failed_msg') diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 6d625e7db..310e1b1b1 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -43,6 +43,10 @@ module ApplicationHelper content_tag(:i, nil, attributes.merge(class: class_names.join(' '))) end + def custom_emoji_tag(custom_emoji) + image_tag(custom_emoji.image.url, class: 'emojione', alt: ":#{custom_emoji.shortcode}:") + end + def opengraph(property, content) tag(:meta, content: content, property: property) end diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb index 28b6a2b0b..a77b53c98 100644 --- a/app/models/custom_emoji.rb +++ b/app/models/custom_emoji.rb @@ -25,6 +25,8 @@ class CustomEmoji < ApplicationRecord :(#{SHORTCODE_RE_FRAGMENT}): (?=[^[:alnum:]:]|$)/x + has_one :local_counterpart, -> { where(domain: nil) }, class_name: 'CustomEmoji', primary_key: :shortcode, foreign_key: :shortcode + has_attached_file :image, styles: { static: { format: 'png', convert_options: '-coalesce -strip' } } validates_attachment :image, content_type: { content_type: 'image/png' }, presence: true, size: { in: 0..50.kilobytes } diff --git a/app/views/admin/custom_emojis/_custom_emoji.html.haml b/app/views/admin/custom_emojis/_custom_emoji.html.haml index 399d13bbd..bab34bc8d 100644 --- a/app/views/admin/custom_emojis/_custom_emoji.html.haml +++ b/app/views/admin/custom_emojis/_custom_emoji.html.haml @@ -1,6 +1,6 @@ %tr %td - = image_tag custom_emoji.image.url, class: 'emojione', alt: ":#{custom_emoji.shortcode}:" + = custom_emoji_tag(custom_emoji) %td %samp= ":#{custom_emoji.shortcode}:" %td @@ -15,7 +15,10 @@ - else = table_link_to 'eye-slash', t('admin.custom_emojis.unlisted'), admin_custom_emoji_path(custom_emoji, custom_emoji: { visible_in_picker: true }), method: :patch - else - = table_link_to 'copy', t('admin.custom_emojis.copy'), copy_admin_custom_emoji_path(custom_emoji, page: params[:page]), method: :post + - if custom_emoji.local_counterpart.present? + = link_to safe_join([custom_emoji_tag(custom_emoji.local_counterpart), t('admin.custom_emojis.overwrite')]), copy_admin_custom_emoji_path(custom_emoji, page: params[:page]), method: :post, class: 'table-action-link' + - else + = table_link_to 'copy', t('admin.custom_emojis.copy'), copy_admin_custom_emoji_path(custom_emoji, page: params[:page]), method: :post %td - if custom_emoji.disabled? = table_link_to 'power-off', t('admin.custom_emojis.enable'), enable_admin_custom_emoji_path(custom_emoji), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } diff --git a/config/brakeman.ignore b/config/brakeman.ignore index f198eebac..f7cf89dff 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -10,7 +10,7 @@ "line": 122, "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).inbox_url, Account.find(params[:id]).inbox_url)", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/show" @@ -29,7 +29,7 @@ "line": 128, "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).shared_inbox_url, Account.find(params[:id]).shared_inbox_url)", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/show" @@ -48,7 +48,7 @@ "line": 35, "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).url, Account.find(params[:id]).url)", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/show" @@ -57,25 +57,6 @@ "confidence": "Weak", "note": "" }, - { - "warning_type": "Dynamic Render Path", - "warning_code": 15, - "fingerprint": "3b0a20b08aef13cf8cf865384fae0cfd3324d8200a83262bf4abbc8091b5fec5", - "check_name": "Render", - "message": "Render path contains parameter value", - "file": "app/views/admin/custom_emojis/index.html.haml", - "line": 31, - "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", - "code": "render(action => filtered_custom_emojis.page(params[:page]), {})", - "render_path": [{"type":"controller","class":"Admin::CustomEmojisController","method":"index","line":9,"file":"app/controllers/admin/custom_emojis_controller.rb"}], - "location": { - "type": "template", - "template": "admin/custom_emojis/index" - }, - "user_input": "params[:page]", - "confidence": "Weak", - "note": "" - }, { "warning_type": "Dynamic Render Path", "warning_code": 15, @@ -105,7 +86,7 @@ "line": 131, "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).followers_url, Account.find(params[:id]).followers_url)", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/show" @@ -124,7 +105,7 @@ "line": 106, "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).salmon_url, Account.find(params[:id]).salmon_url)", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/show" @@ -133,6 +114,25 @@ "confidence": "Weak", "note": "" }, + { + "warning_type": "Dynamic Render Path", + "warning_code": 15, + "fingerprint": "8d843713d99e8403f7992f3e72251b633817cf9076ffcbbad5613859d2bbc127", + "check_name": "Render", + "message": "Render path contains parameter value", + "file": "app/views/admin/custom_emojis/index.html.haml", + "line": 31, + "link": "http://brakemanscanner.org/docs/warning_types/dynamic_render_path/", + "code": "render(action => filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page]), {})", + "render_path": [{"type":"controller","class":"Admin::CustomEmojisController","method":"index","line":9,"file":"app/controllers/admin/custom_emojis_controller.rb"}], + "location": { + "type": "template", + "template": "admin/custom_emojis/index" + }, + "user_input": "params[:page]", + "confidence": "Weak", + "note": "" + }, { "warning_type": "SQL Injection", "warning_code": 0, @@ -140,7 +140,7 @@ "check_name": "SQL", "message": "Possible SQL injection", "file": "lib/mastodon/snowflake.rb", - "line": 86, + "line": 87, "link": "http://brakemanscanner.org/docs/warning_types/sql_injection/", "code": "connection.execute(\" CREATE OR REPLACE FUNCTION timestamp_id(table_name text)\\n RETURNS bigint AS\\n $$\\n DECLARE\\n time_part bigint;\\n sequence_base bigint;\\n tail bigint;\\n BEGIN\\n time_part := (\\n -- Get the time in milliseconds\\n ((date_part('epoch', now()) * 1000))::bigint\\n -- And shift it over two bytes\\n << 16);\\n\\n sequence_base := (\\n 'x' ||\\n -- Take the first two bytes (four hex characters)\\n substr(\\n -- Of the MD5 hash of the data we documented\\n md5(table_name ||\\n '#{SecureRandom.hex(16)}' ||\\n time_part::text\\n ),\\n 1, 4\\n )\\n -- And turn it into a bigint\\n )::bit(16)::bigint;\\n\\n -- Finally, add our sequence number to our base, and chop\\n -- it to the last two bytes\\n tail := (\\n (sequence_base + nextval(table_name || '_id_seq'))\\n & 65535);\\n\\n -- Return the time part and the sequence part. OR appears\\n -- faster here than addition, but they're equivalent:\\n -- time_part has no trailing two bytes, and tail is only\\n -- the last two bytes.\\n RETURN time_part | tail;\\n END\\n $$ LANGUAGE plpgsql VOLATILE;\\n\")", "render_path": null, @@ -182,7 +182,7 @@ "line": 95, "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).remote_url, Account.find(params[:id]).remote_url)", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/show" @@ -240,7 +240,7 @@ "line": 125, "link": "http://brakemanscanner.org/docs/warning_types/link_to_href", "code": "link_to(Account.find(params[:id]).outbox_url, Account.find(params[:id]).outbox_url)", - "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":13,"file":"app/controllers/admin/accounts_controller.rb"}], + "render_path": [{"type":"controller","class":"Admin::AccountsController","method":"show","line":15,"file":"app/controllers/admin/accounts_controller.rb"}], "location": { "type": "template", "template": "admin/accounts/show" @@ -269,6 +269,6 @@ "note": "" } ], - "updated": "2017-10-07 19:24:02 +0200", + "updated": "2017-10-20 00:00:54 +0900", "brakeman_version": "4.0.1" } diff --git a/config/locales/en.yml b/config/locales/en.yml index d72ec92d0..ce439029c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -134,6 +134,7 @@ en: listed: Listed new: title: Add new custom emoji + overwrite: Overwrite shortcode: Shortcode shortcode_hint: At least 2 characters, only alphanumeric characters and underscores title: Custom emojis -- cgit From 1032f3994fdbd61c2f517057261ddc3559199b6b Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 7 Nov 2017 19:06:44 +0100 Subject: Add ability to disable login and mark accounts as memorial (#5615) Fix #5597 --- app/controllers/admin/accounts_controller.rb | 22 ++++++++++++++- app/controllers/admin/suspensions_controller.rb | 2 +- app/javascript/styles/mastodon/landing_strip.scss | 7 ++++- app/mailers/notification_mailer.rb | 18 ++++++++---- app/mailers/user_mailer.rb | 6 ++++ app/models/account.rb | 15 ++++++++++ app/models/user.rb | 18 +++++++++++- app/services/suspend_account_service.rb | 25 +++++++++------- app/views/accounts/_header.html.haml | 33 +++++++++++----------- app/views/accounts/show.html.haml | 4 ++- app/views/admin/accounts/show.html.haml | 11 ++++++++ app/workers/admin/suspension_worker.rb | 2 +- config/locales/en.yml | 7 +++++ config/routes.rb | 3 ++ .../20171107143332_add_memorial_to_accounts.rb | 15 ++++++++++ db/migrate/20171107143624_add_disabled_to_users.rb | 15 ++++++++++ db/schema.rb | 4 ++- 17 files changed, 168 insertions(+), 39 deletions(-) create mode 100644 db/migrate/20171107143332_add_memorial_to_accounts.rb create mode 100644 db/migrate/20171107143624_add_disabled_to_users.rb (limited to 'app/views') diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb index ffa4dc850..7503b880d 100644 --- a/app/controllers/admin/accounts_controller.rb +++ b/app/controllers/admin/accounts_controller.rb @@ -2,8 +2,9 @@ module Admin class AccountsController < BaseController - before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload] + before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :enable, :disable, :memorialize] before_action :require_remote_account!, only: [:subscribe, :unsubscribe, :redownload] + before_action :require_local_account!, only: [:enable, :disable, :memorialize] def index @accounts = filtered_accounts.page(params[:page]) @@ -24,6 +25,21 @@ module Admin redirect_to admin_account_path(@account.id) end + def memorialize + @account.memorialize! + redirect_to admin_account_path(@account.id) + end + + def enable + @account.user.enable! + redirect_to admin_account_path(@account.id) + end + + def disable + @account.user.disable! + redirect_to admin_account_path(@account.id) + end + def redownload @account.reset_avatar! @account.reset_header! @@ -42,6 +58,10 @@ module Admin redirect_to admin_account_path(@account.id) if @account.local? end + def require_local_account! + redirect_to admin_account_path(@account.id) unless @account.local? && @account.user.present? + end + def filtered_accounts AccountFilter.new(filter_params).results end diff --git a/app/controllers/admin/suspensions_controller.rb b/app/controllers/admin/suspensions_controller.rb index 5d9048d94..5eaf1a2e9 100644 --- a/app/controllers/admin/suspensions_controller.rb +++ b/app/controllers/admin/suspensions_controller.rb @@ -10,7 +10,7 @@ module Admin end def destroy - @account.update(suspended: false) + @account.unsuspend! redirect_to admin_accounts_path end diff --git a/app/javascript/styles/mastodon/landing_strip.scss b/app/javascript/styles/mastodon/landing_strip.scss index 15ff84912..0bf9daafd 100644 --- a/app/javascript/styles/mastodon/landing_strip.scss +++ b/app/javascript/styles/mastodon/landing_strip.scss @@ -1,4 +1,5 @@ -.landing-strip { +.landing-strip, +.memoriam-strip { background: rgba(darken($ui-base-color, 7%), 0.8); color: $ui-primary-color; font-weight: 400; @@ -29,3 +30,7 @@ margin-bottom: 0; } } + +.memoriam-strip { + background: rgba($base-shadow-color, 0.7); +} diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb index 80c9d8ccf..d79f26366 100644 --- a/app/mailers/notification_mailer.rb +++ b/app/mailers/notification_mailer.rb @@ -7,6 +7,8 @@ class NotificationMailer < ApplicationMailer @me = recipient @status = notification.target_status + return if @me.user.disabled? + locale_for_account(@me) do thread_by_conversation(@status.conversation) mail to: @me.user.email, subject: I18n.t('notification_mailer.mention.subject', name: @status.account.acct) @@ -17,6 +19,8 @@ class NotificationMailer < ApplicationMailer @me = recipient @account = notification.from_account + return if @me.user.disabled? + locale_for_account(@me) do mail to: @me.user.email, subject: I18n.t('notification_mailer.follow.subject', name: @account.acct) end @@ -27,6 +31,8 @@ class NotificationMailer < ApplicationMailer @account = notification.from_account @status = notification.target_status + return if @me.user.disabled? + locale_for_account(@me) do thread_by_conversation(@status.conversation) mail to: @me.user.email, subject: I18n.t('notification_mailer.favourite.subject', name: @account.acct) @@ -38,6 +44,8 @@ class NotificationMailer < ApplicationMailer @account = notification.from_account @status = notification.target_status + return if @me.user.disabled? + locale_for_account(@me) do thread_by_conversation(@status.conversation) mail to: @me.user.email, subject: I18n.t('notification_mailer.reblog.subject', name: @account.acct) @@ -48,6 +56,8 @@ class NotificationMailer < ApplicationMailer @me = recipient @account = notification.from_account + return if @me.user.disabled? + locale_for_account(@me) do mail to: @me.user.email, subject: I18n.t('notification_mailer.follow_request.subject', name: @account.acct) end @@ -59,15 +69,11 @@ class NotificationMailer < ApplicationMailer @notifications = Notification.where(account: @me, activity_type: 'Mention').where('created_at > ?', @since) @follows_since = Notification.where(account: @me, activity_type: 'Follow').where('created_at > ?', @since).count - return if @notifications.empty? + return if @me.user.disabled? || @notifications.empty? locale_for_account(@me) do mail to: @me.user.email, - subject: I18n.t( - :subject, - scope: [:notification_mailer, :digest], - count: @notifications.size - ) + subject: I18n.t(:subject, scope: [:notification_mailer, :digest], count: @notifications.size) end end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index c475a9911..bdb29ebad 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -10,6 +10,8 @@ class UserMailer < Devise::Mailer @token = token @instance = Rails.configuration.x.local_domain + return if @resource.disabled? + I18n.with_locale(@resource.locale || I18n.default_locale) do mail to: @resource.unconfirmed_email.blank? ? @resource.email : @resource.unconfirmed_email, subject: I18n.t('devise.mailer.confirmation_instructions.subject', instance: @instance) end @@ -20,6 +22,8 @@ class UserMailer < Devise::Mailer @token = token @instance = Rails.configuration.x.local_domain + return if @resource.disabled? + I18n.with_locale(@resource.locale || I18n.default_locale) do mail to: @resource.email, subject: I18n.t('devise.mailer.reset_password_instructions.subject') end @@ -29,6 +33,8 @@ class UserMailer < Devise::Mailer @resource = user @instance = Rails.configuration.x.local_domain + return if @resource.disabled? + I18n.with_locale(@resource.locale || I18n.default_locale) do mail to: @resource.email, subject: I18n.t('devise.mailer.password_change.subject') end diff --git a/app/models/account.rb b/app/models/account.rb index 3dc2a95ab..1142e7c79 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -41,6 +41,7 @@ # shared_inbox_url :string default(""), not null # followers_url :string default(""), not null # protocol :integer default("ostatus"), not null +# memorial :boolean default(FALSE), not null # class Account < ApplicationRecord @@ -150,6 +151,20 @@ class Account < ApplicationRecord ResolveRemoteAccountService.new.call(acct) end + def unsuspend! + transaction do + user&.enable! if local? + update!(suspended: false) + end + end + + def memorialize! + transaction do + user&.disable! if local? + update!(memorial: true) + end + end + def keypair @keypair ||= OpenSSL::PKey::RSA.new(private_key || public_key) end diff --git a/app/models/user.rb b/app/models/user.rb index 325e27f44..836d54d15 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -5,7 +5,6 @@ # # id :integer not null, primary key # email :string default(""), not null -# account_id :integer not null # created_at :datetime not null # updated_at :datetime not null # encrypted_password :string default(""), not null @@ -31,10 +30,13 @@ # last_emailed_at :datetime # otp_backup_codes :string is an Array # filtered_languages :string default([]), not null, is an Array +# account_id :integer not null +# disabled :boolean default(FALSE), not null # class User < ApplicationRecord include Settings::Extend + ACTIVE_DURATION = 14.days devise :registerable, :recoverable, @@ -72,12 +74,26 @@ class User < ApplicationRecord confirmed_at.present? end + def disable! + update!(disabled: true, + last_sign_in_at: current_sign_in_at, + current_sign_in_at: nil) + end + + def enable! + update!(disabled: false) + end + def disable_two_factor! self.otp_required_for_login = false otp_backup_codes&.clear save! end + def active_for_authentication? + super && !disabled? + end + def setting_default_privacy settings.default_privacy || (account.locked? ? 'private' : 'public') end diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb index 983c5495b..5b37ba9ba 100644 --- a/app/services/suspend_account_service.rb +++ b/app/services/suspend_account_service.rb @@ -1,22 +1,27 @@ # frozen_string_literal: true class SuspendAccountService < BaseService - def call(account, remove_user = false) + def call(account, options = {}) @account = account + @options = options - purge_user if remove_user - purge_profile - purge_content - unsubscribe_push_subscribers + purge_user! + purge_profile! + purge_content! + unsubscribe_push_subscribers! end private - def purge_user - @account.user.destroy + def purge_user! + if @options[:remove_user] + @account.user&.destroy + else + @account.user&.disable! + end end - def purge_content + def purge_content! @account.statuses.reorder(nil).find_in_batches do |statuses| BatchedRemoveStatusService.new.call(statuses) end @@ -33,7 +38,7 @@ class SuspendAccountService < BaseService end end - def purge_profile + def purge_profile! @account.suspended = true @account.display_name = '' @account.note = '' @@ -42,7 +47,7 @@ class SuspendAccountService < BaseService @account.save! end - def unsubscribe_push_subscribers + def unsubscribe_push_subscribers! destroy_all(@account.subscriptions) end diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml index 08c3891d2..5530fcc20 100644 --- a/app/views/accounts/_header.html.haml +++ b/app/views/accounts/_header.html.haml @@ -1,21 +1,22 @@ .card.h-card.p-author{ style: "background-image: url(#{account.header.url(:original)})" } .card__illustration - - if user_signed_in? && current_account.id != account.id && !current_account.requested?(account) - .controls - - if current_account.following?(account) - = link_to account_unfollow_path(account), data: { method: :post }, class: 'icon-button' do - = fa_icon 'user-times' - = t('accounts.unfollow') - - else - = link_to account_follow_path(account), data: { method: :post }, class: 'icon-button' do - = fa_icon 'user-plus' - = t('accounts.follow') - - elsif !user_signed_in? - .controls - .remote-follow - = link_to account_remote_follow_path(account), class: 'icon-button' do - = fa_icon 'user-plus' - = t('accounts.remote_follow') + - unless account.memorial? + - if user_signed_in? && current_account.id != account.id && !current_account.requested?(account) + .controls + - if current_account.following?(account) + = link_to account_unfollow_path(account), data: { method: :post }, class: 'icon-button' do + = fa_icon 'user-times' + = t('accounts.unfollow') + - else + = link_to account_follow_path(account), data: { method: :post }, class: 'icon-button' do + = fa_icon 'user-plus' + = t('accounts.follow') + - elsif !user_signed_in? + .controls + .remote-follow + = link_to account_remote_follow_path(account), class: 'icon-button' do + = fa_icon 'user-plus' + = t('accounts.remote_follow') .avatar= image_tag account.avatar.url(:original), class: 'u-photo' diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index 6c90b2c04..fd8ad5530 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -12,7 +12,9 @@ = opengraph 'og:type', 'profile' = render 'og', account: @account, url: short_account_url(@account, only_path: false) -- if show_landing_strip? +- if @account.memorial? + .memoriam-strip= t('in_memoriam_html') +- elsif show_landing_strip? = render partial: 'shared/landing_strip', locals: { account: @account } .h-feed diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index 1f5c8fcf5..b5ce56dbc 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -18,6 +18,15 @@ %tr %th= t('admin.accounts.email') %td= @account.user_email + %tr + %th= t('admin.accounts.login_status') + %td + - if @account.user&.disabled? + = t('admin.accounts.disabled') + = table_link_to 'unlock', t('admin.accounts.enable'), enable_admin_account_path(@account.id), method: :post + - else + = t('admin.accounts.enabled') + = table_link_to 'lock', t('admin.accounts.disable'), disable_admin_account_path(@account.id), method: :post %tr %th= t('admin.accounts.most_recent_ip') %td= @account.user_current_sign_in_ip @@ -65,6 +74,8 @@ = link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' - if @account.user&.otp_required_for_login? = link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' + - unless @account.memorial? + = link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' - else = link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' diff --git a/app/workers/admin/suspension_worker.rb b/app/workers/admin/suspension_worker.rb index 6338b1130..e41465ccc 100644 --- a/app/workers/admin/suspension_worker.rb +++ b/app/workers/admin/suspension_worker.rb @@ -6,6 +6,6 @@ class Admin::SuspensionWorker sidekiq_options queue: 'pull' def perform(account_id, remove_user = false) - SuspendAccountService.new.call(Account.find(account_id), remove_user) + SuspendAccountService.new.call(Account.find(account_id), remove_user: remove_user) end end diff --git a/config/locales/en.yml b/config/locales/en.yml index ce439029c..41a636760 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -62,11 +62,15 @@ en: by_domain: Domain confirm: Confirm confirmed: Confirmed + disable: Disable disable_two_factor_authentication: Disable 2FA + disabled: Disabled display_name: Display name domain: Domain edit: Edit email: E-mail + enable: Enable + enabled: Enabled feed_url: Feed URL followers: Followers followers_url: Followers URL @@ -78,7 +82,9 @@ en: local: Local remote: Remote title: Location + login_status: Login status media_attachments: Media attachments + memorialize: Turn into memoriam moderation: all: All silenced: Silenced @@ -379,6 +385,7 @@ en: following: Following list muting: Muting list upload: Upload + in_memoriam_html: In Memoriam. landing_strip_html: "%{name} is a user on %{link_to_root_path}. You can follow them or interact with them if you have an account anywhere in the fediverse." landing_strip_signup_html: If you don't, you can sign up here. media_attachments: diff --git a/config/routes.rb b/config/routes.rb index bdfcdaff6..e6d6b52f7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -126,7 +126,10 @@ Rails.application.routes.draw do member do post :subscribe post :unsubscribe + post :enable + post :disable post :redownload + post :memorialize end resource :reset, only: [:create] diff --git a/db/migrate/20171107143332_add_memorial_to_accounts.rb b/db/migrate/20171107143332_add_memorial_to_accounts.rb new file mode 100644 index 000000000..f3e012ce8 --- /dev/null +++ b/db/migrate/20171107143332_add_memorial_to_accounts.rb @@ -0,0 +1,15 @@ +require Rails.root.join('lib', 'mastodon', 'migration_helpers') + +class AddMemorialToAccounts < ActiveRecord::Migration[5.1] + include Mastodon::MigrationHelpers + + disable_ddl_transaction! + + def up + safety_assured { add_column_with_default :accounts, :memorial, :bool, default: false } + end + + def down + remove_column :accounts, :memorial + end +end diff --git a/db/migrate/20171107143624_add_disabled_to_users.rb b/db/migrate/20171107143624_add_disabled_to_users.rb new file mode 100644 index 000000000..a71cac1c6 --- /dev/null +++ b/db/migrate/20171107143624_add_disabled_to_users.rb @@ -0,0 +1,15 @@ +require Rails.root.join('lib', 'mastodon', 'migration_helpers') + +class AddDisabledToUsers < ActiveRecord::Migration[5.1] + include Mastodon::MigrationHelpers + + disable_ddl_transaction! + + def up + safety_assured { add_column_with_default :users, :disabled, :bool, default: false } + end + + def down + remove_column :users, :disabled + end +end diff --git a/db/schema.rb b/db/schema.rb index 697a7f374..935fd79c5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171020084748) do +ActiveRecord::Schema.define(version: 20171107143624) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -71,6 +71,7 @@ ActiveRecord::Schema.define(version: 20171020084748) do t.string "shared_inbox_url", default: "", null: false t.string "followers_url", default: "", null: false t.integer "protocol", default: 0, null: false + t.boolean "memorial", default: false, null: false t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower" t.index ["uri"], name: "index_accounts_on_uri" @@ -435,6 +436,7 @@ ActiveRecord::Schema.define(version: 20171020084748) do t.string "otp_backup_codes", array: true t.string "filtered_languages", default: [], null: false, array: true t.bigint "account_id", null: false + t.boolean "disabled", default: false, null: false t.index ["account_id"], name: "index_users_on_account_id" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true -- cgit From eb97bd8af6c9a06a52cc8b12d05ac9ea1aa5370a Mon Sep 17 00:00:00 2001 From: Quenty31 <33203663+Quenty31@users.noreply.github.com> Date: Wed, 8 Nov 2017 15:19:49 +0100 Subject: i10n OC: Memorial (#5615) + #5467 (#5623) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Changed ĩ => ï * Changed ĩ => ï * Add ability to disable login and mark accounts as memorial (#5615) --- app/views/user_mailer/reset_password_instructions.oc.html.erb | 2 +- app/views/user_mailer/reset_password_instructions.oc.text.erb | 2 +- config/locales/oc.yml | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) (limited to 'app/views') diff --git a/app/views/user_mailer/reset_password_instructions.oc.html.erb b/app/views/user_mailer/reset_password_instructions.oc.html.erb index 6c775b3a1..92e4b8f8b 100644 --- a/app/views/user_mailer/reset_password_instructions.oc.html.erb +++ b/app/views/user_mailer/reset_password_instructions.oc.html.erb @@ -1,6 +1,6 @@

Bonjorn <%= @resource.email %> !

-

Qualqu’un a demandat la reĩnicializacion de vòstre senhal per Mastodon. Podètz realizar la reĩnicializacion en clicant sul ligam çai-jos.

+

Qualqu’un a demandat la reïnicializacion de vòstre senhal per Mastodon. Podètz realizar la reïnicializacion en clicant sul ligam çai-jos.

<%= link_to 'Modificar mon senhal', edit_password_url(@resource, reset_password_token: @token) %>

diff --git a/app/views/user_mailer/reset_password_instructions.oc.text.erb b/app/views/user_mailer/reset_password_instructions.oc.text.erb index 26432d2df..5a5219589 100644 --- a/app/views/user_mailer/reset_password_instructions.oc.text.erb +++ b/app/views/user_mailer/reset_password_instructions.oc.text.erb @@ -1,6 +1,6 @@ Bonjorn <%= @resource.email %> ! -Qualqu’un a demandat la reĩnicializacion de vòstre senhal per Mastodon. Podètz realizar la reĩnicializacion en clicant sul ligam çai-jos.

+Qualqu’un a demandat la reïnicializacion de vòstre senhal per Mastodon. Podètz realizar la reïnicializacion en clicant sul ligam çai-jos.

<%= link_to 'Modificar mon senhal', edit_password_url(@resource, reset_password_token: @token) %> diff --git a/config/locales/oc.yml b/config/locales/oc.yml index 8e7602106..914cc7e9d 100644 --- a/config/locales/oc.yml +++ b/config/locales/oc.yml @@ -62,11 +62,15 @@ oc: by_domain: Domeni confirm: Confirmar confirmed: Confirmat + disable: Desactivar disable_two_factor_authentication: Desactivar 2FA + disabled: Desactivat display_name: Escais-nom domain: Domeni edit: Modificar email: Corrièl + enable: Activar + enabled: Activat feed_url: Flux URL followers: Seguidors followers_url: URL dels seguidors @@ -78,7 +82,9 @@ oc: local: Locals remote: Alonhats title: Emplaçament + login_status: Estat formulari de connexion media_attachments: Mèdias ajustats + memorialize: Passar en memorial moderation: all: Tot silenced: Rescondut @@ -134,6 +140,7 @@ oc: listed: Listat new: title: Ajustar un nòu emoji personal + overwrite: Remplaçar shortcode: Acorchi shortcode_hint: Almens 2 caractèrs, solament alfanumerics e jonhent bas title: Emojis personals @@ -456,6 +463,7 @@ oc: following: Lista de mond que seguètz muting: Lista de mond que volètz pas legir upload: Importar + in_memoriam_html: En Memòria. landing_strip_html: "%{name} utiliza %{link_to_root_path}. Podètz lo/la sègre o interagir amb el o ela s’avètz un compte ont que siasque sul fediverse." landing_strip_signup_html: S’es pas lo cas, podètz vos marcar aquí. media_attachments: -- cgit From 7bb8b0b2fc0e2e42a4234fed18198cbb7439fe9f Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 11 Nov 2017 20:23:33 +0100 Subject: Add moderator role and add pundit policies for admin actions (#5635) * Add moderator role and add pundit policies for admin actions * Add rake task for turning user into mod and revoking it again * Fix handling of unauthorized exception * Deliver new report e-mails to staff, not just admins * Add promote/demote to admin UI, hide some actions conditionally * Fix unused i18n --- .../admin/account_moderation_notes_controller.rb | 56 +++++++++++++--------- app/controllers/admin/accounts_controller.rb | 9 ++++ app/controllers/admin/base_controller.rb | 4 +- app/controllers/admin/confirmations_controller.rb | 9 ++-- app/controllers/admin/custom_emojis_controller.rb | 11 +++++ app/controllers/admin/domain_blocks_controller.rb | 9 +++- .../admin/email_domain_blocks_controller.rb | 5 ++ app/controllers/admin/instances_controller.rb | 2 + .../admin/reported_statuses_controller.rb | 9 ++-- app/controllers/admin/reports_controller.rb | 3 ++ app/controllers/admin/resets_controller.rb | 9 ++-- app/controllers/admin/roles_controller.rb | 25 ++++++++++ app/controllers/admin/settings_controller.rb | 3 ++ app/controllers/admin/silences_controller.rb | 2 + app/controllers/admin/statuses_controller.rb | 17 ++++--- app/controllers/admin/subscriptions_controller.rb | 1 + app/controllers/admin/suspensions_controller.rb | 2 + .../admin/two_factor_authentications_controller.rb | 1 + app/controllers/api/v1/reports_controller.rb | 2 +- app/controllers/application_controller.rb | 5 ++ app/controllers/concerns/authorization.rb | 1 + app/helpers/application_helper.rb | 5 ++ app/models/user.rb | 42 +++++++++++++++- app/policies/account_moderation_note_policy.rb | 17 +++++++ app/policies/account_policy.rb | 43 +++++++++++++++++ app/policies/application_policy.rb | 18 +++++++ app/policies/custom_emoji_policy.rb | 31 ++++++++++++ app/policies/domain_block_policy.rb | 19 ++++++++ app/policies/email_domain_block_policy.rb | 15 ++++++ app/policies/instance_policy.rb | 11 +++++ app/policies/report_policy.rb | 15 ++++++ app/policies/settings_policy.rb | 11 +++++ app/policies/status_policy.rb | 35 +++++++------- app/policies/subscription_policy.rb | 7 +++ app/policies/user_policy.rb | 41 ++++++++++++++++ .../_account_moderation_note.html.haml | 2 +- app/views/admin/accounts/show.html.haml | 46 ++++++++++++------ config/i18n-tasks.yml | 2 + config/locales/en.yml | 7 +++ config/navigation.rb | 16 +++---- config/routes.rb | 7 +++ .../20171109012327_add_moderator_to_accounts.rb | 15 ++++++ db/schema.rb | 3 +- lib/tasks/mastodon.rake | 31 +++++++++++- 44 files changed, 536 insertions(+), 88 deletions(-) create mode 100644 app/controllers/admin/roles_controller.rb create mode 100644 app/policies/account_moderation_note_policy.rb create mode 100644 app/policies/account_policy.rb create mode 100644 app/policies/application_policy.rb create mode 100644 app/policies/custom_emoji_policy.rb create mode 100644 app/policies/domain_block_policy.rb create mode 100644 app/policies/email_domain_block_policy.rb create mode 100644 app/policies/instance_policy.rb create mode 100644 app/policies/report_policy.rb create mode 100644 app/policies/settings_policy.rb create mode 100644 app/policies/subscription_policy.rb create mode 100644 app/policies/user_policy.rb create mode 100644 db/migrate/20171109012327_add_moderator_to_accounts.rb (limited to 'app/views') diff --git a/app/controllers/admin/account_moderation_notes_controller.rb b/app/controllers/admin/account_moderation_notes_controller.rb index 414a875d0..7f69a3363 100644 --- a/app/controllers/admin/account_moderation_notes_controller.rb +++ b/app/controllers/admin/account_moderation_notes_controller.rb @@ -1,31 +1,41 @@ # frozen_string_literal: true -class Admin::AccountModerationNotesController < Admin::BaseController - def create - @account_moderation_note = current_account.account_moderation_notes.new(resource_params) - if @account_moderation_note.save - @target_account = @account_moderation_note.target_account - redirect_to admin_account_path(@target_account.id), notice: I18n.t('admin.account_moderation_notes.created_msg') - else - @account = @account_moderation_note.target_account - @moderation_notes = @account.targeted_moderation_notes.latest - render template: 'admin/accounts/show' +module Admin + class AccountModerationNotesController < BaseController + before_action :set_account_moderation_note, only: [:destroy] + + def create + authorize AccountModerationNote, :create? + + @account_moderation_note = current_account.account_moderation_notes.new(resource_params) + + if @account_moderation_note.save + redirect_to admin_account_path(@account_moderation_note.target_account_id), notice: I18n.t('admin.account_moderation_notes.created_msg') + else + @account = @account_moderation_note.target_account + @moderation_notes = @account.targeted_moderation_notes.latest + + render template: 'admin/accounts/show' + end end - end - def destroy - @account_moderation_note = AccountModerationNote.find(params[:id]) - @target_account = @account_moderation_note.target_account - @account_moderation_note.destroy - redirect_to admin_account_path(@target_account.id), notice: I18n.t('admin.account_moderation_notes.destroyed_msg') - end + def destroy + authorize @account_moderation_note, :destroy? + @account_moderation_note.destroy + redirect_to admin_account_path(@account_moderation_note.target_account_id), notice: I18n.t('admin.account_moderation_notes.destroyed_msg') + end - private + private - def resource_params - params.require(:account_moderation_note).permit( - :content, - :target_account_id - ) + def resource_params + params.require(:account_moderation_note).permit( + :content, + :target_account_id + ) + end + + def set_account_moderation_note + @account_moderation_note = AccountModerationNote.find(params[:id]) + end end end diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb index 7503b880d..0829bc769 100644 --- a/app/controllers/admin/accounts_controller.rb +++ b/app/controllers/admin/accounts_controller.rb @@ -7,40 +7,49 @@ module Admin before_action :require_local_account!, only: [:enable, :disable, :memorialize] def index + authorize :account, :index? @accounts = filtered_accounts.page(params[:page]) end def show + authorize @account, :show? @account_moderation_note = current_account.account_moderation_notes.new(target_account: @account) @moderation_notes = @account.targeted_moderation_notes.latest end def subscribe + authorize @account, :subscribe? Pubsubhubbub::SubscribeWorker.perform_async(@account.id) redirect_to admin_account_path(@account.id) end def unsubscribe + authorize @account, :unsubscribe? Pubsubhubbub::UnsubscribeWorker.perform_async(@account.id) redirect_to admin_account_path(@account.id) end def memorialize + authorize @account, :memorialize? @account.memorialize! redirect_to admin_account_path(@account.id) end def enable + authorize @account.user, :enable? @account.user.enable! redirect_to admin_account_path(@account.id) end def disable + authorize @account.user, :disable? @account.user.disable! redirect_to admin_account_path(@account.id) end def redownload + authorize @account, :redownload? + @account.reset_avatar! @account.reset_header! @account.save! diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb index 11fe326bc..db4839a8f 100644 --- a/app/controllers/admin/base_controller.rb +++ b/app/controllers/admin/base_controller.rb @@ -2,7 +2,9 @@ module Admin class BaseController < ApplicationController - before_action :require_admin! + include Authorization + + before_action :require_staff! layout 'admin' end diff --git a/app/controllers/admin/confirmations_controller.rb b/app/controllers/admin/confirmations_controller.rb index 2542e21ee..c10b0ebee 100644 --- a/app/controllers/admin/confirmations_controller.rb +++ b/app/controllers/admin/confirmations_controller.rb @@ -2,15 +2,18 @@ module Admin class ConfirmationsController < BaseController + before_action :set_user + def create - account_user.confirm + authorize @user, :confirm? + @user.confirm! redirect_to admin_accounts_path end private - def account_user - Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound) + def set_user + @user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound) end end end diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb index daa1460fb..693d28b1f 100644 --- a/app/controllers/admin/custom_emojis_controller.rb +++ b/app/controllers/admin/custom_emojis_controller.rb @@ -5,14 +5,18 @@ module Admin before_action :set_custom_emoji, except: [:index, :new, :create] def index + authorize :custom_emoji, :index? @custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page]) end def new + authorize :custom_emoji, :create? @custom_emoji = CustomEmoji.new end def create + authorize :custom_emoji, :create? + @custom_emoji = CustomEmoji.new(resource_params) if @custom_emoji.save @@ -23,6 +27,8 @@ module Admin end def update + authorize @custom_emoji, :update? + if @custom_emoji.update(resource_params) redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.updated_msg') else @@ -31,11 +37,14 @@ module Admin end def destroy + authorize @custom_emoji, :destroy? @custom_emoji.destroy redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.destroyed_msg') end def copy + authorize @custom_emoji, :copy? + emoji = CustomEmoji.find_or_create_by(domain: nil, shortcode: @custom_emoji.shortcode) if emoji.update(image: @custom_emoji.image) @@ -48,11 +57,13 @@ module Admin end def enable + authorize @custom_emoji, :enable? @custom_emoji.update!(disabled: false) redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.enabled_msg') end def disable + authorize @custom_emoji, :disable? @custom_emoji.update!(disabled: true) redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.disabled_msg') end diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb index 1ab620e03..e383dc831 100644 --- a/app/controllers/admin/domain_blocks_controller.rb +++ b/app/controllers/admin/domain_blocks_controller.rb @@ -5,14 +5,18 @@ module Admin before_action :set_domain_block, only: [:show, :destroy] def index + authorize :domain_block, :index? @domain_blocks = DomainBlock.page(params[:page]) end def new + authorize :domain_block, :create? @domain_block = DomainBlock.new end def create + authorize :domain_block, :create? + @domain_block = DomainBlock.new(resource_params) if @domain_block.save @@ -23,9 +27,12 @@ module Admin end end - def show; end + def show + authorize @domain_block, :show? + end def destroy + authorize @domain_block, :destroy? UnblockDomainService.new.call(@domain_block, retroactive_unblock?) redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.destroyed_msg') end diff --git a/app/controllers/admin/email_domain_blocks_controller.rb b/app/controllers/admin/email_domain_blocks_controller.rb index 09275d5dc..01058bf46 100644 --- a/app/controllers/admin/email_domain_blocks_controller.rb +++ b/app/controllers/admin/email_domain_blocks_controller.rb @@ -5,14 +5,18 @@ module Admin before_action :set_email_domain_block, only: [:show, :destroy] def index + authorize :email_domain_block, :index? @email_domain_blocks = EmailDomainBlock.page(params[:page]) end def new + authorize :email_domain_block, :create? @email_domain_block = EmailDomainBlock.new end def create + authorize :email_domain_block, :create? + @email_domain_block = EmailDomainBlock.new(resource_params) if @email_domain_block.save @@ -23,6 +27,7 @@ module Admin end def destroy + authorize @email_domain_block, :destroy? @email_domain_block.destroy redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.destroyed_msg') end diff --git a/app/controllers/admin/instances_controller.rb b/app/controllers/admin/instances_controller.rb index 22f02e5d0..8ed0ea421 100644 --- a/app/controllers/admin/instances_controller.rb +++ b/app/controllers/admin/instances_controller.rb @@ -3,10 +3,12 @@ module Admin class InstancesController < BaseController def index + authorize :instance, :index? @instances = ordered_instances end def resubscribe + authorize :instance, :resubscribe? params.require(:by_domain) Pubsubhubbub::SubscribeWorker.push_bulk(subscribeable_accounts.pluck(:id)) redirect_to admin_instances_path diff --git a/app/controllers/admin/reported_statuses_controller.rb b/app/controllers/admin/reported_statuses_controller.rb index 5a31adecf..4f66ce708 100644 --- a/app/controllers/admin/reported_statuses_controller.rb +++ b/app/controllers/admin/reported_statuses_controller.rb @@ -2,19 +2,20 @@ module Admin class ReportedStatusesController < BaseController - include Authorization - before_action :set_report before_action :set_status, only: [:update, :destroy] def create - @form = Form::StatusBatch.new(form_status_batch_params) - flash[:alert] = t('admin.statuses.failed_to_execute') unless @form.save + authorize :status, :update? + + @form = Form::StatusBatch.new(form_status_batch_params) + flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save redirect_to admin_report_path(@report) end def update + authorize @status, :update? @status.update(status_params) redirect_to admin_report_path(@report) end diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index 226467739..745757ee8 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -5,14 +5,17 @@ module Admin before_action :set_report, except: [:index] def index + authorize :report, :index? @reports = filtered_reports.page(params[:page]) end def show + authorize @report, :show? @form = Form::StatusBatch.new end def update + authorize @report, :update? process_report redirect_to admin_report_path(@report) end diff --git a/app/controllers/admin/resets_controller.rb b/app/controllers/admin/resets_controller.rb index 6db648403..00b590bf6 100644 --- a/app/controllers/admin/resets_controller.rb +++ b/app/controllers/admin/resets_controller.rb @@ -2,17 +2,18 @@ module Admin class ResetsController < BaseController - before_action :set_account + before_action :set_user def create - @account.user.send_reset_password_instructions + authorize @user, :reset_password? + @user.send_reset_password_instructions redirect_to admin_accounts_path end private - def set_account - @account = Account.find(params[:account_id]) + def set_user + @user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound) end end end diff --git a/app/controllers/admin/roles_controller.rb b/app/controllers/admin/roles_controller.rb new file mode 100644 index 000000000..8f8685827 --- /dev/null +++ b/app/controllers/admin/roles_controller.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Admin + class RolesController < BaseController + before_action :set_user + + def promote + authorize @user, :promote? + @user.promote! + redirect_to admin_account_path(@user.account_id) + end + + def demote + authorize @user, :demote? + @user.demote! + redirect_to admin_account_path(@user.account_id) + end + + private + + def set_user + @user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound) + end + end +end diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb index a2f86b8a9..e81290228 100644 --- a/app/controllers/admin/settings_controller.rb +++ b/app/controllers/admin/settings_controller.rb @@ -28,10 +28,13 @@ module Admin ).freeze def edit + authorize :settings, :show? @admin_settings = Form::AdminSettings.new end def update + authorize :settings, :update? + settings_params.each do |key, value| if UPLOAD_SETTINGS.include?(key) upload = SiteUpload.where(var: key).first_or_initialize(var: key) diff --git a/app/controllers/admin/silences_controller.rb b/app/controllers/admin/silences_controller.rb index 81a3008b9..01fb292de 100644 --- a/app/controllers/admin/silences_controller.rb +++ b/app/controllers/admin/silences_controller.rb @@ -5,11 +5,13 @@ module Admin before_action :set_account def create + authorize @account, :silence? @account.update(silenced: true) redirect_to admin_accounts_path end def destroy + authorize @account, :unsilence? @account.update(silenced: false) redirect_to admin_accounts_path end diff --git a/app/controllers/admin/statuses_controller.rb b/app/controllers/admin/statuses_controller.rb index b05000b16..b54a9b824 100644 --- a/app/controllers/admin/statuses_controller.rb +++ b/app/controllers/admin/statuses_controller.rb @@ -2,8 +2,6 @@ module Admin class StatusesController < BaseController - include Authorization - helper_method :current_params before_action :set_account @@ -12,24 +10,30 @@ module Admin PER_PAGE = 20 def index + authorize :status, :index? + @statuses = @account.statuses + if params[:media] account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).distinct @statuses.merge!(Status.where(id: account_media_status_ids)) end - @statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PER_PAGE) - @form = Form::StatusBatch.new + @statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PER_PAGE) + @form = Form::StatusBatch.new end def create - @form = Form::StatusBatch.new(form_status_batch_params) - flash[:alert] = t('admin.statuses.failed_to_execute') unless @form.save + authorize :status, :update? + + @form = Form::StatusBatch.new(form_status_batch_params) + flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save redirect_to admin_account_statuses_path(@account.id, current_params) end def update + authorize @status, :update? @status.update(status_params) redirect_to admin_account_statuses_path(@account.id, current_params) end @@ -60,6 +64,7 @@ module Admin def current_params page = (params[:page] || 1).to_i + { media: params[:media], page: page > 1 && page, diff --git a/app/controllers/admin/subscriptions_controller.rb b/app/controllers/admin/subscriptions_controller.rb index 624a475a3..40500ef43 100644 --- a/app/controllers/admin/subscriptions_controller.rb +++ b/app/controllers/admin/subscriptions_controller.rb @@ -3,6 +3,7 @@ module Admin class SubscriptionsController < BaseController def index + authorize :subscription, :index? @subscriptions = ordered_subscriptions.page(requested_page) end diff --git a/app/controllers/admin/suspensions_controller.rb b/app/controllers/admin/suspensions_controller.rb index 5eaf1a2e9..778feea5e 100644 --- a/app/controllers/admin/suspensions_controller.rb +++ b/app/controllers/admin/suspensions_controller.rb @@ -5,11 +5,13 @@ module Admin before_action :set_account def create + authorize @account, :suspend? Admin::SuspensionWorker.perform_async(@account.id) redirect_to admin_accounts_path end def destroy + authorize @account, :unsuspend? @account.unsuspend! redirect_to admin_accounts_path end diff --git a/app/controllers/admin/two_factor_authentications_controller.rb b/app/controllers/admin/two_factor_authentications_controller.rb index 69c08f605..5a45d25cd 100644 --- a/app/controllers/admin/two_factor_authentications_controller.rb +++ b/app/controllers/admin/two_factor_authentications_controller.rb @@ -5,6 +5,7 @@ module Admin before_action :set_user def destroy + authorize @user, :disable_2fa? @user.disable_two_factor! redirect_to admin_accounts_path end diff --git a/app/controllers/api/v1/reports_controller.rb b/app/controllers/api/v1/reports_controller.rb index 9592cd4bd..22828217d 100644 --- a/app/controllers/api/v1/reports_controller.rb +++ b/app/controllers/api/v1/reports_controller.rb @@ -19,7 +19,7 @@ class Api::V1::ReportsController < Api::BaseController comment: report_params[:comment] ) - User.admins.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later } + User.staff.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later } render json: @report, serializer: REST::ReportSerializer end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d5eca6ffb..f41a7f9be 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -18,6 +18,7 @@ class ApplicationController < ActionController::Base rescue_from ActionController::RoutingError, with: :not_found rescue_from ActiveRecord::RecordNotFound, with: :not_found rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity + rescue_from Mastodon::NotPermittedError, with: :forbidden before_action :store_current_location, except: :raise_not_found, unless: :devise_controller? before_action :check_suspension, if: :user_signed_in? @@ -40,6 +41,10 @@ class ApplicationController < ActionController::Base redirect_to root_path unless current_user&.admin? end + def require_staff! + redirect_to root_path unless current_user&.staff? + end + def check_suspension forbidden if current_user.account.suspended? end diff --git a/app/controllers/concerns/authorization.rb b/app/controllers/concerns/authorization.rb index 7828fe48d..95a37e379 100644 --- a/app/controllers/concerns/authorization.rb +++ b/app/controllers/concerns/authorization.rb @@ -2,6 +2,7 @@ module Authorization extend ActiveSupport::Concern + include Pundit def pundit_user diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 310e1b1b1..7dfab1df1 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -35,6 +35,11 @@ module ApplicationHelper Rails.env.production? ? site_title : "#{site_title} (Dev)" end + def can?(action, record) + return false if record.nil? + policy(record).public_send("#{action}?") + end + def fa_icon(icon, attributes = {}) class_names = attributes[:class]&.split(' ') || [] class_names << 'fa' diff --git a/app/models/user.rb b/app/models/user.rb index 836d54d15..9022e6ea8 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -32,6 +32,7 @@ # filtered_languages :string default([]), not null, is an Array # account_id :integer not null # disabled :boolean default(FALSE), not null +# moderator :boolean default(FALSE), not null # class User < ApplicationRecord @@ -53,8 +54,10 @@ class User < ApplicationRecord validates :locale, inclusion: I18n.available_locales.map(&:to_s), if: :locale? validates_with BlacklistedEmailValidator, if: :email_changed? - scope :recent, -> { order(id: :desc) } - scope :admins, -> { where(admin: true) } + scope :recent, -> { order(id: :desc) } + scope :admins, -> { where(admin: true) } + scope :moderators, -> { where(moderator: true) } + scope :staff, -> { admins.or(moderators) } scope :confirmed, -> { where.not(confirmed_at: nil) } scope :inactive, -> { where(arel_table[:current_sign_in_at].lt(ACTIVE_DURATION.ago)) } scope :active, -> { confirmed.where(arel_table[:current_sign_in_at].gteq(ACTIVE_DURATION.ago)).joins(:account).where(accounts: { suspended: false }) } @@ -74,6 +77,20 @@ class User < ApplicationRecord confirmed_at.present? end + def staff? + admin? || moderator? + end + + def role + if admin? + 'admin' + elsif moderator? + 'moderator' + else + 'user' + end + end + def disable! update!(disabled: true, last_sign_in_at: current_sign_in_at, @@ -84,6 +101,27 @@ class User < ApplicationRecord update!(disabled: false) end + def confirm! + skip_confirmation! + save! + end + + def promote! + if moderator? + update!(moderator: false, admin: true) + elsif !admin? + update!(moderator: true) + end + end + + def demote! + if admin? + update!(admin: false, moderator: true) + elsif moderator? + update!(moderator: false) + end + end + def disable_two_factor! self.otp_required_for_login = false otp_backup_codes&.clear diff --git a/app/policies/account_moderation_note_policy.rb b/app/policies/account_moderation_note_policy.rb new file mode 100644 index 000000000..885411a5b --- /dev/null +++ b/app/policies/account_moderation_note_policy.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AccountModerationNotePolicy < ApplicationPolicy + def create? + staff? + end + + def destroy? + admin? || owner? + end + + private + + def owner? + record.account_id == current_account&.id + end +end diff --git a/app/policies/account_policy.rb b/app/policies/account_policy.rb new file mode 100644 index 000000000..85e2c8419 --- /dev/null +++ b/app/policies/account_policy.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +class AccountPolicy < ApplicationPolicy + def index? + staff? + end + + def show? + staff? + end + + def suspend? + staff? && !record.user&.staff? + end + + def unsuspend? + staff? + end + + def silence? + staff? && !record.user&.staff? + end + + def unsilence? + staff? + end + + def redownload? + admin? + end + + def subscribe? + admin? + end + + def unsubscribe? + admin? + end + + def memorialize? + admin? && !record.user&.admin? + end +end diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb new file mode 100644 index 000000000..3e617001f --- /dev/null +++ b/app/policies/application_policy.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class ApplicationPolicy + attr_reader :current_account, :record + + def initialize(current_account, record) + @current_account = current_account + @record = record + end + + delegate :admin?, :moderator?, :staff?, to: :current_user, allow_nil: true + + private + + def current_user + current_account&.user + end +end diff --git a/app/policies/custom_emoji_policy.rb b/app/policies/custom_emoji_policy.rb new file mode 100644 index 000000000..a8c3cbc73 --- /dev/null +++ b/app/policies/custom_emoji_policy.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class CustomEmojiPolicy < ApplicationPolicy + def index? + staff? + end + + def create? + admin? + end + + def update? + admin? + end + + def copy? + admin? + end + + def enable? + staff? + end + + def disable? + staff? + end + + def destroy? + admin? + end +end diff --git a/app/policies/domain_block_policy.rb b/app/policies/domain_block_policy.rb new file mode 100644 index 000000000..47c0a81af --- /dev/null +++ b/app/policies/domain_block_policy.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class DomainBlockPolicy < ApplicationPolicy + def index? + admin? + end + + def show? + admin? + end + + def create? + admin? + end + + def destroy? + admin? + end +end diff --git a/app/policies/email_domain_block_policy.rb b/app/policies/email_domain_block_policy.rb new file mode 100644 index 000000000..5a75ee183 --- /dev/null +++ b/app/policies/email_domain_block_policy.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class EmailDomainBlockPolicy < ApplicationPolicy + def index? + admin? + end + + def create? + admin? + end + + def destroy? + admin? + end +end diff --git a/app/policies/instance_policy.rb b/app/policies/instance_policy.rb new file mode 100644 index 000000000..d1956e2de --- /dev/null +++ b/app/policies/instance_policy.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class InstancePolicy < ApplicationPolicy + def index? + admin? + end + + def resubscribe? + admin? + end +end diff --git a/app/policies/report_policy.rb b/app/policies/report_policy.rb new file mode 100644 index 000000000..95b5c30c8 --- /dev/null +++ b/app/policies/report_policy.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class ReportPolicy < ApplicationPolicy + def update? + staff? + end + + def index? + staff? + end + + def show? + staff? + end +end diff --git a/app/policies/settings_policy.rb b/app/policies/settings_policy.rb new file mode 100644 index 000000000..2dcb79f51 --- /dev/null +++ b/app/policies/settings_policy.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class SettingsPolicy < ApplicationPolicy + def update? + admin? + end + + def show? + admin? + end +end diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb index 2ded61850..0373fdf04 100644 --- a/app/policies/status_policy.rb +++ b/app/policies/status_policy.rb @@ -1,20 +1,17 @@ # frozen_string_literal: true -class StatusPolicy - attr_reader :account, :status - - def initialize(account, status) - @account = account - @status = status +class StatusPolicy < ApplicationPolicy + def index? + staff? end def show? if direct? - owned? || status.mentions.where(account: account).exists? + owned? || record.mentions.where(account: current_account).exists? elsif private? - owned? || account&.following?(status.account) || status.mentions.where(account: account).exists? + owned? || current_account&.following?(author) || record.mentions.where(account: current_account).exists? else - account.nil? || !status.account.blocking?(account) + current_account.nil? || !author.blocking?(current_account) end end @@ -23,26 +20,30 @@ class StatusPolicy end def destroy? - admin? || owned? + staff? || owned? end alias unreblog? destroy? - private - - def admin? - account&.user&.admin? + def update? + staff? end + private + def direct? - status.direct_visibility? + record.direct_visibility? end def owned? - status.account.id == account&.id + author.id == current_account&.id end def private? - status.private_visibility? + record.private_visibility? + end + + def author + record.account end end diff --git a/app/policies/subscription_policy.rb b/app/policies/subscription_policy.rb new file mode 100644 index 000000000..ac9a8a6c4 --- /dev/null +++ b/app/policies/subscription_policy.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class SubscriptionPolicy < ApplicationPolicy + def index? + admin? + end +end diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb new file mode 100644 index 000000000..aae207d06 --- /dev/null +++ b/app/policies/user_policy.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +class UserPolicy < ApplicationPolicy + def reset_password? + staff? && !record.staff? + end + + def disable_2fa? + admin? && !record.staff? + end + + def confirm? + staff? && !record.confirmed? + end + + def enable? + admin? + end + + def disable? + admin? && !record.admin? + end + + def promote? + admin? && promoteable? + end + + def demote? + admin? && !record.admin? && demoteable? + end + + private + + def promoteable? + !record.staff? || !record.admin? + end + + def demoteable? + record.staff? + end +end diff --git a/app/views/admin/account_moderation_notes/_account_moderation_note.html.haml b/app/views/admin/account_moderation_notes/_account_moderation_note.html.haml index 4651630e9..6761a4319 100644 --- a/app/views/admin/account_moderation_notes/_account_moderation_note.html.haml +++ b/app/views/admin/account_moderation_notes/_account_moderation_note.html.haml @@ -7,4 +7,4 @@ %time.formatted{ datetime: account_moderation_note.created_at.iso8601, title: l(account_moderation_note.created_at) } = l account_moderation_note.created_at %td - = link_to t('admin.account_moderation_notes.delete'), admin_account_moderation_note_path(account_moderation_note), method: :delete + = link_to t('admin.account_moderation_notes.delete'), admin_account_moderation_note_path(account_moderation_note), method: :delete if can?(:destroy, account_moderation_note) diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index b5ce56dbc..f49594828 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -17,16 +17,20 @@ - if @account.local? %tr %th= t('admin.accounts.email') - %td= @account.user_email + %td + = @account.user_email + + - if @account.user_confirmed? + = fa_icon('check') %tr %th= t('admin.accounts.login_status') %td - if @account.user&.disabled? = t('admin.accounts.disabled') - = table_link_to 'unlock', t('admin.accounts.enable'), enable_admin_account_path(@account.id), method: :post + = table_link_to 'unlock', t('admin.accounts.enable'), enable_admin_account_path(@account.id), method: :post if can?(:enable, @account.user) - else = t('admin.accounts.enabled') - = table_link_to 'lock', t('admin.accounts.disable'), disable_admin_account_path(@account.id), method: :post + = table_link_to 'lock', t('admin.accounts.disable'), disable_admin_account_path(@account.id), method: :post if can?(:disable, @account.user) %tr %th= t('admin.accounts.most_recent_ip') %td= @account.user_current_sign_in_ip @@ -71,28 +75,28 @@ %div{ style: 'overflow: hidden' } %div{ style: 'float: right' } - if @account.local? - = link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' + = link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user) - if @account.user&.otp_required_for_login? - = link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' + = link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user) - unless @account.memorial? - = link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' + = link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:memorialize, @account) - else - = link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' + = link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account) %div{ style: 'float: left' } - if @account.silenced? - = link_to t('admin.accounts.undo_silenced'), admin_account_silence_path(@account.id), method: :delete, class: 'button' + = link_to t('admin.accounts.undo_silenced'), admin_account_silence_path(@account.id), method: :delete, class: 'button' if can?(:unsilence, @account) - else - = link_to t('admin.accounts.silence'), admin_account_silence_path(@account.id), method: :post, class: 'button' + = link_to t('admin.accounts.silence'), admin_account_silence_path(@account.id), method: :post, class: 'button' if can?(:silence, @account) - if @account.local? - unless @account.user_confirmed? - = link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button' + = link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button' if can?(:confirm, @account.user) - if @account.suspended? - = link_to t('admin.accounts.undo_suspension'), admin_account_suspension_path(@account.id), method: :delete, class: 'button' + = link_to t('admin.accounts.undo_suspension'), admin_account_suspension_path(@account.id), method: :delete, class: 'button' if can?(:unsuspend, @account) - else - = link_to t('admin.accounts.perform_full_suspension'), admin_account_suspension_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' + = link_to t('admin.accounts.perform_full_suspension'), admin_account_suspension_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:suspend, @account) - unless @account.local? %hr @@ -118,9 +122,9 @@ %div{ style: 'overflow: hidden' } %div{ style: 'float: right' } - = link_to @account.subscribed? ? t('admin.accounts.resubscribe') : t('admin.accounts.subscribe'), subscribe_admin_account_path(@account.id), method: :post, class: 'button' + = link_to @account.subscribed? ? t('admin.accounts.resubscribe') : t('admin.accounts.subscribe'), subscribe_admin_account_path(@account.id), method: :post, class: 'button' if can?(:subscribe, @account) - if @account.subscribed? - = link_to t('admin.accounts.unsubscribe'), unsubscribe_admin_account_path(@account.id), method: :post, class: 'button negative' + = link_to t('admin.accounts.unsubscribe'), unsubscribe_admin_account_path(@account.id), method: :post, class: 'button negative' if can?(:unsubscribe, @account) %hr %h3 ActivityPub @@ -141,6 +145,20 @@ %th= t('admin.accounts.followers_url') %td= link_to @account.followers_url, @account.followers_url +- else + %hr + + .table-wrapper + %table.table + %tbody + %tr + %th= t('admin.accounts.role') + %td + = t("admin.accounts.roles.#{@account.user&.role}") + %td< + = table_link_to 'angle-double-up', t('admin.accounts.promote'), promote_admin_account_role_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:promote, @account.user) + = table_link_to 'angle-double-down', t('admin.accounts.demote'), demote_admin_account_role_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:demote, @account.user) + %hr %h3= t('admin.accounts.moderation_notes') diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index b35e5c09a..08a96f727 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -46,6 +46,7 @@ ignore_missing: - 'terms.body_html' - 'application_mailer.salutation' - 'errors.500' + ignore_unused: - 'activemodel.errors.*' - 'activerecord.attributes.*' @@ -58,3 +59,4 @@ ignore_unused: - 'errors.messages.*' - 'activerecord.errors.models.doorkeeper/*' - 'errors.429' + - 'admin.accounts.roles.*' diff --git a/config/locales/en.yml b/config/locales/en.yml index be0431ed3..e94165317 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -62,6 +62,7 @@ en: by_domain: Domain confirm: Confirm confirmed: Confirmed + demote: Demote disable: Disable disable_two_factor_authentication: Disable 2FA disabled: Disabled @@ -101,6 +102,7 @@ en: outbox_url: Outbox URL perform_full_suspension: Perform full suspension profile_url: Profile URL + promote: Promote protocol: Protocol public: Public push_subscription_expires: PuSH subscription expires @@ -108,6 +110,11 @@ en: reset: Reset reset_password: Reset password resubscribe: Resubscribe + role: Permissions + roles: + admin: Administrator + moderator: Moderator + user: User salmon_url: Salmon URL search: Search shared_inbox_url: Shared Inbox URL diff --git a/config/navigation.rb b/config/navigation.rb index 50bfbd480..5b4800f07 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -20,16 +20,16 @@ SimpleNavigation::Configuration.run do |navigation| development.item :your_apps, safe_join([fa_icon('list fw'), t('settings.your_apps')]), settings_applications_url, highlights_on: %r{/settings/applications} end - primary.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_reports_url, if: proc { current_user.admin? } do |admin| + primary.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_reports_url, if: proc { current_user.staff? } do |admin| admin.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports} admin.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts} - admin.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url, highlights_on: %r{/admin/instances} - admin.item :subscriptions, safe_join([fa_icon('paper-plane-o fw'), t('admin.subscriptions.title')]), admin_subscriptions_url - admin.item :domain_blocks, safe_join([fa_icon('lock fw'), t('admin.domain_blocks.title')]), admin_domain_blocks_url, highlights_on: %r{/admin/domain_blocks} - admin.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_url, highlights_on: %r{/admin/email_domain_blocks} - admin.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' } - admin.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url, link_html: { target: 'pghero' } - admin.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url + admin.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_url, highlights_on: %r{/admin/instances}, if: -> { current_user.admin? } + admin.item :subscriptions, safe_join([fa_icon('paper-plane-o fw'), t('admin.subscriptions.title')]), admin_subscriptions_url, if: -> { current_user.admin? } + admin.item :domain_blocks, safe_join([fa_icon('lock fw'), t('admin.domain_blocks.title')]), admin_domain_blocks_url, highlights_on: %r{/admin/domain_blocks}, if: -> { current_user.admin? } + admin.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_url, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.admin? } + admin.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' }, if: -> { current_user.admin? } + admin.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url, link_html: { target: 'pghero' }, if: -> { current_user.admin? } + admin.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url, if: -> { current_user.admin? } admin.item :custom_emojis, safe_join([fa_icon('smile-o fw'), t('admin.custom_emojis.title')]), admin_custom_emojis_url, highlights_on: %r{/admin/custom_emojis} end diff --git a/config/routes.rb b/config/routes.rb index e6d6b52f7..9301a4e50 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -137,6 +137,13 @@ Rails.application.routes.draw do resource :suspension, only: [:create, :destroy] resource :confirmation, only: [:create] resources :statuses, only: [:index, :create, :update, :destroy] + + resource :role do + member do + post :promote + post :demote + end + end end resources :users, only: [] do diff --git a/db/migrate/20171109012327_add_moderator_to_accounts.rb b/db/migrate/20171109012327_add_moderator_to_accounts.rb new file mode 100644 index 000000000..ddd87583a --- /dev/null +++ b/db/migrate/20171109012327_add_moderator_to_accounts.rb @@ -0,0 +1,15 @@ +require Rails.root.join('lib', 'mastodon', 'migration_helpers') + +class AddModeratorToAccounts < ActiveRecord::Migration[5.1] + include Mastodon::MigrationHelpers + + disable_ddl_transaction! + + def up + safety_assured { add_column_with_default :users, :moderator, :bool, default: false } + end + + def down + remove_column :users, :moderator + end +end diff --git a/db/schema.rb b/db/schema.rb index 935fd79c5..f16b24fd6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20171107143624) do +ActiveRecord::Schema.define(version: 20171109012327) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -437,6 +437,7 @@ ActiveRecord::Schema.define(version: 20171107143624) do t.string "filtered_languages", default: [], null: false, array: true t.bigint "account_id", null: false t.boolean "disabled", default: false, null: false + t.boolean "moderator", default: false, null: false t.index ["account_id"], name: "index_users_on_account_id" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake index 4d519bf90..995cf0d6f 100644 --- a/lib/tasks/mastodon.rake +++ b/lib/tasks/mastodon.rake @@ -10,14 +10,41 @@ namespace :mastodon do desc 'Turn a user into an admin, identified by the USERNAME environment variable' task make_admin: :environment do include RoutingHelper + account_username = ENV.fetch('USERNAME') - user = User.joins(:account).where(accounts: { username: account_username }) + user = User.joins(:account).where(accounts: { username: account_username }) if user.present? user.update(admin: true) puts "Congrats! #{account_username} is now an admin. \\o/\nNavigate to #{edit_admin_settings_url} to get started" else - puts "User could not be found; please make sure an Account with the `#{account_username}` username exists." + puts "User could not be found; please make sure an account with the `#{account_username}` username exists." + end + end + + desc 'Turn a user into a moderator, identified by the USERNAME environment variable' + task make_mod: :environment do + account_username = ENV.fetch('USERNAME') + user = User.joins(:account).where(accounts: { username: account_username }) + + if user.present? + user.update(moderator: true) + puts "Congrats! #{account_username} is now a moderator \\o/" + else + puts "User could not be found; please make sure an account with the `#{account_username}` username exists." + end + end + + desc 'Remove admin and moderator privileges from user identified by the USERNAME environment variable' + task revoke_staff: :environment do + account_username = ENV.fetch('USERNAME') + user = User.joins(:account).where(accounts: { username: account_username }) + + if user.present? + user.update(moderator: false, admin: false) + puts "#{account_username} is no longer admin or moderator." + else + puts "User could not be found; please make sure an account with the `#{account_username}` username exists." end end -- cgit From 0e6c4cb7964df1c2899c5a0c57cc73013d9718a1 Mon Sep 17 00:00:00 2001 From: Anna e só Date: Tue, 14 Nov 2017 00:07:38 -0200 Subject: l10n: PT-BR translation updated (#5681) * Improved e-mail messages; delted repeated words * pt-BR.json translations updated * Revert "pt-BR.json translations updated" This reverts commit 108c460531196fed6e6d14f93e8d8d047c835ffd. * Updated pt-BR.json * pt-BR.yml updated --- app/javascript/mastodon/locales/pt-BR.json | 2 +- .../confirmation_instructions.pt-BR.html.erb | 4 ++-- .../confirmation_instructions.pt-BR.text.erb | 4 ++-- .../user_mailer/password_change.pt-BR.html.erb | 2 +- .../user_mailer/password_change.pt-BR.text.erb | 2 +- .../reset_password_instructions.pt-BR.html.erb | 4 ++-- .../reset_password_instructions.pt-BR.text.erb | 4 ++-- config/locales/pt-BR.yml | 22 +++++++++++++++++++++- 8 files changed, 32 insertions(+), 12 deletions(-) (limited to 'app/views') diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index ddb8b83f5..a04d1cc31 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -161,7 +161,7 @@ "privacy.unlisted.short": "Não listada", "relative_time.days": "{number}d", "relative_time.hours": "{number}h", - "relative_time.just_now": "now", + "relative_time.just_now": "agora", "relative_time.minutes": "{number}m", "relative_time.seconds": "{number}s", "reply_indicator.cancel": "Cancelar", diff --git a/app/views/user_mailer/confirmation_instructions.pt-BR.html.erb b/app/views/user_mailer/confirmation_instructions.pt-BR.html.erb index 80edcfda7..0be16d994 100644 --- a/app/views/user_mailer/confirmation_instructions.pt-BR.html.erb +++ b/app/views/user_mailer/confirmation_instructions.pt-BR.html.erb @@ -1,6 +1,6 @@

Boas vindas, <%= @resource.email %>!

-

Você acabou de criar uma conta no <%= @instance %>.

+

Você acabou de criar uma conta na instância <%= @instance %>.

Para confirmar o seu cadastro, por favor clique no link a seguir:
<%= link_to 'Confirmar cadastro', confirmation_url(@resource, confirmation_token: @token) %> @@ -9,4 +9,4 @@

Atenciosamente,

-

A equipe do <%= @instance %>

+

A equipe da instância <%= @instance %>

diff --git a/app/views/user_mailer/confirmation_instructions.pt-BR.text.erb b/app/views/user_mailer/confirmation_instructions.pt-BR.text.erb index 95efb3436..578f7acb5 100644 --- a/app/views/user_mailer/confirmation_instructions.pt-BR.text.erb +++ b/app/views/user_mailer/confirmation_instructions.pt-BR.text.erb @@ -1,6 +1,6 @@ Boas vindas, <%= @resource.email %>! -Você acabou de criar uma conta no <%= @instance %>. +Você acabou de criar uma conta na instância <%= @instance %>. Para confirmar o seu cadastro, por favor clique no link a seguir: <%= confirmation_url(@resource, confirmation_token: @token) %> @@ -9,4 +9,4 @@ Por favor, leia também os nossos termos e condições de uso <%= terms_url %> Atenciosamente, -A equipe do <%= @instance %> +A equipe da instância <%= @instance %> diff --git a/app/views/user_mailer/password_change.pt-BR.html.erb b/app/views/user_mailer/password_change.pt-BR.html.erb index 5f707ba09..a1aaa265e 100644 --- a/app/views/user_mailer/password_change.pt-BR.html.erb +++ b/app/views/user_mailer/password_change.pt-BR.html.erb @@ -1,3 +1,3 @@

Olá, <%= @resource.email %>!

-

Estamos te contatando para te notificar que a senha senha no <%= @instance %> foi modificada.

+

Estamos te contatando para te notificar que a sua senha na instância <%= @instance %> foi modificada.

diff --git a/app/views/user_mailer/password_change.pt-BR.text.erb b/app/views/user_mailer/password_change.pt-BR.text.erb index d8b76648c..eb7368ba9 100644 --- a/app/views/user_mailer/password_change.pt-BR.text.erb +++ b/app/views/user_mailer/password_change.pt-BR.text.erb @@ -1,3 +1,3 @@ Olá, <%= @resource.email %>! -Estamos te contatando para te notificar que a senha senha no <%= @instance %> foi modificada. +Estamos te contatando para te notificar que a sua senha na instância <%= @instance %> foi modificada. diff --git a/app/views/user_mailer/reset_password_instructions.pt-BR.html.erb b/app/views/user_mailer/reset_password_instructions.pt-BR.html.erb index 940438b7c..9b21aae92 100644 --- a/app/views/user_mailer/reset_password_instructions.pt-BR.html.erb +++ b/app/views/user_mailer/reset_password_instructions.pt-BR.html.erb @@ -1,8 +1,8 @@

Olá, <%= @resource.email %>!

-

Alguém solicitou um link para mudar a sua senha no <%= @instance %>. Você pode fazer isso através do link abaixo:

+

Alguém solicitou um link para mudar a sua senha na instância <%= @instance %>. Você pode fazer isso através do link abaixo:

<%= link_to 'Mudar a minha senha', edit_password_url(@resource, reset_password_token: @token) %>

Se você não solicitou isso, por favor ignore este e-mail.

-

A senha senha não será modificada até que você acesse o link acima e crie uma nova.

+

A senha não será modificada até que você acesse o link acima e crie uma nova.

diff --git a/app/views/user_mailer/reset_password_instructions.pt-BR.text.erb b/app/views/user_mailer/reset_password_instructions.pt-BR.text.erb index f574fe08f..2abff0c0d 100644 --- a/app/views/user_mailer/reset_password_instructions.pt-BR.text.erb +++ b/app/views/user_mailer/reset_password_instructions.pt-BR.text.erb @@ -1,8 +1,8 @@ Olá, <%= @resource.email %>! -Alguém solicitou um link para mudar a sua senha no <%= @instance %>. Você pode fazer isso através do link abaixo: +Alguém solicitou um link para mudar a sua senha na instância <%= @instance %>. Você pode fazer isso através do link abaixo: <%= edit_password_url(@resource, reset_password_token: @token) %> Se você não solicitou isso, por favor ignore este e-mail. -A senha senha não será modificada até que você acesse o link acima e crie uma nova. +A senha não será modificada até que você acesse o link acima e crie uma nova. diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 4b9ea152f..f5c61c01c 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -43,7 +43,7 @@ pt-BR: people_followed_by: Pessoas que %{name} segue people_who_follow: Pessoas que seguem %{name} posts: Toots - posts_with_replies: Toots com respostas + posts_with_replies: Toots e respostas remote_follow: Siga remotamente reserved_username: Este usuário está reservado roles: @@ -59,13 +59,19 @@ pt-BR: destroyed_msg: Nota de moderação excluída com sucesso! accounts: are_you_sure: Você tem certeza? + by_domain: Domínio confirm: Confirmar confirmed: Confirmado + demote: Rebaixar + disable: Desativar disable_two_factor_authentication: Desativar 2FA + disabled: Desativado display_name: Nome de exibição domain: Domínio edit: Editar email: E-mail + enable: Ativar + enabled: Ativado feed_url: URL do feed followers: Seguidores followers_url: URL de seguidores @@ -77,7 +83,9 @@ pt-BR: local: Local remote: Remoto title: Localização + login_status: Status de login media_attachments: Mídia(s) anexada(s) + memorialize: Tornar um memorial moderation: all: Todos silenced: Silenciados @@ -94,6 +102,7 @@ pt-BR: outbox_url: URL da Outbox perform_full_suspension: Efetue suspensão total profile_url: URL do perfil + promote: Promover protocol: Protocolo public: Público push_subscription_expires: Inscrição PuSH expira @@ -101,6 +110,11 @@ pt-BR: reset: Anular reset_password: Modificar senha resubscribe: Reinscrever-se + role: Permissões + roles: + admin: Administrador + moderator: Moderador + user: Usuário salmon_url: Salmon URL search: Pesquisar shared_inbox_url: URL da Inbox Compartilhada @@ -130,11 +144,16 @@ pt-BR: enable: Habilitar enabled_msg: Emoji habilitado com sucesso! image_hint: PNG de até 50KB + listed: Listado new: title: Adicionar novo emoji customizado + overwrite: Sobrescrever shortcode: Atalho shortcode_hint: Pelo menos 2 caracteres, apenas caracteres alfanuméricos e underscores title: Emojis customizados + unlisted: Não listado + update_failed_msg: Não foi possível atualizar esse emoji + updated_msg: Emoji atualizado com sucesso! upload: Enviar domain_blocks: add_new: Adicionar novo @@ -373,6 +392,7 @@ pt-BR: following: Pessoas que você segue muting: Lista de silêncio upload: Enviar + in_memoriam_html: Em memória de landing_strip_html: "%{name} é um usuário no %{link_to_root_path}. Você pode segui-lo ou interagir com ele se você tiver uma conta em qualquer lugar no fediverso." landing_strip_signup_html: Se não, você pode se cadastrar aqui. media_attachments: -- cgit From fbef909c2a1ff8d24811f76237e62fbef6cc63cc Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 14 Nov 2017 21:12:57 +0100 Subject: Add option to block direct messages from people you don't follow (#5669) * Add option to block direct messages from people you don't follow Fix #5326 * If the DM responds to a toot by recipient, allow it through * i18n: Update Polish translation (for #5669) (#5673) --- .../settings/notifications_controller.rb | 2 +- app/services/notify_service.rb | 59 ++++++++++++++++++---- app/views/settings/notifications/show.html.haml | 3 +- config/locales/simple_form.en.yml | 1 + config/locales/simple_form.pl.yml | 1 + config/settings.yml | 1 + spec/services/notify_service_spec.rb | 33 ++++++++++++ 7 files changed, 89 insertions(+), 11 deletions(-) (limited to 'app/views') diff --git a/app/controllers/settings/notifications_controller.rb b/app/controllers/settings/notifications_controller.rb index 09839f16e..ce2530c54 100644 --- a/app/controllers/settings/notifications_controller.rb +++ b/app/controllers/settings/notifications_controller.rb @@ -26,7 +26,7 @@ class Settings::NotificationsController < ApplicationController def user_settings_params params.require(:user).permit( notification_emails: %i(follow follow_request reblog favourite mention digest), - interactions: %i(must_be_follower must_be_following) + interactions: %i(must_be_follower must_be_following must_be_following_dm) ) end end diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index ca53c61c5..6a24a8247 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -36,17 +36,58 @@ class NotifyService < BaseService false end + def following_sender? + return @following_sender if defined?(@following_sender) + @following_sender = @recipient.following?(@notification.from_account) || @recipient.requested?(@notification.from_account) + end + + def optional_non_follower? + @recipient.user.settings.interactions['must_be_follower'] && !@notification.from_account.following?(@recipient) + end + + def optional_non_following? + @recipient.user.settings.interactions['must_be_following'] && !following_sender? + end + + def direct_message? + @notification.type == :mention && @notification.target_status.direct_visibility? + end + + def response_to_recipient? + @notification.target_status.in_reply_to_account_id == @recipient.id + end + + def optional_non_following_and_direct? + direct_message? && + @recipient.user.settings.interactions['must_be_following_dm'] && + !following_sender? && + !response_to_recipient? + end + + def hellbanned? + @notification.from_account.silenced? && !following_sender? + end + + def from_self? + @recipient.id == @notification.from_account.id + end + + def domain_blocking? + @recipient.domain_blocking?(@notification.from_account.domain) && !following_sender? + end + def blocked? - blocked = @recipient.suspended? # Skip if the recipient account is suspended anyway - blocked ||= @recipient.id == @notification.from_account.id # Skip for interactions with self - blocked ||= @recipient.domain_blocking?(@notification.from_account.domain) && !@recipient.following?(@notification.from_account) # Skip for domain blocked accounts - blocked ||= @recipient.blocking?(@notification.from_account) # Skip for blocked accounts - blocked ||= @recipient.muting?(@notification.from_account) # Skip for muted accounts - blocked ||= (@notification.from_account.silenced? && !@recipient.following?(@notification.from_account)) # Hellban - blocked ||= (@recipient.user.settings.interactions['must_be_follower'] && !@notification.from_account.following?(@recipient)) # Options - blocked ||= (@recipient.user.settings.interactions['must_be_following'] && !@recipient.following?(@notification.from_account)) # Options + blocked = @recipient.suspended? # Skip if the recipient account is suspended anyway + blocked ||= from_self? # Skip for interactions with self + blocked ||= domain_blocking? # Skip for domain blocked accounts + blocked ||= @recipient.blocking?(@notification.from_account) # Skip for blocked accounts + blocked ||= @recipient.muting?(@notification.from_account) # Skip for muted accounts + blocked ||= hellbanned? # Hellban + blocked ||= optional_non_follower? # Options + blocked ||= optional_non_following? # Options + blocked ||= optional_non_following_and_direct? # Options blocked ||= conversation_muted? - blocked ||= send("blocked_#{@notification.type}?") # Type-dependent filters + blocked ||= send("blocked_#{@notification.type}?") # Type-dependent filters blocked end diff --git a/app/views/settings/notifications/show.html.haml b/app/views/settings/notifications/show.html.haml index 80cd615c7..b718b62df 100644 --- a/app/views/settings/notifications/show.html.haml +++ b/app/views/settings/notifications/show.html.haml @@ -11,7 +11,7 @@ = ff.input :reblog, as: :boolean, wrapper: :with_label = ff.input :favourite, as: :boolean, wrapper: :with_label = ff.input :mention, as: :boolean, wrapper: :with_label - + .fields-group = f.simple_fields_for :notification_emails, hash_to_object(current_user.settings.notification_emails) do |ff| = ff.input :digest, as: :boolean, wrapper: :with_label @@ -20,6 +20,7 @@ = f.simple_fields_for :interactions, hash_to_object(current_user.settings.interactions) do |ff| = ff.input :must_be_follower, as: :boolean, wrapper: :with_label = ff.input :must_be_following, as: :boolean, wrapper: :with_label + = ff.input :must_be_following_dm, as: :boolean, wrapper: :with_label .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index aafae48ce..faf41f316 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -54,6 +54,7 @@ en: interactions: must_be_follower: Block notifications from non-followers must_be_following: Block notifications from people you don't follow + must_be_following_dm: Block direct messages from people you don't follow notification_emails: digest: Send digest e-mails favourite: Send e-mail when someone favourites your status diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml index 68f84d109..8b539662c 100644 --- a/config/locales/simple_form.pl.yml +++ b/config/locales/simple_form.pl.yml @@ -58,6 +58,7 @@ pl: interactions: must_be_follower: Nie wyświetlaj powiadomień od osób, które Cię nie śledzą must_be_following: Nie wyświetlaj powiadomień od osób, których nie śledzisz + must_be_following_dm: Nie wyświetlaj wiadomości bezpośrednich od osób, których nie śledzisz notification_emails: digest: Wysyłaj podsumowania e-mailem favourite: Powiadamiaj mnie e-mailem, gdy ktoś polubi mój wpis diff --git a/config/settings.yml b/config/settings.yml index 11681d7ec..a4df4094d 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -36,6 +36,7 @@ defaults: &defaults interactions: must_be_follower: false must_be_following: false + must_be_following_dm: false reserved_usernames: - admin - support diff --git a/spec/services/notify_service_spec.rb b/spec/services/notify_service_spec.rb index 7a66bd0fe..58ee66ded 100644 --- a/spec/services/notify_service_spec.rb +++ b/spec/services/notify_service_spec.rb @@ -38,6 +38,39 @@ RSpec.describe NotifyService do is_expected.to_not change(Notification, :count) end + context 'for direct messages' do + let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct)) } + + before do + user.settings.interactions = user.settings.interactions.merge('must_be_following_dm' => enabled) + end + + context 'if recipient is supposed to be following sender' do + let(:enabled) { true } + + it 'does not notify' do + is_expected.to_not change(Notification, :count) + end + + context 'if the message chain initiated by recipient' do + let(:reply_to) { Fabricate(:status, account: recipient) } + let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: reply_to)) } + + it 'does notify' do + is_expected.to change(Notification, :count) + end + end + end + + context 'if recipient is NOT supposed to be following sender' do + let(:enabled) { false } + + it 'does notify' do + is_expected.to change(Notification, :count) + end + end + end + context do let(:asshole) { Fabricate(:account, username: 'asshole') } let(:reply_to) { Fabricate(:status, account: asshole) } -- cgit