diff --git a/.env.production.sample b/.env.production.sample index 0bf01bdc361d93..f06826b62a33a6 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -75,3 +75,8 @@ S3_ALIAS_HOST=files.example.com # ----------------------- IP_RETENTION_PERIOD=31556952 SESSION_RETENTION_PERIOD=31556952 + +# C3 link +# ------- +C3_OFFICIAL_SITE_URL= +C3_TOYBOX_URL= diff --git a/Gemfile.lock b/Gemfile.lock index 4c9345e9a782a5..c13726b5b49585 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -730,6 +730,7 @@ GEM redlock (~> 1.0) strong_migrations (0.8.0) activerecord (>= 5.2) + strscan (3.0.9) swd (1.3.0) activesupport (>= 3) attr_required (>= 0.0.5) diff --git a/app/controllers/settings/request_custom_emojis_controller.rb b/app/controllers/settings/request_custom_emojis_controller.rb new file mode 100644 index 00000000000000..1e02cbe15522b8 --- /dev/null +++ b/app/controllers/settings/request_custom_emojis_controller.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +class Settings::RequestCustomEmojisController < Settings::BaseController + include Authorization + + def index + @is_admin = authorize? + @custom_emojis = RequestCustomEmoji.order(:state, :shortcode).page(params[:page]) + @form = Form::RequestCustomEmojiBatch.new + end + + def new + @custom_emoji = RequestCustomEmoji.new + end + + def create + @custom_emoji = RequestCustomEmoji.new(resource_params) + @custom_emoji.account_id = current_account.id + if CustomEmoji.find_by(shortcode: @custom_emoji.shortcode, domain: nil) + @custom_emoji.errors.add(:shortcode, I18n.t('settings.request_custom_emoji.errors.already_exists')) + render :new + return + end + + if @custom_emoji.save + redirect_to settings_request_custom_emojis_path, notice: I18n.t('settings.request_custom_emoji.created_msg') + else + render :new + end + end + + def batch + @form = Form::RequestCustomEmojiBatch.new(form_custom_emoji_batch_params.merge(current_account: current_account, action: action_from_button)) + @form.save + rescue ActionController::ParameterMissing + flash[:alert] = I18n.t('admin.accounts.no_account_selected') + rescue Mastodon::NotPermittedError + flash[:alert] = I18n.t('admin.custom_emojis.not_permitted') + ensure + redirect_to settings_request_custom_emojis_path + end + + private + + def resource_params + params.require(:request_custom_emoji).permit(:shortcode, :image) + end + + def form_custom_emoji_batch_params + params.require(:form_request_custom_emoji_batch).permit(:action, request_custom_emoji_ids: []) + end + + def action_from_button + if params[:approve] + 'approve' + elsif params[:reject] + 'reject' + elsif params[:delete] + 'delete' + end + end + + def authorize? + begin + authorize(:custom_emoji, :index?) + rescue Mastodon::NotPermittedError + return false + end + return true + end +end diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js index e6b361c0c9c956..f8c6684100030c 100644 --- a/app/javascript/mastodon/actions/importer/normalizer.js +++ b/app/javascript/mastodon/actions/importer/normalizer.js @@ -11,6 +11,18 @@ const makeEmojiMap = emojis => emojis.reduce((obj, emoji) => { return obj; }, {}); +const rewrite = txt => { + let edit_txt = txt.replaceAll('

', ' ').replaceAll('
', ' ') + const e = document.createElement('div'); + e.innerHTML = edit_txt; + return e.innerText; +} + +const checkOnlyIconStatus = content => { + const trimContent = rewrite(content).trim(); + return trimContent.match("^:[0-9a-zA-Z_]+:([  \r\t\s\n]+:[0-9a-zA-Z_]+:)*$"); +}; + export function searchTextFromRawStatus (status) { const spoilerText = status.spoiler_text || ''; const searchContent = ([spoilerText, status.content].concat((status.poll && status.poll.options) ? status.poll.options.map(option => option.title) : [])).concat(status.media_attachments.map(att => att.description)).join('\n\n').replace(//g, '\n').replace(/<\/p>

