Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
32308b6536
78 changed files with 985 additions and 709 deletions
2
.github/workflows/crowdin-download.yml
vendored
2
.github/workflows/crowdin-download.yml
vendored
|
@ -53,7 +53,7 @@ jobs:
|
|||
|
||||
# Create or update the pull request
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6.0.0
|
||||
uses: peter-evans/create-pull-request@v6.0.1
|
||||
with:
|
||||
commit-message: 'New Crowdin translations'
|
||||
title: 'New Crowdin Translations (automated)'
|
||||
|
|
|
@ -357,7 +357,7 @@ GEM
|
|||
jmespath (1.6.2)
|
||||
json (2.7.1)
|
||||
json-canonicalization (1.0.0)
|
||||
json-jwt (1.15.3)
|
||||
json-jwt (1.15.3.1)
|
||||
activesupport (>= 4.2)
|
||||
aes_key_wrap
|
||||
bindata
|
||||
|
@ -535,7 +535,7 @@ GEM
|
|||
rack (2.2.8.1)
|
||||
rack-attack (6.7.0)
|
||||
rack (>= 1.0, < 4)
|
||||
rack-cors (2.0.1)
|
||||
rack-cors (2.0.2)
|
||||
rack (>= 2.0.0)
|
||||
rack-oauth2 (1.21.3)
|
||||
activesupport
|
||||
|
|
152
README.md
152
README.md
|
@ -27,15 +27,155 @@ Original glitch-soc readme is below.
|
|||
|
||||
# Mastodon Glitch Edition
|
||||
|
||||
> Now with automated deploys!
|
||||
[![Ruby Testing](https://github.com/glitch-soc/mastodon/actions/workflows/test-ruby.yml/badge.svg)](https://github.com/glitch-soc/mastodon/actions/workflows/test-ruby.yml)
|
||||
[![Crowdin](https://badges.crowdin.net/glitch-soc/localized.svg)][glitch-crowdin]
|
||||
|
||||
[![Build Status](https://img.shields.io/circleci/project/github/glitch-soc/mastodon.svg)][circleci]
|
||||
[![Code Climate](https://img.shields.io/codeclimate/maintainability/glitch-soc/mastodon.svg)][code_climate]
|
||||
|
||||
[circleci]: https://circleci.com/gh/glitch-soc/mastodon
|
||||
[code_climate]: https://codeclimate.com/github/glitch-soc/mastodon
|
||||
[glitch-crowdin]: https://crowdin.com/project/glitch-soc
|
||||
|
||||
So here's the deal: we all work on this code, and anyone who uses that does so absolutely at their own risk. can you dig it?
|
||||
|
||||
- You can view documentation for this project at [glitch-soc.github.io/docs/](https://glitch-soc.github.io/docs/).
|
||||
- And contributing guidelines are available [here](CONTRIBUTING.md) and [here](https://glitch-soc.github.io/docs/contributing/).
|
||||
|
||||
Mastodon Glitch Edition is a fork of [Mastodon](https://github.com/mastodon/mastodon). Upstream's README file is reproduced below.
|
||||
|
||||
---
|
||||
|
||||
<h1><picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./lib/assets/wordmark.dark.png?raw=true">
|
||||
<source media="(prefers-color-scheme: light)" srcset="./lib/assets/wordmark.light.png?raw=true">
|
||||
<img alt="Mastodon" src="./lib/assets/wordmark.light.png?raw=true" height="34">
|
||||
</picture></h1>
|
||||
|
||||
[![GitHub release](https://img.shields.io/github/release/mastodon/mastodon.svg)][releases]
|
||||
[![Ruby Testing](https://github.com/mastodon/mastodon/actions/workflows/test-ruby.yml/badge.svg)](https://github.com/mastodon/mastodon/actions/workflows/test-ruby.yml)
|
||||
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/mastodon/localized.svg)][crowdin]
|
||||
|
||||
[releases]: https://github.com/mastodon/mastodon/releases
|
||||
[crowdin]: https://crowdin.com/project/mastodon
|
||||
|
||||
Mastodon is a **free, open-source social network server** based on ActivityPub where users can follow friends and discover new ones. On Mastodon, users can publish anything they want: links, pictures, text, and video. All Mastodon servers are interoperable as a federated network (users on one server can seamlessly communicate with users from another one, including non-Mastodon software that implements ActivityPub!)
|
||||
|
||||
Click below to **learn more** in a video:
|
||||
|
||||
[![Screenshot](https://blog.joinmastodon.org/2018/06/why-activitypub-is-the-future/ezgif-2-60f1b00403.gif)][youtube_demo]
|
||||
|
||||
[youtube_demo]: https://www.youtube.com/watch?v=IPSbNdBmWKE
|
||||
|
||||
## Navigation
|
||||
|
||||
- [Project homepage 🐘](https://joinmastodon.org)
|
||||
- [Support the development via Patreon][patreon]
|
||||
- [View sponsors](https://joinmastodon.org/sponsors)
|
||||
- [Blog](https://blog.joinmastodon.org)
|
||||
- [Documentation](https://docs.joinmastodon.org)
|
||||
- [Roadmap](https://joinmastodon.org/roadmap)
|
||||
- [Official Docker image](https://github.com/mastodon/mastodon/pkgs/container/mastodon)
|
||||
- [Browse Mastodon servers](https://joinmastodon.org/communities)
|
||||
- [Browse Mastodon apps](https://joinmastodon.org/apps)
|
||||
|
||||
[patreon]: https://www.patreon.com/mastodon
|
||||
|
||||
## Features
|
||||
|
||||
<img src="/app/javascript/images/elephant_ui_working.svg?raw=true" align="right" width="30%" />
|
||||
|
||||
### No vendor lock-in: Fully interoperable with any conforming platform
|
||||
|
||||
It doesn't have to be Mastodon; whatever implements ActivityPub is part of the social network! [Learn more](https://blog.joinmastodon.org/2018/06/why-activitypub-is-the-future/)
|
||||
|
||||
### Real-time, chronological timeline updates
|
||||
|
||||
Updates of people you're following appear in real-time in the UI via WebSockets. There's a firehose view as well!
|
||||
|
||||
### Media attachments like images and short videos
|
||||
|
||||
Upload and view images and WebM/MP4 videos attached to the updates. Videos with no audio track are treated like GIFs; normal videos loop continuously!
|
||||
|
||||
### Safety and moderation tools
|
||||
|
||||
Mastodon includes private posts, locked accounts, phrase filtering, muting, blocking, and all sorts of other features, along with a reporting and moderation system. [Learn more](https://blog.joinmastodon.org/2018/07/cage-the-mastodon/)
|
||||
|
||||
### OAuth2 and a straightforward REST API
|
||||
|
||||
Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Streaming APIs. This results in a rich app ecosystem with a lot of choices!
|
||||
|
||||
## Deployment
|
||||
|
||||
### Tech stack
|
||||
|
||||
- **Ruby on Rails** powers the REST API and other web pages
|
||||
- **React.js** and Redux are used for the dynamic parts of the interface
|
||||
- **Node.js** powers the streaming API
|
||||
|
||||
### Requirements
|
||||
|
||||
- **PostgreSQL** 12+
|
||||
- **Redis** 4+
|
||||
- **Ruby** 3.0+
|
||||
- **Node.js** 16+
|
||||
|
||||
The repository includes deployment configurations for **Docker and docker-compose** as well as specific platforms like **Heroku**, **Scalingo**, and **Nanobox**. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). The [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation.
|
||||
|
||||
## Development
|
||||
|
||||
### Vagrant
|
||||
|
||||
A **Vagrant** configuration is included for development purposes. To use it, complete the following steps:
|
||||
|
||||
- Install Vagrant and Virtualbox
|
||||
- Install the `vagrant-hostsupdater` plugin: `vagrant plugin install vagrant-hostsupdater`
|
||||
- Run `vagrant up`
|
||||
- Run `vagrant ssh -c "cd /vagrant && bin/dev"`
|
||||
- Open `http://mastodon.local` in your browser
|
||||
|
||||
### MacOS
|
||||
|
||||
To set up **MacOS** for native development, complete the following steps:
|
||||
|
||||
- Use a Ruby version manager to install the specified version from `.ruby-version`
|
||||
- Run `brew install postgresql@14 redis imagemagick libidn` to install required dependencies
|
||||
- Navigate to Mastodon's root directory and run `brew install nvm` then `nvm use` to use the version from `.nvmrc`
|
||||
- Run `corepack enable && corepack prepare`
|
||||
- Run `bundle exec rails db:setup` (optionally prepend `RAILS_ENV=development` to target the dev environment)
|
||||
- Finally, run `bin/dev` which will launch the local services via `overmind` (if installed) or `foreman`
|
||||
|
||||
### Docker
|
||||
|
||||
For development with **Docker**, complete the following steps:
|
||||
|
||||
- Install Docker Desktop
|
||||
- Run `docker compose -f .devcontainer/docker-compose.yml up -d`
|
||||
- Run `docker compose -f .devcontainer/docker-compose.yml exec app .devcontainer/post-create.sh`
|
||||
- Finally, run `docker compose -f .devcontainer/docker-compose.yml exec app bin/dev`
|
||||
|
||||
If you are using an IDE with [support for the Development Container specification](https://containers.dev/supporting), it will run the above `docker compose` commands automatically. For **Visual Studio Code** this requires the [Dev Container extension](https://containers.dev/supporting#dev-containers).
|
||||
|
||||
### GitHub Codespaces
|
||||
|
||||
To get you coding in just a few minutes, GitHub Codespaces provides a web-based version of Visual Studio Code and a cloud-hosted development environment fully configured with the software needed for this project..
|
||||
|
||||
- Click this button to create a new codespace:<br>
|
||||
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=52281283&devcontainer_path=.devcontainer%2Fcodespaces%2Fdevcontainer.json)
|
||||
- Wait for the environment to build. This will take a few minutes.
|
||||
- When the editor is ready, run `bin/dev` in the terminal.
|
||||
- After a few seconds, a popup will appear with a button labeled _Open in Browser_. This will open Mastodon.
|
||||
- On the _Ports_ tab, right click on the “stream” row and select _Port visibility_ → _Public_.
|
||||
|
||||
## Contributing
|
||||
|
||||
Mastodon is **free, open-source software** licensed under **AGPLv3**.
|
||||
|
||||
You can open issues for bugs you've found or features you think are missing. You can also submit pull requests to this repository or submit translations using Crowdin. To get started, take a look at [CONTRIBUTING.md](CONTRIBUTING.md). If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon).
|
||||
|
||||
**IRC channel**: #mastodon on irc.libera.chat
|
||||
|
||||
## License
|
||||
|
||||
Copyright (C) 2016-2024 Eugen Rochko & other Mastodon contributors (see [AUTHORS.md](AUTHORS.md))
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
|
2
Vagrantfile
vendored
2
Vagrantfile
vendored
|
@ -188,7 +188,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
|||
|
||||
config.vm.post_up_message = <<MESSAGE
|
||||
To start server
|
||||
$ vagrant ssh -c "cd /vagrant && foreman start"
|
||||
$ vagrant ssh -c "cd /vagrant && bin/dev"
|
||||
MESSAGE
|
||||
|
||||
end
|
||||
|
|
|
@ -109,6 +109,7 @@ module LanguagesHelper
|
|||
mn: ['Mongolian', 'Монгол хэл'].freeze,
|
||||
mr: ['Marathi', 'मराठी'].freeze,
|
||||
ms: ['Malay', 'Bahasa Melayu'].freeze,
|
||||
'ms-Arab': ['Jawi Malay', 'بهاس ملايو'].freeze,
|
||||
mt: ['Maltese', 'Malti'].freeze,
|
||||
my: ['Burmese', 'ဗမာစာ'].freeze,
|
||||
na: ['Nauru', 'Ekakairũ Naoero'].freeze,
|
||||
|
@ -127,7 +128,7 @@ module LanguagesHelper
|
|||
om: ['Oromo', 'Afaan Oromoo'].freeze,
|
||||
or: ['Oriya', 'ଓଡ଼ିଆ'].freeze,
|
||||
os: ['Ossetian', 'ирон æвзаг'].freeze,
|
||||
pa: ['Panjabi', 'ਪੰਜਾਬੀ'].freeze,
|
||||
pa: ['Punjabi', 'ਪੰਜਾਬੀ'].freeze,
|
||||
pi: ['Pāli', 'पाऴि'].freeze,
|
||||
pl: ['Polish', 'Polski'].freeze,
|
||||
ps: ['Pashto', 'پښتو'].freeze,
|
||||
|
@ -191,15 +192,19 @@ module LanguagesHelper
|
|||
chr: ['Cherokee', 'ᏣᎳᎩ ᎦᏬᏂᎯᏍᏗ'].freeze,
|
||||
ckb: ['Sorani (Kurdish)', 'سۆرانی'].freeze,
|
||||
cnr: ['Montenegrin', 'crnogorski'].freeze,
|
||||
csb: ['Kashubian', 'Kaszëbsczi'].freeze,
|
||||
jbo: ['Lojban', 'la .lojban.'].freeze,
|
||||
kab: ['Kabyle', 'Taqbaylit'].freeze,
|
||||
ldn: ['Láadan', 'Láadan'].freeze,
|
||||
lfn: ['Lingua Franca Nova', 'lingua franca nova'].freeze,
|
||||
moh: ['Mohawk', 'Kanienʼkéha'].freeze,
|
||||
pdc: ['Pennsylvania Dutch', 'Pennsilfaani-Deitsch'].freeze,
|
||||
sco: ['Scots', 'Scots'].freeze,
|
||||
sma: ['Southern Sami', 'Åarjelsaemien Gïele'].freeze,
|
||||
smj: ['Lule Sami', 'Julevsámegiella'].freeze,
|
||||
szl: ['Silesian', 'ślůnsko godka'].freeze,
|
||||
tok: ['Toki Pona', 'toki pona'].freeze,
|
||||
vai: ['Vai', 'ꕙꔤ'].freeze,
|
||||
xal: ['Kalmyk', 'Хальмг келн'].freeze,
|
||||
zba: ['Balaibalan', 'باليبلن'].freeze,
|
||||
zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze,
|
||||
|
|
|
@ -66,11 +66,9 @@ export const FOLLOW_REQUESTS_EXPAND_SUCCESS = 'FOLLOW_REQUESTS_EXPAND_SUCCESS';
|
|||
export const FOLLOW_REQUESTS_EXPAND_FAIL = 'FOLLOW_REQUESTS_EXPAND_FAIL';
|
||||
|
||||
export const FOLLOW_REQUEST_AUTHORIZE_REQUEST = 'FOLLOW_REQUEST_AUTHORIZE_REQUEST';
|
||||
export const FOLLOW_REQUEST_AUTHORIZE_SUCCESS = 'FOLLOW_REQUEST_AUTHORIZE_SUCCESS';
|
||||
export const FOLLOW_REQUEST_AUTHORIZE_FAIL = 'FOLLOW_REQUEST_AUTHORIZE_FAIL';
|
||||
|
||||
export const FOLLOW_REQUEST_REJECT_REQUEST = 'FOLLOW_REQUEST_REJECT_REQUEST';
|
||||
export const FOLLOW_REQUEST_REJECT_SUCCESS = 'FOLLOW_REQUEST_REJECT_SUCCESS';
|
||||
export const FOLLOW_REQUEST_REJECT_FAIL = 'FOLLOW_REQUEST_REJECT_FAIL';
|
||||
|
||||
export const PINNED_ACCOUNTS_FETCH_REQUEST = 'PINNED_ACCOUNTS_FETCH_REQUEST';
|
||||
|
@ -93,11 +91,6 @@ export * from './accounts_typed';
|
|||
export function fetchAccount(id) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(fetchRelationships([id]));
|
||||
|
||||
if (getState().getIn(['accounts', id], null) !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(fetchAccountRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/accounts/${id}`).then(response => {
|
||||
|
|
|
@ -266,12 +266,14 @@ export function submitCompose(routerHistory, overridePrivacy = null) {
|
|||
insertIfOnline('direct');
|
||||
}
|
||||
|
||||
dispatch(showAlert({
|
||||
message: statusId === null ? messages.published : messages.saved,
|
||||
action: messages.open,
|
||||
dismissAfter: 10000,
|
||||
onClick: () => routerHistory.push(`/@${response.data.account.username}/${response.data.id}`),
|
||||
}));
|
||||
if (getState().getIn(['local_settings', 'show_published_toast'])) {
|
||||
dispatch(showAlert({
|
||||
message: statusId === null ? messages.published : messages.saved,
|
||||
action: messages.open,
|
||||
dismissAfter: 10000,
|
||||
onClick: () => routerHistory.push(`/@${response.data.account.username}/${response.data.id}`),
|
||||
}));
|
||||
}
|
||||
}).catch(function (error) {
|
||||
dispatch(submitComposeFail(error));
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import api from '../api';
|
||||
|
||||
import { ensureComposeIsVisible, setComposeToStatus } from './compose';
|
||||
import { importFetchedStatus, importFetchedStatuses } from './importer';
|
||||
import { importFetchedStatus, importFetchedStatuses, importFetchedAccount } from './importer';
|
||||
import { deleteFromTimelines } from './timelines';
|
||||
|
||||
export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST';
|
||||
|
@ -138,10 +138,10 @@ export function deleteStatus(id, routerHistory, withRedraft = false) {
|
|||
api(getState).delete(`/api/v1/statuses/${id}`).then(response => {
|
||||
dispatch(deleteStatusSuccess(id));
|
||||
dispatch(deleteFromTimelines(id));
|
||||
dispatch(importFetchedAccount(response.data.account));
|
||||
|
||||
if (withRedraft) {
|
||||
dispatch(redraft(status, response.data.text, response.data.content_type));
|
||||
|
||||
ensureComposeIsVisible(getState, routerHistory);
|
||||
}
|
||||
}).catch(error => {
|
||||
|
|
|
@ -10,7 +10,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
import LinkIcon from '@/material-icons/400-24px/link.svg?react';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
|
||||
|
||||
const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
|
||||
|
||||
export default class AttachmentList extends ImmutablePureComponent {
|
||||
|
|
|
@ -16,7 +16,6 @@ import { Icon } from 'flavours/glitch/components/icon';
|
|||
import { ButtonInTabsBar, useColumnsContext } from 'flavours/glitch/features/ui/util/columns_context';
|
||||
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
|
||||
|
||||
|
||||
import { useAppHistory } from './router';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
|
|
@ -5,7 +5,6 @@ import { FormattedMessage, injectIntl } from 'react-intl';
|
|||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
import ArrowDropDownIcon from '@/material-icons/400-24px/arrow_drop_down.svg?react';
|
||||
import { openModal } from 'flavours/glitch/actions/modal';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
import logo from '@/images/logo.svg';
|
||||
|
||||
export const WordmarkLogo = () => (
|
||||
export const WordmarkLogo: React.FC = () => (
|
||||
<svg viewBox='0 0 261 66' className='logo logo--wordmark' role='img'>
|
||||
<title>Mastodon</title>
|
||||
<use xlinkHref='#logo-symbol-wordmark' />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const SymbolLogo = () => (
|
||||
export const SymbolLogo: React.FC = () => (
|
||||
<img src={logo} alt='Mastodon' className='logo logo--icon' />
|
||||
);
|
||||
|
||||
export default WordmarkLogo;
|
|
@ -1,6 +1,6 @@
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import illustration from 'flavours/glitch/images/elephant_ui_working.svg';
|
||||
import illustration from '@/images/elephant_ui_working.svg';
|
||||
|
||||
const RegenerationIndicator = () => (
|
||||
<div className='regeneration-indicator'>
|
||||
|
|
|
@ -10,7 +10,6 @@ import { List as ImmutableList } from 'immutable';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
||||
import ExpandMoreIcon from '@/material-icons/400-24px/expand_more.svg?react';
|
||||
import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'flavours/glitch/actions/server';
|
||||
|
|
|
@ -12,7 +12,6 @@ import { Blurhash } from 'flavours/glitch/components/blurhash';
|
|||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import { autoPlayGif, displayMedia, useBlurhash } from 'flavours/glitch/initial_state';
|
||||
|
||||
|
||||
export default class MediaItem extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
|
|
@ -18,7 +18,6 @@ import VolumeUpIcon from '@/material-icons/400-24px/volume_up-fill.svg?react';
|
|||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import { formatTime, getPointerPosition, fileNameFromURL } from 'flavours/glitch/features/video';
|
||||
|
||||
|
||||
import { Blurhash } from '../../components/blurhash';
|
||||
import { displayMedia, useBlurhash } from '../../initial_state';
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import { Helmet } from 'react-helmet';
|
|||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
|
||||
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
|
||||
import { domain } from 'flavours/glitch/initial_state';
|
||||
|
|
|
@ -4,14 +4,23 @@ import { uploadCompose } from '../../../actions/compose';
|
|||
import { openModal, closeModal } from '../../../actions/modal';
|
||||
import UploadButton from '../components/upload_button';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
disabled: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size + state.getIn(['compose', 'pending_media_attachments']) > 3 || state.getIn(['compose', 'media_attachments']).some(m => ['video', 'audio'].includes(m.get('type')))),
|
||||
resetFileKey: state.getIn(['compose', 'resetFileKey']),
|
||||
});
|
||||
const mapStateToProps = state => {
|
||||
const isUploading = state.getIn(['compose', 'is_uploading']);
|
||||
const readyAttachmentsSize = state.getIn(['compose', 'media_attachments']).size ?? 0;
|
||||
const pendingAttachmentsSize = state.getIn(['compose', 'pending_media_attachments']).size ?? 0;
|
||||
const attachmentsSize = readyAttachmentsSize + pendingAttachmentsSize;
|
||||
const isOverLimit = attachmentsSize > 3;
|
||||
const hasVideoOrAudio = state.getIn(['compose', 'media_attachments']).some(m => ['video', 'audio'].includes(m.get('type')));
|
||||
|
||||
return {
|
||||
disabled: isUploading || isOverLimit || hasVideoOrAudio,
|
||||
resetFileKey: state.getIn(['compose', 'resetFileKey']),
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
onSelectFile (files) {
|
||||
onSelectFile(files) {
|
||||
dispatch(uploadCompose(files));
|
||||
},
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ import { List as ImmutableList } from 'immutable';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
|
||||
import { addColumn, removeColumn, moveColumn, changeColumnParams } from 'flavours/glitch/actions/columns';
|
||||
import { fetchDirectory, expandDirectory } from 'flavours/glitch/actions/directory';
|
||||
|
|
|
@ -8,7 +8,6 @@ import { NavLink, Switch, Route } from 'react-router-dom';
|
|||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
import ExploreIcon from '@/material-icons/400-24px/explore.svg?react';
|
||||
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
|
||||
import Column from 'flavours/glitch/components/column';
|
||||
|
|
|
@ -9,7 +9,6 @@ import { List as ImmutableList } from 'immutable';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
import FindInPageIcon from '@/material-icons/400-24px/find_in_page.svg?react';
|
||||
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
|
||||
import TagIcon from '@/material-icons/400-24px/tag.svg?react';
|
||||
|
|
|
@ -180,7 +180,7 @@ class SelectFilter extends PureComponent {
|
|||
|
||||
<div className='emoji-mart-search'>
|
||||
<input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} autoFocus />
|
||||
<button className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}>{!isSearching ? loupeIcon : deleteIcon}</button>
|
||||
<button type='button' className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}>{!isSearching ? loupeIcon : deleteIcon}</button>
|
||||
</div>
|
||||
|
||||
<div className='language-dropdown__dropdown__results emoji-mart-scroll' role='listbox' ref={this.setListRef}>
|
||||
|
|
|
@ -6,7 +6,6 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
|
|||
import { Helmet } from 'react-helmet';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
|
||||
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
|
||||
import { addColumn } from 'flavours/glitch/actions/columns';
|
||||
import { changeSetting } from 'flavours/glitch/actions/settings';
|
||||
|
|
|
@ -8,7 +8,6 @@ import { Helmet } from 'react-helmet';
|
|||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
import CampaignIcon from '@/material-icons/400-24px/campaign.svg?react';
|
||||
import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
|
||||
import { fetchAnnouncements, toggleShowAnnouncements } from 'flavours/glitch/actions/announcements';
|
||||
|
|
|
@ -7,7 +7,6 @@ import { Helmet } from 'react-helmet';
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
import InfoIcon from '@/material-icons/400-24px/info.svg?react';
|
||||
import Column from 'flavours/glitch/components/column';
|
||||
import ColumnHeader from 'flavours/glitch/components/column_header';
|
||||
|
|
|
@ -11,7 +11,6 @@ import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
|||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
|
||||
|
||||
import { removeFromListAdder, addToListAdder } from '../../../actions/lists';
|
||||
import { IconButton } from '../../../components/icon_button';
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ import CancelIcon from '@/material-icons/400-24px/cancel.svg?react';
|
|||
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
|
||||
|
||||
import { fetchListSuggestions, clearListSuggestions, changeListSuggestions } from '../../../actions/lists';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
|
|
@ -222,8 +222,6 @@ class ListTimeline extends PureComponent {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<hr />
|
||||
</ColumnHeader>
|
||||
|
||||
<StatusListContainer
|
||||
|
|
|
@ -9,7 +9,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||
import { fetchLists } from 'flavours/glitch/actions/lists';
|
||||
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||
|
|
|
@ -224,6 +224,14 @@ class LocalSettingsPage extends PureComponent {
|
|||
>
|
||||
<FormattedMessage id='settings.show_content_type_choice' defaultMessage='Show content-type choice when authoring toots' />
|
||||
</LocalSettingsPageItem>
|
||||
<LocalSettingsPageItem
|
||||
settings={settings}
|
||||
item={['show_published_toast']}
|
||||
id='mastodon-settings--show_published_toast'
|
||||
onChange={onChange}
|
||||
>
|
||||
<FormattedMessage id='settings.show_published_toast' defaultMessage='Display toast when publishing/saving a post' />
|
||||
</LocalSettingsPageItem>
|
||||
<LocalSettingsPageItem
|
||||
settings={settings}
|
||||
item={['side_arm']}
|
||||
|
|
|
@ -6,7 +6,6 @@ import { FormattedMessage } from 'react-intl';
|
|||
import DeleteForeverIcon from '@/material-icons/400-24px/delete_forever.svg?react';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
|
||||
|
||||
export default class ClearColumnButton extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
|
|
@ -12,7 +12,6 @@ import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
|
|||
import StarIcon from '@/material-icons/400-24px/star.svg?react';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
|
||||
|
||||
const tooltips = defineMessages({
|
||||
mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
|
||||
favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Favorites' },
|
||||
|
|
|
@ -5,7 +5,6 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import TuneIcon from '@/material-icons/400-24px/tune.svg?react';
|
||||
import { requestBrowserPermission } from 'flavours/glitch/actions/notifications';
|
||||
|
|
|
@ -6,7 +6,6 @@ import ArrowRightAltIcon from '@/material-icons/400-24px/arrow_right_alt.svg?rea
|
|||
import CheckIcon from '@/material-icons/400-24px/done.svg?react';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
|
||||
|
||||
export const Step = ({ label, description, icon, iconComponent, completed, onClick, href, to }) => {
|
||||
const content = (
|
||||
<>
|
||||
|
|
|
@ -8,7 +8,6 @@ import { Link, Switch, Route, useHistory } from 'react-router-dom';
|
|||
import { useDispatch } from 'react-redux';
|
||||
|
||||
|
||||
|
||||
import illustration from '@/images/elephant_ui_conversation.svg';
|
||||
import AccountCircleIcon from '@/material-icons/400-24px/account_circle.svg?react';
|
||||
import ArrowRightAltIcon from '@/material-icons/400-24px/arrow_right_alt.svg?react';
|
||||
|
|
|
@ -9,7 +9,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
import OpenInNewIcon from '@/material-icons/400-24px/open_in_new.svg?react';
|
||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
|
|
|
@ -8,7 +8,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import { Avatar } from 'flavours/glitch/components/avatar';
|
||||
import { DisplayName } from 'flavours/glitch/components/display_name';
|
||||
|
|
|
@ -11,7 +11,6 @@ import { connect } from 'react-redux';
|
|||
import PushPinIcon from '@/material-icons/400-24px/push_pin.svg?react';
|
||||
import { getStatusList } from 'flavours/glitch/selectors';
|
||||
|
||||
|
||||
import { fetchPinnedStatuses } from '../../actions/pin_statuses';
|
||||
import StatusList from '../../components/status_list';
|
||||
import Column from '../ui/components/column';
|
||||
|
|
|
@ -7,7 +7,6 @@ import { Helmet } from 'react-helmet';
|
|||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
|
||||
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
|
||||
import { domain } from 'flavours/glitch/initial_state';
|
||||
|
|
|
@ -14,7 +14,6 @@ import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react';
|
|||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
|
||||
|
||||
import { fetchReblogs, expandReblogs } from '../../actions/interactions';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import { LoadingIndicator } from '../../components/loading_indicator';
|
||||
|
|
|
@ -6,7 +6,6 @@ import classNames from 'classnames';
|
|||
import CheckIcon from '@/material-icons/400-24px/done.svg?react';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
|
||||
|
||||
export default class Option extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
|
|
@ -19,6 +19,7 @@ class StatusCheckBox extends PureComponent {
|
|||
status: ImmutablePropTypes.map.isRequired,
|
||||
checked: PropTypes.bool,
|
||||
onToggle: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleStatusesToggle = (value, checked) => {
|
||||
|
|
|
@ -18,8 +18,8 @@ import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
|
|||
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
||||
import StarBorderIcon from '@/material-icons/400-24px/star.svg?react';
|
||||
import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react';
|
||||
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg';
|
||||
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg';
|
||||
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
|
||||
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
|
||||
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react';
|
||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
|
||||
import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links';
|
||||
|
|
|
@ -8,7 +8,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import { followAccount } from 'flavours/glitch/actions/accounts';
|
||||
import { Button } from 'flavours/glitch/components/button';
|
||||
|
|
|
@ -58,6 +58,7 @@ const TabsBarPortal = () => {
|
|||
export default class ColumnsArea extends ImmutablePureComponent {
|
||||
static propTypes = {
|
||||
columns: ImmutablePropTypes.list.isRequired,
|
||||
isModalOpen: PropTypes.bool.isRequired,
|
||||
singleColumn: PropTypes.bool,
|
||||
children: PropTypes.node,
|
||||
openSettings: PropTypes.func,
|
||||
|
@ -145,7 +146,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { columns, children, singleColumn, openSettings } = this.props;
|
||||
const { columns, children, singleColumn, isModalOpen, openSettings } = this.props;
|
||||
const { renderComposePanel } = this.state;
|
||||
|
||||
if (singleColumn) {
|
||||
|
@ -172,7 +173,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className='columns-area' ref={this.setRef}>
|
||||
<div className={`columns-area ${ isModalOpen ? 'unscrollable' : '' }`} ref={this.setRef}>
|
||||
{columns.map(column => {
|
||||
const params = column.get('params', null) === null ? null : column.get('params').toJS();
|
||||
const other = params && params.other ? params.other : {};
|
||||
|
|
|
@ -4,7 +4,6 @@ import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
|||
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import api from 'flavours/glitch/api';
|
||||
import { IconButton } from 'flavours/glitch/components/icon_button';
|
||||
|
|
|
@ -5,7 +5,6 @@ import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import { fetchFilters, createFilter, createFilterStatus } from 'flavours/glitch/actions/filters';
|
||||
import { fetchStatus } from 'flavours/glitch/actions/statuses';
|
||||
|
|
|
@ -181,14 +181,14 @@ class FocalPointModal extends ImmutablePureComponent {
|
|||
|
||||
handleKeyDown = (e) => {
|
||||
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.onChangeDescription(e.target.value);
|
||||
this.handleSubmit();
|
||||
this.handleSubmit(e);
|
||||
}
|
||||
};
|
||||
|
||||
handleSubmit = () => {
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.onSave(this.props.description, this.props.focusX, this.props.focusY);
|
||||
};
|
||||
|
||||
|
@ -318,7 +318,7 @@ class FocalPointModal extends ImmutablePureComponent {
|
|||
</div>
|
||||
|
||||
<div className='report-modal__container'>
|
||||
<div className='report-modal__comment'>
|
||||
<form className='report-modal__comment' onSubmit={this.handleSubmit} >
|
||||
{focals && <p><FormattedMessage id='upload_modal.hint' defaultMessage='Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.' /></p>}
|
||||
|
||||
{thumbnailable && (
|
||||
|
@ -367,12 +367,23 @@ class FocalPointModal extends ImmutablePureComponent {
|
|||
</div>
|
||||
|
||||
<div className='setting-text__toolbar'>
|
||||
<button disabled={detecting || media.get('type') !== 'image' || is_changing_upload} className='link-button' onClick={this.handleTextDetection}><FormattedMessage id='upload_modal.detect_text' defaultMessage='Detect text from picture' /></button>
|
||||
<button
|
||||
type='button'
|
||||
disabled={detecting || media.get('type') !== 'image' || is_changing_upload}
|
||||
className='link-button'
|
||||
onClick={this.handleTextDetection}
|
||||
>
|
||||
<FormattedMessage id='upload_modal.detect_text' defaultMessage='Detect text from picture' />
|
||||
</button>
|
||||
<CharacterCounter max={1500} text={detecting ? '' : description} />
|
||||
</div>
|
||||
|
||||
<Button disabled={!dirty || detecting || isUploadingThumbnail || length(description) > 1500 || is_changing_upload} text={intl.formatMessage(is_changing_upload ? messages.applying : messages.apply)} onClick={this.handleSubmit} />
|
||||
</div>
|
||||
<Button
|
||||
type='submit'
|
||||
disabled={!dirty || detecting || isUploadingThumbnail || length(description) > 1500 || is_changing_upload}
|
||||
text={intl.formatMessage(is_changing_upload ? messages.applying : messages.apply)}
|
||||
/>
|
||||
</form>
|
||||
|
||||
<div className='focal-point-modal__content'>
|
||||
{focals && (
|
||||
|
|
|
@ -3,6 +3,7 @@ import { connect } from 'react-redux';
|
|||
import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?react';
|
||||
import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge';
|
||||
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
count: state.getIn(['local_settings', 'notifications', 'tab_badge']) ? state.getIn(['notifications', 'unread']) : 0,
|
||||
id: 'bell',
|
||||
|
|
|
@ -6,6 +6,7 @@ import ColumnsArea from '../components/columns_area';
|
|||
|
||||
const mapStateToProps = state => ({
|
||||
columns: state.getIn(['settings', 'columns']),
|
||||
isModalOpen: !!state.get('modal').modalType,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
export function EmojiPicker () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/emoji_picker" */'flavours/glitch/features/emoji/emoji_picker');
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/emoji_picker" */'../../emoji/emoji_picker');
|
||||
}
|
||||
|
||||
export function Compose () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/compose" */'flavours/glitch/features/compose');
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/compose" */'../../compose');
|
||||
}
|
||||
|
||||
export function Notifications () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/notifications" */'flavours/glitch/features/notifications');
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/notifications" */'../../notifications');
|
||||
}
|
||||
|
||||
export function HomeTimeline () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/home_timeline" */'flavours/glitch/features/home_timeline');
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/home_timeline" */'../../home_timeline');
|
||||
}
|
||||
|
||||
export function PublicTimeline () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/public_timeline" */'flavours/glitch/features/public_timeline');
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/public_timeline" */'../../public_timeline');
|
||||
}
|
||||
|
||||
export function CommunityTimeline () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/community_timeline" */'flavours/glitch/features/community_timeline');
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/community_timeline" */'../../community_timeline');
|
||||
}
|
||||
|
||||
export function Firehose () {
|
||||
|
@ -27,177 +27,177 @@ export function Firehose () {
|
|||
}
|
||||
|
||||
export function HashtagTimeline () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/hashtag_timeline" */'flavours/glitch/features/hashtag_timeline');
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/hashtag_timeline" */'../../hashtag_timeline');
|
||||
}
|
||||
|
||||
export function ListTimeline () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/list_timeline" */'flavours/glitch/features/list_timeline');
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/list_timeline" */'../../list_timeline');
|
||||
}
|
||||
|
||||
export function Lists () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/lists" */'flavours/glitch/features/lists');
|
||||
}
|
||||
|
||||
export function ListEditor () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/list_editor" */'flavours/glitch/features/list_editor');
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/lists" */'../../lists');
|
||||
}
|
||||
|
||||
export function PinnedAccountsEditor () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/pinned_accounts_editor" */'flavours/glitch/features/pinned_accounts_editor');
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/pinned_accounts_editor" */'../../pinned_accounts_editor');
|
||||
}
|
||||
|
||||
export function DirectTimeline() {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/direct_timeline" */'flavours/glitch/features/direct_timeline');
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/direct_timeline" */'../../direct_timeline');
|
||||
}
|
||||
|
||||
export function Status () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/status" */'flavours/glitch/features/status');
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/status" */'../../status');
|
||||
}
|
||||
|
||||
export function GettingStarted () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/getting_started" */'flavours/glitch/features/getting_started');
|
||||
}
|
||||
|
||||
export function KeyboardShortcuts () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/keyboard_shortcuts" */'flavours/glitch/features/keyboard_shortcuts');
|
||||
}
|
||||
|
||||
export function PinnedStatuses () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/pinned_statuses" */'flavours/glitch/features/pinned_statuses');
|
||||
}
|
||||
|
||||
export function AccountTimeline () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/account_timeline" */'flavours/glitch/features/account_timeline');
|
||||
}
|
||||
|
||||
export function AccountGallery () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/account_gallery" */'flavours/glitch/features/account_gallery');
|
||||
}
|
||||
|
||||
export function Followers () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/followers" */'flavours/glitch/features/followers');
|
||||
}
|
||||
|
||||
export function Following () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/following" */'flavours/glitch/features/following');
|
||||
}
|
||||
|
||||
export function Reblogs () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/reblogs" */'flavours/glitch/features/reblogs');
|
||||
}
|
||||
|
||||
export function Favourites () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/favourites" */'flavours/glitch/features/favourites');
|
||||
}
|
||||
|
||||
export function FollowRequests () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/follow_requests" */'flavours/glitch/features/follow_requests');
|
||||
}
|
||||
|
||||
export function FavouritedStatuses () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/favourited_statuses" */'flavours/glitch/features/favourited_statuses');
|
||||
}
|
||||
|
||||
export function FollowedTags () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/followed_tags" */'flavours/glitch/features/followed_tags');
|
||||
}
|
||||
|
||||
export function BookmarkedStatuses () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/bookmarked_statuses" */'flavours/glitch/features/bookmarked_statuses');
|
||||
}
|
||||
|
||||
export function Blocks () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/blocks" */'flavours/glitch/features/blocks');
|
||||
}
|
||||
|
||||
export function DomainBlocks () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/domain_blocks" */'flavours/glitch/features/domain_blocks');
|
||||
}
|
||||
|
||||
export function Mutes () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/mutes" */'flavours/glitch/features/mutes');
|
||||
}
|
||||
|
||||
export function MuteModal () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/mute_modal" */'flavours/glitch/features/ui/components/mute_modal');
|
||||
}
|
||||
|
||||
export function BlockModal () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/block_modal" */'flavours/glitch/features/ui/components/block_modal');
|
||||
}
|
||||
|
||||
export function ReportModal () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/report_modal" */'flavours/glitch/features/ui/components/report_modal');
|
||||
}
|
||||
|
||||
export function SettingsModal () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/settings_modal" */'flavours/glitch/features/local_settings');
|
||||
}
|
||||
|
||||
export function MediaGallery () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/media_gallery" */'flavours/glitch/components/media_gallery');
|
||||
}
|
||||
|
||||
export function Video () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/video" */'flavours/glitch/features/video');
|
||||
}
|
||||
|
||||
export function Audio () {
|
||||
return import(/* webpackChunkName: "features/glitch/async/audio" */'flavours/glitch/features/audio');
|
||||
}
|
||||
|
||||
export function EmbedModal () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/embed_modal" */'flavours/glitch/features/ui/components/embed_modal');
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/getting_started" */'../../getting_started');
|
||||
}
|
||||
|
||||
export function GettingStartedMisc () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/getting_started_misc" */'flavours/glitch/features/getting_started_misc');
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/getting_started_misc" */'../../getting_started_misc');
|
||||
}
|
||||
|
||||
export function KeyboardShortcuts () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/keyboard_shortcuts" */'../../keyboard_shortcuts');
|
||||
}
|
||||
|
||||
export function PinnedStatuses () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/pinned_statuses" */'../../pinned_statuses');
|
||||
}
|
||||
|
||||
export function AccountTimeline () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/account_timeline" */'../../account_timeline');
|
||||
}
|
||||
|
||||
export function AccountGallery () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/account_gallery" */'../../account_gallery');
|
||||
}
|
||||
|
||||
export function Followers () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/followers" */'../../followers');
|
||||
}
|
||||
|
||||
export function Following () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/following" */'../../following');
|
||||
}
|
||||
|
||||
export function Reblogs () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/reblogs" */'../../reblogs');
|
||||
}
|
||||
|
||||
export function Favourites () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/favourites" */'../../favourites');
|
||||
}
|
||||
|
||||
export function FollowRequests () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/follow_requests" */'../../follow_requests');
|
||||
}
|
||||
|
||||
export function FavouritedStatuses () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/favourited_statuses" */'../../favourited_statuses');
|
||||
}
|
||||
|
||||
export function FollowedTags () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/followed_tags" */'../../followed_tags');
|
||||
}
|
||||
|
||||
export function BookmarkedStatuses () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/bookmarked_statuses" */'../../bookmarked_statuses');
|
||||
}
|
||||
|
||||
export function Blocks () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/blocks" */'../../blocks');
|
||||
}
|
||||
|
||||
export function DomainBlocks () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/domain_blocks" */'../../domain_blocks');
|
||||
}
|
||||
|
||||
export function Mutes () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/mutes" */'../../mutes');
|
||||
}
|
||||
|
||||
export function MuteModal () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/mute_modal" */'../components/mute_modal');
|
||||
}
|
||||
|
||||
export function BlockModal () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/block_modal" */'../components/block_modal');
|
||||
}
|
||||
|
||||
export function ReportModal () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/report_modal" */'../components/report_modal');
|
||||
}
|
||||
|
||||
export function SettingsModal () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/settings_modal" */'../../local_settings');
|
||||
}
|
||||
|
||||
export function MediaGallery () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/media_gallery" */'../../../components/media_gallery');
|
||||
}
|
||||
|
||||
export function Video () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/video" */'../../video');
|
||||
}
|
||||
|
||||
export function EmbedModal () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/embed_modal" */'../components/embed_modal');
|
||||
}
|
||||
|
||||
export function ListEditor () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/list_editor" */'../../list_editor');
|
||||
}
|
||||
|
||||
export function ListAdder () {
|
||||
return import(/* webpackChunkName: "features/glitch/async/list_adder" */'flavours/glitch/features/list_adder');
|
||||
return import(/* webpackChunkName: "features/glitch/async/list_adder" */'../../list_adder');
|
||||
}
|
||||
|
||||
export function Tesseract () {
|
||||
return import(/*webpackChunkName: "tesseract" */'tesseract.js');
|
||||
}
|
||||
|
||||
export function Audio () {
|
||||
return import(/* webpackChunkName: "features/glitch/async/audio" */'../../audio');
|
||||
}
|
||||
|
||||
export function Directory () {
|
||||
return import(/* webpackChunkName: "features/glitch/async/directory" */'flavours/glitch/features/directory');
|
||||
return import(/* webpackChunkName: "features/glitch/async/directory" */'../../directory');
|
||||
}
|
||||
|
||||
export function Onboarding () {
|
||||
return import(/* webpackChunkName: "features/glitch/async/onboarding" */'flavours/glitch/features/onboarding');
|
||||
return import(/* webpackChunkName: "features/glitch/async/onboarding" */'../../onboarding');
|
||||
}
|
||||
|
||||
export function CompareHistoryModal () {
|
||||
return import(/*webpackChunkName: "flavours/glitch/async/compare_history_modal" */'flavours/glitch/features/ui/components/compare_history_modal');
|
||||
}
|
||||
|
||||
export function FilterModal () {
|
||||
return import(/*webpackChunkName: "flavours/glitch/async/filter_modal" */'flavours/glitch/features/ui/components/filter_modal');
|
||||
return import(/*webpackChunkName: "flavours/glitch/async/compare_history_modal" */'../components/compare_history_modal');
|
||||
}
|
||||
|
||||
export function Explore () {
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/explore" */'flavours/glitch/features/explore');
|
||||
return import(/* webpackChunkName: "flavours/glitch/async/explore" */'../../explore');
|
||||
}
|
||||
|
||||
export function FilterModal () {
|
||||
return import(/*webpackChunkName: "flavours/glitch/async/filter_modal" */'../components/filter_modal');
|
||||
}
|
||||
|
||||
export function InteractionModal () {
|
||||
return import(/*webpackChunkName: "flavours/glitch/async/modals/interaction_modal" */'flavours/glitch/features/interaction_modal');
|
||||
return import(/*webpackChunkName: "flavours/glitch/async/modals/interaction_modal" */'../../interaction_modal');
|
||||
}
|
||||
|
||||
export function SubscribedLanguagesModal () {
|
||||
return import(/*webpackChunkName: "flavours/glitch/async/modals/subscribed_languages_modal" */'flavours/glitch/features/subscribed_languages_modal');
|
||||
return import(/*webpackChunkName: "flavours/glitch/async/modals/subscribed_languages_modal" */'../../subscribed_languages_modal');
|
||||
}
|
||||
|
||||
export function ClosedRegistrationsModal () {
|
||||
return import(/*webpackChunkName: "flavours/glitch/async/modals/closed_registrations_modal" */'flavours/glitch/features/closed_registrations_modal');
|
||||
return import(/*webpackChunkName: "flavours/glitch/async/modals/closed_registrations_modal" */'../../closed_registrations_modal');
|
||||
}
|
||||
|
||||
export function About () {
|
||||
return import(/*webpackChunkName: "features/glitch/async/about" */'flavours/glitch/features/about');
|
||||
return import(/*webpackChunkName: "features/glitch/async/about" */'../../about');
|
||||
}
|
||||
|
||||
export function PrivacyPolicy () {
|
||||
return import(/*webpackChunkName: "features/glitch/async/privacy_policy" */'flavours/glitch/features/privacy_policy');
|
||||
return import(/*webpackChunkName: "features/glitch/async/privacy_policy" */'../../privacy_policy');
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 8.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 193 KiB |
BIN
app/javascript/flavours/glitch/images/glitch-preview.png
Normal file
BIN
app/javascript/flavours/glitch/images/glitch-preview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 122 KiB |
|
@ -47,20 +47,11 @@
|
|||
* @property {string} version
|
||||
* @property {number} visible_reactions
|
||||
* @property {string} sso_redirect
|
||||
* @property {boolean} translation_enabled
|
||||
* @property {string} status_page_url
|
||||
* @property {boolean} system_emoji_font
|
||||
* @property {string} default_content_type
|
||||
*/
|
||||
|
||||
/** @type {string} */
|
||||
const initialPath = document.querySelector("head meta[name=initialPath]")?.getAttribute("content") ?? '';
|
||||
/** @type {boolean} */
|
||||
export const hasMultiColumnPath = initialPath === '/'
|
||||
|| initialPath === '/getting-started'
|
||||
|| initialPath === '/home'
|
||||
|| initialPath.startsWith('/deck');
|
||||
|
||||
/**
|
||||
* @typedef InitialState
|
||||
* @property {Record<string, import("./api_types/accounts").ApiAccountJSON>} accounts
|
||||
|
@ -77,6 +68,14 @@ const element = document.getElementById('initial-state');
|
|||
/** @type {InitialState | undefined} */
|
||||
const initialState = element?.textContent && JSON.parse(element.textContent);
|
||||
|
||||
/** @type {string} */
|
||||
const initialPath = document.querySelector("head meta[name=initialPath]")?.getAttribute("content") ?? '';
|
||||
/** @type {boolean} */
|
||||
export const hasMultiColumnPath = initialPath === '/'
|
||||
|| initialPath === '/getting-started'
|
||||
|| initialPath === '/home'
|
||||
|| initialPath.startsWith('/deck');
|
||||
|
||||
// Glitch-soc-specific “local settings”
|
||||
if (initialState) {
|
||||
try {
|
||||
|
|
|
@ -131,6 +131,7 @@
|
|||
"settings.shared_settings_link": "user preferences",
|
||||
"settings.show_action_bar": "Show action buttons in collapsed toots",
|
||||
"settings.show_content_type_choice": "Show content-type choice when authoring toots",
|
||||
"settings.show_published_toast": "Display toast when publishing/saving a post",
|
||||
"settings.show_reply_counter": "Display an estimate of the reply count",
|
||||
"settings.side_arm": "Secondary toot button:",
|
||||
"settings.side_arm.none": "None",
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
{
|
||||
"compose.attach.doodle": "Ssuneɣ-d kra",
|
||||
"navigation_bar.keyboard_shortcuts": "Inezgumen n unasiw",
|
||||
"settings.auto_collapse_notifications": "Ilɣa",
|
||||
"settings.close": "Mdel",
|
||||
"settings.content_warnings": "Content warnings",
|
||||
"settings.preferences": "Preferences"
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
"column_subheading.lists": "리스트",
|
||||
"column_subheading.navigation": "탐색",
|
||||
"community.column_settings.allow_local_only": "로컬 전용 글 보기",
|
||||
"compose.attach.doodle": "뭔가 그려보세요",
|
||||
"compose.change_federation": "연합 설정 변경",
|
||||
"compose.content-type.change": "고급 양식 옵션 변경",
|
||||
"compose.content-type.html": "HTML",
|
||||
|
@ -22,6 +23,8 @@
|
|||
"compose.content-type.markdown_meta": "게시물을 마크다운 형식으로 구성",
|
||||
"compose.content-type.plain": "일반 텍스트",
|
||||
"compose.content-type.plain_meta": "고급 양식 없이 작성",
|
||||
"compose.disable_threaded_mode": "글타래 모드 비활성화",
|
||||
"compose.enable_threaded_mode": "글타래 모드 활성화",
|
||||
"confirmation_modal.do_not_ask_again": "다음부터 확인창을 띄우지 않기",
|
||||
"confirmations.deprecated_settings.confirm": "마스토돈 설정 사용",
|
||||
"confirmations.deprecated_settings.message": "사용하던 몇몇 기기별 글리치 {app_settings}은 마스토돈 {preferences}으로 대체되었습니다:",
|
||||
|
|
|
@ -61,6 +61,7 @@ const initialState = ImmutableMap({
|
|||
media: true,
|
||||
visibility: true,
|
||||
}),
|
||||
show_published_toast: true,
|
||||
});
|
||||
|
||||
const hydrate = (state, localSettings) => state.mergeDeep(localSettings);
|
||||
|
|
|
@ -12,9 +12,6 @@ import { uuid } from '../uuid';
|
|||
const initialState = ImmutableMap({
|
||||
saved: true,
|
||||
|
||||
onboarded: false,
|
||||
layout: 'auto',
|
||||
|
||||
skinTone: 1,
|
||||
|
||||
trends: ImmutableMap({
|
||||
|
|
|
@ -1334,19 +1334,19 @@ body > [data-popper-placement] {
|
|||
}
|
||||
|
||||
.status__content__spoiler-link {
|
||||
display: inline-flex;
|
||||
display: inline-flex; // glitch: media icon in spoiler button
|
||||
border-radius: 2px;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: $inverted-text-color;
|
||||
font-weight: 700;
|
||||
font-size: 11px;
|
||||
padding: 0 5px;
|
||||
padding: 0 6px;
|
||||
text-transform: uppercase;
|
||||
line-height: inherit;
|
||||
line-height: 20px;
|
||||
cursor: pointer;
|
||||
vertical-align: top;
|
||||
align-items: center;
|
||||
align-items: center; // glitch: content indicator
|
||||
|
||||
&:hover {
|
||||
background: lighten($ui-base-color, 33%);
|
||||
|
@ -1662,6 +1662,7 @@ body > [data-popper-placement] {
|
|||
font-size: 15px;
|
||||
padding-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: start; // glitch: changed because of our different layout
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
|
@ -1878,7 +1879,12 @@ body > [data-popper-placement] {
|
|||
.media-gallery,
|
||||
.video-player,
|
||||
.audio-player {
|
||||
margin-top: 8px;
|
||||
margin-top: 8px; // glitch: reduced margins
|
||||
}
|
||||
|
||||
.status__prepend {
|
||||
padding: 0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2082,8 +2088,6 @@ body > [data-popper-placement] {
|
|||
position: relative;
|
||||
|
||||
& > div {
|
||||
@include avatar-radius;
|
||||
|
||||
float: left;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
|
@ -2235,7 +2239,7 @@ a.account__display-name {
|
|||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: lighten($ui-base-color, 29%);
|
||||
background: lighten($ui-base-lighter-color, 7%);
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
@ -2635,6 +2639,10 @@ a.account__display-name {
|
|||
overflow-x: auto;
|
||||
position: relative;
|
||||
|
||||
&.unscrollable {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
&__panels {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -4717,35 +4725,6 @@ a.status-card {
|
|||
z-index: 100;
|
||||
}
|
||||
|
||||
.media-spoiler {
|
||||
background: $base-overlay-background;
|
||||
color: $darker-text-color;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
appearance: none;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
padding: 0;
|
||||
color: lighten($darker-text-color, 8%);
|
||||
}
|
||||
}
|
||||
|
||||
.media-spoiler__warning {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.media-spoiler__trigger {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.spoiler-button {
|
||||
top: 0;
|
||||
inset-inline-start: 0;
|
||||
|
@ -4755,12 +4734,11 @@ a.status-card {
|
|||
z-index: 100;
|
||||
|
||||
&--minified {
|
||||
display: flex; // glitch: media icon in spoiler button
|
||||
display: block;
|
||||
inset-inline-start: 4px;
|
||||
top: 4px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
align-items: center; // glitch: media icon in spoiler button
|
||||
}
|
||||
|
||||
&--click-thru {
|
||||
|
@ -6668,8 +6646,6 @@ a.status-card {
|
|||
}
|
||||
}
|
||||
|
||||
& > .react-toggle,
|
||||
& > .icon,
|
||||
button:first-child {
|
||||
margin-inline-end: 10px;
|
||||
}
|
||||
|
|
|
@ -210,12 +210,6 @@ html {
|
|||
}
|
||||
}
|
||||
|
||||
// Change the background colors of media and video spoilers
|
||||
.media-spoiler,
|
||||
.video-player__spoiler {
|
||||
background: $ui-base-color;
|
||||
}
|
||||
|
||||
.account-gallery__item a {
|
||||
background-color: $ui-base-color;
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ inherit_locales: vanilla
|
|||
|
||||
# (OPTIONAL) A file to use as the preview screenshot for the flavour,
|
||||
# or an array thereof. These are the full path from `app/javascript/`.
|
||||
screenshot: flavours/glitch/images/glitch-preview.jpg
|
||||
screenshot: flavours/glitch/images/glitch-preview.png
|
||||
|
||||
# (OPTIONAL) The directory which contains the pack files.
|
||||
# Defaults to the theme directory (`app/javascript/themes/[theme]`),
|
||||
|
|
|
@ -28,7 +28,7 @@ locales: ../../mastodon/locales
|
|||
|
||||
# (OPTIONAL) A file to use as the preview screenshot for the flavour,
|
||||
# or an array thereof. These are the full path from `app/javascript/`.
|
||||
screenshot: images/screenshot.jpg
|
||||
screenshot: images/screenshot.png
|
||||
|
||||
# (OPTIONAL) The directory which contains the pack files.
|
||||
# Defaults to this directory (`app/javascript/flavour/[flavour]`),
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 234 KiB |
BIN
app/javascript/images/screenshot.png
Normal file
BIN
app/javascript/images/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 155 KiB |
|
@ -3,14 +3,24 @@ import { connect } from 'react-redux';
|
|||
import { uploadCompose } from '../../../actions/compose';
|
||||
import UploadButton from '../components/upload_button';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
disabled: state.getIn(['compose', 'poll']) !== null || state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size + state.getIn(['compose', 'pending_media_attachments']) > 3 || state.getIn(['compose', 'media_attachments']).some(m => ['video', 'audio'].includes(m.get('type')))),
|
||||
resetFileKey: state.getIn(['compose', 'resetFileKey']),
|
||||
});
|
||||
const mapStateToProps = state => {
|
||||
const isPoll = state.getIn(['compose', 'poll']) !== null;
|
||||
const isUploading = state.getIn(['compose', 'is_uploading']);
|
||||
const readyAttachmentsSize = state.getIn(['compose', 'media_attachments']).size ?? 0;
|
||||
const pendingAttachmentsSize = state.getIn(['compose', 'pending_media_attachments']).size ?? 0;
|
||||
const attachmentsSize = readyAttachmentsSize + pendingAttachmentsSize;
|
||||
const isOverLimit = attachmentsSize > 3;
|
||||
const hasVideoOrAudio = state.getIn(['compose', 'media_attachments']).some(m => ['video', 'audio'].includes(m.get('type')));
|
||||
|
||||
return {
|
||||
disabled: isPoll || isUploading || isOverLimit || hasVideoOrAudio,
|
||||
resetFileKey: state.getIn(['compose', 'resetFileKey']),
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
onSelectFile (files) {
|
||||
onSelectFile(files) {
|
||||
dispatch(uploadCompose(files));
|
||||
},
|
||||
|
||||
|
|
|
@ -2,31 +2,15 @@
|
|||
|
||||
class AccountSuggestions::FriendsOfFriendsSource < AccountSuggestions::Source
|
||||
def get(account, limit: DEFAULT_LIMIT)
|
||||
Account.find_by_sql([<<~SQL.squish, { id: account.id, limit: limit }]).map { |row| [row.id, key] }
|
||||
WITH first_degree AS (
|
||||
SELECT target_account_id
|
||||
FROM follows
|
||||
JOIN accounts AS target_accounts ON follows.target_account_id = target_accounts.id
|
||||
WHERE account_id = :id
|
||||
AND NOT target_accounts.hide_collections
|
||||
)
|
||||
SELECT accounts.id, COUNT(*) AS frequency
|
||||
FROM accounts
|
||||
JOIN follows ON follows.target_account_id = accounts.id
|
||||
JOIN account_stats ON account_stats.account_id = accounts.id
|
||||
LEFT OUTER JOIN follow_recommendation_mutes ON follow_recommendation_mutes.target_account_id = accounts.id AND follow_recommendation_mutes.account_id = :id
|
||||
WHERE follows.account_id IN (SELECT * FROM first_degree)
|
||||
AND NOT EXISTS (SELECT 1 FROM follows f WHERE f.target_account_id = follows.target_account_id AND f.account_id = :id)
|
||||
AND follows.target_account_id <> :id
|
||||
AND accounts.discoverable
|
||||
AND accounts.suspended_at IS NULL
|
||||
AND accounts.silenced_at IS NULL
|
||||
AND accounts.moved_to_account_id IS NULL
|
||||
AND follow_recommendation_mutes.target_account_id IS NULL
|
||||
GROUP BY accounts.id, account_stats.id
|
||||
ORDER BY frequency DESC, account_stats.followers_count ASC
|
||||
LIMIT :limit
|
||||
SQL
|
||||
first_degree = account.following.where.not(hide_collections: true).select(:id).reorder(nil)
|
||||
base_account_scope(account)
|
||||
.joins(:account_stat)
|
||||
.where(id: Follow.where(account_id: first_degree).select(:target_account_id))
|
||||
.group('accounts.id, account_stats.id')
|
||||
.reorder('frequency DESC, followers_count DESC')
|
||||
.limit(limit)
|
||||
.pluck(Arel.sql('accounts.id, COUNT(*) AS frequency'))
|
||||
.map { |id, _frequency| [id, key] }
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -51,7 +51,8 @@ class AccountSuggestions::SimilarProfilesSource < AccountSuggestions::Source
|
|||
recently_followed_account_ids = account.active_relationships.recent.limit(5).pluck(:target_account_id)
|
||||
|
||||
if Chewy.enabled? && !recently_followed_account_ids.empty?
|
||||
QueryBuilder.new(recently_followed_account_ids, account).build.limit(limit).hits.pluck('_id').map(&:to_i).zip([key].cycle)
|
||||
ids_from_es = QueryBuilder.new(recently_followed_account_ids, account).build.limit(limit).hits.pluck('_id').map(&:to_i)
|
||||
base_account_scope(account).where(id: ids_from_es).pluck(:id).zip([key].cycle)
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
|
|
@ -12,6 +12,8 @@ class AccountSuggestions::Source
|
|||
def base_account_scope(account)
|
||||
Account
|
||||
.searchable
|
||||
.where(discoverable: true)
|
||||
.without_silenced
|
||||
.where.not(follows_sql, id: account.id)
|
||||
.where.not(follow_requests_sql, id: account.id)
|
||||
.not_excluded_by_account(account)
|
||||
|
|
|
@ -38,7 +38,6 @@ class Form::AdminSettings
|
|||
noindex
|
||||
outgoing_spoilers
|
||||
require_invite_text
|
||||
captcha_enabled
|
||||
media_cache_retention_period
|
||||
content_cache_retention_period
|
||||
backups_retention_period
|
||||
|
|
20
bin/dev
Executable file
20
bin/dev
Executable file
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
# Default to port 3000 if not specified
|
||||
export PORT="${PORT:-3000}"
|
||||
|
||||
# Get around our boot.rb ENV check
|
||||
export RAILS_ENV="${RAILS_ENV:-development}"
|
||||
|
||||
if command -v overmind &> /dev/null
|
||||
then
|
||||
overmind start -f Procfile.dev "$@"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
if gem list --no-installed --exact --silent foreman; then
|
||||
echo "Installing foreman..."
|
||||
gem install foreman
|
||||
fi
|
||||
|
||||
foreman start -f Procfile.dev "$@"
|
|
@ -42,6 +42,7 @@ Rails.application.configure do
|
|||
:hu,
|
||||
:hy,
|
||||
:id,
|
||||
:ie,
|
||||
:ig,
|
||||
:io,
|
||||
:is,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@mastodon/mastodon",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"packageManager": "yarn@4.1.0",
|
||||
"packageManager": "yarn@4.1.1",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AccountSuggestions::FriendsOfFriendsSource do
|
||||
describe '#get' do
|
||||
subject { described_class.new }
|
||||
|
||||
let!(:bob) { Fabricate(:account, discoverable: true, hide_collections: false) }
|
||||
let!(:alice) { Fabricate(:account, discoverable: true, hide_collections: true) }
|
||||
let!(:eve) { Fabricate(:account, discoverable: true, hide_collections: false) }
|
||||
let!(:mallory) { Fabricate(:account, discoverable: false, hide_collections: false) }
|
||||
let!(:eugen) { Fabricate(:account, discoverable: true, hide_collections: false) }
|
||||
let!(:john) { Fabricate(:account, discoverable: true, hide_collections: false) }
|
||||
let!(:jerk) { Fabricate(:account, discoverable: true, hide_collections: false) }
|
||||
let!(:neil) { Fabricate(:account, discoverable: true, hide_collections: false) }
|
||||
let!(:larry) { Fabricate(:account, discoverable: true, hide_collections: false) }
|
||||
|
||||
context 'with follows and blocks' do
|
||||
before do
|
||||
bob.block!(jerk)
|
||||
FollowRecommendationMute.create!(account: bob, target_account: neil)
|
||||
|
||||
# bob follows eugen, alice and larry
|
||||
[eugen, alice, larry].each { |account| bob.follow!(account) }
|
||||
|
||||
# alice follows eve and mallory
|
||||
[john, mallory].each { |account| alice.follow!(account) }
|
||||
|
||||
# eugen follows eve, john, jerk, larry and neil
|
||||
[eve, mallory, jerk, larry, neil].each { |account| eugen.follow!(account) }
|
||||
end
|
||||
|
||||
it 'returns eligible accounts', :aggregate_failures do
|
||||
results = subject.get(bob)
|
||||
|
||||
# eve is returned through eugen
|
||||
expect(results).to include([eve.id, :friends_of_friends])
|
||||
|
||||
# john is not reachable because alice hides who she follows
|
||||
expect(results).to_not include([john.id, :friends_of_friends])
|
||||
|
||||
# mallory is not discoverable
|
||||
expect(results).to_not include([mallory.id, :friends_of_friends])
|
||||
|
||||
# larry is not included because he's followed already
|
||||
expect(results).to_not include([larry.id, :friends_of_friends])
|
||||
|
||||
# jerk is blocked
|
||||
expect(results).to_not include([jerk.id, :friends_of_friends])
|
||||
|
||||
# the suggestion for neil has already been rejected
|
||||
expect(results).to_not include([neil.id, :friends_of_friends])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with deterministic order' do
|
||||
before do
|
||||
# bob follows eve and mallory
|
||||
[eve, mallory].each { |account| bob.follow!(account) }
|
||||
|
||||
# eve follows eugen, john, and jerk
|
||||
[jerk, eugen, john].each { |account| eve.follow!(account) }
|
||||
|
||||
# mallory follows eugen, john, and neil
|
||||
[neil, eugen, john].each { |account| mallory.follow!(account) }
|
||||
|
||||
john.follow!(eugen)
|
||||
john.follow!(neil)
|
||||
end
|
||||
|
||||
it 'returns eligible accounts in the expected order' do
|
||||
expect(subject.get(bob)).to eq [
|
||||
[eugen.id, :friends_of_friends], # followed by 2 friends, 3 followers total
|
||||
[john.id, :friends_of_friends], # followed by 2 friends, 2 followers total
|
||||
[neil.id, :friends_of_friends], # followed by 1 friend, 2 followers total
|
||||
[jerk.id, :friends_of_friends], # followed by 1 friend, 1 follower total
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -11,14 +11,16 @@ RSpec.describe AccountSuggestions::Source do
|
|||
end
|
||||
|
||||
context 'with follows and follow requests' do
|
||||
let!(:account_domain_blocked_account) { Fabricate(:account, domain: 'blocked.host') }
|
||||
let!(:account) { Fabricate(:account) }
|
||||
let!(:blocked_account) { Fabricate(:account) }
|
||||
let!(:eligible_account) { Fabricate(:account) }
|
||||
let!(:follow_recommendation_muted_account) { Fabricate(:account) }
|
||||
let!(:follow_requested_account) { Fabricate(:account) }
|
||||
let!(:following_account) { Fabricate(:account) }
|
||||
let!(:moved_account) { Fabricate(:account, moved_to_account: Fabricate(:account)) }
|
||||
let!(:account_domain_blocked_account) { Fabricate(:account, domain: 'blocked.host', discoverable: true) }
|
||||
let!(:account) { Fabricate(:account, discoverable: true) }
|
||||
let!(:blocked_account) { Fabricate(:account, discoverable: true) }
|
||||
let!(:eligible_account) { Fabricate(:account, discoverable: true) }
|
||||
let!(:follow_recommendation_muted_account) { Fabricate(:account, discoverable: true) }
|
||||
let!(:follow_requested_account) { Fabricate(:account, discoverable: true) }
|
||||
let!(:following_account) { Fabricate(:account, discoverable: true) }
|
||||
let!(:moved_account) { Fabricate(:account, moved_to_account: Fabricate(:account), discoverable: true) }
|
||||
let!(:silenced_account) { Fabricate(:account, silenced: true, discoverable: true) }
|
||||
let!(:undiscoverable_account) { Fabricate(:account, discoverable: false) }
|
||||
|
||||
before do
|
||||
Fabricate :account_domain_block, account: account, domain: account_domain_blocked_account.domain
|
||||
|
@ -40,6 +42,8 @@ RSpec.describe AccountSuggestions::Source do
|
|||
.and not_include(follow_requested_account)
|
||||
.and not_include(following_account)
|
||||
.and not_include(moved_account)
|
||||
.and not_include(silenced_account)
|
||||
.and not_include(undiscoverable_account)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@mastodon/streaming",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"packageManager": "yarn@4.1.0",
|
||||
"packageManager": "yarn@4.1.1",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue