Compare commits

...

9 Commits

Author SHA1 Message Date
anna 0da3340f0d
add emoji reaction database model 1 year ago
anna 8380f9fc2c
merge catstodon/main into main 1 year ago
Jeremy Kescher 41ce71cc92
Merge remote-tracking branch 'upstream/main' into develop 1 year ago
Claire 43dbc62568
Fix privacy dropdown in boost modal on mobile (#1967)
Fixes #1965
1 year ago
Claire bdc61d467d
Merge pull request #1966 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes
1 year ago
Claire ff42233aae Merge branch 'main' into glitch-soc/merge-upstream 1 year ago
BtbN f343ed42ff
Add missing procps package to Dockerfile (#21028)
The new Debian-Base does not come with this by default, making the ps based health-check in the compose file fail
1 year ago
Claire 51a33ce77a
Fix not being able to follow more than one hashtag (#21285)
Fixes regression from #20860
1 year ago
David Leadbeater 69378eac99
Don't allow URLs that contain non-normalized paths to be verified (#20999)
* Don't allow URLs that contain non-normalized paths to be verified

This stops things like https://example.com/otheruser/../realuser where
"/otheruser" appears to be the verified URL, but the actual URL being
verified is "/realuser" due to the "/../".

Also fix a test to use 'https', so it is testing the right thing, now
that since #20304 https is required.

* missing do
1 year ago

@ -56,6 +56,7 @@ RUN apt-get update && \
useradd -u "$UID" -g "${GID}" -m -d /opt/mastodon mastodon && \
apt-get -y --no-install-recommends install whois \
wget \
procps \
libssl1.1 \
libpq5 \
imagemagick \

@ -12,7 +12,7 @@ class Api::V1::TagsController < Api::BaseController
end
def follow
TagFollow.first_or_create!(tag: @tag, account: current_account, rate_limit: true)
TagFollow.create_with(rate_limit: true).find_or_create_by!(tag: @tag, account: current_account)
render json: @tag, serializer: REST::TagSerializer
end

@ -356,10 +356,8 @@ class ComposeForm extends ImmutablePureComponent {
<OptionsContainer
advancedOptions={advancedOptions}
disabled={isSubmitting}
onChangeVisibility={onChangeVisibility}
onToggleSpoiler={spoilersAlwaysOn ? null : onChangeSpoilerness}
onUpload={onPaste}
privacy={privacy}
isEditing={isEditing}
sensitive={sensitive || (spoilersAlwaysOn && spoilerText && spoilerText.length > 0)}
spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler}

@ -9,13 +9,13 @@ import IconButton from 'flavours/glitch/components/icon_button';
import DropdownMenu from './dropdown_menu';
// Utils.
import { isUserTouching } from 'flavours/glitch/is_mobile';
import { assignHandlers } from 'flavours/glitch/utils/react_helpers';
// The component.
export default class ComposerOptionsDropdown extends React.PureComponent {
static propTypes = {
isUserTouching: PropTypes.func,
disabled: PropTypes.bool,
icon: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.shape({
@ -49,7 +49,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
const { onModalOpen } = this.props;
const { open } = this.state;
if (isUserTouching()) {
if (this.props.isUserTouching && this.props.isUserTouching()) {
if (this.state.open) {
this.props.onModalClose();
} else {

@ -10,8 +10,8 @@ import { connect } from 'react-redux';
// Components.
import IconButton from 'flavours/glitch/components/icon_button';
import TextIconButton from './text_icon_button';
import Dropdown from './dropdown';
import PrivacyDropdown from './privacy_dropdown';
import DropdownContainer from '../containers/dropdown_container';
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
import LanguageDropdown from '../containers/language_dropdown_container';
import ImmutablePureComponent from 'react-immutable-pure-component';
@ -126,15 +126,11 @@ class ComposerOptions extends ImmutablePureComponent {
hasPoll: PropTypes.bool,
intl: PropTypes.object.isRequired,
onChangeAdvancedOption: PropTypes.func,
onChangeVisibility: PropTypes.func,
onChangeContentType: PropTypes.func,
onTogglePoll: PropTypes.func,
onDoodleOpen: PropTypes.func,
onModalClose: PropTypes.func,
onModalOpen: PropTypes.func,
onToggleSpoiler: PropTypes.func,
onUpload: PropTypes.func,
privacy: PropTypes.string,
contentType: PropTypes.string,
resetFileKey: PropTypes.number,
spoiler: PropTypes.bool,
@ -195,12 +191,8 @@ class ComposerOptions extends ImmutablePureComponent {
hasPoll,
onChangeAdvancedOption,
onChangeContentType,
onChangeVisibility,
onTogglePoll,
onModalClose,
onModalOpen,
onToggleSpoiler,
privacy,
resetFileKey,
spoiler,
showContentTypeChoice,
@ -239,7 +231,7 @@ class ComposerOptions extends ImmutablePureComponent {
multiple
style={{ display: 'none' }}
/>
<Dropdown
<DropdownContainer
disabled={disabled || !allowMedia}
icon='paperclip'
items={[
@ -255,8 +247,6 @@ class ComposerOptions extends ImmutablePureComponent {
},
]}
onChange={this.handleClickAttach}
onModalClose={onModalClose}
onModalOpen={onModalOpen}
title={formatMessage(messages.attach)}
/>
{!!pollLimits && (
@ -275,15 +265,9 @@ class ComposerOptions extends ImmutablePureComponent {
/>
)}
<hr />
<PrivacyDropdown
disabled={disabled || isEditing}
onChange={onChangeVisibility}
onModalClose={onModalClose}
onModalOpen={onModalOpen}
value={privacy}
/>
<PrivacyDropdownContainer disabled={disabled || isEditing} />
{showContentTypeChoice && (
<Dropdown
<DropdownContainer
disabled={disabled}
icon={(contentTypeItems[contentType.split('/')[1]] || {}).icon}
items={[
@ -292,8 +276,6 @@ class ComposerOptions extends ImmutablePureComponent {
contentTypeItems.markdown,
]}
onChange={onChangeContentType}
onModalClose={onModalClose}
onModalOpen={onModalOpen}
title={formatMessage(messages.content_type)}
value={contentType}
/>
@ -308,7 +290,7 @@ class ComposerOptions extends ImmutablePureComponent {
/>
)}
<LanguageDropdown />
<Dropdown
<DropdownContainer
disabled={disabled || isEditing}
icon='ellipsis-h'
items={advancedOptions ? [
@ -325,8 +307,6 @@ class ComposerOptions extends ImmutablePureComponent {
] : null}
onChange={onChangeAdvancedOption}
renderItemContents={this.renderToggleItemContents}
onModalClose={onModalClose}
onModalOpen={onModalOpen}
title={formatMessage(messages.advanced_options_icon_title)}
closeOnChange={false}
/>

@ -32,7 +32,7 @@ class PrivacyDropdown extends React.PureComponent {
};
render () {
const { value, onChange, onModalOpen, onModalClose, disabled, noDirect, container, intl: { formatMessage } } = this.props;
const { value, onChange, onModalOpen, onModalClose, disabled, noDirect, container, isUserTouching, intl: { formatMessage } } = this.props;
// We predefine our privacy items so that we can easily pick the
// dropdown icon later.
@ -75,6 +75,7 @@ class PrivacyDropdown extends React.PureComponent {
icon={(privacyItems[value] || {}).icon}
items={items}
onChange={onChange}
isUserTouching={isUserTouching}
onModalClose={onModalClose}
onModalOpen={onModalOpen}
title={formatMessage(messages.change_privacy)}

@ -0,0 +1,12 @@
import { connect } from 'react-redux';
import { isUserTouching } from 'flavours/glitch/is_mobile';
import { openModal, closeModal } from 'flavours/glitch/actions/modal';
import Dropdown from '../components/dropdown';
const mapDispatchToProps = dispatch => ({
isUserTouching,
onModalOpen: props => dispatch(openModal('ACTIONS', props)),
onModalClose: () => dispatch(closeModal()),
});
export default connect(null, mapDispatchToProps)(Dropdown);

@ -6,7 +6,7 @@ import {
addPoll,
removePoll,
} from 'flavours/glitch/actions/compose';
import { closeModal, openModal } from 'flavours/glitch/actions/modal';
import { openModal } from 'flavours/glitch/actions/modal';
function mapStateToProps (state) {
const spoilersAlwaysOn = state.getIn(['local_settings', 'always_show_spoilers_field']);
@ -48,14 +48,6 @@ const mapDispatchToProps = (dispatch) => ({
onDoodleOpen() {
dispatch(openModal('DOODLE', { noEsc: true }));
},
onModalClose() {
dispatch(closeModal());
},
onModalOpen(props) {
dispatch(openModal('ACTIONS', props));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(Options);

@ -0,0 +1,23 @@
import { connect } from 'react-redux';
import PrivacyDropdown from '../components/privacy_dropdown';
import { changeComposeVisibility } from 'flavours/glitch/actions/compose';
import { openModal, closeModal } from 'flavours/glitch/actions/modal';
import { isUserTouching } from 'flavours/glitch/is_mobile';
const mapStateToProps = state => ({
value: state.getIn(['compose', 'privacy']),
});
const mapDispatchToProps = dispatch => ({
onChange (value) {
dispatch(changeComposeVisibility(value));
},
isUserTouching,
onModalOpen: props => dispatch(openModal('ACTIONS', props)),
onModalClose: () => dispatch(closeModal()),
});
export default connect(mapStateToProps, mapDispatchToProps)(PrivacyDropdown);

@ -46,7 +46,8 @@ class Account::Field < ActiveModelSerializers::Model
parsed_url.user.nil? &&
parsed_url.password.nil? &&
parsed_url.host.present? &&
parsed_url.normalized_host == parsed_url.host
parsed_url.normalized_host == parsed_url.host &&
(parsed_url.path.empty? || parsed_url.path == parsed_url.normalized_path)
rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
false
end

@ -0,0 +1,16 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: emoji_reactions
#
# id :bigint(8) not null, primary key
# emoji :string
# status_id :bigint(8) not null
# account_id :bigint(8) not null
# created_at :datetime not null
# updated_at :datetime not null
#
class EmojiReaction < ApplicationRecord
belongs_to :status
belongs_to :account
end

@ -70,6 +70,7 @@ class Status < ApplicationRecord
has_many :mentions, dependent: :destroy, inverse_of: :status
has_many :active_mentions, -> { active }, class_name: 'Mention', inverse_of: :status
has_many :media_attachments, dependent: :nullify
has_many :emoji_reactions
has_and_belongs_to_many :tags
has_and_belongs_to_many :preview_cards

@ -0,0 +1,11 @@
class CreateEmojiReactions < ActiveRecord::Migration[6.1]
def change
create_table :emoji_reactions do |t|
t.string :emoji
t.references :status, null: false, foreign_key: true
t.references :account, null: false, foreign_key: true
t.timestamps
end
end
end

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2022_11_04_133904) do
ActiveRecord::Schema.define(version: 2022_11_23_004534) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -411,6 +411,16 @@ ActiveRecord::Schema.define(version: 2022_11_04_133904) do
t.index ["domain"], name: "index_email_domain_blocks_on_domain", unique: true
end
create_table "emoji_reactions", force: :cascade do |t|
t.string "emoji"
t.bigint "status_id", null: false
t.bigint "account_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["account_id"], name: "index_emoji_reactions_on_account_id"
t.index ["status_id"], name: "index_emoji_reactions_on_status_id"
end
create_table "encrypted_messages", id: :bigint, default: -> { "timestamp_id('encrypted_messages'::text)" }, force: :cascade do |t|
t.bigint "device_id"
t.bigint "from_account_id"
@ -781,6 +791,16 @@ ActiveRecord::Schema.define(version: 2022_11_04_133904) do
t.index ["status_id", "preview_card_id"], name: "index_preview_cards_statuses_on_status_id_and_preview_card_id"
end
create_table "reactions", force: :cascade do |t|
t.string "emoji"
t.bigint "status_id", null: false
t.bigint "account_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["account_id"], name: "index_reactions_on_account_id"
t.index ["status_id"], name: "index_reactions_on_status_id"
end
create_table "relays", force: :cascade do |t|
t.string "inbox_url", default: "", null: false
t.string "follow_activity_id"
@ -1158,6 +1178,8 @@ ActiveRecord::Schema.define(version: 2022_11_04_133904) do
add_foreign_key "devices", "accounts", on_delete: :cascade
add_foreign_key "devices", "oauth_access_tokens", column: "access_token_id", on_delete: :cascade
add_foreign_key "email_domain_blocks", "email_domain_blocks", column: "parent_id", on_delete: :cascade
add_foreign_key "emoji_reactions", "accounts"
add_foreign_key "emoji_reactions", "statuses"
add_foreign_key "encrypted_messages", "accounts", column: "from_account_id", on_delete: :cascade
add_foreign_key "encrypted_messages", "devices", on_delete: :cascade
add_foreign_key "favourites", "accounts", name: "fk_5eb6c2b873", on_delete: :cascade
@ -1198,6 +1220,8 @@ ActiveRecord::Schema.define(version: 2022_11_04_133904) do
add_foreign_key "polls", "accounts", on_delete: :cascade
add_foreign_key "polls", "statuses", on_delete: :cascade
add_foreign_key "preview_card_trends", "preview_cards", on_delete: :cascade
add_foreign_key "reactions", "accounts"
add_foreign_key "reactions", "statuses"
add_foreign_key "report_notes", "accounts", on_delete: :cascade
add_foreign_key "report_notes", "reports", on_delete: :cascade
add_foreign_key "reports", "accounts", column: "action_taken_by_account_id", name: "fk_bca45b75fd", on_delete: :nullify

@ -25,7 +25,7 @@ module Mastodon
end
def suffix_version
'+1.0.2'
'+1.0.3'
end
def post_suffix

@ -33,7 +33,11 @@ RSpec.describe Api::V1::TagsController, type: :controller do
end
describe 'POST #follow' do
let!(:unrelated_tag) { Fabricate(:tag) }
before do
TagFollow.create!(account: user.account, tag: unrelated_tag)
post :follow, params: { id: name }
end

@ -0,0 +1,5 @@
Fabricator(:emoji_reaction) do
emoji "MyString"
status nil
account nil
end

@ -67,7 +67,15 @@ RSpec.describe Account::Field, type: :model do
end
context 'for an IDN URL' do
let(:value) { 'http://twitter.comdougalljstatus1590357240443437057.ê.cc/twitter.html' }
let(:value) { 'https://twitter.comdougalljstatus1590357240443437057.ê.cc/twitter.html' }
it 'returns false' do
expect(subject.verifiable?).to be false
end
end
context 'for a URL with a non-normalized path' do
let(:value) { 'https://github.com/octocatxxxxxxxx/../mastodon' }
it 'returns false' do
expect(subject.verifiable?).to be false

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe EmojiReaction, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end
Loading…
Cancel
Save