/g, '\n\n'); @@ -91,9 +103,10 @@ export function normalizeStatus(status, normalOldStatus) { const spoilerText = normalStatus.spoiler_text || ''; const searchContent = ([spoilerText, status.content].concat((status.poll && status.poll.options) ? status.poll.options.map(option => option.title) : [])).concat(status.media_attachments.map(att => att.description)).join('\n\n').replace(//g, '\n').replace(/<\/p>

/g, '\n\n'); const emojiMap = makeEmojiMap(normalStatus.emojis); + const toBigIcon = checkOnlyIconStatus(normalStatus.content); normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent; - normalStatus.contentHtml = emojify(normalStatus.content, emojiMap); + normalStatus.contentHtml = emojify(normalStatus.content, emojiMap, toBigIcon); normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap); normalStatus.hidden = expandSpoilers ? false : spoilerText.length > 0 || normalStatus.sensitive; } diff --git a/app/javascript/mastodon/actions/settings.js b/app/javascript/mastodon/actions/settings.js index fbd89f9d4b0b52..3685b0684e0b83 100644 --- a/app/javascript/mastodon/actions/settings.js +++ b/app/javascript/mastodon/actions/settings.js @@ -20,7 +20,7 @@ export function changeSetting(path, value) { } const debouncedSave = debounce((dispatch, getState) => { - if (getState().getIn(['settings', 'saved']) || !getState().getIn(['meta', 'me'])) { + if (getState().getIn(['settings', 'saved'])) { return; } diff --git a/app/javascript/mastodon/components/admin/ReportReasonSelector.jsx b/app/javascript/mastodon/components/admin/ReportReasonSelector.jsx index 90f4334a6e1ad2..ecce92b309429d 100644 --- a/app/javascript/mastodon/components/admin/ReportReasonSelector.jsx +++ b/app/javascript/mastodon/components/admin/ReportReasonSelector.jsx @@ -124,7 +124,7 @@ class ReportReasonSelector extends PureComponent { api().put(`/api/v1/admin/reports/${id}`, { category, - rule_ids: category === 'violation' ? rule_ids : [], + rule_ids, }).catch(err => { console.error(err); }); diff --git a/app/javascript/mastodon/components/dropdown_menu.jsx b/app/javascript/mastodon/components/dropdown_menu.jsx index 2457692cd8675d..fd66310e856ee6 100644 --- a/app/javascript/mastodon/components/dropdown_menu.jsx +++ b/app/javascript/mastodon/components/dropdown_menu.jsx @@ -40,7 +40,6 @@ class DropdownMenu extends PureComponent { if (this.node && !this.node.contains(e.target)) { this.props.onClose(); e.stopPropagation(); - e.preventDefault(); } }; diff --git a/app/javascript/mastodon/components/navigation_portal.jsx b/app/javascript/mastodon/components/navigation_portal.jsx index a64bc7b1d26f0b..49b12a9fa076ca 100644 --- a/app/javascript/mastodon/components/navigation_portal.jsx +++ b/app/javascript/mastodon/components/navigation_portal.jsx @@ -4,15 +4,21 @@ import { Switch, Route, withRouter } from 'react-router-dom'; import AccountNavigation from 'mastodon/features/account/navigation'; import Trends from 'mastodon/features/getting_started/containers/trends_container'; -import { showTrends } from 'mastodon/initial_state'; +import { showTrends, mascot } from 'mastodon/initial_state'; + +import elephantUIPlane from 'images/elephant_ui_plane.svg'; const DefaultNavigation = () => ( - showTrends ? ( - <> -

- - - ) : null + <> +
+ +
+ { + showTrends ? ( + + ) : null + } + ); class NavigationPortal extends PureComponent { diff --git a/app/javascript/mastodon/features/compose/components/compose_form.jsx b/app/javascript/mastodon/features/compose/components/compose_form.jsx index 9222b2dc87703b..ebc2ed9187fca3 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.jsx +++ b/app/javascript/mastodon/features/compose/components/compose_form.jsx @@ -28,6 +28,8 @@ import { countableText } from '../util/counter'; import CharacterCounter from './character_counter'; +import LiteracyCautionComponent from './literacy_caution' + 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({ @@ -312,6 +314,7 @@ class ComposeForm extends ImmutablePureComponent { />
+ ); } diff --git a/app/javascript/mastodon/features/compose/components/literacy_caution.jsx b/app/javascript/mastodon/features/compose/components/literacy_caution.jsx new file mode 100644 index 00000000000000..08816bb74f0505 --- /dev/null +++ b/app/javascript/mastodon/features/compose/components/literacy_caution.jsx @@ -0,0 +1,29 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { defineMessages, injectIntl } from 'react-intl'; + +const messages = defineMessages({ + cautionMessage: { id: 'custom.caution_message', defaultMessage: 'CAUTION' }, +}); + +class LiteracyCaution extends ImmutablePureComponent { + static propTypes = { + intl: PropTypes.object.isRequired, + } + + render() { + const { intl } = this.props; + return ( +
+ +

+ { intl.formatMessage(messages.cautionMessage) } +

+
+
+ ) + } +} + +export default injectIntl(LiteracyCaution); diff --git a/app/javascript/mastodon/features/compose/components/search.jsx b/app/javascript/mastodon/features/compose/components/search.jsx index 56a4292c445ce5..7e1d8b76094d03 100644 --- a/app/javascript/mastodon/features/compose/components/search.jsx +++ b/app/javascript/mastodon/features/compose/components/search.jsx @@ -274,7 +274,6 @@ class Search extends PureComponent { } _calculateOptions (value) { - const { signedIn } = this.context.identity; const trimmedValue = value.trim(); const options = []; @@ -299,7 +298,7 @@ class Search extends PureComponent { const couldBeStatusSearch = searchEnabled; - if (couldBeStatusSearch && signedIn) { + if (couldBeStatusSearch) { options.push({ key: 'status-search', label: {trimmedValue} }} />, action: this.handleStatusSearch }); } @@ -376,7 +375,7 @@ class Search extends PureComponent {

- {searchEnabled && signedIn ? ( + {searchEnabled ? (
{this.defaultOptions.map(({ key, label, action }, i) => (
) : (
- {searchEnabled ? ( - - ) : ( - - )} +
)} diff --git a/app/javascript/mastodon/features/emoji/emoji.js b/app/javascript/mastodon/features/emoji/emoji.js index c11ef458c74eaa..285ba2c2d4928f 100644 --- a/app/javascript/mastodon/features/emoji/emoji.js +++ b/app/javascript/mastodon/features/emoji/emoji.js @@ -22,7 +22,7 @@ const emojiFilename = (filename) => { return borderedEmoji.includes(filename) ? (filename + '_border') : filename; }; -const emojifyTextNode = (node, customEmojis) => { +const emojifyTextNode = (node, customEmojis, bigIcon) => { const VS15 = 0xFE0E; const VS16 = 0xFE0F; @@ -72,9 +72,10 @@ const emojifyTextNode = (node, customEmojis) => { // now got a replacee as ':shortcode:' // if you want additional emoji handler, add statements below which set replacement and return true. const filename = autoPlayGif ? custom_emoji.url : custom_emoji.static_url; + const bigIconClass = bigIcon ? " big_icon" : "" ; replacement = document.createElement('img'); replacement.setAttribute('draggable', 'false'); - replacement.setAttribute('class', 'emojione custom-emoji'); + replacement.setAttribute('class', `emojione custom-emoji${bigIconClass}`); replacement.setAttribute('alt', shortcode); replacement.setAttribute('title', shortcode); replacement.setAttribute('src', filename); @@ -111,28 +112,28 @@ const emojifyTextNode = (node, customEmojis) => { node.parentElement.replaceChild(fragment, node); }; -const emojifyNode = (node, customEmojis) => { +const emojifyNode = (node, customEmojis, bigIcon) => { for (const child of node.childNodes) { switch(child.nodeType) { case Node.TEXT_NODE: - emojifyTextNode(child, customEmojis); + emojifyTextNode(child, customEmojis, bigIcon); break; case Node.ELEMENT_NODE: if (!child.classList.contains('invisible')) - emojifyNode(child, customEmojis); + emojifyNode(child, customEmojis, bigIcon); break; } } }; -const emojify = (str, customEmojis = {}) => { +const emojify = (str, customEmojis = {}, bigIcon = null) => { const wrapper = document.createElement('div'); wrapper.innerHTML = str; if (!Object.keys(customEmojis).length) customEmojis = null; - emojifyNode(wrapper, customEmojis); + emojifyNode(wrapper, customEmojis, bigIcon); return wrapper.innerHTML; }; diff --git a/app/javascript/mastodon/features/getting_started/index.jsx b/app/javascript/mastodon/features/getting_started/index.jsx index f0cd70d7a14006..74c61e7e210eaf 100644 --- a/app/javascript/mastodon/features/getting_started/index.jsx +++ b/app/javascript/mastodon/features/getting_started/index.jsx @@ -21,6 +21,8 @@ import ColumnSubheading from '../ui/components/column_subheading'; import TrendsContainer from './containers/trends_container'; +import { c3_official_site_url, c3_toybox_url } from 'mastodon/initial_state'; + const messages = defineMessages({ home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' }, notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' }, @@ -42,6 +44,10 @@ const messages = defineMessages({ personal: { id: 'navigation_bar.personal', defaultMessage: 'Personal' }, security: { id: 'navigation_bar.security', defaultMessage: 'Security' }, menu: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, + + officialSite: {id: 'external_url.official_site', defaultMessage: 'C3 Official Site'}, + toybox: {id: 'external_url.toybox', defaultMessage: 'ToyBox'}, + c3: { id: 'navigation_bar.c3', defaultMessage: 'C3' }, }); const mapStateToProps = state => ({ @@ -125,6 +131,22 @@ class GettingStarted extends ImmutablePureComponent { navItems.push(); } + if (c3_official_site_url || c3_toybox_url) { + navItems.push( + , + ) + if(c3_official_site_url) { + navItems.push( + , + ) + } + if(c3_toybox_url) { + navItems.push( + + ) + } + } + navItems.push( , , diff --git a/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx b/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx index 8ce3733b7d2352..0c2e3901ea5e6e 100644 --- a/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx @@ -221,7 +221,7 @@ class FocalPointModal extends ImmutablePureComponent { const worker = createWorker({ workerPath: tesseractWorkerPath, corePath: tesseractCorePath, - langPath: `${assetHost}/ocr/lang-data`, + langPath: `${assetHost}/ocr/lang-data/`, logger: ({ status, progress }) => { if (status === 'recognizing text') { this.setState({ ocrStatus: 'detecting', progress }); diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx index b16eb5e1795131..94772a6cf21e1e 100644 --- a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx +++ b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx @@ -7,7 +7,7 @@ import { Link } from 'react-router-dom'; import { WordmarkLogo } from 'mastodon/components/logo'; import NavigationPortal from 'mastodon/components/navigation_portal'; -import { timelinePreview, trendsEnabled } from 'mastodon/initial_state'; +import { timelinePreview, trendsEnabled, c3_official_site_url, c3_toybox_url } from 'mastodon/initial_state'; import { transientSingleColumn } from 'mastodon/is_mobile'; import ColumnLink from './column_link'; @@ -32,6 +32,9 @@ const messages = defineMessages({ search: { id: 'navigation_bar.search', defaultMessage: 'Search' }, advancedInterface: { id: 'navigation_bar.advanced_interface', defaultMessage: 'Open in advanced web interface' }, openedInClassicInterface: { id: 'navigation_bar.opened_in_classic_interface', defaultMessage: 'Posts, accounts, and other specific pages are opened by default in the classic web interface.' }, + + officialSite: {id: 'external_url.official_site', defaultMessage: 'C3 Official Site'}, + toybox: {id: 'external_url.toybox', defaultMessage: 'ToyBox'}, }); class NavigationPanel extends Component { @@ -113,6 +116,16 @@ class NavigationPanel extends Component {
+ { + (c3_official_site_url || c3_toybox_url) && ( + <> + { c3_official_site_url && } + { c3_toybox_url && } +
+ + ) + } + )} diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js index 11cd2a16732781..a1dc2e580229e1 100644 --- a/app/javascript/mastodon/initial_state.js +++ b/app/javascript/mastodon/initial_state.js @@ -81,6 +81,8 @@ * @property {boolean=} use_pending_items * @property {string} version * @property {string} sso_redirect + * @property {string | null} c3_official_site_url + * @property {string | null} c3_toybox_url */ /** @@ -140,6 +142,10 @@ export const unfollowModal = getMeta('unfollow_modal'); export const useBlurhash = getMeta('use_blurhash'); export const usePendingItems = getMeta('use_pending_items'); export const version = getMeta('version'); + +export const c3_official_site_url = getMeta('c3_official_site_url'); +export const c3_toybox_url = getMeta('c3_toybox_url'); + export const languages = initialState?.languages; export const criticalUpdatesPending = initialState?.critical_updates_pending; // @ts-expect-error diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 10d9cf3a219088..5244ee4bb2e8c5 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -248,6 +248,8 @@ "error.unexpected_crash.next_steps_addons": "Try disabling them and refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.", "errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard", "errors.unexpected_crash.report_issue": "Report issue", + "external_url.official_site": "C3 Official Site", + "external_url.toybox": "ToyBox", "explore.search_results": "Search results", "explore.suggested_follows": "People", "explore.title": "Explore", @@ -399,6 +401,7 @@ "navigation_bar.advanced_interface": "Open in advanced web interface", "navigation_bar.blocks": "Blocked users", "navigation_bar.bookmarks": "Bookmarks", + "navigation_bar.c3": "c3", "navigation_bar.community_timeline": "Local timeline", "navigation_bar.compose": "Compose new post", "navigation_bar.direct": "Private mentions", @@ -591,7 +594,6 @@ "search.quick_action.status_search": "Posts matching {x}", "search.search_or_paste": "Search or paste URL", "search_popout.full_text_search_disabled_message": "Not available on {domain}.", - "search_popout.full_text_search_logged_out_message": "Only available when logged in.", "search_popout.language_code": "ISO language code", "search_popout.options": "Search options", "search_popout.quick_actions": "Quick actions", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 0bf0a96a272dfd..9eaf0b5cdb7caf 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -248,6 +248,8 @@ "error.unexpected_crash.next_steps_addons": "それらを無効化してからリロードをお試しください。それでも解決しない場合、他のブラウザやアプリで Mastodon をお試しください。", "errors.unexpected_crash.copy_stacktrace": "スタックトレースをクリップボードにコピー", "errors.unexpected_crash.report_issue": "問題を報告", + "external_url.official_site": "C3 公式サイト", + "external_url.toybox": "ToyBox", "explore.search_results": "検索結果", "explore.suggested_follows": "ユーザー", "explore.title": "エクスプローラー", @@ -399,6 +401,7 @@ "navigation_bar.advanced_interface": "上級者向けUIに戻る", "navigation_bar.blocks": "ブロックしたユーザー", "navigation_bar.bookmarks": "ブックマーク", + "navigation_bar.c3": "c3", "navigation_bar.community_timeline": "ローカルタイムライン", "navigation_bar.compose": "投稿の新規作成", "navigation_bar.direct": "非公開の返信", @@ -725,5 +728,6 @@ "video.mute": "ミュート", "video.pause": "一時停止", "video.play": "再生", - "video.unmute": "ミュートを解除する" + "video.unmute": "ミュートを解除する", + "custom.caution_message": "このMastodonに公開、未収載でトゥートされた内容はすべてインターネット上の誰でも閲覧することができます。\n個人情報、著作権等のネットリテラシーには十分注意してトゥートしましょう。" } diff --git a/app/javascript/styles/application.scss b/app/javascript/styles/application.scss index 1b2969c2348fd4..f0e869521d7933 100644 --- a/app/javascript/styles/application.scss +++ b/app/javascript/styles/application.scss @@ -24,3 +24,18 @@ @import 'mastodon/rtl'; @import 'mastodon/accessibility'; @import 'mastodon/rich_text'; + + +// 非上級者向けUIのマスコット配置 +.drawer__inner__mastodon.navigation_icon { + background: none; +} +// 絵文字単体を大きく +.emojione.custom-emoji.big_icon { + width: 40px; + height: 40px; +} +.detailed-status .emojione.custom-emoji.big_icon { + width: 50px; + height: 50px; +} diff --git a/app/javascript/styles/c3.scss b/app/javascript/styles/c3.scss new file mode 100644 index 00000000000000..85f6c6b03a6353 --- /dev/null +++ b/app/javascript/styles/c3.scss @@ -0,0 +1,83 @@ +// Colors +$ui-base-color: #202020; +$ui-base-lighter-color: #88882b; +$ui-primary-color: #ffff00; +$ui-secondary-color: #6e6e00; +$ui-highlight-color: #ffff00; +$primary-text-color: #ffffff; + +// Import defaults +@import 'application'; + +.compose-form__publish-button-wrapper button.button { + color: $ui-base-color; +} + +a.column-link.column-link--transparent:hover { + color: $ui-highlight-color; +} + +h1.column-header>button { + color: $ui-highlight-color; +} + +.admin-wrapper div.content h2 { + color: $ui-highlight-color; +} + +.simple_form .block-button, .simple_form .button, .simple_form button { + color: $ui-base-color; +} + +.admin-wrapper .sidebar ul .simple-navigation-active-leaf a { + color: $ui-base-color; +} + +.dashboard__quick-access { + color: $ui-base-color; +} + +.button { + color: $ui-base-color; +} + +.button.logo-button { + color: $ui-base-color; +} + +.simple_form .block-button.negative, .simple_form .button.negative, .simple_form button.negative { + color: $primary-text-color; +} + +.getting-started__wrapper .column-link { + color: $ui-primary-color; +} + +.getting-started__wrapper .column-link:hover { + background-color: $ui-highlight-color; + color: $ui-base-color; +} + +.account__section-headline a.active, .account__section-headline button.active, .notification__filter-bar a.active, .notification__filter-bar button.active { + color: $primary-text-color; +} + +.muted .status__content, .muted .status__content a, .muted .status__content p, .muted .status__display-name strong { + color: #abab95; +} + +.account__section-headline a, .account__section-headline button, .notification__filter-bar a, .notification__filter-bar button { + color: $ui-secondary-color; +} + +.account__section-headline a:hover, .account__section-headline button:hover, .notification__filter-bar a:hover, .notification__filter-bar button:hover { + color: $ui-primary-color; +} + +.account__section-headline a, .account__section-headline button, .notification__filter-bar a, .notification__filter-bar button { + color: $ui-secondary-color; +} + +.account__section-headline a:hover, .account__section-headline button:hover, .notification__filter-bar a:hover, .notification__filter-bar button:hover { + color: $ui-primary-color; +} diff --git a/app/javascript/styles/gruvbox.scss b/app/javascript/styles/gruvbox.scss new file mode 100644 index 00000000000000..72b2378f9dfc8b --- /dev/null +++ b/app/javascript/styles/gruvbox.scss @@ -0,0 +1,30 @@ +// Colors +$ui-base-color: #282828; +$ui-base-lighter-color: #665C54; +$ui-primary-color: #D5C4A1; +$ui-secondary-color: #689D6A; +$ui-highlight-color: #8EC07C; +$primary-text-color: #EBDBB2; + +// Import defaults +@import 'application'; + +// Columns width +//.column { +// flex-grow: 1; +//} + +// Stream +.activity-stream { + .status.light { + .status__content { + color: $primary-text-color; + } + + .display-name { + strong { + color: $primary-text-color; + } + } + } +} diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index 0f02563b48d31e..630d94cfba7610 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -1,6 +1,21 @@ $maximum-width: 1235px; $fluid-breakpoint: $maximum-width + 20px; +$column-breakpoint: 700px; +$small-breakpoint: 960px; + +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: url('~material-design-icons/iconfont/MaterialIcons-Regular.eot'); + src: local('Material Icons'), + local('MaterialIcons-Regular'), + url('~material-design-icons/iconfont/MaterialIcons-Regular.woff2') format('woff2'), + url('~material-design-icons/iconfont/MaterialIcons-Regular.woff') format('woff'), + url('~material-design-icons/iconfont/MaterialIcons-Regular.ttf') format('ttf') +} + .container { box-sizing: border-box; max-width: $maximum-width; @@ -13,6 +28,875 @@ $fluid-breakpoint: $maximum-width + 20px; } } +.rich-formatting { + font-family: $font-sans-serif, sans-serif; + font-size: 14px; + font-weight: 400; + line-height: 1.7; + word-wrap: break-word; + color: $darker-text-color; + + a { + color: $highlight-text-color; + text-decoration: underline; + + &:hover, + &:focus, + &:active { + text-decoration: none; + } + } + + p, + li { + color: $darker-text-color; + } + + p { + margin-top: 0; + margin-bottom: .85em; + + &:last-child { + margin-bottom: 0; + } + } + + strong { + font-weight: 700; + color: $secondary-text-color; + } + + em { + font-style: italic; + color: $secondary-text-color; + } + + code { + font-size: 0.85em; + background: darken($ui-base-color, 8%); + border-radius: 4px; + padding: 0.2em 0.3em; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + font-family: $font-display, sans-serif; + margin-top: 1.275em; + margin-bottom: .85em; + font-weight: 500; + color: $secondary-text-color; + } + + h1 { + font-size: 2em; + } + + h2 { + font-size: 1.75em; + } + + h3 { + font-size: 1.5em; + } + + h4 { + font-size: 1.25em; + } + + h5, + h6 { + font-size: 1em; + } + + ul { + list-style: disc; + } + + ol { + list-style: decimal; + } + + ul, + ol { + margin: 0; + padding: 0; + padding-left: 2em; + margin-bottom: 0.85em; + + &[type='a'] { + list-style-type: lower-alpha; + } + + &[type='i'] { + list-style-type: lower-roman; + } + } + + hr { + width: 100%; + height: 0; + border: 0; + border-bottom: 1px solid lighten($ui-base-color, 4%); + margin: 1.7em 0; + + &.spacer { + height: 1px; + border: 0; + } + } + + table { + width: 100%; + border-collapse: collapse; + break-inside: auto; + margin-top: 24px; + margin-bottom: 32px; + + thead tr, + tbody tr { + border-bottom: 1px solid lighten($ui-base-color, 4%); + font-size: 1em; + line-height: 1.625; + font-weight: 400; + text-align: left; + color: $darker-text-color; + } + + thead tr { + border-bottom-width: 2px; + line-height: 1.5; + font-weight: 500; + color: $dark-text-color; + } + + th, + td { + padding: 8px; + align-self: start; + align-items: start; + word-break: break-all; + + &.nowrap { + width: 25%; + position: relative; + + &::before { + content: ' '; + visibility: hidden; + } + + span { + position: absolute; + left: 8px; + right: 8px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + } + } + + & > :first-child { + margin-top: 0; + } + + .mdi { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; /* Preferred icon size */ + display: inline-block; + line-height: 1; + text-transform: none; + letter-spacing: normal; + word-wrap: normal; + white-space: nowrap; + direction: ltr; + + /* Support for all WebKit browsers. */ + -webkit-font-smoothing: antialiased; + /* Support for Safari and Chrome. */ + text-rendering: optimizeLegibility; + + /* Support for Firefox. */ + -moz-osx-font-smoothing: grayscale; + + /* Support for IE. */ + font-feature-settings: 'liga'; + } +} + +.information-board { + background: darken($ui-base-color, 4%); + padding: 20px 0; + + .container-alt { + position: relative; + padding-right: 280px + 15px; + } + + &__sections { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + } + + &__section { + flex: 1 0 0; + font-family: $font-sans-serif, sans-serif; + font-size: 16px; + line-height: 28px; + color: $primary-text-color; + text-align: right; + padding: 10px 15px; + + span, + strong { + display: block; + } + + span { + &:last-child { + color: $secondary-text-color; + } + } + + strong { + font-family: $font-display, sans-serif; + font-weight: 500; + font-size: 32px; + line-height: 48px; + } + + @media screen and (max-width: $column-breakpoint) { + text-align: center; + } + } + + .panel { + position: absolute; + width: 280px; + box-sizing: border-box; + background: darken($ui-base-color, 8%); + padding: 20px; + padding-top: 10px; + border-radius: 4px 4px 0 0; + right: 0; + bottom: -40px; + + .panel-header { + font-family: $font-display, sans-serif; + font-size: 14px; + line-height: 24px; + font-weight: 500; + color: $darker-text-color; + padding-bottom: 5px; + margin-bottom: 15px; + border-bottom: 1px solid lighten($ui-base-color, 4%); + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + + a, + span { + font-weight: 400; + color: darken($darker-text-color, 10%); + } + + a { + text-decoration: none; + } + } + } + + .owner { + text-align: center; + + .avatar { + width: 80px; + height: 80px; + margin: 0 auto; + margin-bottom: 15px; + + img { + display: block; + width: 80px; + height: 80px; + border-radius: 48px; + } + } + + .name { + font-size: 14px; + + a { + display: block; + color: $primary-text-color; + text-decoration: none; + + &:hover { + .display_name { + text-decoration: underline; + } + } + } + + .username { + display: block; + color: $darker-text-color; + } + } + } +} + +.landing-page { + p, + li { + font-family: $font-sans-serif, sans-serif; + font-size: 16px; + font-weight: 400; + line-height: 30px; + margin-bottom: 12px; + color: $darker-text-color; + + a { + color: $highlight-text-color; + text-decoration: underline; + } + } + + em { + display: inline; + margin: 0; + padding: 0; + font-weight: 700; + background: transparent; + font-family: inherit; + font-size: inherit; + line-height: inherit; + color: lighten($darker-text-color, 10%); + } + + h1 { + font-family: $font-display, sans-serif; + font-size: 26px; + line-height: 30px; + font-weight: 500; + margin-bottom: 20px; + color: $secondary-text-color; + + small { + font-family: $font-sans-serif, sans-serif; + display: block; + font-size: 18px; + font-weight: 400; + color: lighten($darker-text-color, 10%); + } + } + + h2 { + font-family: $font-display, sans-serif; + font-size: 22px; + line-height: 26px; + font-weight: 500; + margin-bottom: 20px; + color: $secondary-text-color; + } + + h3 { + font-family: $font-display, sans-serif; + font-size: 18px; + line-height: 24px; + font-weight: 500; + margin-bottom: 20px; + color: $secondary-text-color; + } + + h4 { + font-family: $font-display, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 500; + margin-bottom: 20px; + color: $secondary-text-color; + } + + h5 { + font-family: $font-display, sans-serif; + font-size: 14px; + line-height: 24px; + font-weight: 500; + margin-bottom: 20px; + color: $secondary-text-color; + } + + h6 { + font-family: $font-display, sans-serif; + font-size: 12px; + line-height: 24px; + font-weight: 500; + margin-bottom: 20px; + color: $secondary-text-color; + } + + ul, + ol { + margin-left: 20px; + + &[type='a'] { + list-style-type: lower-alpha; + } + + &[type='i'] { + list-style-type: lower-roman; + } + } + + ul { + list-style: disc; + } + + ol { + list-style: decimal; + } + + li > ol, + li > ul { + margin-top: 6px; + } + + hr { + width: 100%; + height: 0; + border: 0; + border-bottom: 1px solid rgba($ui-base-lighter-color, .6); + margin: 20px 0; + + &.spacer { + height: 1px; + border: 0; + } + } + + &__information, + &__forms { + padding: 20px; + } + + &__call-to-action { + background: $ui-base-color; + border-radius: 4px; + padding: 25px 40px; + overflow: hidden; + box-sizing: border-box; + + .row { + width: 100%; + display: flex; + flex-direction: row-reverse; + flex-wrap: nowrap; + justify-content: space-between; + align-items: center; + } + + .row__information-board { + display: flex; + justify-content: flex-end; + align-items: flex-end; + + .information-board__section { + flex: 1 0 auto; + padding: 0 10px; + } + + @media screen and (max-width: $no-gap-breakpoint) { + width: 100%; + justify-content: space-between; + } + } + + .row__mascot { + flex: 1; + margin: 10px -50px 0 0; + + @media screen and (max-width: $no-gap-breakpoint) { + display: none; + } + } + } + + &__logo { + margin-right: 20px; + + img { + height: 50px; + width: auto; + mix-blend-mode: lighten; + } + } + + &__information { + padding: 45px 40px; + margin-bottom: 10px; + + &:last-child { + margin-bottom: 0; + } + + strong { + font-weight: 500; + color: lighten($darker-text-color, 10%); + } + + .account { + border-bottom: 0; + padding: 0; + + &__display-name { + align-items: center; + display: flex; + margin-right: 5px; + } + + div.account__display-name { + &:hover { + .display-name strong { + text-decoration: none; + } + } + + .account__avatar { + cursor: default; + } + } + + &__avatar-wrapper { + margin-left: 0; + flex: 0 0 auto; + } + + .display-name { + font-size: 15px; + + &__account { + font-size: 14px; + } + } + } + + @media screen and (max-width: $small-breakpoint) { + .contact { + margin-top: 30px; + } + } + + @media screen and (max-width: $column-breakpoint) { + padding: 25px 20px; + } + } + + &__information, + &__forms, + #mastodon-timeline { + box-sizing: border-box; + background: $ui-base-color; + border-radius: 4px; + box-shadow: 0 0 6px rgba($black, 0.1); + } + + &__mascot { + height: 104px; + position: relative; + left: -40px; + bottom: 25px; + + img { + height: 190px; + width: auto; + } + } + + &__short-description { + .row { + display: flex; + flex-wrap: wrap; + align-items: center; + margin-bottom: 40px; + } + + @media screen and (max-width: $column-breakpoint) { + .row { + margin-bottom: 20px; + } + } + + p a { + color: $secondary-text-color; + } + + h1 { + font-weight: 500; + color: $primary-text-color; + margin-bottom: 0; + + small { + color: $darker-text-color; + + span { + color: $secondary-text-color; + } + } + } + + p:last-child { + margin-bottom: 0; + } + } + + &__hero { + margin-bottom: 10px; + + img { + display: block; + margin: 0; + max-width: 100%; + height: auto; + border-radius: 4px; + } + } + + @media screen and (max-width: 840px) { + .information-board { + .container-alt { + padding-right: 20px; + } + + .panel { + position: static; + margin-top: 20px; + width: 100%; + border-radius: 4px; + + .panel-header { + text-align: center; + } + } + } + } + + @media screen and (max-width: 675px) { + .header-wrapper { + padding-top: 0; + + &.compact { + padding-bottom: 0; + } + + &.compact .hero .heading { + text-align: initial; + } + } + + .header .container-alt, + .features .container-alt { + display: block; + } + } + + .cta { + margin: 20px; + } +} + +.landing { + margin-bottom: 100px; + + @media screen and (max-width: 738px) { + margin-bottom: 0; + } + + &__brand { + display: flex; + justify-content: center; + align-items: center; + padding: 50px; + + svg { + fill: $primary-text-color; + height: 52px; + } + + @media screen and (max-width: $no-gap-breakpoint) { + padding: 0; + margin-bottom: 30px; + } + } + + .directory { + margin-top: 30px; + background: transparent; + box-shadow: none; + border-radius: 0; + } + + .hero-widget { + margin-top: 30px; + margin-bottom: 0; + + h4 { + padding: 10px; + text-transform: uppercase; + font-weight: 700; + font-size: 13px; + color: $darker-text-color; + } + + &__text { + border-radius: 0; + padding-bottom: 0; + } + + &__footer { + background: $ui-base-color; + padding: 10px; + border-radius: 0 0 4px 4px; + display: flex; + + &__column { + flex: 1 1 50%; + overflow-x: hidden; + } + } + + .account { + padding: 10px 0; + border-bottom: 0; + + .account__display-name { + display: flex; + align-items: center; + } + } + + &__counters__wrapper { + display: flex; + } + + &__counter { + padding: 10px; + width: 50%; + + strong { + font-family: $font-display, sans-serif; + font-size: 15px; + font-weight: 700; + display: block; + } + + span { + font-size: 14px; + color: $darker-text-color; + } + } + } + + .simple_form .user_agreement .label_input > label { + font-weight: 400; + color: $darker-text-color; + } + + .simple_form p.lead { + color: $darker-text-color; + font-size: 15px; + line-height: 20px; + font-weight: 400; + margin-bottom: 25px; + } + + &__grid { + max-width: 960px; + margin: 0 auto; + display: grid; + grid-template-columns: minmax(0, 50%) minmax(0, 50%); + grid-gap: 30px; + + @media screen and (max-width: 738px) { + grid-template-columns: minmax(0, 100%); + grid-gap: 10px; + + &__column-login { + grid-row: 1; + display: flex; + flex-direction: column; + + .box-widget { + order: 2; + flex: 0 0 auto; + } + + .hero-widget { + margin-top: 0; + margin-bottom: 10px; + order: 1; + flex: 0 0 auto; + } + } + + &__column-registration { + grid-row: 2; + } + + .directory { + margin-top: 10px; + } + } + + @media screen and (max-width: $no-gap-breakpoint) { + grid-gap: 0; + + .hero-widget { + display: block; + margin-bottom: 0; + box-shadow: none; + + &__img, + &__img img, + &__footer { + border-radius: 0; + } + } + + .hero-widget, + .box-widget, + .directory__tag { + border-bottom: 1px solid lighten($ui-base-color, 8%); + } + + .directory { + margin-top: 0; + + &__tag { + margin-bottom: 0; + + & > a, + & > div { + border-radius: 0; + box-shadow: none; + } + + &:last-child { + border-bottom: 0; + } + } + } + } + } +} .brand { position: relative; text-decoration: none; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 5f773c1cf3f469..03da16bb5ccea3 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -705,6 +705,18 @@ body > [data-popper-placement] { } } +.literacy-caution { + margin-top: 10px; + padding: 1rem; + border-radius: 4px; + background-color: $ui-base-color; +} +.literacy-caution a, .literacy-caution a:link, .literacy-caution a:visited, .literacy-caution a:hover, .literacy-caution a:active { + color: $primary-text-color; + text-decoration: none; +} + + .character-counter { cursor: default; font-family: $font-sans-serif, sans-serif; diff --git a/app/javascript/styles/suumo.scss b/app/javascript/styles/suumo.scss new file mode 100644 index 00000000000000..a653f1a38c14ba --- /dev/null +++ b/app/javascript/styles/suumo.scss @@ -0,0 +1,3 @@ +@import 'suumo/variables'; +@import 'application'; +@import 'suumo/diff'; diff --git a/app/javascript/styles/suumo/diff.scss b/app/javascript/styles/suumo/diff.scss new file mode 100644 index 00000000000000..f3cc08376c650f --- /dev/null +++ b/app/javascript/styles/suumo/diff.scss @@ -0,0 +1,86 @@ +// components.scss +.compose-form { + .compose-form__modifiers { + .compose-form__upload { + &-description { + input { + &::placeholder { + opacity: 1; + } + } + } + } + } +} + +.rich-formatting a, +.rich-formatting p a, +.rich-formatting li a, +.landing-page__short-description p a, +.status__content a, +.reply-indicator__content a { + color: lighten($ui-highlight-color, 12%); + text-decoration: underline; + + &.mention { + text-decoration: none; + } + + &.mention span { + text-decoration: underline; + + &:hover, + &:focus, + &:active { + text-decoration: none; + } + } + + &:hover, + &:focus, + &:active { + text-decoration: none; + } + + &.status__content__spoiler-link { + color: $secondary-text-color; + text-decoration: none; + } +} + +.status__content__read-more-button { + text-decoration: underline; + + &:hover, + &:focus, + &:active { + text-decoration: none; + } +} + +.getting-started__footer a { + text-decoration: underline; + + &:hover, + &:focus, + &:active { + 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; +} + +.compose-form__publish-button-wrapper button.button::before { + content: "スーモ!"; + font-size: 14px; +} + +.compose-form__publish-button-wrapper button.button { + font-size: 0; +} diff --git a/app/javascript/styles/suumo/variables.scss b/app/javascript/styles/suumo/variables.scss new file mode 100644 index 00000000000000..817e06b64b869c --- /dev/null +++ b/app/javascript/styles/suumo/variables.scss @@ -0,0 +1,24 @@ +// Dependent colors +$black: #0c1505; + +$classic-base-color: #18290a; +$classic-primary-color: #9baec8; +$classic-secondary-color: #d9e1e8; +$classic-highlight-color: #62a527; + +$ui-base-color: $classic-base-color !default; +$ui-primary-color: $classic-primary-color !default; +$ui-secondary-color: $classic-secondary-color !default; + +// Differences +$ui-highlight-color: #6eb92b; + +$darker-text-color: lighten($ui-primary-color, 20%) !default; +$dark-text-color: lighten($ui-primary-color, 12%) !default; +$secondary-text-color: lighten($ui-secondary-color, 6%) !default; +$highlight-text-color: $classic-highlight-color !default; +$action-button-color: #8d9ac2; + +$inverted-text-color: $black !default; +$lighter-text-color: darken($ui-base-color, 6%) !default; +$light-text-color: darken($ui-primary-color, 40%) !default; diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index d93497521ada0a..1581555bde8087 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -332,15 +332,13 @@ def resolve_thread(status) def fetch_replies(status) collection = @object['replies'] - return if collection.blank? + return if collection.nil? replies = ActivityPub::FetchRepliesService.new.call(status, collection, allow_synchronous_requests: false, request_id: @options[:request_id]) return unless replies.nil? uri = value_or_id(collection) ActivityPub::FetchRepliesWorker.perform_async(status.id, uri, { 'request_id' => @options[:request_id] }) unless uri.nil? - rescue => e - Rails.logger.warn "Error fetching replies: #{e}" end def conversation_from_uri(uri) diff --git a/app/lib/activitypub/adapter.rb b/app/lib/activitypub/adapter.rb index 5b9437eb8dad40..098b6296fb053e 100644 --- a/app/lib/activitypub/adapter.rb +++ b/app/lib/activitypub/adapter.rb @@ -20,6 +20,6 @@ def serializable_hash(options = nil) serialized_hash = serialized_hash.select { |k, _| options[:fields].include?(k) } if options[:fields] serialized_hash = self.class.transform_key_casing!(serialized_hash, instance_options) - { '@context': serialized_context(named_contexts, context_extensions) }.merge(serialized_hash) + { '@context' => serialized_context(named_contexts, context_extensions) }.merge(serialized_hash) end end diff --git a/app/lib/video_metadata_extractor.rb b/app/lib/video_metadata_extractor.rb index 2155766251e35d..df5409375f16ef 100644 --- a/app/lib/video_metadata_extractor.rb +++ b/app/lib/video_metadata_extractor.rb @@ -41,8 +41,8 @@ def parse_metadata @colorspace = video_stream[:pix_fmt] @width = video_stream[:width] @height = video_stream[:height] - @frame_rate = parse_framerate(video_stream[:avg_frame_rate]) - @r_frame_rate = parse_framerate(video_stream[:r_frame_rate]) + @frame_rate = video_stream[:avg_frame_rate] == '0/0' ? nil : Rational(video_stream[:avg_frame_rate]) + @r_frame_rate = video_stream[:r_frame_rate] == '0/0' ? nil : Rational(video_stream[:r_frame_rate]) # For some video streams the frame_rate reported by `ffprobe` will be 0/0, but for these streams we # should use `r_frame_rate` instead. Video screencast generated by Gnome Screencast have this issue. @frame_rate ||= @r_frame_rate @@ -55,10 +55,4 @@ def parse_metadata @invalid = true if @metadata.key?(:error) end - - def parse_framerate(raw) - Rational(raw) - rescue ZeroDivisionError - nil - end end diff --git a/app/lib/webfinger.rb b/app/lib/webfinger.rb index 01a5dbc21d914b..ae8a3b1eae0264 100644 --- a/app/lib/webfinger.rb +++ b/app/lib/webfinger.rb @@ -6,8 +6,6 @@ class GoneError < Error; end class RedirectError < Error; end class Response - ACTIVITYPUB_READY_TYPE = ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].freeze - attr_reader :uri def initialize(uri, body) @@ -22,28 +20,17 @@ def subject end def link(rel, attribute) - links.dig(rel, 0, attribute) - end - - def self_link_href - self_link.fetch('href') + links.dig(rel, attribute) end private def links - @links ||= @json.fetch('links', []).group_by { |link| link['rel'] } - end - - def self_link - links.fetch('self', []).find do |link| - ACTIVITYPUB_READY_TYPE.include?(link['type']) - end + @links ||= @json['links'].index_by { |link| link['rel'] } end def validate_response! raise Webfinger::Error, "Missing subject in response for #{@uri}" if subject.blank? - raise Webfinger::Error, "Missing self link in response for #{@uri}" if self_link.blank? end end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 35f0b5fee18a9a..69fd84be01353d 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -20,4 +20,10 @@ def set_autoreply_headers! headers['X-Auto-Response-Suppress'] = 'All' headers['Auto-Submitted'] = 'auto-generated' end + + def set_autoreply_headers! + headers['Precedence'] = 'list' + headers['X-Auto-Response-Suppress'] = 'All' + headers['Auto-Submitted'] = 'auto-generated' + end end diff --git a/app/models/concerns/ldap_authenticable.rb b/app/models/concerns/ldap_authenticable.rb index 1a46b4e80e38a8..775df081764d96 100644 --- a/app/models/concerns/ldap_authenticable.rb +++ b/app/models/concerns/ldap_authenticable.rb @@ -22,7 +22,7 @@ def ldap_get_user(attributes = {}) safe_username = safe_username.gsub(keys, replacement) end - resource = joins(:account).merge(Account.where(Account.arel_table[:username].lower.eq safe_username.downcase)).take + resource = joins(:account).find_by(accounts: { username: safe_username }) if resource.blank? resource = new(email: attributes[Devise.ldap_mail.to_sym].first, agreement: true, account_attributes: { username: safe_username }, admin: false, external: true, confirmed_at: Time.now.utc) diff --git a/app/models/form/request_custom_emoji_batch.rb b/app/models/form/request_custom_emoji_batch.rb new file mode 100644 index 00000000000000..f8a8e773d8078c --- /dev/null +++ b/app/models/form/request_custom_emoji_batch.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +class Form::RequestCustomEmojiBatch + include ActiveModel::Model + include Authorization + include AccountableConcern + + attr_accessor :request_custom_emoji_ids, :action, :current_account + + def save + case action + when 'approve' + approve! + when 'reject' + reject! + when 'delete' + delete! + end + end + + private + + def request_custom_emojis + @request_custom_emojis ||= RequestCustomEmoji.where(id: request_custom_emoji_ids) + end + + def approve! + request_custom_emojis.each { |request_custom_emoji| authorize(request_custom_emoji, :update?) } + + request_custom_emojis.each do |request_custom_emoji| + if request_custom_emoji.state != 0 + next + end + request_custom_emoji.update(state: 1) + new_emoji = CustomEmoji.new( + shortcode: request_custom_emoji.shortcode, + image: request_custom_emoji.image + ) + new_emoji.save + end + end + + def reject! + request_custom_emojis.each { |request_custom_emoji| authorize(request_custom_emoji, :update?) } + + request_custom_emojis.each do |request_custom_emoji| + if request_custom_emoji.state == 0 + request_custom_emoji.update(state: 2) + end + end + end + + def delete! + request_custom_emojis.each { |request_custom_emoji| authorize(request_custom_emoji, :destroy?) } + + request_custom_emojis.each do |request_custom_emoji| + request_custom_emoji.destroy + end + end +end diff --git a/app/models/report.rb b/app/models/report.rb index d0f19e12340927..eaf662d1e29441 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -44,9 +44,9 @@ class Report < ApplicationRecord delegate :local?, to: :account validates :comment, length: { maximum: 1_000 }, if: :local? - validates :rule_ids, absence: true, if: -> { (category_changed? || rule_ids_changed?) && !violation? } + validates :rule_ids, absence: true, unless: :violation? - validate :validate_rule_ids, if: -> { (category_changed? || rule_ids_changed?) && violation? } + validate :validate_rule_ids # entries here need to be kept in sync with the front-end: # - app/javascript/mastodon/features/notifications/components/report.jsx @@ -154,6 +154,8 @@ def set_uri end def validate_rule_ids + return unless violation? + errors.add(:rule_ids, I18n.t('reports.errors.invalid_rules')) unless rules.size == rule_ids&.size end diff --git a/app/models/request_custom_emoji.rb b/app/models/request_custom_emoji.rb new file mode 100644 index 00000000000000..d21da0a2a3f123 --- /dev/null +++ b/app/models/request_custom_emoji.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true +# == Schema Information +# +# Table name: request_custom_emojis +# +# id :bigint(8) not null, primary key +# state :integer default(0) +# shortcode :string default(""), not null +# image_file_name :string +# image_content_type :string +# image_file_size :integer +# image_updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null +# image_storage_schema_version :integer +# account_id :bigint(8) not null +# + +class RequestCustomEmoji < ApplicationRecord + include Attachmentable + + belongs_to :account + + has_attached_file :image, styles: { static: { format: 'png', convert_options: '-coalesce +profile "!icc,*" +set modify-date +set create-date' } }, validate_media_type: false + + validates_attachment :image, content_type: { content_type: CustomEmoji::IMAGE_MIME_TYPES }, presence: true, size: { less_than: CustomEmoji::LIMIT } + validates :shortcode, uniqueness: true, format: { with: CustomEmoji::SHORTCODE_ONLY_RE }, length: { minimum: 2 } + + scope :alphabetic, -> { order(shortcode: :asc) } + + remotable_attachment :image, CustomEmoji::LIMIT + + def object_type + :emoji + end + + class << self + def from_text(text, domain = nil) + return [] if text.blank? + + shortcodes = text.scan(CustomEmoji::SCAN_RE).map(&:first).uniq + + return [] if shortcodes.empty? + + EntityCache.instance.emoji(shortcodes, domain) + end + + def search(shortcode) + where('"custom_emojis"."shortcode" ILIKE ?', "%#{shortcode}%") + end + end + + private +end diff --git a/app/models/tag.rb b/app/models/tag.rb index 2d75aa2cc5096d..8fab98fb5c0107 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -35,7 +35,7 @@ class Tag < ApplicationRecord HASHTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)' HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}" - HASHTAG_RE = %r{(? e raise Error, e.message rescue Webfinger::Error => e diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index c4a5f5115b69b3..ec983510b91414 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -172,9 +172,9 @@ def update_metadata! as_array(@json['tag']).each do |tag| if equals_or_includes?(tag['type'], 'Hashtag') - @raw_tags << tag['name'] if tag['name'].present? + @raw_tags << tag['name'] elsif equals_or_includes?(tag['type'], 'Mention') - @raw_mentions << tag['href'] if tag['href'].present? + @raw_mentions << tag['href'] elsif equals_or_includes?(tag['type'], 'Emoji') @raw_emojis << tag end diff --git a/app/services/backup_service.rb b/app/services/backup_service.rb index 5bfa69f32ad3bd..b3282c409b41dc 100644 --- a/app/services/backup_service.rb +++ b/app/services/backup_service.rb @@ -19,8 +19,8 @@ def call(backup) def build_outbox_json!(file) skeleton = serialize(collection_presenter, ActivityPub::CollectionSerializer) - skeleton[:@context] = full_context - skeleton[:orderedItems] = ['!PLACEHOLDER!'] + skeleton['@context'] = full_context + skeleton['orderedItems'] = ['!PLACEHOLDER!'] skeleton = Oj.dump(skeleton) prepend, append = skeleton.split('"!PLACEHOLDER!"') add_comma = false @@ -33,7 +33,7 @@ def build_outbox_json!(file) file.write(statuses.map do |status| item = serialize_payload(ActivityPub::ActivityPresenter.from_status(status), ActivityPub::ActivitySerializer) - item.delete(:@context) + item.delete('@context') unless item[:type] == 'Announce' || item[:object][:attachment].blank? item[:object][:attachment].each do |attachment| diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index 515248979ae076..759d6e393730ca 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -86,8 +86,7 @@ def response_to_recipient? end def from_staff? - sender = @notification.from_account - sender.local? && sender.user.present? && sender.user_role&.overrides?(@recipient.user_role) && sender.user_role&.highlighted? && sender.user_role&.can?(*UserRole::Flags::CATEGORIES[:moderation].map(&:to_sym)) + @notification.from_account.local? && @notification.from_account.user.present? && @notification.from_account.user_role&.overrides?(@recipient.user_role) end def optional_non_following_and_direct? diff --git a/app/services/resolve_account_service.rb b/app/services/resolve_account_service.rb index 842c0040a2e881..6204fefd6ff790 100644 --- a/app/services/resolve_account_service.rb +++ b/app/services/resolve_account_service.rb @@ -104,6 +104,8 @@ def split_acct(acct) end def fetch_account! + return unless activitypub_ready? + with_redis_lock("resolve:#{@username}@#{@domain}") do @account = ActivityPub::FetchRemoteAccountService.new.call(actor_url, suppress_errors: @options[:suppress_errors]) end @@ -118,8 +120,12 @@ def webfinger_update_due? @options[:skip_cache] || @account.nil? || @account.possibly_stale? end + def activitypub_ready? + ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(@webfinger.link('self', 'type')) + end + def actor_url - @actor_url ||= @webfinger.self_link_href + @actor_url ||= @webfinger.link('self', 'href') end def gone_from_origin? diff --git a/app/views/admin/custom_emojis/new.html.haml b/app/views/admin/custom_emojis/new.html.haml index a03676b001c9e3..95996dec86164d 100644 --- a/app/views/admin/custom_emojis/new.html.haml +++ b/app/views/admin/custom_emojis/new.html.haml @@ -7,7 +7,7 @@ .fields-group = f.input :shortcode, wrapper: :with_label, label: t('admin.custom_emojis.shortcode'), hint: t('admin.custom_emojis.shortcode_hint') .fields-group - = f.input :image, wrapper: :with_label, input_html: { accept: CustomEmoji::IMAGE_MIME_TYPES.join(',') }, hint: t('admin.custom_emojis.image_hint', size: number_to_human_size(CustomEmoji::LIMIT)) + = f.input :image, wrapper: :with_label, input_html: { accept: CustomEmoji::IMAGE_MIME_TYPES.join(' ') }, hint: t('admin.custom_emojis.image_hint', size: number_to_human_size(CustomEmoji::LIMIT)) .actions = f.button :button, t('admin.custom_emojis.upload'), type: :submit diff --git a/app/views/settings/request_custom_emojis/_custom_emoji.html.haml b/app/views/settings/request_custom_emojis/_custom_emoji.html.haml new file mode 100644 index 00000000000000..459ed967f9b872 --- /dev/null +++ b/app/views/settings/request_custom_emojis/_custom_emoji.html.haml @@ -0,0 +1,25 @@ +.batch-table__row + %label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox + = f.check_box :request_custom_emoji_ids, { multiple: true, include_hidden: false }, custom_emoji.id + .batch-table__row__content.batch-table__row__content--simple.batch-table__row__content--with-image + .batch-table__row__content__image + = custom_emoji_tag(custom_emoji) + + .batch-table__row__content__text + %samp= ":#{custom_emoji.shortcode}:" + + .batch-table__row__content__image + - if prefers_autoplay? + = image_tag custom_emoji.account.avatar_original_url, alt: custom_emoji.account.username, class: 'emojione' + - else + = image_tag custom_emoji.account.avatar_static_url, alt: custom_emoji.account.username, class: 'emojione custom-emoji', 'data-original' => custom_emoji.account.avatar_original_url, 'data-static' => custom_emoji.account.avatar_static_url + + .batch-table__row__content__extra + - if custom_emoji.state == 0 + = t('settings.request_custom_emoji.state.pending') + - elsif custom_emoji.state == 1 + = t('settings.request_custom_emoji.state.approved') + - elsif custom_emoji.state == 2 + = t('settings.request_custom_emoji.state.rejected') + - else + = 'error' diff --git a/app/views/settings/request_custom_emojis/index.html.haml b/app/views/settings/request_custom_emojis/index.html.haml new file mode 100644 index 00000000000000..b16cefd65437bf --- /dev/null +++ b/app/views/settings/request_custom_emojis/index.html.haml @@ -0,0 +1,32 @@ +- content_for :page_title do + = t('settings.request_custom_emoji.title') + +- content_for :header_tags do + = javascript_pack_tag 'admin', async: true, crossorigin: 'anonymous' + +- content_for :heading_actions do + = link_to t('settings.request_custom_emoji.upload'), new_settings_request_custom_emoji_path, class: 'button' + += form_for(@form, url: batch_settings_request_custom_emojis_path) do |f| + = hidden_field_tag :page, params[:page] || 1 + + .batch-table.batch-table--simple + .batch-table__toolbar + %label.batch-table__toolbar__select.batch-checkbox-all + = check_box_tag :batch_checkbox_all, nil, false + .batch-table__toolbar__actions + - if @is_admin + = f.button safe_join([fa_icon('thumbs-up'), t('settings.request_custom_emoji.action.approve')]), name: :approve, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + + = f.button safe_join([fa_icon('thumbs-down'), t('settings.request_custom_emoji.action.reject')]), name: :reject, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + + = f.button safe_join([fa_icon('times'), t('admin.custom_emojis.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + + .batch-table__body + .batch-table__top_rid + - if @custom_emojis.empty? + = nothing_here 'nothing-here--under-tabs' + - else + = render partial: 'custom_emoji', collection: @custom_emojis, locals: { f: f } + += paginate @custom_emojis diff --git a/app/views/settings/request_custom_emojis/new.html.haml b/app/views/settings/request_custom_emojis/new.html.haml new file mode 100644 index 00000000000000..e200cd577e1bb4 --- /dev/null +++ b/app/views/settings/request_custom_emojis/new.html.haml @@ -0,0 +1,13 @@ +- content_for :page_title do + = t('settings.request_custom_emoji.title') + += simple_form_for @custom_emoji, url: settings_request_custom_emojis_path do |f| + = render 'shared/error_messages', object: @custom_emoji + + .fields-group + = f.input :shortcode, wrapper: :with_label, label: t('admin.custom_emojis.shortcode'), hint: t('admin.custom_emojis.shortcode_hint') + .fields-group + = f.input :image, wrapper: :with_label, input_html: { accept: CustomEmoji::IMAGE_MIME_TYPES.join(' ') }, hint: t('admin.custom_emojis.image_hint', size: number_to_human_size(CustomEmoji::LIMIT)) + + .actions + = f.button :button, t('settings.request_custom_emoji.upload'), type: :submit diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index 8125b335f95539..6d8284e2b45b40 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -142,7 +142,7 @@ def paging_request? end throttle('throttle_password_change/account', limit: 10, period: 10.minutes) do |req| - req.warden_user_id if (req.put? || req.patch?) && (req.path_matches?('/auth') || req.path_matches?('/auth/password')) + req.warden_user_id if req.put? || (req.patch? && req.path_matches?('/auth')) end self.throttled_responder = lambda do |request| diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 9d2abf0745eca9..b847e654692d16 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -3,6 +3,11 @@ require_relative '../../lib/mastodon/sidekiq_middleware' Sidekiq.configure_server do |config| + if Rails.configuration.database_configuration.dig('production', 'adapter') == 'postgresql_makara' + STDERR.puts 'ERROR: Database replication is not currently supported in Sidekiq workers. Check your configuration.' + exit 1 + end + config.redis = REDIS_SIDEKIQ_PARAMS config.server_middleware do |chain| diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 6a72c1ca14d3a1..b2c9b7c4fc5825 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -1608,6 +1608,19 @@ ja: preferences: ユーザー設定 profile: プロフィール relationships: フォロー・フォロワー + request_custom_emoji: + action: + approve: 承認 + reject: 不承認 + errors: + already_exists: はすでにカスタム絵文字として存在します + created_msg: 絵文字の申請に成功しました! + state: + approved: 承認済 + pending: 申請中 + rejected: 不承認 + title: カスタム絵文字の申請 + upload: 申請 statuses_cleanup: 投稿の自動削除 strikes: モデレーションストライク two_factor_authentication: 二要素認証 @@ -1701,6 +1714,9 @@ ja: contrast: Mastodon (ハイコントラスト) default: Mastodon (ダーク) mastodon-light: Mastodon (ライト) + suumo: スーモ + gruvbox: gruvbox + c3: C3 time: formats: default: "%Y年%m月%d日 %H:%M" diff --git a/config/navigation.rb b/config/navigation.rb index e86c695a98db77..4cb72e247ef9fb 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -30,6 +30,7 @@ end n.item :invites, safe_join([fa_icon('user-plus fw'), t('invites.title')]), invites_path, if: -> { current_user.can?(:invite_users) && current_user.functional? } + n.item :request_custom_emojis, safe_join([fa_icon('smile-o fw'), t('settings.request_custom_emoji.title')]), settings_request_custom_emojis_url n.item :development, safe_join([fa_icon('code fw'), t('settings.development')]), settings_applications_path, if: -> { current_user.functional? } n.item :trends, safe_join([fa_icon('fire fw'), t('admin.trends.title')]), admin_trends_statuses_path, if: -> { current_user.can?(:manage_taxonomies) } do |s| diff --git a/config/routes/settings.rb b/config/routes/settings.rb index 888fa9ecb55543..eb0e6e1bbe3805 100644 --- a/config/routes/settings.rb +++ b/config/routes/settings.rb @@ -71,4 +71,10 @@ resources :sessions, only: [:destroy] resources :featured_tags, only: [:index, :create, :destroy] resources :login_activities, only: [:index] + + resources :request_custom_emojis, only: [:index, :new, :create] do + collection do + post :batch + end + end end diff --git a/config/themes.yml b/config/themes.yml index 9c21c9459f3bcf..e01cc17c3cf882 100644 --- a/config/themes.yml +++ b/config/themes.yml @@ -1,3 +1,6 @@ default: styles/application.scss contrast: styles/contrast.scss mastodon-light: styles/mastodon-light.scss +suumo: styles/suumo.scss +gruvbox: styles/gruvbox.scss +c3: styles/c3.scss diff --git a/db/migrate/20221115101411_create_request_custom_emojis.rb b/db/migrate/20221115101411_create_request_custom_emojis.rb new file mode 100644 index 00000000000000..d7227c6eca7228 --- /dev/null +++ b/db/migrate/20221115101411_create_request_custom_emojis.rb @@ -0,0 +1,17 @@ +class CreateRequestCustomEmojis < ActiveRecord::Migration[6.1] + def change + create_table :request_custom_emojis do |t| + t.integer :state, null: false, default: 0 + t.string :shortcode, null: false, default: '' + t.string :image_file_name + t.string :image_content_type + t.integer :image_file_size + t.datetime :image_updated_at + t.integer :image_storage_schema_version + t.bigint :account_id + + t.timestamps null: false + end + add_foreign_key :request_custom_emojis, :accounts, column: :account_id, primary_key: :id, on_update: :cascade, on_delete: :cascade, validate: false + end +end diff --git a/db/seeds/04_admin.rb b/db/seeds/04_admin.rb index 887b4a22130376..c9b0369c9f3281 100644 --- a/db/seeds/04_admin.rb +++ b/db/seeds/04_admin.rb @@ -7,7 +7,5 @@ admin = Account.where(username: 'admin').first_or_initialize(username: 'admin') admin.save(validate: false) - user = User.where(email: "admin@#{domain}").first_or_initialize(email: "admin@#{domain}", password: 'mastodonadmin', password_confirmation: 'mastodonadmin', confirmed_at: Time.now.utc, role: UserRole.find_by(name: 'Owner'), account: admin, agreement: true, approved: true) - user.save! - user.approve! + User.where(email: "admin@#{domain}").first_or_initialize(email: "admin@#{domain}", password: 'mastodonadmin', password_confirmation: 'mastodonadmin', confirmed_at: Time.now.utc, role: UserRole.find_by(name: 'Owner'), account: admin, agreement: true, approved: true).save! end diff --git a/lib/mastodon/cli/media.rb b/lib/mastodon/cli/media.rb index 61132646192c11..5879c532e80a99 100644 --- a/lib/mastodon/cli/media.rb +++ b/lib/mastodon/cli/media.rb @@ -13,7 +13,6 @@ class Media < Base option :remove_headers, type: :boolean, default: false option :include_follows, type: :boolean, default: false option :concurrency, type: :numeric, default: 5, aliases: [:c] - option :verbose, type: :boolean, default: false, aliases: [:v] option :dry_run, type: :boolean, default: false desc 'remove', 'Remove remote media files, headers or avatars' long_desc <<-DESC diff --git a/lib/sanitize_ext/sanitize_config.rb b/lib/sanitize_ext/sanitize_config.rb index 520e6cba9ea760..bdb4c56a085c0f 100644 --- a/lib/sanitize_ext/sanitize_config.rb +++ b/lib/sanitize_ext/sanitize_config.rb @@ -65,7 +65,7 @@ module Config end MASTODON_STRICT ||= freeze_config( - elements: %w(p br span a del s pre blockquote code b strong u i em ul ol li), + elements: %w(p br span a del pre blockquote code b strong u i em ul ol li), attributes: { 'a' => %w(href rel class translate), diff --git a/lib/tasks/statistics.rake b/lib/tasks/statistics.rake index 82840f4fdc842e..dde7890f6b445d 100644 --- a/lib/tasks/statistics.rake +++ b/lib/tasks/statistics.rake @@ -9,13 +9,11 @@ namespace :mastodon do [ ['App Libraries', 'app/lib'], %w(Presenters app/presenters), - %w(Policies app/policies), - %w(Serializers app/serializers), %w(Services app/services), %w(Validators app/validators), %w(Workers app/workers), ].each do |name, dir| - STATS_DIRECTORIES << [name, dir] + STATS_DIRECTORIES << [name, Rails.root.join(dir)] end end end diff --git a/package.json b/package.json index 35a236c8cc57ef..74a1f0b3ada5e2 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "lodash": "^4.17.21", "mark-loader": "^0.1.6", "marky": "^1.2.5", + "material-design-icons": "^3.0.1", "mini-css-extract-plugin": "^1.6.2", "mkdirp": "^3.0.1", "npmlog": "^7.0.1", diff --git a/public/avatars/original/missing.png b/public/avatars/original/missing.png index 781370782ecf61..aa21449ebcb99b 100644 Binary files a/public/avatars/original/missing.png and b/public/avatars/original/missing.png differ diff --git a/public/robots.txt b/public/robots.txt index 6672eeba1f22d7..64d64d64cb0487 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -6,3 +6,4 @@ Disallow: / User-agent: * Disallow: /media_proxy/ Disallow: /interact/ +Disallow: /web/ diff --git a/spec/fixtures/requests/activitypub-webfinger.txt b/spec/fixtures/requests/activitypub-webfinger.txt index 733b1693dc8807..465066d84e1032 100644 --- a/spec/fixtures/requests/activitypub-webfinger.txt +++ b/spec/fixtures/requests/activitypub-webfinger.txt @@ -4,4 +4,4 @@ Content-Type: application/jrd+json; charset=utf-8 X-Content-Type-Options: nosniff Date: Sun, 17 Sep 2017 06:22:50 GMT -{"subject":"acct:foo@ap.example.com","aliases":["https://ap.example.com/@foo","https://ap.example.com/users/foo"],"links":[{"rel":"http://webfinger.net/rel/profile-page","type":"text/html","href":"https://ap.example.com/@foo"},{"rel":"http://schemas.google.com/g/2010#updates-from","type":"application/atom+xml","href":"https://ap.example.com/users/foo.atom"},{"rel":"self","type":"application/html","href":"https://ap.example.com/users/foo.html"},{"rel":"self","type":"application/activity+json","href":"https://ap.example.com/users/foo"},{"rel":"self","type":"application/json","href":"https://ap.example.com/users/foo.json"},{"rel":"salmon","href":"https://ap.example.com/api/salmon/1"},{"rel":"magic-public-key","href":"data:application/magic-public-key,RSA.u3L4vnpNLzVH31MeWI394F0wKeJFsLDAsNXGeOu0QF2x-h1zLWZw_agqD2R3JPU9_kaDJGPIV2Sn5zLyUA9S6swCCMOtn7BBR9g9sucgXJmUFB0tACH2QSgHywMAybGfmSb3LsEMNKsGJ9VsvYoh8lDET6X4Pyw-ZJU0_OLo_41q9w-OrGtlsTm_PuPIeXnxa6BLqnDaxC-4IcjG_FiPahNCTINl_1F_TgSSDZ4Taf4U9XFEIFw8wmgploELozzIzKq-t8nhQYkgAkt64euWpva3qL5KD1mTIZQEP-LZvh3s2WHrLi3fhbdRuwQ2c0KkJA2oSTFPDpqqbPGZ3QvuHQ==.AQAB"},{"rel":"http://ostatus.org/schema/1.0/subscribe","template":"https://ap.example.com/authorize_follow?acct={uri}"}]} \ No newline at end of file +{"subject":"acct:foo@ap.example.com","aliases":["https://ap.example.com/@foo","https://ap.example.com/users/foo"],"links":[{"rel":"http://webfinger.net/rel/profile-page","type":"text/html","href":"https://ap.example.com/@foo"},{"rel":"http://schemas.google.com/g/2010#updates-from","type":"application/atom+xml","href":"https://ap.example.com/users/foo.atom"},{"rel":"self","type":"application/activity+json","href":"https://ap.example.com/users/foo"},{"rel":"salmon","href":"https://ap.example.com/api/salmon/1"},{"rel":"magic-public-key","href":"data:application/magic-public-key,RSA.u3L4vnpNLzVH31MeWI394F0wKeJFsLDAsNXGeOu0QF2x-h1zLWZw_agqD2R3JPU9_kaDJGPIV2Sn5zLyUA9S6swCCMOtn7BBR9g9sucgXJmUFB0tACH2QSgHywMAybGfmSb3LsEMNKsGJ9VsvYoh8lDET6X4Pyw-ZJU0_OLo_41q9w-OrGtlsTm_PuPIeXnxa6BLqnDaxC-4IcjG_FiPahNCTINl_1F_TgSSDZ4Taf4U9XFEIFw8wmgploELozzIzKq-t8nhQYkgAkt64euWpva3qL5KD1mTIZQEP-LZvh3s2WHrLi3fhbdRuwQ2c0KkJA2oSTFPDpqqbPGZ3QvuHQ==.AQAB"},{"rel":"http://ostatus.org/schema/1.0/subscribe","template":"https://ap.example.com/authorize_follow?acct={uri}"}]} \ No newline at end of file diff --git a/spec/fixtures/requests/webfinger.txt b/spec/fixtures/requests/webfinger.txt index fce821bddbf5c8..f337ecae6f1501 100644 --- a/spec/fixtures/requests/webfinger.txt +++ b/spec/fixtures/requests/webfinger.txt @@ -8,4 +8,4 @@ Access-Control-Allow-Origin: * Vary: Accept-Encoding,Cookie Strict-Transport-Security: max-age=31536000; includeSubdomains; -{"subject":"acct:gargron@quitter.no","aliases":["https:\/\/quitter.no\/user\/7477","https:\/\/quitter.no\/gargron","https:\/\/quitter.no\/index.php\/user\/7477","https:\/\/quitter.no\/index.php\/gargron"],"links":[{"rel":"http:\/\/webfinger.net\/rel\/profile-page","type":"text\/html","href":"https:\/\/quitter.no\/gargron"},{"rel":"http:\/\/gmpg.org\/xfn\/11","type":"text\/html","href":"https:\/\/quitter.no\/gargron"},{"rel":"describedby","type":"application\/rdf+xml","href":"https:\/\/quitter.no\/gargron\/foaf"},{"rel":"self","type":"application/activity+json","href":"https://ap.example.com/users/foo"},{"rel":"http:\/\/apinamespace.org\/atom","type":"application\/atomsvc+xml","href":"https:\/\/quitter.no\/api\/statusnet\/app\/service\/gargron.xml"},{"rel":"http:\/\/apinamespace.org\/twitter","href":"https:\/\/quitter.no\/api\/"},{"rel":"http:\/\/specs.openid.net\/auth\/2.0\/provider","href":"https:\/\/quitter.no\/gargron"},{"rel":"http:\/\/schemas.google.com\/g\/2010#updates-from","type":"application\/atom+xml","href":"https:\/\/quitter.no\/api\/statuses\/user_timeline\/7477.atom"},{"rel":"magic-public-key","href":"data:application\/magic-public-key,RSA.1ZBkHTavLvxH3FzlKv4O6WtlILKRFfNami3_Rcu8EuogtXSYiS-bB6hElZfUCSHbC4uLemOA34PEhz__CDMozax1iI_t8dzjDnh1x0iFSup7pSfW9iXk_WU3Dm74yWWW2jildY41vWgrEstuQ1dJ8vVFfSJ9T_tO4c-T9y8vDI8=.AQAB"},{"rel":"salmon","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/salmon-protocol.org\/ns\/salmon-replies","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/salmon-protocol.org\/ns\/salmon-mention","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/ostatus.org\/schema\/1.0\/subscribe","template":"https:\/\/quitter.no\/main\/ostatussub?profile={uri}"}]} +{"subject":"acct:gargron@quitter.no","aliases":["https:\/\/quitter.no\/user\/7477","https:\/\/quitter.no\/gargron","https:\/\/quitter.no\/index.php\/user\/7477","https:\/\/quitter.no\/index.php\/gargron"],"links":[{"rel":"http:\/\/webfinger.net\/rel\/profile-page","type":"text\/html","href":"https:\/\/quitter.no\/gargron"},{"rel":"http:\/\/gmpg.org\/xfn\/11","type":"text\/html","href":"https:\/\/quitter.no\/gargron"},{"rel":"describedby","type":"application\/rdf+xml","href":"https:\/\/quitter.no\/gargron\/foaf"},{"rel":"http:\/\/apinamespace.org\/atom","type":"application\/atomsvc+xml","href":"https:\/\/quitter.no\/api\/statusnet\/app\/service\/gargron.xml"},{"rel":"http:\/\/apinamespace.org\/twitter","href":"https:\/\/quitter.no\/api\/"},{"rel":"http:\/\/specs.openid.net\/auth\/2.0\/provider","href":"https:\/\/quitter.no\/gargron"},{"rel":"http:\/\/schemas.google.com\/g\/2010#updates-from","type":"application\/atom+xml","href":"https:\/\/quitter.no\/api\/statuses\/user_timeline\/7477.atom"},{"rel":"magic-public-key","href":"data:application\/magic-public-key,RSA.1ZBkHTavLvxH3FzlKv4O6WtlILKRFfNami3_Rcu8EuogtXSYiS-bB6hElZfUCSHbC4uLemOA34PEhz__CDMozax1iI_t8dzjDnh1x0iFSup7pSfW9iXk_WU3Dm74yWWW2jildY41vWgrEstuQ1dJ8vVFfSJ9T_tO4c-T9y8vDI8=.AQAB"},{"rel":"salmon","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/salmon-protocol.org\/ns\/salmon-replies","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/salmon-protocol.org\/ns\/salmon-mention","href":"https:\/\/quitter.no\/main\/salmon\/user\/7477"},{"rel":"http:\/\/ostatus.org\/schema\/1.0\/subscribe","template":"https:\/\/quitter.no\/main\/ostatussub?profile={uri}"}]} diff --git a/spec/lib/activitypub/adapter_spec.rb b/spec/lib/activitypub/adapter_spec.rb index bd63ebb9c87657..f9f8b8dce0d81d 100644 --- a/spec/lib/activitypub/adapter_spec.rb +++ b/spec/lib/activitypub/adapter_spec.rb @@ -59,7 +59,7 @@ def virtual_object let(:serializer_class) { TestWithBasicContextSerializer } it 'renders a basic @context' do - expect(subject).to include({ '@context': 'https://www.w3.org/ns/activitystreams' }) + expect(subject).to include({ '@context' => 'https://www.w3.org/ns/activitystreams' }) end end @@ -67,7 +67,7 @@ def virtual_object let(:serializer_class) { TestWithNamedContextSerializer } it 'renders a @context with both items' do - expect(subject).to include({ '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] }) + expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] }) end end @@ -75,7 +75,7 @@ def virtual_object let(:serializer_class) { TestWithNestedNamedContextSerializer } it 'renders a @context with both items' do - expect(subject).to include({ '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] }) + expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] }) end end @@ -83,7 +83,7 @@ def virtual_object let(:serializer_class) { TestWithContextExtensionSerializer } it 'renders a @context with the extension' do - expect(subject).to include({ '@context': ['https://www.w3.org/ns/activitystreams', { 'sensitive' => 'as:sensitive' }] }) + expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', { 'sensitive' => 'as:sensitive' }] }) end end @@ -91,7 +91,7 @@ def virtual_object let(:serializer_class) { TestWithNestedContextExtensionSerializer } it 'renders a @context with both extensions' do - expect(subject).to include({ '@context': ['https://www.w3.org/ns/activitystreams', { 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', 'sensitive' => 'as:sensitive' }] }) + expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', { 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', 'sensitive' => 'as:sensitive' }] }) end end end diff --git a/spec/lib/webfinger_spec.rb b/spec/lib/webfinger_spec.rb deleted file mode 100644 index 5015deac7ffd87..00000000000000 --- a/spec/lib/webfinger_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Webfinger do - describe 'self link' do - context 'when self link is specified with type application/activity+json' do - let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/activity+json' }] } } - - it 'correctly parses the response' do - stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) - - response = described_class.new('acct:alice@example.com').perform - - expect(response.self_link_href).to eq 'https://example.com/alice' - end - end - - context 'when self link is specified with type application/ld+json' do - let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' }] } } - - it 'correctly parses the response' do - stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) - - response = described_class.new('acct:alice@example.com').perform - - expect(response.self_link_href).to eq 'https://example.com/alice' - end - end - - context 'when self link is specified with incorrect type' do - let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/json"' }] } } - - it 'raises an error' do - stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' }) - - expect { described_class.new('acct:alice@example.com').perform }.to raise_error(Webfinger::Error) - end - end - end -end diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 5fcbf46073f78b..fc7a43110b20e4 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -703,14 +703,6 @@ it 'does not match URL query string' do expect(subject.match('https://example.com/?x=@alice')).to be_nil end - - it 'matches usernames immediately following the letter ß' do - expect(subject.match('Hello toß @alice from me')[1]).to eq 'alice' - end - - it 'matches usernames containing uppercase characters' do - expect(subject.match('Hello to @aLice@Example.com from me')[1]).to eq 'aLice@Example.com' - end end describe 'validations' do diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb index 830f2f6085fd0c..0093dcd8de9496 100644 --- a/spec/models/report_spec.rb +++ b/spec/models/report_spec.rb @@ -133,18 +133,5 @@ report = Fabricate.build(:report, account: remote_account, comment: Faker::Lorem.characters(number: 1001)) expect(report.valid?).to be true end - - it 'is invalid if it references invalid rules' do - report = Fabricate.build(:report, category: :violation, rule_ids: [-1]) - expect(report.valid?).to be false - expect(report).to model_have_error_on_field(:rule_ids) - end - - it 'is invalid if it references rules but category is not "violation"' do - rule = Fabricate(:rule) - report = Fabricate.build(:report, category: :spam, rule_ids: rule.id) - expect(report.valid?).to be false - expect(report).to model_have_error_on_field(:rule_ids) - end end end diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb index 529de32695aaf9..6177b7a25a6d02 100644 --- a/spec/models/tag_spec.rb +++ b/spec/models/tag_spec.rb @@ -36,10 +36,6 @@ expect(subject.match('https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111895#c4')).to be_nil end - it 'does not match URLs with hashtag-like anchors after a non-ascii character' do - expect(subject.match('https://example.org/testé#foo')).to be_nil - end - it 'does not match URLs with hashtag-like anchors after an empty query parameter' do expect(subject.match('https://en.wikipedia.org/wiki/Ghostbusters_(song)?foo=#Lawsuit')).to be_nil end @@ -95,14 +91,6 @@ it 'does not match purely-numeric hashtags' do expect(subject.match('hello #0123456')).to be_nil end - - it 'matches hashtags immediately following the letter ß' do - expect(subject.match('Hello toß #ruby').to_s).to eq '#ruby' - end - - it 'matches hashtags containing uppercase characters' do - expect(subject.match('Hello #rubyOnRails').to_s).to eq '#rubyOnRails' - end end describe '#to_param' do diff --git a/spec/services/activitypub/fetch_featured_collection_service_spec.rb b/spec/services/activitypub/fetch_featured_collection_service_spec.rb index 237fc7123e9ae4..583212c3756031 100644 --- a/spec/services/activitypub/fetch_featured_collection_service_spec.rb +++ b/spec/services/activitypub/fetch_featured_collection_service_spec.rb @@ -42,22 +42,12 @@ } end - let(:featured_with_null) do - { - '@context': 'https://www.w3.org/ns/activitystreams', - id: 'https://example.com/account/collections/featured', - totalItems: 0, - type: 'OrderedCollection', - } - end - let(:items) do [ 'https://example.com/account/pinned/known', # known status_json_pinned_unknown_inlined, # unknown inlined 'https://example.com/account/pinned/unknown-unreachable', # unknown unreachable 'https://example.com/account/pinned/unknown-reachable', # unknown reachable - 'https://example.com/account/collections/featured', # featured with null ] end @@ -76,7 +66,6 @@ stub_request(:get, 'https://example.com/account/pinned/unknown-inlined').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_inlined), headers: { 'Content-Type': 'application/activity+json' }) stub_request(:get, 'https://example.com/account/pinned/unknown-unreachable').to_return(status: 404) stub_request(:get, 'https://example.com/account/pinned/unknown-reachable').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_reachable), headers: { 'Content-Type': 'application/activity+json' }) - stub_request(:get, 'https://example.com/account/collections/featured').to_return(status: 200, body: Oj.dump(featured_with_null), headers: { 'Content-Type': 'application/activity+json' }) subject.call(actor, note: true, hashtag: false) end diff --git a/spec/services/activitypub/fetch_remote_account_service_spec.rb b/spec/services/activitypub/fetch_remote_account_service_spec.rb index a69a43f5291ca1..42badde0517980 100644 --- a/spec/services/activitypub/fetch_remote_account_service_spec.rb +++ b/spec/services/activitypub/fetch_remote_account_service_spec.rb @@ -39,7 +39,7 @@ end context 'when the account does not have a inbox' do - let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/activity+json' }] } } + let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice' }] } } before do actor[:inbox] = nil @@ -64,7 +64,7 @@ end context 'when URI and WebFinger share the same host' do - let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/activity+json' }] } } + let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice' }] } } before do stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' }) @@ -90,7 +90,7 @@ end context 'when WebFinger presents different domain than URI' do - let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/activity+json' }] } } + let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/alice' }] } } before do stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' }) @@ -122,7 +122,7 @@ end context 'when WebFinger returns a different URI' do - let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/bob', type: 'application/activity+json' }] } } + let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/bob' }] } } before do stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' }) @@ -145,7 +145,7 @@ end context 'when WebFinger returns a different URI after a redirection' do - let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/bob', type: 'application/activity+json' }] } } + let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/bob' }] } } before do stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' }) diff --git a/spec/services/activitypub/fetch_remote_actor_service_spec.rb b/spec/services/activitypub/fetch_remote_actor_service_spec.rb index c92705130b9c87..6d264b7b825276 100644 --- a/spec/services/activitypub/fetch_remote_actor_service_spec.rb +++ b/spec/services/activitypub/fetch_remote_actor_service_spec.rb @@ -39,7 +39,7 @@ end context 'when the account does not have a inbox' do - let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/activity+json' }] } } + let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice' }] } } before do actor[:inbox] = nil @@ -64,7 +64,7 @@ end context 'when URI and WebFinger share the same host' do - let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/activity+json' }] } } + let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice' }] } } before do stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' }) @@ -90,7 +90,7 @@ end context 'when WebFinger presents different domain than URI' do - let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/activity+json' }] } } + let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/alice' }] } } before do stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' }) @@ -122,7 +122,7 @@ end context 'when WebFinger returns a different URI' do - let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/bob', type: 'application/activity+json' }] } } + let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/bob' }] } } before do stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' }) @@ -145,7 +145,7 @@ end context 'when WebFinger returns a different URI after a redirection' do - let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/bob', type: 'application/activity+json' }] } } + let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/bob' }] } } before do stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' }) diff --git a/spec/services/activitypub/fetch_remote_key_service_spec.rb b/spec/services/activitypub/fetch_remote_key_service_spec.rb index 011f12915725b1..478778cc9f5762 100644 --- a/spec/services/activitypub/fetch_remote_key_service_spec.rb +++ b/spec/services/activitypub/fetch_remote_key_service_spec.rb @@ -5,7 +5,7 @@ RSpec.describe ActivityPub::FetchRemoteKeyService, type: :service do subject { described_class.new } - let(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/activity+json' }] } } + let(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice' }] } } let(:public_key_pem) do <<~TEXT diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb index a3a084d23e99d9..60214d2ed839c1 100644 --- a/spec/services/activitypub/process_account_service_spec.rb +++ b/spec/services/activitypub/process_account_service_spec.rb @@ -217,7 +217,7 @@ }.with_indifferent_access webfinger = { subject: "acct:user#{i}@foo.test", - links: [{ rel: 'self', href: "https://foo.test/users/#{i}", type: 'application/activity+json' }], + links: [{ rel: 'self', href: "https://foo.test/users/#{i}" }], }.with_indifferent_access stub_request(:get, "https://foo.test/users/#{i}").to_return(status: 200, body: actor_json.to_json, headers: { 'Content-Type': 'application/activity+json' }) stub_request(:get, "https://foo.test/users/#{i}/featured").to_return(status: 200, body: featured_json.to_json, headers: { 'Content-Type': 'application/activity+json' }) diff --git a/spec/services/backup_service_spec.rb b/spec/services/backup_service_spec.rb index befff0b530e5d0..1eb789d1f5837f 100644 --- a/spec/services/backup_service_spec.rb +++ b/spec/services/backup_service_spec.rb @@ -60,7 +60,6 @@ def expect_outbox_export aggregate_failures do expect(body.scan('@context').count).to eq 1 - expect(body.scan('orderedItems').count).to eq 1 expect(json['@context']).to_not be_nil expect(json['type']).to eq 'OrderedCollection' expect(json['totalItems']).to eq 2 diff --git a/spec/services/notify_service_spec.rb b/spec/services/notify_service_spec.rb index 568fb1cdfdb6ec..c2664e79c23bb5 100644 --- a/spec/services/notify_service_spec.rb +++ b/spec/services/notify_service_spec.rb @@ -18,17 +18,6 @@ expect { subject }.to_not change(Notification, :count) end - context 'when the sender is a local moderator' do - let(:sender) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } - let(:type) { :mention } - let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender)) } - - it 'does notify when the sender is blocked' do - recipient.block!(sender) - expect { subject }.to change(Notification, :count).by(1) - end - end - it 'does not notify when sender is muted with hide_notifications' do recipient.mute!(sender, notifications: true) expect { subject }.to_not change(Notification, :count) diff --git a/yarn.lock b/yarn.lock index 70b791d8f82a94..5bef29bdf508cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8365,6 +8365,11 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== +material-design-icons@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/material-design-icons/-/material-design-icons-3.0.1.tgz#9a71c48747218ebca51e51a66da682038cdcb7bf" + integrity sha512-t19Z+QZBwSZulxptEu05kIm+UyfIdJY1JDwI+nx02j269m6W414whiQz9qfvQIiLrdx71RQv+T48nHhuQXOCIQ== + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"