Compare commits

...

15 Commits

Author SHA1 Message Date
khr 9cd0511ba9 Bio length -> 413 characters
Increase the cybre.space profile bio text length limit to 413
characters. Also modifies the account settings page to automatically
resize the textbox to the size of the contained text, so that it's
easier to type longer bios.
2019-05-22 16:03:42 -07:00
Eugen Rochko 370ec7e771 Bump version to 2.8.3 2019-05-19 22:35:49 +02:00
ThibG 9222c26e19 Fix “invited by” not showing up for invited accounts in admin interface (#10791) 2019-05-19 22:32:25 +02:00
Hinaloe 94439a1da7 fix `isSubmitting` prop case (#10785) 2019-05-19 22:32:14 +02:00
ThibG a6815a7578 Add post-deployment migration script to delete public-boosts-of-private-toots (#10783) 2019-05-19 16:27:11 +02:00
Ben Lubar d587a943a5 add og:image:alt for media attachments in embeds (#10779) 2019-05-19 16:26:00 +02:00
ThibG 3c27687a6e Prevent from publicly boosting one's own private toots (#10775) 2019-05-19 16:25:40 +02:00
ThibG ee17d81b8a Minor performance improvements and cleanup in formatter (#10765) 2019-05-19 16:25:39 +02:00
Neil Moore 9e95af3391 Adds click-able div that expands status (#10733) (#10766)
The clickable div is positioned under the account avatar and covers
all empty space below it to the end of the status.
2019-05-19 16:25:20 +02:00
nzws 91e25a20ce Fix some colors in light theme (#10754)
* Fix typo in light theme

* Fix background color of empty column
2019-05-19 16:25:20 +02:00
ThibG 47e0928c5b Change icon and label depending on whether media is marked as sensitive (#10748)
* Change icon and label depending on whether media is marked as sensitive

* WiP use a checkbox
2019-05-19 16:25:20 +02:00
Maciek Baron c407a4edf8 Improve poll link accessibility (#10720)
* Add distinction between hover and active/focus states
* Resolves #10198
2019-05-19 16:25:20 +02:00
Jeong Arm 7a6464bea0 Bring back crossed eye icon on gallery (#10715) 2019-05-19 16:25:20 +02:00
nzws 9679ec4fcb Fix some colors of high contrast theme (#10711)
* Fix "nothing here" text color of high contrast

* Fix counter border color of high contrast
2019-05-19 16:25:20 +02:00
ThibG b40dfc124b Add description on hover in media gallery (#10713) 2019-05-19 16:25:20 +02:00
23 changed files with 209 additions and 45 deletions

View File

@ -3,6 +3,28 @@ Changelog
All notable changes to this project will be documented in this file.
## [2.8.3] - 2019-05-19
### Added
- Add `og:image:alt` OpenGraph tag ([BenLubar](https://github.com/tootsuite/mastodon/pull/10779))
- Add clickable area below avatar in statuses in web UI ([Dar13](https://github.com/tootsuite/mastodon/pull/10766))
- Add crossed-out eye icon on account gallery in web UI ([Kjwon15](https://github.com/tootsuite/mastodon/pull/10715))
- Add media description tooltip to thumbnails in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10713))
### Changed
- Change "mark as sensitive" button into a checkbox for clarity ([ThibG](https://github.com/tootsuite/mastodon/pull/10748))
### Fixed
- Fix bug allowing users to publicly boost their private statuses ([ThibG](https://github.com/tootsuite/mastodon/pull/10775), [ThibG](https://github.com/tootsuite/mastodon/pull/10783))
- Fix performance in formatter by a little ([ThibG](https://github.com/tootsuite/mastodon/pull/10765))
- Fix some colors in the light theme ([yuzulabo](https://github.com/tootsuite/mastodon/pull/10754))
- Fix some colors of the high contrast theme ([yuzulabo](https://github.com/tootsuite/mastodon/pull/10711))
- Fix ambivalent active state of poll refresh button in web UI ([MaciekBaron](https://github.com/tootsuite/mastodon/pull/10720))
- Fix duplicate posting being possible from web UI ([hinaloe](https://github.com/tootsuite/mastodon/pull/10785))
- Fix "invited by" not showing up in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/10791))
## [2.8.2] - 2019-05-05
### Added

View File

@ -356,6 +356,7 @@ class Status extends ImmutablePureComponent {
{prepend}
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted, read: unread === false })} data-id={status.get('id')}>
<div className='status__expand' onClick={this.handleClick} role='presentation' />
<div className='status__info'>
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>

View File

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Icon from 'mastodon/components/icon';
import { autoPlayGif, displayMedia } from 'mastodon/initial_state';
import classNames from 'classnames';
import { decode } from 'blurhash';
@ -88,8 +89,10 @@ export default class MediaItem extends ImmutablePureComponent {
const width = `${Math.floor((displayWidth - 4) / 3) - 4}px`;
const height = width;
const status = attachment.get('status');
const title = status.get('spoiler_text') || attachment.get('description');
let thumbnail = '';
let icon;
if (attachment.get('type') === 'unknown') {
// Skip
@ -131,11 +134,20 @@ export default class MediaItem extends ImmutablePureComponent {
);
}
if (!visible) {
icon = (
<span className='account-gallery__item__icons'>
<Icon id='eye-slash' />
</span>
);
}
return (
<div className='account-gallery__item' style={{ width, height }}>
<a className='media-gallery__item-thumbnail' href={status.get('url')} target='_blank' onClick={this.handleClick}>
<a className='media-gallery__item-thumbnail' href={status.get('url')} target='_blank' onClick={this.handleClick} title={title}>
<canvas width={32} height={32} ref={this.setCanvasRef} className={classNames('media-gallery__preview', { 'media-gallery__preview--hidden': visible && loaded })} />
{visible && thumbnail}
{!visible && icon}
</a>
</div>
);

View File

@ -20,7 +20,7 @@ const mapStateToProps = state => ({
focusDate: state.getIn(['compose', 'focusDate']),
caretPosition: state.getIn(['compose', 'caretPosition']),
preselectDate: state.getIn(['compose', 'preselectDate']),
is_submitting: state.getIn(['compose', 'is_submitting']),
isSubmitting: state.getIn(['compose', 'is_submitting']),
isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
isUploading: state.getIn(['compose', 'is_uploading']),
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),

View File

@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import { changeComposeSensitivity } from 'mastodon/actions/compose';
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
import Icon from 'mastodon/components/icon';
const messages = defineMessages({
marked: { id: 'compose_form.sensitive.marked', defaultMessage: 'Media is marked as sensitive' },
@ -38,9 +37,19 @@ class SensitiveButton extends React.PureComponent {
return (
<div className='compose-form__sensitive-button'>
<button className={classNames('icon-button', { active })} onClick={onClick} disabled={disabled} title={intl.formatMessage(active ? messages.marked : messages.unmarked)}>
<Icon id='eye-slash' /> <FormattedMessage id='compose_form.sensitive.hide' defaultMessage='Mark media as sensitive' />
</button>
<label className={classNames('icon-button', { active })} title={intl.formatMessage(active ? messages.marked : messages.unmarked)}>
<input
name='mark-sensitive'
type='checkbox'
checked={active}
onChange={onClick}
disabled={disabled}
/>
<span className={classNames('checkbox', { active })} />
<FormattedMessage id='compose_form.sensitive.hide' defaultMessage='Mark media as sensitive' />
</label>
</div>
);
}

View File

@ -18,6 +18,12 @@ window.addEventListener('message', e => {
id: data.id,
height: document.getElementsByTagName('html')[0].scrollHeight,
}, '*');
if (document.fonts && document.fonts.ready) {
document.fonts.ready.then(sizeBioText);
} else {
sizeBioText();
}
});
});
@ -116,6 +122,17 @@ function main() {
document.head.appendChild(scrollbarWidthStyle);
scrollbarWidthStyle.sheet.insertRule(`body.with-modals--active { margin-right: ${scrollbarWidth}px; }`, 0);
}
[].forEach.call(document.querySelectorAll('[data-component="Card"]'), (content) => {
const props = JSON.parse(content.getAttribute('data-props'));
ReactDOM.render(<CardContainer locale={locale} {...props} />, content);
});
if (document.fonts && document.fonts.ready) {
document.fonts.ready.then(sizeBioText);
} else {
sizeBioText();
}
});
delegate(document, '.webapp-btn', 'click', ({ target, button }) => {
@ -237,6 +254,22 @@ function main() {
input.readonly = oldReadOnly;
});
delegate(document, '#account_note', 'input', sizeBioText);
function sizeBioText() {
const noteCounter = document.querySelector('.note-counter');
const bioTextArea = document.querySelector('#account_note');
if (noteCounter) {
noteCounter.textContent = 413 - length(bioTextArea.value);
}
if (bioTextArea) {
bioTextArea.style.height = 'auto';
bioTextArea.style.height = (bioTextArea.scrollHeight+3) + 'px';
}
}
}
loadPolyfills().then(main).catch(error => {

View File

@ -67,3 +67,11 @@
text-decoration: none;
}
}
.nothing-here {
color: $darker-text-color;
}
.public-layout .public-account-header__tabs__tabs .counter.active::after {
border-bottom: 4px solid $ui-highlight-color;
}

View File

@ -162,7 +162,7 @@
.actions-modal ul li:not(:empty) a:focus button,
.actions-modal ul li:not(:empty) a:hover,
.actions-modal ul li:not(:empty) a:hover button,
.admin-wrapper .sidebar ul ul a.selected,
.admin-wrapper .sidebar ul li a.selected,
.simple_form .block-button,
.simple_form .button,
.simple_form button {
@ -230,6 +230,7 @@
.empty-column-indicator,
.error-column {
color: $primary-text-color;
background: $white;
}
// Change the default colors used on some parts of the profile pages

View File

@ -268,9 +268,34 @@
padding: 10px;
padding-top: 0;
.icon-button {
font-size: 14px;
font-weight: 500;
font-size: 14px;
font-weight: 500;
&.active {
color: $highlight-text-color;
}
input[type=checkbox] {
display: none;
}
.checkbox {
display: inline-block;
position: relative;
border: 1px solid $ui-primary-color;
box-sizing: border-box;
width: 18px;
height: 18px;
flex: 0 0 auto;
margin-right: 10px;
top: -1px;
border-radius: 4px;
vertical-align: middle;
&.active {
border-color: $highlight-text-color;
background: $highlight-text-color;
}
}
}
@ -1386,6 +1411,15 @@ a.account__display-name {
width: 48px;
}
.status__expand {
width: 68px;
position: absolute;
left: 0;
top: 0;
height: 100%;
cursor: pointer;
}
.muted {
.status__content p,
.status__content a {
@ -4829,6 +4863,14 @@ a.status-card.compact:hover {
border-radius: 4px;
overflow: hidden;
margin: 2px;
&__icons {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
}
}
.notification__filter-bar,

View File

@ -114,11 +114,14 @@
text-decoration: underline;
font-size: inherit;
&:hover,
&:focus,
&:active {
&:hover {
text-decoration: none;
}
&:active,
&:focus {
background-color: rgba($dark-text-color, .1);
}
}
.button {

View File

@ -187,7 +187,7 @@ class Formatter
end
def rewrite(text, entities)
chars = text.to_s.to_char_a
text = text.to_s
# Sort by start index
entities = entities.sort_by do |entity|
@ -199,12 +199,12 @@ class Formatter
last_index = entities.reduce(0) do |index, entity|
indices = entity.respond_to?(:indices) ? entity.indices : entity[:indices]
result << encode(chars[index...indices.first].join)
result << encode(text[index...indices.first])
result << yield(entity)
indices.last
end
result << encode(chars[last_index..-1].join)
result << encode(text[last_index..-1])
result.flatten.join
end
@ -231,23 +231,14 @@ class Formatter
# Note: I couldn't obtain list_slug with @user/list-name format
# for mention so this requires additional check
special = Extractor.extract_urls_with_indices(escaped, options).map do |extract|
# exactly one of :url, :hashtag, :screen_name, :cashtag keys is present
key = (extract.keys & [:url, :hashtag, :screen_name, :cashtag]).first
new_indices = [
old_to_new_index.find_index(extract[:indices].first),
old_to_new_index.find_index(extract[:indices].last),
]
has_prefix_char = [:hashtag, :screen_name, :cashtag].include?(key)
value_indices = [
new_indices.first + (has_prefix_char ? 1 : 0), # account for #, @ or $
new_indices.last - 1,
]
next extract.merge(
:indices => new_indices,
key => text[value_indices.first..value_indices.last]
indices: new_indices,
url: text[new_indices.first..new_indices.last - 1]
)
end

View File

@ -75,7 +75,8 @@ class Account < ApplicationRecord
validates_with UniqueUsernameValidator, if: -> { local? && will_save_change_to_username? }
validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? }
validates :display_name, length: { maximum: 30 }, if: -> { local? && will_save_change_to_display_name? }
validates :note, note_length: { maximum: 160 }, if: -> { local? && will_save_change_to_note? }
validates :note, note_length: { maximum: 413 }, if: -> { local? && will_save_change_to_note? }
validate :note_has_eight_newlines?, if: -> { local? && will_save_change_to_note? }
validates :fields, length: { maximum: 4 }, if: -> { local? && will_save_change_to_fields? }
scope :remote, -> { where.not(domain: nil) }
@ -299,10 +300,8 @@ class Account < ApplicationRecord
def save_with_optional_media!
save!
rescue ActiveRecord::RecordInvalid
self.avatar = nil
self.header = nil
self[:avatar_remote_url] = ''
self[:header_remote_url] = ''
self.avatar = nil if errors[:avatar].present?
self.header = nil if errors[:header].present?
save!
end
@ -326,6 +325,10 @@ class Account < ApplicationRecord
shared_inbox_url.presence || inbox_url
end
def note_has_eight_newlines?
errors.add(:note, 'Bio can\'t have more then 8 newlines') unless note.count("\n") <= 8
end
class Field < ActiveModelSerializers::Model
attributes :name, :value, :verified_at, :account, :errors

View File

@ -114,6 +114,10 @@ class User < ApplicationRecord
end
def invited?
invite_id.present?
end
def valid_invitation?
invite_id.present? && invite.valid_for_use?
end
@ -274,7 +278,7 @@ class User < ApplicationRecord
private
def set_approved
self.approved = open_registrations? || invited? || external?
self.approved = open_registrations? || valid_invitation? || external?
end
def open_registrations?

View File

@ -18,7 +18,9 @@ class ReblogService < BaseService
return reblog unless reblog.nil?
reblog = account.statuses.create!(reblog: reblogged_status, text: '', visibility: options[:visibility] || account.user&.setting_default_privacy)
visibility = options[:visibility] || account.user&.setting_default_privacy
visibility = reblogged_status.visibility if reblogged_status.hidden?
reblog = account.statuses.create!(reblog: reblogged_status, text: '', visibility: visibility)
DistributionWorker.perform_async(reblog.id)
Pubsubhubbub::DistributionWorker.perform_async(reblog.stream_entry.id)

View File

@ -2,7 +2,7 @@
class BlacklistedEmailValidator < ActiveModel::Validator
def validate(user)
return if user.invited?
return if user.valid_invitation?
@email = user.email

View File

@ -7,7 +7,7 @@
.fields-row
.fields-row__column.fields-group.fields-row__column-6
= f.input :display_name, wrapper: :with_label, input_html: { maxlength: 30 }, hint: false
= f.input :note, wrapper: :with_label, input_html: { maxlength: 160 }, hint: false
= f.input :note, wrapper: :with_label, input_html: { maxlength: 413 }, hint: false
.fields-row
.fields-row__column.fields-row__column-6

View File

@ -7,6 +7,8 @@
- unless media.file.meta.nil?
= opengraph 'og:image:width', media.file.meta.dig('original', 'width')
= opengraph 'og:image:height', media.file.meta.dig('original', 'height')
- if media.description.present?
= opengraph 'og:image:alt', media.description
- elsif media.video? || media.gifv?
- player_card = true
= opengraph 'og:image', full_asset_url(media.file.url(:small))

View File

@ -0,0 +1,23 @@
class RemoveBoostsWideningAudience < ActiveRecord::Migration[5.2]
disable_ddl_transaction!
def up
public_boosts = Status.find_by_sql(<<-SQL)
SELECT boost.id
FROM statuses AS boost
LEFT JOIN statuses AS boosted ON boost.reblog_of_id = boosted.id
WHERE
boost.id > 101746055577600000
AND (boost.local = TRUE OR boost.uri IS NULL)
AND boost.visibility IN (0, 1)
AND boost.reblog_of_id IS NOT NULL
AND boosted.visibility = 2
SQL
RemovalWorker.push_bulk(public_boosts.pluck(:id))
end
def down
raise ActiveRecord::IrreversibleMigration
end
end

View File

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2019_04_20_025523) do
ActiveRecord::Schema.define(version: 2019_05_19_130537) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"

View File

@ -13,7 +13,7 @@ module Mastodon
end
def patch
2
3
end
def pre

View File

@ -601,8 +601,8 @@ RSpec.describe Account, type: :model do
expect(account).to model_have_error_on_field(:display_name)
end
it 'is invalid if the note is longer than 160 characters' do
account = Fabricate.build(:account, note: Faker::Lorem.characters(161))
it 'is invalid if the note is longer than 413 characters' do
account = Fabricate.build(:account, note: Faker::Lorem.characters(414))
account.valid?
expect(account).to model_have_error_on_field(:note)
end
@ -647,8 +647,8 @@ RSpec.describe Account, type: :model do
expect(account).not_to model_have_error_on_field(:display_name)
end
it 'is valid even if the note is longer than 160 characters' do
account = Fabricate.build(:account, domain: 'domain', note: Faker::Lorem.characters(161))
it 'is valid even if the note is longer than 413 characters' do
account = Fabricate.build(:account, domain: 'domain', note: Faker::Lorem.characters(414))
account.valid?
expect(account).not_to model_have_error_on_field(:note)
end

View File

@ -4,10 +4,9 @@ RSpec.describe ReblogService, type: :service do
let(:alice) { Fabricate(:account, username: 'alice') }
context 'creates a reblog with appropriate visibility' do
let(:bob) { Fabricate(:account, username: 'bob') }
let(:visibility) { :public }
let(:reblog_visibility) { :public }
let(:status) { Fabricate(:status, account: bob, visibility: visibility) }
let(:status) { Fabricate(:status, account: alice, visibility: visibility) }
subject { ReblogService.new }
@ -22,6 +21,15 @@ RSpec.describe ReblogService, type: :service do
expect(status.reblogs.first.visibility).to eq 'private'
end
end
describe 'public reblogs of private toots should remain private' do
let(:visibility) { :private }
let(:reblog_visibility) { :public }
it 'reblogs privately' do
expect(status.reblogs.first.visibility).to eq 'private'
end
end
end
context 'OStatus' do

View File

@ -8,7 +8,7 @@ RSpec.describe BlacklistedEmailValidator, type: :validator do
let(:errors) { double(add: nil) }
before do
allow(user).to receive(:invited?) { false }
allow(user).to receive(:valid_invitation?) { false }
allow_any_instance_of(described_class).to receive(:blocked_email?) { blocked_email }
described_class.new.validate(user)
end