Compare commits

...

18 Commits

Author SHA1 Message Date
dongzhimin-xz
dade625358
Merge a479b2ac6b into e6591bf322 2025-05-06 13:14:00 +00:00
Claire
e6591bf322 Fix code style issue
Some checks are pending
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
Haml Linting / lint (push) Waiting to run
JavaScript Linting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
JavaScript Testing / test (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Historical data migration test / test (16-alpine) (push) Waiting to run
Historical data migration test / test (17-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.1) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.4) (push) Blocked by required conditions
Ruby Testing / Libvips tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / Libvips tests (3.1) (push) Blocked by required conditions
Ruby Testing / Libvips tests (3.2) (push) Blocked by required conditions
Ruby Testing / Libvips tests (3.4) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.1) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.4) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.1, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.4, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
2025-05-06 15:08:57 +02:00
Claire
30e25ff7fc Bump version to v4.3.8 2025-05-06 15:04:34 +02:00
Claire
5ef82d7937 Update dependency net-imap 2025-05-06 15:04:34 +02:00
Claire
e14bf631b5 Update dependency nokogiri 2025-05-06 15:04:34 +02:00
Claire
6d46225718
Merge commit from fork
* Check scheme in account and post links

* Harden media attachments

* Client-side mitigation

* Client-side mitigation for media attachments
2025-05-06 15:02:13 +02:00
dongzhimin
a479b2ac6b fixed i18n issue 2025-05-02 15:12:26 +08:00
dongzhimin
b0049e20b3 已添加文字 2025-04-20 12:58:55 +08:00
dongzhimin
64fca51f68 上次提交的BUG已修复 2025-04-20 12:25:43 +08:00
dongzhimin
d288c1cc40 前端响应已修复。目前存在BUG:必须多次修改时间,或打开计划按钮,否则会变为立即发布 2025-04-20 01:00:09 +08:00
dongzhimin
3582cb29d0 action已编写,后端正常,但是前端无法正确应对后端响应 2025-04-19 23:43:08 +08:00
dongzhimin
a9784838fd 控件完成,但是只能使用北京时间 2025-04-19 22:20:34 +08:00
dongzhimin
c7248c80fc 去除了reducer里的函数 2025-04-19 16:04:43 +08:00
dongzhimin
dc2fc434f2 无法传递schedule_time状态,修改initialState后再次崩溃 2025-04-19 15:23:12 +08:00
dongzhimin
6cd5993d80 修正错误 2025-04-19 12:50:16 +08:00
dongzhimin
056db885d1 修改了reducer,action,container。出现错误,无法加载css,json parse error 2025-04-19 11:10:49 +08:00
dongzhimin
6f83b7cb91 schedule图标和界面初步制作 2025-04-15 23:32:24 +08:00
dongzhimin
fb7f0f6310 添加了计划发文的图标 2025-04-14 21:28:38 +08:00
18 changed files with 209 additions and 12 deletions

View File

