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 (
+
+ )
+ }
+}
+
+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"