Merge commit 'b91724fb9d0839365391310e20c2589ff6062d4f' into glitch-soc/merge-upstream

Conflicts:
- `Vagrantfile`:
  Upstream bumped a bunch of values, including one that was already bumped by
  glitch-soc.
  Took upstream's version.
- `lib/paperclip/transcoder.rb`:
  glitch-soc already had a partial fix for this.
  Took upstream's version.
This commit is contained in:
Claire
2023-08-24 21:46:17 +02:00
17 changed files with 176 additions and 103 deletions

View File

@@ -22,6 +22,7 @@ module ContextHelper
blurhash: { 'toot' => 'http://joinmastodon.org/ns#', 'blurhash' => 'toot:blurhash' },
discoverable: { 'toot' => 'http://joinmastodon.org/ns#', 'discoverable' => 'toot:discoverable' },
indexable: { 'toot' => 'http://joinmastodon.org/ns#', 'indexable' => 'toot:indexable' },
memorial: { 'toot' => 'http://joinmastodon.org/ns#', 'memorial' => 'toot:memorial' },
voters_count: { 'toot' => 'http://joinmastodon.org/ns#', 'votersCount' => 'toot:votersCount' },
olm: {
'toot' => 'http://joinmastodon.org/ns#', 'Device' => 'toot:Device', 'Ed25519Signature' => 'toot:Ed25519Signature', 'Ed25519Key' => 'toot:Ed25519Key', 'Curve25519Key' => 'toot:Curve25519Key', 'EncryptedMessage' => 'toot:EncryptedMessage', 'publicKeyBase64' => 'toot:publicKeyBase64', 'deviceId' => 'toot:deviceId',

View File

@@ -105,6 +105,21 @@ describe('computeHashtagBarForStatus', () => {
);
});
it('handles server-side normalized tags with accentuated characters', () => {
const status = createStatus(
'<p>Text</p><p><a href="test">#éaa</a> <a href="test">#Éaa</a></p>',
['eaa'], // The server may normalize the hashtags in the `tags` attribute
);
const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);
expect(hashtagsInBar).toEqual(['Éaa']);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p>Text</p>"`,
);
});
it('does not display in bar a hashtag in content with a case difference', () => {
const status = createStatus(
'<p>Text <a href="test">#Éaa</a></p><p><a href="test">#éaa</a></p>',

View File

@@ -23,8 +23,9 @@ export type StatusLike = Record<{
}>;
function normalizeHashtag(hashtag: string) {
if (hashtag && hashtag.startsWith('#')) return hashtag.slice(1);
else return hashtag;
return (
hashtag && hashtag.startsWith('#') ? hashtag.slice(1) : hashtag
).normalize('NFKC');
}
function isNodeLinkHashtag(element: Node): element is HTMLLinkElement {
@@ -70,9 +71,16 @@ function uniqueHashtagsWithCaseHandling(hashtags: string[]) {
}
// Create the collator once, this is much more efficient
const collator = new Intl.Collator(undefined, { sensitivity: 'accent' });
const collator = new Intl.Collator(undefined, {
sensitivity: 'base', // we use this to emulate the ASCII folding done on the server-side, hopefuly more efficiently
});
function localeAwareInclude(collection: string[], value: string) {
return collection.find((item) => collator.compare(item, value) === 0);
const normalizedValue = value.normalize('NFKC');
return !!collection.find(
(item) => collator.compare(item.normalize('NFKC'), normalizedValue) === 0,
);
}
// We use an intermediate function here to make it easier to test
@@ -121,11 +129,13 @@ export function computeHashtagBarForStatus(status: StatusLike): {
// try to see if the last line is only hashtags
let onlyHashtags = true;
const normalizedTagNames = tagNames.map((tag) => tag.normalize('NFKC'));
Array.from(lastChild.childNodes).forEach((node) => {
if (isNodeLinkHashtag(node) && node.textContent) {
const normalized = normalizeHashtag(node.textContent);
if (!localeAwareInclude(tagNames, normalized)) {
if (!localeAwareInclude(normalizedTagNames, normalized)) {
// stop here, this is not a real hashtag, so consider it as text
onlyHashtags = false;
return;
@@ -140,12 +150,14 @@ export function computeHashtagBarForStatus(status: StatusLike): {
}
});
const hashtagsInBar = tagNames.filter(
(tag) =>
// the tag does not appear at all in the status content, it is an out-of-band tag
!localeAwareInclude(contentHashtags, tag) &&
!localeAwareInclude(lastLineHashtags, tag),
);
const hashtagsInBar = tagNames.filter((tag) => {
const normalizedTag = tag.normalize('NFKC');
// the tag does not appear at all in the status content, it is an out-of-band tag
return (
!localeAwareInclude(contentHashtags, normalizedTag) &&
!localeAwareInclude(lastLineHashtags, normalizedTag)
);
});
const isOnlyOneLine = contentWithoutLastLine.content.childElementCount === 0;
const hasMedia = status.get('media_attachments').size > 0;
@@ -204,7 +216,7 @@ const HashtagBar: React.FC<{
<div className='hashtag-bar'>
{revealedHashtags.map((hashtag) => (
<Link key={hashtag} to={`/tags/${hashtag}`}>
#{hashtag}
#<span>{hashtag}</span>
</Link>
))}

View File

@@ -546,6 +546,7 @@ class Status extends ImmutablePureComponent {
const visibilityIcon = visibilityIconInfo[status.get('visibility')];
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
const expanded = !status.get('hidden')
return (
<HotKeys handlers={handlers}>
@@ -574,7 +575,7 @@ class Status extends ImmutablePureComponent {
<StatusContent
status={status}
onClick={this.handleClick}
expanded={!status.get('hidden')}
expanded={expanded}
onExpandedToggle={this.handleExpandedToggle}
onTranslate={this.handleTranslate}
collapsible
@@ -584,7 +585,7 @@ class Status extends ImmutablePureComponent {
{media}
{hashtagBar}
{expanded && hashtagBar}
<StatusActionBar scrollKey={scrollKey} status={status} account={account} onFilter={matchedFilters ? this.handleFilterClick : null} {...other} />
</div>

View File

@@ -108,10 +108,10 @@ class Results extends PureComponent {
return (
<>
<div className='account__section-headline'>
<button onClick={this.handleSelectAll} className={type === 'all' && 'active'}><FormattedMessage id='search_results.all' defaultMessage='All' /></button>
<button onClick={this.handleSelectAccounts} className={type === 'accounts' && 'active'}><FormattedMessage id='search_results.accounts' defaultMessage='Profiles' /></button>
<button onClick={this.handleSelectHashtags} className={type === 'hashtags' && 'active'}><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></button>
<button onClick={this.handleSelectStatuses} className={type === 'statuses' && 'active'}><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></button>
<button onClick={this.handleSelectAll} className={type === 'all' ? 'active' : undefined}><FormattedMessage id='search_results.all' defaultMessage='All' /></button>
<button onClick={this.handleSelectAccounts} className={type === 'accounts' ? 'active' : undefined}><FormattedMessage id='search_results.accounts' defaultMessage='Profiles' /></button>
<button onClick={this.handleSelectHashtags} className={type === 'hashtags' ? 'active' : undefined}><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></button>
<button onClick={this.handleSelectStatuses} className={type === 'statuses' ? 'active' : undefined}><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></button>
</div>
<div className='explore__search-results'>

View File

@@ -293,6 +293,7 @@ class DetailedStatus extends ImmutablePureComponent {
}
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
const expanded = !status.get('hidden')
return (
<div style={outerStyle}>
@@ -318,7 +319,7 @@ class DetailedStatus extends ImmutablePureComponent {
{media}
{hashtagBar}
{expanded && hashtagBar}
<div className='detailed-status__meta'>
<a className='detailed-status__datetime' href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} target='_blank' rel='noopener noreferrer'>

View File

@@ -568,7 +568,7 @@ class Status extends ImmutablePureComponent {
onMoveUp={this.handleMoveUp}
onMoveDown={this.handleMoveDown}
contextType='thread'
previousId={i > 0 && list.get(i - 1)}
previousId={i > 0 ? list.get(i - 1) : undefined}
nextId={list.get(i + 1) || (ancestors && statusId)}
rootId={statusId}
/>

View File

@@ -115,7 +115,10 @@ export default class ModalRoot extends PureComponent {
{visible && (
<>
<BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading(type)} error={this.renderError} renderDelay={200}>
{(SpecificComponent) => <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={this.handleClose} ref={this.setModalRef} />}
{(SpecificComponent) => {
const ref = typeof SpecificComponent !== 'function' ? this.setModalRef : undefined;
return <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={this.handleClose} ref={ref} />
}}
</BundleContainer>
<Helmet>

View File

@@ -9305,16 +9305,15 @@ noscript {
a {
display: inline-flex;
border-radius: 4px;
background: rgba($highlight-text-color, 0.2);
color: $highlight-text-color;
padding: 0.4em 0.6em;
color: $dark-text-color;
text-decoration: none;
&:hover,
&:focus,
&:active {
background: rgba($highlight-text-color, 0.3);
&:hover {
text-decoration: none;
span {
text-decoration: underline;
}
}
}
}

View File

@@ -7,13 +7,14 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
context :security
context_extensions :manually_approves_followers, :featured, :also_known_as,
:moved_to, :property_value, :discoverable, :olm, :suspended
:moved_to, :property_value, :discoverable, :olm, :suspended,
:memorial
attributes :id, :type, :following, :followers,
:inbox, :outbox, :featured, :featured_tags,
:preferred_username, :name, :summary,
:url, :manually_approves_followers,
:discoverable, :published
:discoverable, :published, :memorial
has_one :public_key, serializer: ActivityPub::PublicKeySerializer

View File

@@ -124,7 +124,7 @@ class AccountSearchService < BaseService
multi_match: {
query: @query,
type: 'bool_prefix',
fields: %w(username username.* display_name display_name.*),
fields: %w(username^2 username.*^2 display_name display_name.*),
},
}
end

View File

@@ -116,6 +116,7 @@ class ActivityPub::ProcessAccountService < BaseService
@account.also_known_as = as_array(@json['alsoKnownAs'] || []).map { |item| value_or_id(item) }
@account.discoverable = @json['discoverable'] || false
@account.indexable = @json['indexable'] || false
@account.memorial = @json['memorial'] || false
end
def set_fetchable_key!