From a744042cf109cca8c133675ac21c246be8cc3ec4 Mon Sep 17 00:00:00 2001 From: Evgeny Petrov Date: Tue, 22 May 2018 12:06:53 +0300 Subject: [PATCH 01/14] [RU] Added mastodon-light string (#7583) --- config/locales/ru.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 0f4565bbcfb..89aefc1cd97 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -795,6 +795,7 @@ ru: themes: contrast: Высококонтрастная default: Mastodon + mastodon-light: Mastodon (светлая) time: formats: default: "%b %d, %Y, %H:%M" From 92b09d90c8f48e30e83d98cab4f3bc1724e32dae Mon Sep 17 00:00:00 2001 From: bsky Date: Tue, 22 May 2018 20:24:54 +0900 Subject: [PATCH 02/14] Remove outline (#7582) --- app/javascript/styles/mastodon/components.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 0765b3ffc58..412f8d770c8 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1984,6 +1984,7 @@ a.account__display-name { padding: 15px; margin: 0; z-index: 3; + outline: 0; &:hover { text-decoration: underline; From 06252ec71ed67cb84e6730fe7148552dc6481c7a Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Tue, 22 May 2018 20:26:06 +0900 Subject: [PATCH 03/14] Change column params on pinned timeline (#7581) --- app/javascript/mastodon/actions/columns.js | 19 ++++++++++-- .../features/community_timeline/index.js | 31 ++++++++++++++++--- .../features/public_timeline/index.js | 31 ++++++++++++++++--- app/javascript/mastodon/reducers/settings.js | 15 ++++++++- 4 files changed, 84 insertions(+), 12 deletions(-) diff --git a/app/javascript/mastodon/actions/columns.js b/app/javascript/mastodon/actions/columns.js index bcb0cdf9837..f550e6c48d5 100644 --- a/app/javascript/mastodon/actions/columns.js +++ b/app/javascript/mastodon/actions/columns.js @@ -1,8 +1,9 @@ import { saveSettings } from './settings'; -export const COLUMN_ADD = 'COLUMN_ADD'; -export const COLUMN_REMOVE = 'COLUMN_REMOVE'; -export const COLUMN_MOVE = 'COLUMN_MOVE'; +export const COLUMN_ADD = 'COLUMN_ADD'; +export const COLUMN_REMOVE = 'COLUMN_REMOVE'; +export const COLUMN_MOVE = 'COLUMN_MOVE'; +export const COLUMN_PARAMS_CHANGE = 'COLUMN_PARAMS_CHANGE'; export function addColumn(id, params) { return dispatch => { @@ -38,3 +39,15 @@ export function moveColumn(uuid, direction) { dispatch(saveSettings()); }; }; + +export function changeColumnParams(uuid, params) { + return dispatch => { + dispatch({ + type: COLUMN_PARAMS_CHANGE, + uuid, + params, + }); + + dispatch(saveSettings()); + }; +} diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js index b6489432f2a..bae856c4bc3 100644 --- a/app/javascript/mastodon/features/community_timeline/index.js +++ b/app/javascript/mastodon/features/community_timeline/index.js @@ -1,13 +1,13 @@ import React from 'react'; import { connect } from 'react-redux'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { NavLink, Link } from 'react-router-dom'; +import { NavLink } from 'react-router-dom'; import PropTypes from 'prop-types'; import StatusListContainer from '../ui/containers/status_list_container'; import Column from '../../components/column'; import ColumnHeader from '../../components/column_header'; import { expandCommunityTimeline } from '../../actions/timelines'; -import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; +import { addColumn, removeColumn, moveColumn, changeColumnParams } from '../../actions/columns'; import ColumnSettingsContainer from './containers/column_settings_container'; import { connectCommunityStream } from '../../actions/streaming'; @@ -62,6 +62,16 @@ export default class CommunityTimeline extends React.PureComponent { this.disconnect = dispatch(connectCommunityStream({ onlyMedia })); } + componentDidUpdate (prevProps) { + if (prevProps.onlyMedia !== this.props.onlyMedia) { + const { dispatch, onlyMedia } = this.props; + + this.disconnect(); + dispatch(expandCommunityTimeline({ onlyMedia })); + this.disconnect = dispatch(connectCommunityStream({ onlyMedia })); + } + } + componentWillUnmount () { if (this.disconnect) { this.disconnect(); @@ -79,14 +89,27 @@ export default class CommunityTimeline extends React.PureComponent { dispatch(expandCommunityTimeline({ maxId, onlyMedia })); } + handleHeadlineLinkClick = e => { + e.preventDefault(); + + const { columnId, dispatch } = this.props; + const onlyMedia = /\/media$/.test(e.currentTarget.href); + + dispatch(changeColumnParams(columnId, { other: { onlyMedia } })); + } + render () { const { intl, hasUnread, columnId, multiColumn, onlyMedia } = this.props; const pinned = !!columnId; const headline = pinned ? (
- - + + + + + +
) : (
diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.js index da59b7167c9..84d61e81b53 100644 --- a/app/javascript/mastodon/features/public_timeline/index.js +++ b/app/javascript/mastodon/features/public_timeline/index.js @@ -1,13 +1,13 @@ import React from 'react'; import { connect } from 'react-redux'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { NavLink, Link } from 'react-router-dom'; +import { NavLink } from 'react-router-dom'; import PropTypes from 'prop-types'; import StatusListContainer from '../ui/containers/status_list_container'; import Column from '../../components/column'; import ColumnHeader from '../../components/column_header'; import { expandPublicTimeline } from '../../actions/timelines'; -import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; +import { addColumn, removeColumn, moveColumn, changeColumnParams } from '../../actions/columns'; import ColumnSettingsContainer from './containers/column_settings_container'; import { connectPublicStream } from '../../actions/streaming'; @@ -62,6 +62,16 @@ export default class PublicTimeline extends React.PureComponent { this.disconnect = dispatch(connectPublicStream({ onlyMedia })); } + componentDidUpdate (prevProps) { + if (prevProps.onlyMedia !== this.props.onlyMedia) { + const { dispatch, onlyMedia } = this.props; + + this.disconnect(); + dispatch(expandPublicTimeline({ onlyMedia })); + this.disconnect = dispatch(connectPublicStream({ onlyMedia })); + } + } + componentWillUnmount () { if (this.disconnect) { this.disconnect(); @@ -79,14 +89,27 @@ export default class PublicTimeline extends React.PureComponent { dispatch(expandPublicTimeline({ maxId, onlyMedia })); } + handleHeadlineLinkClick = e => { + e.preventDefault(); + + const { columnId, dispatch } = this.props; + const onlyMedia = /\/media$/.test(e.currentTarget.href); + + dispatch(changeColumnParams(columnId, { other: { onlyMedia } })); + } + render () { const { intl, columnId, hasUnread, multiColumn, onlyMedia } = this.props; const pinned = !!columnId; const headline = pinned ? (
- - + + + + + +
) : (
diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js index 9ec52a7fafe..de8865e4333 100644 --- a/app/javascript/mastodon/reducers/settings.js +++ b/app/javascript/mastodon/reducers/settings.js @@ -1,5 +1,5 @@ import { SETTING_CHANGE, SETTING_SAVE } from '../actions/settings'; -import { COLUMN_ADD, COLUMN_REMOVE, COLUMN_MOVE } from '../actions/columns'; +import { COLUMN_ADD, COLUMN_REMOVE, COLUMN_MOVE, COLUMN_PARAMS_CHANGE } from '../actions/columns'; import { STORE_HYDRATE } from '../actions/store'; import { EMOJI_USE } from '../actions/emojis'; import { LIST_DELETE_SUCCESS, LIST_FETCH_FAIL } from '../actions/lists'; @@ -89,6 +89,17 @@ const moveColumn = (state, uuid, direction) => { .set('saved', false); }; +const changeColumnParams = (state, uuid, params) => { + const columns = state.get('columns'); + const index = columns.findIndex(item => item.get('uuid') === uuid); + + const newColumns = columns.update(index, column => column.update('params', () => fromJS(params))); + + return state + .set('columns', newColumns) + .set('saved', false); +}; + const updateFrequentEmojis = (state, emoji) => state.update('frequentlyUsedEmojis', ImmutableMap(), map => map.update(emoji.id, 0, count => count + 1)).set('saved', false); const filterDeadListColumns = (state, listId) => state.update('columns', columns => columns.filterNot(column => column.get('id') === 'LIST' && column.get('params').get('id') === listId)); @@ -111,6 +122,8 @@ export default function settings(state = initialState, action) { .set('saved', false); case COLUMN_MOVE: return moveColumn(state, action.uuid, action.direction); + case COLUMN_PARAMS_CHANGE: + return changeColumnParams(state, action.uuid, action.params); case EMOJI_USE: return updateFrequentEmojis(state, action.emoji); case SETTING_SAVE: From 4eeda6772796bcd08b8c63ec2e1f3e68a95cbca4 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Tue, 22 May 2018 21:44:32 +0900 Subject: [PATCH 04/14] Hide section headline for timelines in production (#7590) * Hide section headline for timelines in production * comment out --- .../components/section_headline.js | 59 +++++++++++++++++++ .../features/community_timeline/index.js | 32 ++++------ .../features/public_timeline/index.js | 32 ++++------ 3 files changed, 85 insertions(+), 38 deletions(-) create mode 100644 app/javascript/mastodon/features/community_timeline/components/section_headline.js diff --git a/app/javascript/mastodon/features/community_timeline/components/section_headline.js b/app/javascript/mastodon/features/community_timeline/components/section_headline.js new file mode 100644 index 00000000000..c7176d04b80 --- /dev/null +++ b/app/javascript/mastodon/features/community_timeline/components/section_headline.js @@ -0,0 +1,59 @@ +import PropTypes from 'prop-types'; +import React, { Component, Fragment } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { NavLink } from 'react-router-dom'; + +export default class SectionHeadline extends Component { + + static propTypes = { + timelineId: PropTypes.string.isRequired, + to: PropTypes.string.isRequired, + pinned: PropTypes.bool.isRequired, + onlyMedia: PropTypes.bool.isRequired, + onClick: PropTypes.func, + }; + + shouldComponentUpdate (nextProps) { + return ( + this.props.onlyMedia !== nextProps.onlyMedia || + this.props.pinned !== nextProps.pinned || + this.props.to !== nextProps.to || + this.props.timelineId !== nextProps.timelineId + ); + } + + handleClick = e => { + const { onClick } = this.props; + + if (typeof onClick === 'function') { + e.preventDefault(); + + onClick.call(this, e); + } + } + + render () { + const { timelineId, to, pinned, onlyMedia } = this.props; + + return ( +
+ {pinned ? ( + + + + + + + + + ) : ( + + + + + )} +
+ ); + } + +} diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js index bae856c4bc3..58b8a8b731c 100644 --- a/app/javascript/mastodon/features/community_timeline/index.js +++ b/app/javascript/mastodon/features/community_timeline/index.js @@ -1,7 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { NavLink } from 'react-router-dom'; import PropTypes from 'prop-types'; import StatusListContainer from '../ui/containers/status_list_container'; import Column from '../../components/column'; @@ -9,6 +8,7 @@ import ColumnHeader from '../../components/column_header'; import { expandCommunityTimeline } from '../../actions/timelines'; import { addColumn, removeColumn, moveColumn, changeColumnParams } from '../../actions/columns'; import ColumnSettingsContainer from './containers/column_settings_container'; +// import SectionHeadline from './components/section_headline'; import { connectCommunityStream } from '../../actions/streaming'; const messages = defineMessages({ @@ -90,8 +90,6 @@ export default class CommunityTimeline extends React.PureComponent { } handleHeadlineLinkClick = e => { - e.preventDefault(); - const { columnId, dispatch } = this.props; const onlyMedia = /\/media$/.test(e.currentTarget.href); @@ -102,21 +100,17 @@ export default class CommunityTimeline extends React.PureComponent { const { intl, hasUnread, columnId, multiColumn, onlyMedia } = this.props; const pinned = !!columnId; - const headline = pinned ? ( - - ) : ( -
- - -
- ); + // pending + // + // const headline = ( + // + // ); return ( @@ -134,7 +128,7 @@ export default class CommunityTimeline extends React.PureComponent { { - e.preventDefault(); - const { columnId, dispatch } = this.props; const onlyMedia = /\/media$/.test(e.currentTarget.href); @@ -102,21 +100,17 @@ export default class PublicTimeline extends React.PureComponent { const { intl, columnId, hasUnread, multiColumn, onlyMedia } = this.props; const pinned = !!columnId; - const headline = pinned ? ( - - ) : ( -
- - -
- ); + // pending + // + // const headline = ( + // + // ); return ( @@ -134,7 +128,7 @@ export default class PublicTimeline extends React.PureComponent { Date: Tue, 22 May 2018 14:44:53 +0200 Subject: [PATCH 05/14] Enforce order on authorized applications page (#7587) Fix #7586 --- app/controllers/settings/applications_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/settings/applications_controller.rb b/app/controllers/settings/applications_controller.rb index 8fc9a0fa999..2a496231157 100644 --- a/app/controllers/settings/applications_controller.rb +++ b/app/controllers/settings/applications_controller.rb @@ -8,7 +8,7 @@ class Settings::ApplicationsController < ApplicationController before_action :prepare_scopes, only: [:create, :update] def index - @applications = current_user.applications.page(params[:page]) + @applications = current_user.applications.order(id: :desc).page(params[:page]) end def new From 12e590edd74daf85c6ab0880ef4556ad47068e7a Mon Sep 17 00:00:00 2001 From: Shuhei Kitagawa Date: Tue, 22 May 2018 21:45:10 +0900 Subject: [PATCH 06/14] Add tests for report notes controller (#7589) --- .../admin/report_notes_controller_spec.rb | 91 +++++++++++++++++++ spec/fabricators/report_note_fabricator.rb | 5 + 2 files changed, 96 insertions(+) create mode 100644 spec/controllers/admin/report_notes_controller_spec.rb create mode 100644 spec/fabricators/report_note_fabricator.rb diff --git a/spec/controllers/admin/report_notes_controller_spec.rb b/spec/controllers/admin/report_notes_controller_spec.rb new file mode 100644 index 00000000000..2c32303fb39 --- /dev/null +++ b/spec/controllers/admin/report_notes_controller_spec.rb @@ -0,0 +1,91 @@ +require 'rails_helper' + +describe Admin::ReportNotesController do + render_views + + let(:user) { Fabricate(:user, admin: true) } + + before do + sign_in user, scope: :user + end + + describe 'POST #create' do + subject { post :create, params: params } + + let(:report) { Fabricate(:report, action_taken: action_taken, action_taken_by_account_id: account_id) } + + context 'when parameter is valid' do + + context 'when report is unsolved' do + let(:action_taken) { false } + let(:account_id) { nil } + + context 'when create_and_resolve flag is on' do + let(:params) { { report_note: { content: 'test content', report_id: report.id }, create_and_resolve: nil } } + + it 'creates a report note and resolves report' do + expect{ subject }.to change{ ReportNote.count }.by(1) + expect(report.reload).to be_action_taken + expect(subject).to redirect_to admin_reports_path + end + end + + context 'when create_and_resolve flag is false' do + let(:params) { { report_note: { content: 'test content', report_id: report.id } } } + + it 'creates a report note and does not resolve report' do + expect{ subject }.to change{ ReportNote.count }.by(1) + expect(report.reload).not_to be_action_taken + expect(subject).to redirect_to admin_report_path(report) + end + end + end + + context 'when report is resolved' do + let(:action_taken) { true } + let(:account_id) { user.account.id } + + context 'when create_and_unresolve flag is on' do + let(:params) { { report_note: { content: 'test content', report_id: report.id }, create_and_unresolve: nil } } + + it 'creates a report note and unresolves report' do + expect{ subject }.to change{ ReportNote.count }.by(1) + expect(report.reload).not_to be_action_taken + expect(subject).to redirect_to admin_report_path(report) + end + end + + context 'when create_and_unresolve flag is false' do + let(:params) { { report_note: { content: 'test content', report_id: report.id } } } + + it 'creates a report note and does not unresolve report' do + expect{ subject }.to change{ ReportNote.count }.by(1) + expect(report.reload).to be_action_taken + expect(subject).to redirect_to admin_report_path(report) + end + end + end + end + + context 'when parameter is invalid' do + let(:params) { { report_note: { content: '', report_id: report.id } } } + let(:action_taken) { false } + let(:account_id) { nil } + + it 'renders admin/reports/show' do + expect(subject).to render_template 'admin/reports/show' + end + end + end + + describe 'DELETE #destroy' do + subject { delete :destroy, params: { id: report_note.id } } + + let!(:report_note) { Fabricate(:report_note) } + + it 'deletes note' do + expect{ subject }.to change{ ReportNote.count }.by(-1) + expect(subject).to redirect_to admin_report_path(report_note.report) + end + end +end diff --git a/spec/fabricators/report_note_fabricator.rb b/spec/fabricators/report_note_fabricator.rb new file mode 100644 index 00000000000..e139efffbc4 --- /dev/null +++ b/spec/fabricators/report_note_fabricator.rb @@ -0,0 +1,5 @@ +Fabricator(:report_note) do + report + account { Fabricate(:account) } + content "Test Content" +end From 4c9d5a500d59d2a440f61167e6552ede29033bae Mon Sep 17 00:00:00 2001 From: SerCom_KC Date: Tue, 22 May 2018 21:26:42 +0800 Subject: [PATCH 07/14] [WIP] i18n: Update Simplified Chinese translations (#7576) * i18n: (zh-CN) #7027 * Rewording This placeholder is also seen on single user page at Moderation > Accounts, where "this report" doesn't make sense. * i18n: (zh-CN) #6425 * i18n: (zh-CN) #6497 * i18n: (zh-CN) #6246 * i18n: (zh-CN) Improvements --- app/javascript/mastodon/locales/zh-CN.json | 8 ++++---- config/locales/en.yml | 4 ++-- config/locales/simple_form.zh-CN.yml | 2 +- config/locales/zh-CN.yml | 18 +++++++++++++----- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index b920bc19ead..b67afd29fa7 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -39,7 +39,7 @@ "bundle_modal_error.close": "关闭", "bundle_modal_error.message": "载入这个组件时发生了错误。", "bundle_modal_error.retry": "重试", - "column.blocks": "屏蔽用户", + "column.blocks": "已屏蔽的用户", "column.community": "本站时间轴", "column.direct": "私信", "column.domain_blocks": "已屏蔽的网站", @@ -47,7 +47,7 @@ "column.follow_requests": "关注请求", "column.home": "主页", "column.lists": "列表", - "column.mutes": "被隐藏的用户", + "column.mutes": "已隐藏的用户", "column.notifications": "通知", "column.pins": "置顶嘟文", "column.public": "跨站公共时间轴", @@ -157,7 +157,7 @@ "missing_indicator.label": "找不到内容", "missing_indicator.sublabel": "无法找到此资源", "mute_modal.hide_notifications": "同时隐藏来自这个用户的通知", - "navigation_bar.blocks": "被屏蔽的用户", + "navigation_bar.blocks": "已屏蔽的用户", "navigation_bar.community_timeline": "本站时间轴", "navigation_bar.direct": "私信", "navigation_bar.domain_blocks": "已屏蔽的网站", @@ -168,7 +168,7 @@ "navigation_bar.keyboard_shortcuts": "快捷键列表", "navigation_bar.lists": "列表", "navigation_bar.logout": "注销", - "navigation_bar.mutes": "被隐藏的用户", + "navigation_bar.mutes": "已隐藏的用户", "navigation_bar.pins": "置顶嘟文", "navigation_bar.preferences": "首选项", "navigation_bar.public_timeline": "跨站公共时间轴", diff --git a/config/locales/en.yml b/config/locales/en.yml index 65b5b1e0b02..262883e2cb8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -283,7 +283,7 @@ en: create_and_resolve: Resolve with note create_and_unresolve: Reopen with note delete: Delete - placeholder: Describe what actions have been taken, or any other updates to this report… + placeholder: Describe what actions have been taken, or any other related updates... reopen: Reopen report report: 'Report #%{id}' report_contents: Contents @@ -550,7 +550,7 @@ en: subject: one: "1 new notification since your last visit \U0001F418" other: "%{count} new notifications since your last visit \U0001F418" - title: In your absence… + title: In your absence... favourite: body: 'Your status was favourited by %{name}:' subject: "%{name} favourited your status" diff --git a/config/locales/simple_form.zh-CN.yml b/config/locales/simple_form.zh-CN.yml index b12538b3568..5b3b25ab13e 100644 --- a/config/locales/simple_form.zh-CN.yml +++ b/config/locales/simple_form.zh-CN.yml @@ -13,7 +13,7 @@ zh-CN: note: 还能输入 %{count} 个字符 setting_hide_network: 你关注的人和关注你的人将不会在你的个人资料页上展示 setting_noindex: 此设置会影响到你的公开个人资料以及嘟文页面 - setting_theme: 此设置会影响到你从任意设备登录时 Mastodon 的显示样式 + setting_theme: 此设置会影响到所有已登录设备上 Mastodon 的显示样式 imports: data: 请上传从其他 Mastodon 实例导出的 CSV 文件 sessions: diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index f7fc1a7a92d..357575a3b3a 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -26,9 +26,9 @@ zh-CN: within_reach_title: 始终触手可及 generic_description: "%{domain} 是这个庞大网络中的一台服务器" hosted_on: 一个在 %{domain} 上运行的 Mastodon 实例 - learn_more: 详细了解 + learn_more: 了解详情 other_instances: 其他实例 - source_code: 源码 + source_code: 源代码 status_count_after: 条嘟文 status_count_before: 他们共嘟出了 user_count_after: 位用户 @@ -281,7 +281,7 @@ zh-CN: create_and_resolve: 添加记录并标记为“已处理” create_and_unresolve: 添加记录并重开 delete: 删除 - placeholder: 描述已经执行的操作,或其他任何与此条举报相关的跟进情况 + placeholder: 描述已经执行的操作,或其他任何相关的跟进情况 reopen: 重开举报 report: '举报 #%{id}' report_contents: 内容 @@ -405,6 +405,9 @@ zh-CN: migrate_account_html: 如果你希望引导他人关注另一个帐户,请点击这里进行设置。 or: 或者 or_log_in_with: 或通过其他方式登录 + providers: + cas: CAS + saml: SAML register: 注册 register_elsewhere: 前往其他实例注册 resend_confirmation: 重新发送确认邮件 @@ -460,7 +463,7 @@ zh-CN: archive_takeout: date: 日期 download: 下载你的存档 - hint_html: 你可以请求一份包含你的嘟文和已上传的媒体文件的存档。导出的数据为 ActivityPub 格式,因而可以通过相应的软件读取。每次可以请求存档的间隔至少为 7 天。 + hint_html: 你可以请求一份帐户数据存档,其中包含你的嘟文和已上传的媒体文件。导出的数据为 ActivityPub 格式,因而可以被兼容的软件读取。每次允许请求存档的间隔至少为 7 天。 in_progress: 正在准备你的存档…… request: 请求你的存档 size: 大小 @@ -574,7 +577,9 @@ zh-CN: trillion: T unit: '' pagination: + newer: 更新 next: 下一页 + older: 更早 prev: 上一页 truncate: "…" preferences: @@ -599,12 +604,14 @@ zh-CN: blackberry: Blackberry chrome: Chrome edge: Microsoft Edge + electron: Electron firefox: Firefox generic: 未知浏览器 ie: Internet Explorer micro_messenger: 微信 nokia: Nokia S40 Ovi 浏览器 opera: Opera + otter: Otter phantom_js: PhantomJS qq: QQ浏览器 safari: Safari @@ -632,7 +639,7 @@ zh-CN: title: 会话 settings: authorized_apps: 已授权的应用 - back: 回到 Mastodon + back: 返回 Mastodon delete: 删除帐户 development: 开发 edit_profile: 更改个人资料 @@ -681,6 +688,7 @@ zh-CN: themes: contrast: 高对比度 default: Mastodon + mastodon-light: Mastodon(亮色主题) time: formats: default: "%Y年%-m月%d日 %H:%M" From 1a564df586acc4554bba7e3200ced5bcf2ea017e Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 22 May 2018 18:12:45 +0200 Subject: [PATCH 08/14] Do not encode HTML entities in initial Web Push payload body (#7592) --- app/serializers/web/notification_serializer.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/serializers/web/notification_serializer.rb b/app/serializers/web/notification_serializer.rb index f3c4ffc477c..43ba4d92a26 100644 --- a/app/serializers/web/notification_serializer.rb +++ b/app/serializers/web/notification_serializer.rb @@ -33,6 +33,7 @@ class Web::NotificationSerializer < ActiveModel::Serializer end def body - truncate(strip_tags(object.target_status&.spoiler_text&.presence || object.target_status&.text || object.from_account.note), length: 140) + str = truncate(strip_tags(object.target_status&.spoiler_text&.presence || object.target_status&.text || object.from_account.note), length: 140) + HTMLEntities.new.decode(str.to_str) # Do not encode entities, since this value will not be used in HTML end end From 39d70f375f2f9cd7e12cbf4486059dfc4a945755 Mon Sep 17 00:00:00 2001 From: ThibG Date: Tue, 22 May 2018 19:32:02 +0200 Subject: [PATCH 09/14] Remove unneeded code now that attachment URL isn't appended to toots (#7593) --- .../features/compose/components/compose_form.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index 39eb02362d1..9cd39be8b7f 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -104,23 +104,13 @@ export default class ComposeForm extends ImmutablePureComponent { this.props.onChangeSpoilerText(e.target.value); } - componentWillReceiveProps (nextProps) { - // If this is the update where we've finished uploading, - // save the last caret position so we can restore it below! - if (!nextProps.is_uploading && this.props.is_uploading) { - this._restoreCaret = this.autosuggestTextarea.textarea.selectionStart; - } - } - componentDidUpdate (prevProps) { // This statement does several things: // - If we're beginning a reply, and, // - Replying to zero or one users, places the cursor at the end of the textbox. // - Replying to more than one user, selects any usernames past the first; // this provides a convenient shortcut to drop everyone else from the conversation. - // - If we've just finished uploading an image, and have a saved caret position, - // restores the cursor to that position after the text changes! - if (this.props.focusDate !== prevProps.focusDate || (prevProps.is_uploading && !this.props.is_uploading && typeof this._restoreCaret === 'number')) { + if (this.props.focusDate !== prevProps.focusDate) { let selectionEnd, selectionStart; if (this.props.preselectDate !== prevProps.preselectDate) { From f8cf85db3b3a4541a0add4b985a9bf0fbe8f2aa0 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 22 May 2018 19:38:37 +0200 Subject: [PATCH 10/14] Remove pointer to light theme until it is more polished (#7594) --- config/themes.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/config/themes.yml b/config/themes.yml index 9c21c9459f3..f0bb1e6f725 100644 --- a/config/themes.yml +++ b/config/themes.yml @@ -1,3 +1,2 @@ default: styles/application.scss contrast: styles/contrast.scss -mastodon-light: styles/mastodon-light.scss From 7745a22ec748be8bd6410cda0a61d197e49a72a3 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 22 May 2018 19:39:35 +0200 Subject: [PATCH 11/14] Bump version to 2.4.0 --- lib/mastodon/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 7f7b5505a91..81cc347568b 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -21,7 +21,7 @@ module Mastodon end def flags - 'rc5' + '' end def to_a From 4d81809f36fcbfe787e23d490f2cb0ad943ab32c Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 22 May 2018 20:05:24 +0200 Subject: [PATCH 12/14] Yeah, it was supposed to be #create? --- app/controllers/invites_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index 8e87c63cfd2..26ef99b7e00 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -8,7 +8,7 @@ class InvitesController < ApplicationController before_action :authenticate_user! def index - authorize :invite, :index? + authorize :invite, :create? @invites = invites @invite = Invite.new(expires_in: 1.day.to_i) From 6d99a0b652d866d41a56140aa695864b20830411 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Wed, 23 May 2018 13:32:10 +0900 Subject: [PATCH 13/14] Fix tests for invites controller (regression from 4d81809f36fcbfe787e23d490f2cb0ad943ab32c) (#7597) --- spec/controllers/invites_controller_spec.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb index c5c6cb651a8..9f5ab67c303 100644 --- a/spec/controllers/invites_controller_spec.rb +++ b/spec/controllers/invites_controller_spec.rb @@ -7,15 +7,21 @@ describe InvitesController do sign_in user end + around do |example| + min_invite_role = Setting.min_invite_role + example.run + Setting.min_invite_role = min_invite_role + end + describe 'GET #index' do subject { get :index } + let(:user) { Fabricate(:user, moderator: false, admin: false) } let!(:invite) { Fabricate(:invite, user: user) } context 'when user is a staff' do - let(:user) { Fabricate(:user, moderator: true, admin: false) } - it 'renders index page' do + Setting.min_invite_role = 'user' expect(subject).to render_template :index expect(assigns(:invites)).to include invite expect(assigns(:invites).count).to eq 1 @@ -23,9 +29,8 @@ describe InvitesController do end context 'when user is not a staff' do - let(:user) { Fabricate(:user, moderator: false, admin: false) } - it 'returns 403' do + Setting.min_invite_role = 'modelator' expect(subject).to have_http_status 403 end end From d8864b9e9d4fd56606a3b809bedeb1216b6620ae Mon Sep 17 00:00:00 2001 From: ThibG Date: Wed, 23 May 2018 15:20:15 +0200 Subject: [PATCH 14/14] Fix caret position after selected suggestion and media upload (#7595) * Fix media upload reseting caret position to last inserted emoji * Fix caret position after inserting suggestions (fixes #6089) --- .../features/compose/components/compose_form.js | 10 ++++------ .../compose/containers/compose_form_container.js | 1 + app/javascript/mastodon/reducers/compose.js | 7 ++++++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index 9cd39be8b7f..6cc594c889b 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -40,6 +40,7 @@ export default class ComposeForm extends ImmutablePureComponent { privacy: PropTypes.string, spoiler_text: PropTypes.string, focusDate: PropTypes.instanceOf(Date), + caretPosition: PropTypes.number, preselectDate: PropTypes.instanceOf(Date), is_submitting: PropTypes.bool, is_uploading: PropTypes.bool, @@ -96,7 +97,6 @@ export default class ComposeForm extends ImmutablePureComponent { } onSuggestionSelected = (tokenStart, token, value) => { - this._restoreCaret = null; this.props.onSuggestionSelected(tokenStart, token, value); } @@ -116,9 +116,9 @@ export default class ComposeForm extends ImmutablePureComponent { if (this.props.preselectDate !== prevProps.preselectDate) { selectionEnd = this.props.text.length; selectionStart = this.props.text.search(/\s/) + 1; - } else if (typeof this._restoreCaret === 'number') { - selectionStart = this._restoreCaret; - selectionEnd = this._restoreCaret; + } else if (typeof this.props.caretPosition === 'number') { + selectionStart = this.props.caretPosition; + selectionEnd = this.props.caretPosition; } else { selectionEnd = this.props.text.length; selectionStart = selectionEnd; @@ -138,10 +138,8 @@ export default class ComposeForm extends ImmutablePureComponent { handleEmojiPick = (data) => { const { text } = this.props; const position = this.autosuggestTextarea.textarea.selectionStart; - const emojiChar = data.native; const needsSpace = data.custom && position > 0 && !allowedAroundShortCode.includes(text[position - 1]); - this._restoreCaret = position + emojiChar.length + 1 + (needsSpace ? 1 : 0); this.props.onPickEmoji(position, data, needsSpace); } diff --git a/app/javascript/mastodon/features/compose/containers/compose_form_container.js b/app/javascript/mastodon/features/compose/containers/compose_form_container.js index c3aa580ee30..3822dd711fe 100644 --- a/app/javascript/mastodon/features/compose/containers/compose_form_container.js +++ b/app/javascript/mastodon/features/compose/containers/compose_form_container.js @@ -19,6 +19,7 @@ const mapStateToProps = state => ({ spoiler_text: state.getIn(['compose', 'spoiler_text']), privacy: state.getIn(['compose', 'privacy']), focusDate: state.getIn(['compose', 'focusDate']), + caretPosition: state.getIn(['compose', 'caretPosition']), preselectDate: state.getIn(['compose', 'preselectDate']), is_submitting: state.getIn(['compose', 'is_submitting']), is_uploading: state.getIn(['compose', 'is_uploading']), diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 46d9d6c8eef..62461d1a7fe 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -44,6 +44,7 @@ const initialState = ImmutableMap({ privacy: null, text: '', focusDate: null, + caretPosition: null, preselectDate: null, in_reply_to: null, is_composing: false, @@ -91,7 +92,6 @@ function appendMedia(state, media) { map.update('media_attachments', list => list.push(media)); map.set('is_uploading', false); map.set('resetFileKey', Math.floor((Math.random() * 0x10000))); - map.set('focusDate', new Date()); map.set('idempotencyKey', uuid()); if (prevSize === 0 && (state.get('default_sensitive') || state.get('spoiler'))) { @@ -119,6 +119,7 @@ const insertSuggestion = (state, position, token, completion) => { map.set('suggestion_token', null); map.update('suggestions', ImmutableList(), list => list.clear()); map.set('focusDate', new Date()); + map.set('caretPosition', position + completion.length + 1); map.set('idempotencyKey', uuid()); }); }; @@ -142,6 +143,7 @@ const insertEmoji = (state, position, emojiData, needsSpace) => { return state.merge({ text: `${oldText.slice(0, position)}${emoji} ${oldText.slice(position)}`, focusDate: new Date(), + caretPosition: position + emoji.length + 1, idempotencyKey: uuid(), }); }; @@ -216,6 +218,7 @@ export default function compose(state = initialState, action) { map.set('text', statusToTextMentions(state, action.status)); map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy'))); map.set('focusDate', new Date()); + map.set('caretPosition', null); map.set('preselectDate', new Date()); map.set('idempotencyKey', uuid()); @@ -259,6 +262,7 @@ export default function compose(state = initialState, action) { return state.withMutations(map => { map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' ')); map.set('focusDate', new Date()); + map.set('caretPosition', null); map.set('idempotencyKey', uuid()); }); case COMPOSE_DIRECT: @@ -266,6 +270,7 @@ export default function compose(state = initialState, action) { map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' ')); map.set('privacy', 'direct'); map.set('focusDate', new Date()); + map.set('caretPosition', null); map.set('idempotencyKey', uuid()); }); case COMPOSE_SUGGESTIONS_CLEAR: