1
0
Fork 0
forked from gitea/nas

Merge remote-tracking branch 'parent/main' into upstream-20241026

This commit is contained in:
KMY 2024-10-26 10:41:00 +09:00
commit 0c99b8fbb0
79 changed files with 2403 additions and 2056 deletions

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
class Api::Web::PushSubscriptionsController < Api::Web::BaseController
before_action :require_user!
before_action :require_user!, except: :destroy
before_action :set_push_subscription, only: :update
before_action :destroy_previous_subscriptions, only: :create, if: :prior_subscriptions?
after_action :update_session_with_subscription, only: :create
@ -17,6 +17,13 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer
end
def destroy
push_subscription = ::Web::PushSubscription.find_by_token_for(:unsubscribe, params[:id])
push_subscription&.destroy
head 200
end
private
def active_session

View file

@ -10,7 +10,7 @@ module Auth::CaptchaConcern
end
def captcha_available?
ENV['HCAPTCHA_SECRET_KEY'].present? && ENV['HCAPTCHA_SITE_KEY'].present?
Rails.configuration.x.captcha.secret_key.present? && Rails.configuration.x.captcha.site_key.present?
end
def captcha_enabled?

View file

@ -2,7 +2,7 @@
module Admin::SettingsHelper
def captcha_available?
ENV['HCAPTCHA_SECRET_KEY'].present? && ENV['HCAPTCHA_SITE_KEY'].present?
Rails.configuration.x.captcha.secret_key.present? && Rails.configuration.x.captcha.site_key.present?
end
def login_activity_title(activity)

View file

@ -122,22 +122,6 @@ module ApplicationHelper
inline_svg_tag 'check.svg'
end
def visibility_icon(status)
if status.public_visibility?
material_symbol('globe', title: I18n.t('statuses.visibilities.public'))
elsif status.unlisted_visibility?
material_symbol('lock_open', title: I18n.t('statuses.visibilities.unlisted'))
elsif status.public_unlisted_visibility?
material_symbol('cloud', title: I18n.t('statuses.visibilities.public_unlisted'))
elsif status.login_visibility?
material_symbol('key', title: I18n.t('statuses.visibilities.login'))
elsif status.private_visibility? || status.limited_visibility?
material_symbol('lock', title: I18n.t('statuses.visibilities.private'))
elsif status.direct_visibility?
material_symbol('alternate_email', title: I18n.t('statuses.visibilities.direct'))
end
end
def interrelationships_icon(relationships, account_id)
if relationships.following[account_id] && relationships.followed_by[account_id]
material_symbol('sync_alt', title: I18n.t('relationships.mutual'), class: 'active passive')

View file

@ -162,7 +162,7 @@ module LanguagesHelper
th: ['Thai', 'ไทย'].freeze,
ti: ['Tigrinya', 'ትግርኛ'].freeze,
tk: ['Turkmen', 'Türkmen'].freeze,
tl: ['Tagalog', 'Wikang Tagalog'].freeze,
tl: ['Tagalog', 'Tagalog'].freeze,
tn: ['Tswana', 'Setswana'].freeze,
to: ['Tonga', 'faka Tonga'].freeze,
tr: ['Turkish', 'Türkçe'].freeze,

View file

@ -327,31 +327,24 @@ Rails.delegate(document, '.input-copy button', 'click', ({ target }) => {
if (!input) return;
const oldReadOnly = input.readOnly;
input.readOnly = false;
input.focus();
input.select();
input.setSelectionRange(0, input.value.length);
try {
if (document.execCommand('copy')) {
input.blur();
navigator.clipboard
.writeText(input.value)
.then(() => {
const parent = target.parentElement;
if (!parent) return;
parent.classList.add('copied');
if (parent) {
parent.classList.add('copied');
setTimeout(() => {
parent.classList.remove('copied');
}, 700);
}
} catch (err) {
console.error(err);
}
setTimeout(() => {
parent.classList.remove('copied');
}, 700);
}
input.readOnly = oldReadOnly;
return true;
})
.catch((error: unknown) => {
console.error(error);
});
});
const toggleSidebar = () => {

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg"><symbol id="mastodon-svg-logo" viewBox="0 0 216.4144 232.00976"><path d="M107.86523 0C78.203984.2425 49.672422 3.4535937 33.044922 11.089844c0 0-32.97656262 14.752031-32.97656262 65.082031 0 11.525-.224375 25.306175.140625 39.919925 1.19750002 49.22 9.02375002 97.72843 54.53124962 109.77343 20.9825 5.55375 38.99711 6.71547 53.505856 5.91797 26.31125-1.45875 41.08203-9.38867 41.08203-9.38867l-.86914-19.08984s-18.80171 5.92758-39.91796 5.20508c-20.921254-.7175-43.006879-2.25516-46.390629-27.94141-.3125-2.25625-.46875-4.66938-.46875-7.20313 0 0 20.536953 5.0204 46.564449 6.21289 15.915.73001 30.8393-.93343 45.99805-2.74218 29.07-3.47125 54.38125-21.3818 57.5625-37.74805 5.0125-25.78125 4.59961-62.916015 4.59961-62.916015 0-50.33-32.97461-65.082031-32.97461-65.082031C166.80539 3.4535938 138.255.2425 108.59375 0h-.72852zM74.296875 39.326172c12.355 0 21.710234 4.749297 27.896485 14.248047l6.01367 10.080078 6.01563-10.080078c6.185-9.49875 15.54023-14.248047 27.89648-14.248047 10.6775 0 19.28156 3.753672 25.85156 11.076172 6.36875 7.3225 9.53907 17.218828 9.53907 29.673828v60.941408h-24.14454V81.869141c0-12.46875-5.24453-18.798829-15.73828-18.798829-11.6025 0-17.41797 7.508516-17.41797 22.353516v32.375002H96.207031V85.423828c0-14.845-5.815468-22.353515-17.417969-22.353516-10.49375 0-15.740234 6.330079-15.740234 18.798829v59.148439H38.904297V80.076172c0-12.455 3.171016-22.351328 9.541015-29.673828 6.568751-7.3225 15.172813-11.076172 25.851563-11.076172z" /></symbol></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,3 @@
<svg width="24" height="20" viewBox="0 0 24 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M23.933 2.82414C22.324 4.07931 21.0726 5.3569 20.1788 6.6569C19.3296 7.91207 18.905 9.1 18.905 10.2207C19.0838 10.131 19.3073 10.0862 19.5754 10.0862C19.8883 10.0414 20.1564 10.019 20.3799 10.019C21.4078 10.019 22.257 10.4448 22.9274 11.2966C23.6425 12.1034 24 13.1121 24 14.3224C24 15.8017 23.5084 17.0345 22.5251 18.0207C21.5419 19.0069 20.3129 19.5 18.838 19.5C17.2737 19.5 16.0447 18.9397 15.1508 17.819C14.257 16.6535 13.8101 15.1069 13.8101 13.1793C13.8101 10.8931 14.5028 8.62931 15.8883 6.38793C17.2737 4.14655 19.3073 2.01724 21.9888 0L23.933 2.82414ZM10.1229 2.82414C8.51397 4.07931 7.26257 5.3569 6.36872 6.6569C5.51955 7.91207 5.09497 9.1 5.09497 10.2207C5.27374 10.131 5.49721 10.0862 5.76536 10.0862C6.07821 10.0414 6.34637 10.019 6.56983 10.019C7.59777 10.019 8.44693 10.4448 9.11732 11.2966C9.8324 12.1034 10.1899 13.1121 10.1899 14.3224C10.1899 15.8017 9.69832 17.0345 8.71508 18.0207C7.73184 19.0069 6.50279 19.5 5.02793 19.5C3.46369 19.5 2.23464 18.9397 1.34078 17.819C0.446927 16.6535 0 15.1069 0 13.1793C0 10.8931 0.692738 8.62931 2.07821 6.38793C3.46369 4.14655 5.49721 2.01724 8.17877 0L10.1229 2.82414Z" fill="currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -1,4 +1,4 @@
import type { PropsWithChildren } from 'react';
import type { PropsWithChildren, JSX } from 'react';
import { useCallback } from 'react';
import classNames from 'classnames';

View file

@ -8,7 +8,7 @@ export const ContentWarning: React.FC<{
<StatusBanner
expanded={expanded}
onClick={onClick}
variant={BannerVariant.Yellow}
variant={BannerVariant.Warning}
>
<p dangerouslySetInnerHTML={{ __html: text }} />
</StatusBanner>

View file

@ -10,13 +10,16 @@ export const FilterWarning: React.FC<{
<StatusBanner
expanded={expanded}
onClick={onClick}
variant={BannerVariant.Blue}
variant={BannerVariant.Filter}
>
<p>
<FormattedMessage
id='filter_warning.matches_filter'
defaultMessage='Matches filter “{title}”'
values={{ title }}
defaultMessage='Matches filter “<span>{title}</span>”'
values={{
title,
span: (chunks) => <span className='filter-name'>{chunks}</span>,
}}
/>
</p>
</StatusBanner>

View file

@ -1,4 +1,5 @@
import { memo } from 'react';
import type { JSX } from 'react';
import { FormattedMessage, FormattedNumber } from 'react-intl';

View file

@ -477,7 +477,7 @@ class Status extends ImmutablePureComponent {
media={status.get('media_attachments')}
/>
);
} else if (['image', 'gifv'].includes(status.getIn(['media_attachments', 0, 'type'])) || status.get('media_attachments').size > 1) {
} else if (['image', 'gifv', 'unknown'].includes(status.getIn(['media_attachments', 0, 'type'])) || status.get('media_attachments').size > 1) {
media = (
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>
{Component => (

View file

@ -1,8 +1,8 @@
import { FormattedMessage } from 'react-intl';
export enum BannerVariant {
Yellow = 'yellow',
Blue = 'blue',
Warning = 'warning',
Filter = 'filter',
}
export const StatusBanner: React.FC<{
@ -11,9 +11,9 @@ export const StatusBanner: React.FC<{
expanded?: boolean;
onClick?: () => void;
}> = ({ children, variant, expanded, onClick }) => (
<div
<label
className={
variant === BannerVariant.Yellow
variant === BannerVariant.Warning
? 'content-warning'
: 'content-warning content-warning--filter'
}
@ -26,6 +26,11 @@ export const StatusBanner: React.FC<{
id='content_warning.hide'
defaultMessage='Hide post'
/>
) : variant === BannerVariant.Warning ? (
<FormattedMessage
id='content_warning.show_more'
defaultMessage='Show more'
/>
) : (
<FormattedMessage
id='content_warning.show'
@ -33,5 +38,5 @@ export const StatusBanner: React.FC<{
/>
)}
</button>
</div>
</label>
);

View file

@ -1,3 +1,5 @@
import type { JSX } from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';

View file

@ -1,4 +1,5 @@
import { useMemo } from 'react';
import type { JSX } from 'react';
import classNames from 'classnames';

View file

@ -291,6 +291,7 @@
"confirmations.unfollow.title": "Unfollow user?",
"content_warning.hide": "Hide post",
"content_warning.show": "Show anyway",
"content_warning.show_more": "Show more",
"conversation.delete": "Delete conversation",
"conversation.mark_as_read": "Mark as read",
"conversation.open": "View conversation",
@ -408,7 +409,7 @@
"filter_modal.select_filter.subtitle": "Use an existing category or create a new one",
"filter_modal.select_filter.title": "Filter this post",
"filter_modal.title.status": "Filter a post",
"filter_warning.matches_filter": "Matches filter “{title}”",
"filter_warning.matches_filter": "Matches filter “<span>{title}</span>”",
"filtered_notifications_banner.pending_requests": "From {count, plural, =0 {no one} one {one person} other {# people}} you may know",
"filtered_notifications_banner.title": "Filtered notifications",
"firehose.all": "All",

View file

@ -57,7 +57,10 @@ export const accountsReducer: Reducer<typeof initialState> = (
return state.setIn([action.payload.id, 'hidden'], false);
else if (importAccounts.match(action))
return normalizeAccounts(state, action.payload.accounts);
else if (followAccountSuccess.match(action)) {
else if (
followAccountSuccess.match(action) &&
!action.payload.alreadyFollowing
) {
return state
.update(action.payload.relationship.id, (account) =>
account?.update('followers_count', (n) => n + 1),

View file

@ -79,4 +79,7 @@ body {
--background-color-tint: rgba(255, 255, 255, 80%);
--background-filter: blur(10px);
--on-surface-color: #{transparentize($ui-base-color, 0.65)};
--rich-text-container-color: rgba(255, 216, 231, 100%);
--rich-text-text-color: rgba(114, 47, 83, 100%);
--rich-text-decorations-color: rgba(255, 175, 212, 100%);
}

View file

@ -11561,19 +11561,21 @@ noscript {
}
.content-warning {
display: block;
box-sizing: border-box;
background: rgba($ui-highlight-color, 0.05);
color: $secondary-text-color;
border-top: 1px solid;
border-bottom: 1px solid;
border-color: rgba($ui-highlight-color, 0.15);
border: 1px solid rgba($ui-highlight-color, 0.15);
border-radius: 8px;
padding: 8px (5px + 8px);
position: relative;
font-size: 15px;
line-height: 22px;
cursor: pointer;
p {
margin-bottom: 8px;
font-weight: 500;
}
.link-button {
@ -11582,32 +11584,17 @@ noscript {
font-weight: 500;
}
&::before,
&::after {
content: '';
display: block;
position: absolute;
height: 100%;
background: url('../images/warning-stripes.svg') repeat-y;
width: 5px;
top: 0;
}
&--filter {
color: $darker-text-color;
&::before {
border-start-start-radius: 4px;
border-end-start-radius: 4px;
inset-inline-start: 0;
}
p {
font-weight: normal;
}
&::after {
border-start-end-radius: 4px;
border-end-end-radius: 4px;
inset-inline-end: 0;
}
&--filter::before,
&--filter::after {
background-image: url('../images/filter-stripes.svg');
.filter-name {
font-weight: 500;
color: $secondary-text-color;
}
}
&--compacted-status::before {

View file

@ -3,9 +3,29 @@
.e-content,
.edit-indicator__content,
.reply-indicator__content {
code {
background: var(--rich-text-container-color);
padding: 4px;
border-radius: 4px;
color: var(--rich-text-text-color);
font-size: 0.85em;
}
pre {
background: var(--rich-text-container-color);
padding: 8px;
border-radius: 4px;
color: var(--rich-text-text-color);
code {
padding: 0;
background: transparent;
}
}
pre,
blockquote {
margin-bottom: 20px;
margin-bottom: 22px;
white-space: pre-wrap;
unicode-bidi: plaintext;
@ -41,19 +61,45 @@
}
blockquote {
padding-inline-start: 10px;
border-inline-start: 3px solid $darker-text-color;
color: $darker-text-color;
padding-inline-start: 32px;
color: var(--rich-text-text-color);
white-space: normal;
position: relative;
p:last-child {
&::before {
display: block;
content: '';
width: 24px;
height: 20px;
mask-image: url('../images/quote.svg');
background-color: var(--rich-text-decorations-color);
position: absolute;
inset-inline-start: 0;
top: 0;
}
blockquote {
margin-top: 4px;
border-inline-start: 3px solid var(--rich-text-decorations-color);
padding-inline-start: 16px;
&::before {
display: none;
}
}
p:last-of-type {
margin-bottom: 0;
}
}
& > ul,
& > ol {
margin-bottom: 20px;
margin-bottom: 22px;
&:last-child {
margin-bottom: 0;
}
}
b,
@ -68,7 +114,15 @@
ul,
ol {
margin-inline-start: 2em;
padding-inline-start: 24px;
li {
padding-inline-start: 8px;
&::marker {
text-align: end;
}
}
p {
margin: 0;
@ -76,7 +130,11 @@
}
ul {
list-style-type: disc;
list-style-type: '';
li::marker {
text-align: start;
}
}
ol {

View file

@ -127,4 +127,7 @@ $font-monospace: 'mastodon-font-monospace' !default;
--error-background-color: #{darken($error-red, 16%)};
--error-active-background-color: #{darken($error-red, 12%)};
--on-error-color: #fff;
--rich-text-container-color: rgba(87, 24, 60, 100%);
--rich-text-text-color: rgba(255, 175, 212, 100%);
--rich-text-decorations-color: rgba(128, 58, 95, 100%);
}

View file

@ -8,17 +8,27 @@ class TranslationService
class UnexpectedResponseError < Error; end
def self.configured
if ENV['DEEPL_API_KEY'].present?
TranslationService::DeepL.new(ENV.fetch('DEEPL_PLAN', 'free'), ENV['DEEPL_API_KEY'])
elsif ENV['LIBRE_TRANSLATE_ENDPOINT'].present?
TranslationService::LibreTranslate.new(ENV['LIBRE_TRANSLATE_ENDPOINT'], ENV['LIBRE_TRANSLATE_API_KEY'])
if configuration.deepl[:api_key].present?
TranslationService::DeepL.new(
configuration.deepl[:plan],
configuration.deepl[:api_key]
)
elsif configuration.libre_translate[:endpoint].present?
TranslationService::LibreTranslate.new(
configuration.libre_translate[:endpoint],
configuration.libre_translate[:api_key]
)
else
raise NotConfiguredError
end
end
def self.configured?
ENV['DEEPL_API_KEY'].present? || ENV['LIBRE_TRANSLATE_ENDPOINT'].present?
configuration.deepl[:api_key].present? || configuration.libre_translate[:endpoint].present?
end
def self.configuration
Rails.configuration.x.translation
end
def languages

View file

@ -69,6 +69,8 @@ class Account < ApplicationRecord
)
BACKGROUND_REFRESH_INTERVAL = 1.week.freeze
REFRESH_DEADLINE = 6.hours
STALE_THRESHOLD = 1.day
DEFAULT_FIELDS_SIZE = 6
INSTANCE_ACTOR_ID = -99
@ -256,13 +258,13 @@ class Account < ApplicationRecord
end
def possibly_stale?
last_webfingered_at.nil? || last_webfingered_at <= 1.day.ago
last_webfingered_at.nil? || last_webfingered_at <= STALE_THRESHOLD.ago
end
def schedule_refresh_if_stale!
return unless last_webfingered_at.present? && last_webfingered_at <= BACKGROUND_REFRESH_INTERVAL.ago
AccountRefreshWorker.perform_in(rand(6.hours.to_i), id)
AccountRefreshWorker.perform_in(rand(REFRESH_DEADLINE), id)
end
def refresh!

View file

@ -36,9 +36,14 @@ class IpBlock < ApplicationRecord
class << self
def blocked?(remote_ip)
blocked_ips_map = Rails.cache.fetch(CACHE_KEY) { FastIpMap.new(IpBlock.where(severity: :no_access).pluck(:ip)) }
blocked_ips_map.include?(remote_ip)
end
private
def blocked_ips_map
Rails.cache.fetch(CACHE_KEY) { FastIpMap.new(severity_no_access.pluck(:ip)) }
end
end
private

View file

@ -19,6 +19,8 @@ class LinkFeed < PublicFeed
scope.merge!(discoverable)
scope.merge!(attached_to_preview_card)
scope.merge!(account_filters_scope) if account?
scope.merge!(language_scope) if account&.chosen_languages.present?
scope.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
end

View file

@ -29,6 +29,8 @@ class Web::PushSubscription < ApplicationRecord
delegate :locale, to: :associated_user
generates_token_for :unsubscribe, expires_in: Web::PushNotificationWorker::TTL
def pushable?(notification)
policy_allows_notification?(notification) && alert_enabled_for_notification_type?(notification)
end

View file

@ -2,10 +2,11 @@
class Web::PushNotificationWorker
include Sidekiq::Worker
include RoutingHelper
sidekiq_options queue: 'push', retry: 5
TTL = 48.hours.to_s
TTL = 48.hours
URGENCY = 'normal'
def perform(subscription_id, notification_id)
@ -23,12 +24,13 @@ class Web::PushNotificationWorker
request.add_headers(
'Content-Type' => 'application/octet-stream',
'Ttl' => TTL,
'Ttl' => TTL.to_s,
'Urgency' => URGENCY,
'Content-Encoding' => 'aesgcm',
'Encryption' => "salt=#{Webpush.encode64(payload.fetch(:salt)).delete('=')}",
'Crypto-Key' => "dh=#{Webpush.encode64(payload.fetch(:server_public_key)).delete('=')};#{web_push_request.crypto_key_header}",
'Authorization' => web_push_request.authorization_header
'Authorization' => web_push_request.authorization_header,
'Unsubscribe-URL' => subscription_url
)
request.perform do |response|
@ -72,4 +74,8 @@ class Web::PushNotificationWorker
def request_pool
RequestPool.current
end
def subscription_url
api_web_push_subscription_url(id: @subscription.generate_token_for(:unsubscribe))
end
end