@ -2,9 +2,34 @@
All notable changes to this project will be documented in this file.
## [4.3.8] - 2025-05-06
### Security
- Update dependencies
- Check scheme on account, profile, and media URLs ([GHSA-x2rc-v5wx-g3m5](https://github.com/mastodon/mastodon/security/advisories/GHSA-x2rc-v5wx-g3m5))
### Added
- Add warning for REDIS_NAMESPACE deprecation at startup (#34581 by @ClearlyClaire)
- Add built-in context for interaction policies (#34574 by @ClearlyClaire)
### Changed
- Change activity distribution error handling to skip retrying for deleted accounts (#33617 by @ClearlyClaire)
### Removed
- Remove double-query for signed query strings (#34610 by @ClearlyClaire)
### Fixed
- Fix incorrect redirect in response to unauthenticated API requests in limited federation mode (#34549 by @ClearlyClaire)
- Fix sign-up e-mail confirmation page reloading on error or redirect (#34548 by @ClearlyClaire)
## [4.3.7] - 2025-04-02
### Add
### Added
- Add delay to profile updates to debounce them (#34137 by @ClearlyClaire)
- Add support for paginating partial collections in `SynchronizeFollowersService` (#34272 and #34277 by @ClearlyClaire)

View File

@ -190,7 +190,7 @@ GEM
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
date (3.3.4)
date (3.4.1)
debug (1.9.2)
irb (~> 1.10)
reline (>= 0.3.8)
@ -447,7 +447,7 @@ GEM
uri
net-http-persistent (4.0.2)
connection_pool (~> 2.2)
net-imap (0.4.19)
net-imap (0.5.8)
date
net-protocol
net-ldap (0.19.0)
@ -458,7 +458,7 @@ GEM
net-smtp (0.5.1)
net-protocol
nio4r (2.7.3)
nokogiri (1.18.3)
nokogiri (1.18.8)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
oj (3.16.6)

View File

@ -81,6 +81,9 @@ export const COMPOSE_CHANGE_MEDIA_ORDER = 'COMPOSE_CHANGE_MEDIA_ORDER';
export const COMPOSE_SET_STATUS = 'COMPOSE_SET_STATUS';
export const COMPOSE_FOCUS = 'COMPOSE_FOCUS';
export const COMPOSE_CHANGE_IS_SCHEDULED = 'COMPOSE_CHANGE_IS_SCHEDULED';
export const COMPOSE_CHANGE_SCHEDULE_TIME = 'COMPOSE_CHANGE_SCHEDULE_TIME';
const messages = defineMessages({
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
@ -188,6 +191,7 @@ export function submitCompose() {
const status = getState().getIn(['compose', 'text'], '');
const media = getState().getIn(['compose', 'media_attachments']);
const statusId = getState().getIn(['compose', 'id'], null);
const is_scheduled = getState().getIn(['compose', 'is_scheduled']);
if ((!status || !status.length) && media.size === 0) {
return;
@ -228,6 +232,7 @@ export function submitCompose() {
visibility: getState().getIn(['compose', 'privacy']),
poll: getState().getIn(['compose', 'poll'], null),
language: getState().getIn(['compose', 'language']),
scheduled_at: is_scheduled ? getState().getIn(['compose', 'scheduled_at']) : null,
},
headers: {
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
@ -237,9 +242,22 @@ export function submitCompose() {
browserHistory.goBack();
}
if ('scheduled_at' in response.data) {
dispatch(showAlert({
message: messages.saved,
dismissAfter: 10000,
}));
dispatch(submitComposeSuccess({ ...response.data.params}));
return;
}
dispatch(insertIntoTagHistory(response.data.tags, status));
dispatch(submitComposeSuccess({ ...response.data }));
// To make the app more responsive, immediately push the status
// into the columns
const insertIfOnline = timelineId => {
@ -835,3 +853,16 @@ export const changeMediaOrder = (a, b) => ({
a,
b,
});
export function changeIsScheduled() {
return {
type: COMPOSE_CHANGE_IS_SCHEDULED,
};
}
export function changeScheduleTime(value) {
return {
type: COMPOSE_CHANGE_SCHEDULE_TIME,
value,
};
}

View File

@ -80,6 +80,17 @@ export function normalizeStatus(status, normalOldStatus) {
normalStatus.contentHtml = emojify(normalStatus.content, emojiMap);
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
normalStatus.hidden = expandSpoilers ? false : spoilerText.length > 0 || normalStatus.sensitive;
if (normalStatus.url && !(normalStatus.url.startsWith('http://') || normalStatus.url.startsWith('https://'))) {
normalStatus.url = null;
}
normalStatus.url ||= normalStatus.uri;
normalStatus.media_attachments.forEach(item => {
if (item.remote_url && !(item.remote_url.startsWith('http://') || item.remote_url.startsWith('https://')))
item.remote_url = null;
});
}
if (normalOldStatus) {

View File

@ -29,6 +29,9 @@ import { PollForm } from "./poll_form";
import { ReplyIndicator } from './reply_indicator';
import { UploadForm } from './upload_form';
import ScheduleButtonContainer from '../containers/schedule_button_container';
import { ScheduleForm } from './schedule_form';
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
const messages = defineMessages({
@ -69,6 +72,11 @@ class ComposeForm extends ImmutablePureComponent {
singleColumn: PropTypes.bool,
lang: PropTypes.string,
maxChars: PropTypes.number,
schedule_time: PropTypes.string,
schedule_timezone: PropTypes.string,
is_scheduled: PropTypes.bool.isRequired,
scheduled_at: PropTypes.string,
};
static defaultProps = {
@ -295,6 +303,7 @@ class ComposeForm extends ImmutablePureComponent {
<PollButtonContainer />
<SpoilerButtonContainer />
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
<ScheduleButtonContainer />
<CharacterCounter max={maxChars} text={this.getFulltextForCharacterCounting()} />
</div>
@ -306,6 +315,7 @@ class ComposeForm extends ImmutablePureComponent {
/>
</div>
</div>
<ScheduleForm />
</div>
</div>
</form>

View File

@ -0,0 +1,41 @@
import PropTypes from 'prop-types';
import { useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import classNames from 'classnames';
import { useDispatch, useSelector} from 'react-redux';
import { changeScheduleTime } from 'mastodon/actions/compose';
const messages = defineMessages({
schedule_time: { id: 'compose_form.schedule_time', defaultMessage: 'This post is scheduled to be published at (UTC+8)' },
});
export const ScheduleForm = () => {
const is_scheduled = useSelector(state => state.getIn(['compose', 'is_scheduled']));
const schedule_time = useSelector(state => state.getIn(['compose', 'schedule_time']));
const dispatch = useDispatch();
const intl = useIntl();
const handleChange = useCallback(({ target: { value } }) => {
dispatch(changeScheduleTime(value));
}, [dispatch]);
if (!is_scheduled) {
return null;
}
return (
<div>
<label>{intl.formatMessage(messages.schedule_time)}</label>
<input
className='search__input'
type='datetime-local'
value={schedule_time}
onChange={handleChange}
/>
</div>
);
}

View File

@ -29,6 +29,10 @@ const mapStateToProps = state => ({
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
lang: state.getIn(['compose', 'language']),
maxChars: state.getIn(['server', 'server', 'configuration', 'statuses', 'max_characters'], 500),
is_scheduled: state.getIn(['compose', 'is_scheduled']),
schedule_time: state.getIn(['compose', 'schedule_time']),
schedule_timezone: state.getIn(['compose', 'schedule_timezone']),
scheduled_at: state.getIn(['compose', 'scheduled_at']),
});
const mapDispatchToProps = (dispatch) => ({

View File

@ -0,0 +1,30 @@
import { injectIntl, defineMessages } from "react-intl";
import { connect } from 'react-redux';
import ScheduleIcon from '@/material-icons/400-20px/schedule.svg?react';
import { IconButton } from "@/mastodon/components/icon_button";
import { changeIsScheduled } from '../../../actions/compose';
const messages = defineMessages({
marked: { id: 'compose_form.schedule.marked', defaultMessage: 'This post will be published at the time chosen below'},
unmarked: { id: 'compose_form.schedule.unmarked', defaultMessage: 'This post will be published at once'},
})
const mapStateToProps = (state, { intl }) => ({
iconComponent: ScheduleIcon,
title: intl.formatMessage(state.getIn(['compose', 'is_scheduled']) ? messages.marked : messages.unmarked),
active: state.getIn(['compose', 'is_scheduled']),
ariaControls: 'schedule-publish',
size: 18,
inverted: true,
});
const mapDispatchToProps = dispatch => ({
onClick () {
dispatch(changeIsScheduled());
},
});
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(IconButton));

View File

@ -166,6 +166,9 @@
"compose_form.publish_form": "New post",
"compose_form.reply": "Reply",
"compose_form.save_changes": "Update",
"compose_form.schedule.marked": "This post will be published at the time chosen below",
"compose_form.schedule.unmarked": "This post will be published at once",
"compose_form.schedule_time": "This post is scheduled to be published at (UTC+8)",
"compose_form.spoiler.marked": "Remove content warning",
"compose_form.spoiler.unmarked": "Add content warning",
"compose_form.spoiler_placeholder": "Content warning (optional)",

View File

@ -166,6 +166,9 @@
"compose_form.publish_form": "发嘟",
"compose_form.reply": "回复",
"compose_form.save_changes": "更改",
"compose_form.schedule.marked": "本文将在以下时间发布",
"compose_form.schedule.unmarked": "本文将立即发布",
"compose_form.schedule_time": "计划发文时间(北京时间)",
"compose_form.spoiler.marked": "移除内容警告",
"compose_form.spoiler.unmarked": "添加内容警告",
"compose_form.spoiler_placeholder": "内容警告 (可选)",

View File

@ -150,5 +150,10 @@ export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
),
note_emojified: emojify(accountJSON.note, emojiMap),
note_plain: unescapeHTML(accountJSON.note),
url:
accountJSON.url.startsWith('http://') ||
accountJSON.url.startsWith('https://')
? accountJSON.url
: accountJSON.uri,
});
}

View File

@ -50,6 +50,8 @@ import {
COMPOSE_CHANGE_MEDIA_ORDER,
COMPOSE_SET_STATUS,
COMPOSE_FOCUS,
COMPOSE_CHANGE_IS_SCHEDULED,
COMPOSE_CHANGE_SCHEDULE_TIME
} from '../actions/compose';
import { REDRAFT } from '../actions/statuses';
import { STORE_HYDRATE } from '../actions/store';
@ -94,6 +96,11 @@ const initialState = ImmutableMap({
focusY: 0,
dirty: false,
}),
schedule_time: null,
schedule_timezone: '+08:00',
is_scheduled: false,
scheduled_at: null,
});
const initialPoll = ImmutableMap({
@ -127,6 +134,9 @@ function clearAll(state) {
map.update('media_attachments', list => list.clear());
map.set('poll', null);
map.set('idempotencyKey', uuid());
map.set('schedule_time', null);
map.set('is_scheduled', false);
map.set('scheduled_at', null);
});
}
@ -560,6 +570,18 @@ export default function compose(state = initialState, action) {
return list.splice(indexA, 1).splice(indexB, 0, moveItem);
});
case COMPOSE_CHANGE_IS_SCHEDULED:
return state.withMutations(map => {
map.set('is_scheduled', !state.get('is_scheduled'));
map.set('scheduled_at', state.get('schedule_time') + ':00.0' + state.get('schedule_timezone'));
map.set('idempotencyKey', uuid());
});
case COMPOSE_CHANGE_SCHEDULE_TIME:
return state.withMutations(map => {
map.set('schedule_time', action.value);
map.set('scheduled_at', action.value + ':00.0' + state.get('schedule_timezone'));
map.set('idempotencyKey', uuid());
});
default:
return state;
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20px" height="20px" viewBox="0 0 20 20" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill-opacity:1;" d="M 13.476562 8.167969 C 15.507812 8.167969 17.382812 9.214844 18.398438 10.917969 C 19.417969 12.609375 19.417969 14.722656 18.398438 16.417969 C 17.382812 18.117188 15.507812 19.167969 13.476562 19.167969 C 10.332031 19.167969 7.785156 16.699219 7.785156 13.667969 C 7.785156 10.632812 10.332031 8.167969 13.476562 8.167969 Z M 7.160156 1.25 L 7.160156 2.058594 L 12.214844 2.058594 L 12.214844 1.25 C 12.214844 1.023438 12.398438 0.832031 12.632812 0.832031 L 13.066406 0.832031 C 13.292969 0.832031 13.484375 1.015625 13.484375 1.25 L 13.484375 2.058594 L 18.125 2.058594 C 18.359375 2.058594 18.542969 2.25 18.542969 2.476562 L 18.542969 8.367188 C 18.542969 8.589844 18.359375 8.785156 18.125 8.785156 L 17.691406 8.785156 C 17.457031 8.785156 17.273438 8.601562 17.273438 8.367188 L 17.273438 6.949219 L 2.101562 6.949219 L 2.101562 17.332031 L 7.375 17.332031 C 7.609375 17.332031 7.792969 17.515625 7.792969 17.75 L 7.792969 18.140625 C 7.792969 18.375 7.609375 18.558594 7.375 18.558594 L 0.832031 18.558594 L 0.832031 2.476562 C 0.832031 2.242188 1.015625 2.058594 1.25 2.058594 L 5.890625 2.058594 L 5.890625 1.25 C 5.890625 1.023438 6.074219 0.832031 6.308594 0.832031 L 6.742188 0.832031 C 6.964844 0.832031 7.160156 1.015625 7.160156 1.25 Z M 13.476562 9.390625 C 11.890625 9.390625 10.433594 10.207031 9.640625 11.535156 C 8.851562 12.859375 8.851562 14.492188 9.640625 15.808594 C 10.433594 17.125 11.890625 17.949219 13.476562 17.949219 C 15.917969 17.949219 17.898438 16.035156 17.898438 13.675781 C 17.898438 11.316406 15.917969 9.390625 13.476562 9.390625 Z M 12.523438 11.226562 L 13.375 11.226562 C 13.609375 11.226562 13.792969 11.410156 13.792969 11.640625 L 13.792969 13.941406 C 13.792969 14.089844 13.875 14.234375 14.007812 14.308594 L 14.976562 14.851562 C 15.074219 14.902344 15.144531 14.996094 15.175781 15.105469 C 15.207031 15.210938 15.191406 15.328125 15.132812 15.425781 L 14.726562 16.117188 L 12.75 15.015625 C 12.617188 14.941406 12.535156 14.800781 12.535156 14.648438 L 12.535156 11.226562 Z M 5.890625 3.273438 L 2.101562 3.273438 L 2.101562 5.714844 L 17.273438 5.714844 L 17.273438 3.273438 L 13.484375 3.273438 L 13.484375 4.082031 C 13.484375 4.316406 13.300781 4.5 13.066406 4.5 L 12.632812 4.5 C 12.398438 4.5 12.214844 4.316406 12.214844 4.082031 L 12.214844 3.273438 L 7.160156 3.273438 L 7.160156 4.082031 C 7.160156 4.316406 6.976562 4.5 6.742188 4.5 L 6.308594 4.5 C 6.074219 4.5 5.890625 4.316406 5.890625 4.082031 Z M 5.890625 3.273438 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -15,13 +15,15 @@ class ActivityPub::Parser::MediaAttachmentParser
end
def remote_url
Addressable::URI.parse(@json['url'])&.normalize&.to_s
url = Addressable::URI.parse(@json['url'])&.normalize&.to_s
url unless unsupported_uri_scheme?(url)
rescue Addressable::URI::InvalidURIError
nil
end
def thumbnail_remote_url
Addressable::URI.parse(@json['icon'].is_a?(Hash) ? @json['icon']['url'] : @json['icon'])&.normalize&.to_s
url = Addressable::URI.parse(@json['icon'].is_a?(Hash) ? @json['icon']['url'] : @json['icon'])&.normalize&.to_s
url unless unsupported_uri_scheme?(url)
rescue Addressable::URI::InvalidURIError
nil
end

View File

@ -28,7 +28,10 @@ class ActivityPub::Parser::StatusParser
end
def url
url_to_href(@object['url'], 'text/html') if @object['url'].present?
return if @object['url'].blank?
url = url_to_href(@object['url'], 'text/html')
url unless unsupported_uri_scheme?(url)
end
def text

View File

@ -4,6 +4,7 @@ require 'singleton'
class ActivityPub::TagManager
include Singleton
include JsonLdHelper
include RoutingHelper
CONTEXT = 'https://www.w3.org/ns/activitystreams'
@ -17,7 +18,7 @@ class ActivityPub::TagManager
end
def url_for(target)
return target.url if target.respond_to?(:local?) && !target.local?
return unsupported_uri_scheme?(target.url) ? nil : target.url if target.respond_to?(:local?) && !target.local?
return unless target.respond_to?(:object_type)

View File

@ -59,7 +59,7 @@ services:
web:
# You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes
# build: .
image: ghcr.io/mastodon/mastodon:v4.3.7
image: ghcr.io/mastodon/mastodon:v4.3.8
restart: always
env_file: .env.production
command: bundle exec puma -C config/puma.rb
@ -83,7 +83,7 @@ services:
# build:
# dockerfile: ./streaming/Dockerfile
# context: .
image: ghcr.io/mastodon/mastodon-streaming:v4.3.7
image: ghcr.io/mastodon/mastodon-streaming:v4.3.8
restart: always
env_file: .env.production
command: node ./streaming/index.js
@ -102,7 +102,7 @@ services:
sidekiq:
# You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes
# build: .
image: ghcr.io/mastodon/mastodon:v4.3.7
image: ghcr.io/mastodon/mastodon:v4.3.8
restart: always
env_file: .env.production
command: bundle exec sidekiq

View File

@ -13,7 +13,7 @@ module Mastodon
end
def patch
7
8
end
def default_prerelease