Merge tag 'v1.0.5+3.2.0' into hometown-v1.0.5+3.3.0

This commit is contained in:
Darius Kazemi 2021-04-22 16:37:11 -07:00
commit 4fe7cfc4be
126 changed files with 2708 additions and 366 deletions

View File

@ -346,7 +346,9 @@ GEM
mime-types (3.3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2020.0512)
mimemagic (0.3.5)
mimemagic (0.3.10)
nokogiri (~> 1)
rake
mini_mime (1.0.2)
mini_portile2 (2.4.0)
minitest (5.14.2)
@ -808,3 +810,9 @@ DEPENDENCIES
webpacker (~> 5.2)
webpush
xorcist (~> 1.1)
RUBY VERSION
ruby 2.6.5p114
BUNDLED WITH
1.17.2

108
README.md
View File

@ -1,88 +1,90 @@
![Mastodon](https://i.imgur.com/NhZc40l.png)
========
# Hometown: a Mastodon fork
[![GitHub release](https://img.shields.io/github/release/tootsuite/mastodon.svg)][releases]
[![Build Status](https://img.shields.io/circleci/project/github/tootsuite/mastodon.svg)][circleci]
[![Code Climate](https://img.shields.io/codeclimate/maintainability/tootsuite/mastodon.svg)][code_climate]
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/mastodon/localized.svg)][crowdin]
[![Docker Pulls](https://img.shields.io/docker/pulls/tootsuite/mastodon.svg)][docker]
<img width="300" src="https://live.staticflickr.com/7005/26777339042_b32cef4e1f_b.jpg" alt="photo of a village of stone huts nestled in a lush green valley">
[releases]: https://github.com/tootsuite/mastodon/releases
[circleci]: https://circleci.com/gh/tootsuite/mastodon
[code_climate]: https://codeclimate.com/github/tootsuite/mastodon
[crowdin]: https://crowdin.com/project/mastodon
[docker]: https://hub.docker.com/r/tootsuite/mastodon/
Photo by [Joana Mujollari](https://www.flickr.com/photos/141654969@N04/26777339042/), CC0 / Public Domain.
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, 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)!
Mastodon is a **free, open-source social network server** based on ActivityPub. This is *not* the official version of Mastodon; this is a separate version (i.e. a fork) maintained by [Darius Kazemi](https://friend.camp/@darius). For more information on Mastodon, you can see the [official website](https://joinmastodon.org) and the [upstream repo](https://github.com/tootsuite/mastodon).
Click below to **learn more** in a video:
__Hometown__ is a light weight fork of Mastodon. This fork is based on the principle of: minimum code change for maximum user experience change. By our best understanding, our major changes are not wanted by the Mastodon project, hence maintaining this fork instead of trying to commit the changes to Mastodon.
[![Screenshot](https://blog.joinmastodon.org/2018/06/why-activitypub-is-the-future/ezgif-2-60f1b00403.gif)][youtube_demo]
Please [check out our wiki](https://github.com/hometown-fork/hometown/wiki) for a list of Hometown-exclusive features. Some but not all of these are covered in this document.
[youtube_demo]: https://www.youtube.com/watch?v=IPSbNdBmWKE
You can also find [a list of running Hometown instances](https://github.com/hometown-fork/hometown/wiki/Hometown-servers), don't hesitate to open an issue to add yours!
## Navigation
## Support this project
- [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)
- [Browse Mastodon servers](https://joinmastodon.org/#getting-started)
- [Browse Mastodon apps](https://joinmastodon.org/apps)
Please consider [supporting Hometown by pledging to my Patreon](https://www.patreon.com/tinysubversions), which supports all my open source projects including this one!
[patreon]: https://www.patreon.com/mastodon
Of course this project couldn't exist without Mastodon so maybe [support the Mastodon project Patreon](https://www.patreon.com/mastodon) too.
## Features
## Migrating from Mastodon to Hometown
<img src="https://docs.joinmastodon.org/elephant.svg" align="right" width="30%" />
Please see [this article in the wiki](https://github.com/hometown-fork/hometown/wiki/Initial-migration) for directions on migration from Mastodon to Hometown.
**No vendor lock-in: Fully interoperable with any conforming platform**
## Local only posting
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/)
Mastodon right now is designed to get your messages out to the entire fediverse. This is great, but there is a huge need for more private communities. And in a federated network I think it makes the most sense for your home server to be that community (hence "Hometown").
**Real-time, chronological timeline updates**
**In the context of Hometown, local only posting is a per-post security option that lets you set whether that post can federate out to other servers or not.**
See the updates of people you're following appear in real-time in the UI via WebSockets. There's a firehose view as well!
I've been running Friend Camp, a Mastodon fork with local only posting, for about a year. Being able to have conversations with people on your server that don't federate is a hugely liberating thing. It allows inside jokes to develop. It allows people the freedom to complain about things that they wouldn't necessarily feel comfortable leaving a trusted server (cops, employers, etc). It also lets us do things like have a server-wide movie night where we flood the local timeline with posts about the movie, and it doesn't pollute the rest of the Fediverse.
**Media attachments like images and short videos**
This feature is based on [the work of Renato Lond](https://github.com/tootsuite/mastodon/pull/8427), which is itself based on a feature in the [Mastodon Glitch Edition](https://glitch-soc.github.io/docs/) fork.
Upload and view images and WebM/MP4 videos attached to the updates. Videos with no audio track are treated like GIFs; normal videos are looped - like vines!
## Reading more content types
**Safety and moderation tools**
Mastodon is microblogging software, meant for Twitter-style shortform posting.
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/)
Hometown is microblogging for _writing_, but its goal is to accept many content types for _reading_. So while I don't plan to let Hometown users publish massive blog posts, I would like your Hometown instance to be your one-stop shop for viewing all sorts of things on the Fediverse.
**OAuth2 and a straightforward REST API**
For Hometown this means if you subscribe to a service that sends out `Article` objects over ActivityPub (such as a blog on [Write As](https://write.as)), then those full articles render in your home timeline, behind a cut for length. Also, Hometown will render a variety of rich text like _italic_ and **bold**.
Mastodon acts as an OAuth2 provider so 3rd party apps can use the REST and Streaming APIs, resulting in a rich app ecosystem with a lot of choices!
Click on this GIF for a brief video demo:
## Deployment
<img src="http://tinysubversions.com/pics/hometown-article.gif" alt="Video demo of someone clicking 'read article' on an incoming article post, which then renders a full article.">
**Tech stack:**
This is based on rich text work by [Thibaut Girka](https://sitedethib.com), and my own work on `Article` support.
- **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
### It's more than just reading more stuff
**Requirements:**
Reading more content types also helps make the fediverse better. ActivityPub supports all kinds of content, but most ActivityPub servers shoehorn all their content into `Note` because that's the type that Mastodon treats as first-class. This has important implications for the fediverse and also on your day to do user experience.
- **PostgreSQL** 9.5+
- **Redis** 4+
- **Ruby** 2.5+
- **Node.js** 10.13+
Take the "quote tweet" debate for example.
The repository includes deployment configurations for **Docker and docker-compose**, but also a few specific platforms like **Heroku**, **Scalingo**, and **Nanobox**. The [**stand-alone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation.
Twitter has a feature called "quote tweeting" that lets you embed what someone else tweets, with your own comment right next to it. It's really useful for provide commentary in context, like this, where I point people to a sale and they can read both my comment on the sale and the original tweet about the sale in one post:
A **Vagrant** configuration is included for development purposes.
<img width="600" src="http://tinysubversions.com/pics/quote-tweet.png" alt="An example of a quote tweet from Twitter.">
## Contributing
But it's also open to abuse with people quote-tweeting things they disagree with to encourage a pile-on from their followers. There's been a lot of debate among Mastodon users as to whether the good of this feature outweighs the bad. So far, Mastodon has avoided implementing quoting.
Mastodon is **free, open-source software** licensed under **AGPLv3**.
What does this have to do with content types? Well, if we support an `Article` content type and a `Note` content type, we can support quoting for an `Article` but not for a `Note`. In practice this means that you can quote blog posts and news articles, but you can't quote a quick personal microblog note.
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).
> Hometown doesn't support quoting articles yet... but it will.
**IRC channel**: #mastodon on irc.freenode.net
## Better list management
If Hometown is going to be a universal reader, you're going to need better control over organizing your feeds than mainline Mastodon provides.
I've introduced a new kind of [exclusive list](https://github.com/hometown-fork/hometown/wiki/Exclusive-lists). In vanilla Mastodon, if you add an account to your "friends I like" list, posts from people on that list appear on that list. But they also appear on your home timeline, and maybe you don't want that! You'd rather treat your "friends I like" list as your "real" home timeline, and then check your home timeline when you're bored. Check out [more details about exclusive lists on the wiki](https://github.com/hometown-fork/hometown/wiki/Exclusive-lists).
## Better accessibility defaults
Look, right now this pretty much just means that we underline hyperlinks by default. I'm of course open to implementing other obviously beneficial accessibilty defaults that Mastodon itself doesn't implement.
## Hometown is still 99.999% Mastodon
I don't intend to stray very far from mainline Mastodon with this fork. If you want something that provides a ton of new features and widgets and stuff, the [Mastodon Glitch Edition](https://glitch-soc.github.io/docs/) fork is a wondrous kitchen sink of major and minor tweaks.
Part of why I don't want to stray far from mainline Mastodon is that this project is going to be just me for the foreseeable future, and I'd like to keep it up to date with new Mastodon versions as easily as possible. The less code I change from Mastodon, the easier that is. Hence the principle of "minimum code change for maximum user experience change."
## Versioning
Hometown uses [semantic versioning](https://semver.org) and follows a versioning convention like `v1.0.0+2.9.3`. The 1.0.0 part is the actual Hometown version number, and then the 2.9.3 after the + sign is what's known in semantic versioning as "build metadata". It just means that a particular release is synchronized with Mastodon version 2.9.3, so for example an upgrade from `v1.0.0+2.9.2` to `v1.0.0+2.9.3` would upgrade _Mastodon_ but not provide any new Hometown features or fixes.
## Contributing to Hometown
Setting up your Hometown development environment is [exactly like setting up your Mastodon development environment](https://docs.joinmastodon.org/dev/overview/). Pull requests should be made to the `hometown-dev` branch, which is our default branch in Github.
## License

View File

@ -28,7 +28,11 @@ class AccountsController < ApplicationController
return
end
@pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses?
if current_user.nil?
@pinned_statuses = cache_collection(@account.pinned_statuses.without_local_only, Status) if show_pinned_statuses?
else
@pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses?
end
@statuses = cached_filtered_status_page
@rss_url = rss_url

View File

@ -38,6 +38,6 @@ class Api::V1::ListsController < Api::BaseController
end
def list_params
params.permit(:title, :replies_policy)
params.permit(:title, :replies_policy, :is_exclusive)
end
end

View File

@ -99,10 +99,6 @@ module AccountsHelper
end
def svg_logo
content_tag(:svg, tag(:use, 'xlink:href' => '#mastodon-svg-logo'), 'viewBox' => '0 0 216.4144 232.00976')
end
def svg_logo_full
content_tag(:svg, tag(:use, 'xlink:href' => '#mastodon-svg-logo-full'), 'viewBox' => '0 0 713.35878 175.8678')
content_tag(:svg, tag(:use, 'xlink:href' => '#hometownlogo'), 'viewBox' => '0 0 216.4144 232.00976')
end
end

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" id="hometownlogo" x="0px" y="0px" viewBox="25 40 50 20" width="100%" height="100%"><g><path d="M55.9,53.9H35.3c-0.7,0-1.3,0.6-1.3,1.3s0.6,1.3,1.3,1.3h20.6c0.7,0,1.3-0.6,1.3-1.3S56.6,53.9,55.9,53.9z"/><path d="M55.9,58.2H35.3c-0.7,0-1.3,0.6-1.3,1.3s0.6,1.3,1.3,1.3h20.6c0.7,0,1.3-0.6,1.3-1.3S56.6,58.2,55.9,58.2z"/><path d="M55.9,62.6H35.3c-0.7,0-1.3,0.6-1.3,1.3s0.6,1.3,1.3,1.3h20.6c0.7,0,1.3-0.6,1.3-1.3S56.6,62.6,55.9,62.6z"/><path d="M64.8,53.9c-0.7,0-1.3,0.6-1.3,1.3v8.8c0,0.7,0.6,1.3,1.3,1.3s1.3-0.6,1.3-1.3v-8.8C66,54.4,65.4,53.9,64.8,53.9z"/><path d="M60.4,53.9c-0.7,0-1.3,0.6-1.3,1.3v8.8c0,0.7,0.6,1.3,1.3,1.3s1.3-0.6,1.3-1.3v-8.8C61.6,54.4,61.1,53.9,60.4,53.9z"/><path d="M63.7,48.3c1.3-0.7,2-2.5,2-5.6c0-3.6-0.9-7.8-3.3-7.8s-3.3,4.2-3.3,7.8c0,3.1,0.7,4.9,2,5.6v2.4c0,0.7,0.6,1.3,1.3,1.3 s1.3-0.6,1.3-1.3V48.3z M62.4,37.8c0.4,0.8,0.8,2.5,0.8,4.9c0,2.5-0.5,3.4-0.8,3.4s-0.8-0.9-0.8-3.4C61.7,40.3,62.1,38.6,62.4,37.8 z"/><path d="M57,42.7c0-0.1-0.1-0.1-0.1-0.2l-3.2-4.1c-0.2-0.3-0.6-0.5-1-0.5h-1.6v-1.9c0-0.7-0.6-1.3-1.3-1.3s-1.3,0.6-1.3,1.3V38 h-3.9h-1.1h-5.2c-0.4,0-0.7,0.2-1,0.5l-3.2,4.1c0,0.1-0.1,0.1-0.1,0.2c0,0-0.1,0.1-0.1,0.1C34,43,34,43.2,34,43.3v7.4 c0,0.7,0.6,1.3,1.3,1.3h5.2h7.4h8c0.7,0,1.3-0.6,1.3-1.3v-7.4c0-0.2,0-0.3-0.1-0.4C57,42.8,57,42.8,57,42.7z M41.7,49.5h-5.2v-4.9 h10.2v4.9H41.7z M48.5,42.1l-1.2-1.6h4.8l1.2,1.6H48.5z M44.1,40.5l1.2,1.6h-7.5l1.2-1.6H44.1z M49.2,44.6h5.5v4.9h-5.5V44.6z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -10,9 +10,10 @@ export const LISTS_FETCH_REQUEST = 'LISTS_FETCH_REQUEST';
export const LISTS_FETCH_SUCCESS = 'LISTS_FETCH_SUCCESS';
export const LISTS_FETCH_FAIL = 'LISTS_FETCH_FAIL';
export const LIST_EDITOR_TITLE_CHANGE = 'LIST_EDITOR_TITLE_CHANGE';
export const LIST_EDITOR_RESET = 'LIST_EDITOR_RESET';
export const LIST_EDITOR_SETUP = 'LIST_EDITOR_SETUP';
export const LIST_EDITOR_TITLE_CHANGE = 'LIST_EDITOR_TITLE_CHANGE';
export const LIST_EDITOR_IS_EXCLUSIVE_CHANGE = 'LIST_EDITOR_IS_EXCLUSIVE_CHANGE';
export const LIST_EDITOR_RESET = 'LIST_EDITOR_RESET';
export const LIST_EDITOR_SETUP = 'LIST_EDITOR_SETUP';
export const LIST_CREATE_REQUEST = 'LIST_CREATE_REQUEST';
export const LIST_CREATE_SUCCESS = 'LIST_CREATE_SUCCESS';
@ -100,13 +101,14 @@ export const fetchListsFail = error => ({
});
export const submitListEditor = shouldReset => (dispatch, getState) => {
const listId = getState().getIn(['listEditor', 'listId']);
const title = getState().getIn(['listEditor', 'title']);
const listId = getState().getIn(['listEditor', 'listId']);
const title = getState().getIn(['listEditor', 'title']);
const isExclusive = getState().getIn(['listEditor', 'isExclusive']);
if (listId === null) {
dispatch(createList(title, shouldReset));
} else {
dispatch(updateList(listId, title, shouldReset));
dispatch(updateList(listId, title, shouldReset, isExclusive));
}
};
@ -124,6 +126,11 @@ export const changeListEditorTitle = value => ({
value,
});
export const changeListEditorIsExclusive = value => ({
type: LIST_EDITOR_IS_EXCLUSIVE_CHANGE,
value,
});
export const createList = (title, shouldReset) => (dispatch, getState) => {
dispatch(createListRequest());
@ -150,10 +157,10 @@ export const createListFail = error => ({
error,
});
export const updateList = (id, title, shouldReset, replies_policy) => (dispatch, getState) => {
export const updateList = (id, title, shouldReset, replies_policy, isExclusive) => (dispatch, getState) => {
dispatch(updateListRequest(id));
api(getState).put(`/api/v1/lists/${id}`, { title, replies_policy }).then(({ data }) => {
api(getState).put(`/api/v1/lists/${id}`, { title, replies_policy, is_exclusive: !!isExclusive }).then(({ data }) => {
dispatch(updateListSuccess(data));
if (shouldReset) {

View File

@ -461,7 +461,7 @@ class Status extends ImmutablePureComponent {
return (
<HotKeys handlers={handlers}>
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}>
<div className={classNames('status__wrapper', `status__wrapper-type-${status.get('activity_pub_type') || 'none'}` , `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}>
{prepend}
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted })} data-id={status.get('id')}>

View File

@ -315,7 +315,7 @@ class StatusActionBar extends ImmutablePureComponent {
reblogTitle = intl.formatMessage(messages.cannot_reblog);
}
const shareButton = ('share' in navigator) && publicStatus && (
const shareButton = ('share' in navigator) && publicStatus && federated && (
<IconButton className='status__action-bar-button' title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShareClick} />
);
@ -326,6 +326,7 @@ class StatusActionBar extends ImmutablePureComponent {
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
{shareButton}
<IconButton className='status__action-bar-button bookmark-icon' disabled={anonymousAccess} active={status.get('bookmarked')} pressed={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />
<div className='status__action-bar-dropdown'>
<DropdownMenuContainer

View File

@ -203,6 +203,12 @@ export default class StatusContent extends React.PureComponent {
</button>
);
const readArticleButton = (
<button className='status__content__read-more-button' onClick={this.props.onClick} key='read-more'>
<FormattedMessage id='status.read_article' defaultMessage='Read article' /><Icon id='angle-right' fixedWidth />
</button>
);
if (status.get('spoiler_text').length > 0) {
let mentionsPlaceholder = '';
@ -218,12 +224,12 @@ export default class StatusContent extends React.PureComponent {
mentionsPlaceholder = <div>{mentionLinks}</div>;
}
return (
const output = [
<div className={classNames} ref={this.setRef} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
<p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
<span dangerouslySetInnerHTML={spoilerContent} />
{' '}
<button tabIndex='0' className={`status__content__spoiler-link ${hidden ? 'status__content__spoiler-link--show-more' : 'status__content__spoiler-link--show-less'}`} onClick={this.handleSpoilerClick}>{toggleText}</button>
{status.get('activity_pub_type') === 'Article' ? '' : <span class="show_more_button"><button tabIndex='0' className={`status__content__spoiler-link ${hidden ? 'status__content__spoiler-link--show-more' : 'status__content__spoiler-link--show-less'}`} onClick={this.handleSpoilerClick}>{toggleText}</button></span>}
</p>
{mentionsPlaceholder}
@ -231,10 +237,15 @@ export default class StatusContent extends React.PureComponent {
<div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} dangerouslySetInnerHTML={content} />
{!hidden && !!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
{renderViewThread && showThreadButton}
</div>
);
</div>,
];
if (status.get('activity_pub_type') === 'Article' && !this.props.expanded) {
output.push(readArticleButton);
}
return output;
} else if (this.props.onClick) {
const output = [
<div className={classNames} ref={this.setRef} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} key='status-content'>

View File

@ -38,7 +38,7 @@ const messages = defineMessages({
showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' },
enableNotifications: { id: 'account.enable_notifications', defaultMessage: 'Notify me when @{name} posts' },
disableNotifications: { id: 'account.disable_notifications', defaultMessage: 'Stop notifying me when @{name} posts' },
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' },
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },

View File

@ -123,8 +123,8 @@ export default class Header extends ImmutablePureComponent {
{!hideTabs && (
<div className='account__section-headline'>
<NavLink exact to={`/accounts/${account.get('id')}`}><FormattedMessage id='account.posts' defaultMessage='Toots' /></NavLink>
<NavLink exact to={`/accounts/${account.get('id')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Toots and replies' /></NavLink>
<NavLink exact to={`/accounts/${account.get('id')}`}><FormattedMessage id='account.posts' defaultMessage='Posts' /></NavLink>
<NavLink exact to={`/accounts/${account.get('id')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Posts and replies' /></NavLink>
<NavLink exact to={`/accounts/${account.get('id')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink>
</div>
)}

View File

@ -37,7 +37,7 @@ const mapStateToProps = (state, { params: { accountId }, withReplies = false })
};
const RemoteHint = ({ url }) => (
<TimelineHint url={url} resource={<FormattedMessage id='timeline_hint.resources.statuses' defaultMessage='Older toots' />} />
<TimelineHint url={url} resource={<FormattedMessage id='timeline_hint.resources.statuses' defaultMessage='Older posts' />} />
);
RemoteHint.propTypes = {
@ -143,7 +143,7 @@ class AccountTimeline extends ImmutablePureComponent {
} else if (remote && statusIds.isEmpty()) {
emptyMessage = <RemoteHint url={remoteUrl} />;
} else {
emptyMessage = <FormattedMessage id='empty_column.account_timeline' defaultMessage='No toots here!' />;
emptyMessage = <FormattedMessage id='empty_column.account_timeline' defaultMessage='No posts here!' />;
}
const remoteMessage = remote ? <RemoteHint url={remoteUrl} /> : null;

View File

@ -6,7 +6,7 @@ import { defineMessages, injectIntl } from 'react-intl';
const messages = defineMessages({
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' },
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },

View File

@ -21,13 +21,14 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { length } from 'stringz';
import { countableText } from '../util/counter';
import Icon from 'mastodon/components/icon';
import { maxChars } from '../../../initial_state';
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({
placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: 'Write your warning here' },
publish: { id: 'compose_form.publish', defaultMessage: 'Toot' },
publish: { id: 'compose_form.publish', defaultMessage: 'Post' },
publishLoud: { id: 'compose_form.publish_loud', defaultMessage: '{publish}!' },
});
@ -88,7 +89,7 @@ class ComposeForm extends ImmutablePureComponent {
const fulltext = this.getFulltextForCharacterCounting();
const isOnlyWhitespace = fulltext.length !== 0 && fulltext.trim().length === 0;
return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > 500 || (isOnlyWhitespace && !anyMedia));
return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > maxChars || (isOnlyWhitespace && !anyMedia));
}
handleSubmit = () => {
@ -252,7 +253,7 @@ class ComposeForm extends ImmutablePureComponent {
<SpoilerButtonContainer />
<FederationDropdownContainer />
</div>
<div className='character-counter__wrapper'><CharacterCounter max={500} text={this.getFulltextForCharacterCounting()} /></div>
<div className='character-counter__wrapper'><CharacterCounter max={maxChars} text={this.getFulltextForCharacterCounting()} /></div>
</div>
<div className='compose-form__publish'>

View File

@ -359,11 +359,11 @@ class EmojiPickerDropdown extends React.PureComponent {
return (
<div className='emoji-picker-dropdown' onKeyDown={this.handleKeyDown}>
<div ref={this.setTargetRef} className='emoji-button' title={title} aria-label={title} aria-expanded={active} role='button' onClick={this.onToggle} onKeyDown={this.onToggle} tabIndex={0}>
{button || <img
<img
className={classNames('emojione', { 'pulse-loading': active && loading })}
alt='🙂'
src={`${assetHost}/emoji/1f602.svg`}
/>}
src={`${assetHost}/emoji/1f600.svg`}
/>
</div>
<Overlay show={active} placement={placement} target={this.findTarget}>

View File

@ -10,9 +10,9 @@ import classNames from 'classnames';
const messages = defineMessages({
federate_short: { id: 'federation.federated.short', defaultMessage: 'Federated' },
federate_long: { id: 'federation.federated.long', defaultMessage: 'Allow toot to reach other instances' },
federate_long: { id: 'federation.federated.long', defaultMessage: 'Allow post to reach other instances' },
local_only_short: { id: 'federation.local_only.short', defaultMessage: 'Local-only' },
local_only_long: { id: 'federation.local_only.long', defaultMessage: 'Restrict this toot only to my instance' },
local_only_long: { id: 'federation.local_only.long', defaultMessage: 'Restrict this post only to my instance' },
change_federation: { id: 'federation.change', defaultMessage: 'Adjust status federation' },
});

View File

@ -85,7 +85,7 @@ class SearchResults extends ImmutablePureComponent {
count += results.get('statuses').size;
statuses = (
<div className='search-results__section'>
<h5><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Toots' /></h5>
<h5><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></h5>
{results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)}
@ -95,10 +95,10 @@ class SearchResults extends ImmutablePureComponent {
} else if(results.get('statuses') && results.get('statuses').size === 0 && !searchEnabled && !(searchTerm.startsWith('@') || searchTerm.startsWith('#') || searchTerm.includes(' '))) {
statuses = (
<div className='search-results__section'>
<h5><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Toots' /></h5>
<h5><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Posts' /></h5>
<div className='search-results__info'>
<FormattedMessage id='search_results.statuses_fts_disabled' defaultMessage='Searching toots by their content is not enabled on this Mastodon server.' />
<FormattedMessage id='search_results.statuses_fts_disabled' defaultMessage='Searching posts by their content is not enabled on this server.' />
</div>
</div>
);

View File

@ -42,7 +42,7 @@ const WarningWrapper = ({ needsLockWarning, hashtagWarning, directMessageWarning
}
if (hashtagWarning) {
return <Warning message={<FormattedMessage id='compose_form.hashtag_warning' defaultMessage="This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag." />} />;
return <Warning message={<FormattedMessage id='compose_form.hashtag_warning' defaultMessage="This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag." />} />;
}
if (directMessageWarning) {

View File

@ -26,7 +26,7 @@ const messages = defineMessages({
community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new toot' },
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new post' },
logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' },
});
@ -36,6 +36,25 @@ const mapStateToProps = (state, ownProps) => ({
showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : ownProps.isSearchPage,
});
let instanceMascot;
if (mascot) {
instanceMascot = <img alt='' draggable='false' src={mascot} />;
} else {
instanceMascot = <svg id='hometownlogo' width="2400" height="460" viewBox="0 0 2400 460" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet">
<g>
<g>
<path d="m326.20431,287.85649l-302.73044,0c-10.28698,0 -19.10436,8.81738 -19.10436,19.10436s8.81738,19.10436 19.10436,19.10436l302.73044,0c10.28698,0 19.10436,-8.81738 19.10436,-19.10436s-8.81738,-19.10436 -19.10436,-19.10436z"/>
<path d="m326.20431,351.04783l-302.73044,0c-10.28698,0 -19.10436,8.81738 -19.10436,19.10436s8.81738,19.10436 19.10436,19.10436l302.73044,0c10.28698,0 19.10436,-8.81738 19.10436,-19.10436s-8.81738,-19.10436 -19.10436,-19.10436z"/>
<path d="m326.20431,415.70867l-302.73044,0c-10.28698,0 -19.10436,8.81738 -19.10436,19.10436s8.81738,19.10436 19.10436,19.10436l302.73044,0c10.28698,0 19.10436,-8.81738 19.10436,-19.10436s-8.81738,-19.10436 -19.10436,-19.10436z"/>
<path d="m456.99565,287.85649c-10.28698,0 -19.10436,8.81738 -19.10436,19.10436l0,129.32173c0,10.28698 8.81738,19.10436 19.10436,19.10436s19.10436,-8.81738 19.10436,-19.10436l0,-129.32173c-1.46955,-11.75653 -10.28698,-19.10436 -19.10436,-19.10436z"/>
<path d="m392.33476,287.85649c-10.28698,0 -19.10436,8.81738 -19.10436,19.10436l0,129.32173c0,10.28698 8.81738,19.10436 19.10436,19.10436s19.10436,-8.81738 19.10436,-19.10436l0,-129.32173c-1.46955,-11.75653 -8.81738,-19.10436 -19.10436,-19.10436z"/>
<path d="m440.83045,205.56085c19.10436,-10.28698 29.39129,-36.73911 29.39129,-82.29564c0,-52.90436 -13.22609,-114.62609 -48.49564,-114.62609s-48.49564,61.72173 -48.49564,114.62609c0,45.55653 10.28698,72.00871 29.39129,82.29564l0,35.26955c0,10.28698 8.81738,19.10436 19.10436,19.10436s19.10436,-8.81738 19.10436,-19.10436l0,-35.26955l-0.00002,0zm-19.10436,-154.30436c5.87827,11.75653 11.75653,36.73911 11.75653,72.00871c0,36.73911 -7.34782,49.9652 -11.75653,49.9652s-11.75653,-13.22609 -11.75653,-49.9652c1.46955,-35.26955 7.34782,-60.25218 11.75653,-72.00871z"/>
<path d="m342.36956,123.26521c0,-1.46955 -1.46955,-1.46955 -1.46955,-2.93911l-47.02609,-60.25218c-2.93911,-4.40871 -8.81738,-7.34782 -14.69564,-7.34782l-23.51307,0l0,-27.92173c0,-10.28698 -8.81738,-19.10436 -19.10436,-19.10436s-19.10436,8.81738 -19.10436,19.10436l0,29.39129l-57.31307,0l-16.1652,0l-76.41738,0c-5.87827,0 -10.28698,2.93911 -14.69564,7.34782l-47.02609,60.25218c0,1.46955 -1.46955,1.46955 -1.46955,2.93911c0,0 -1.46955,1.46955 -1.46955,1.46955c1.46955,1.46955 1.46955,4.40871 1.46955,5.87827l0,108.74782c0,10.28698 8.81738,19.10436 19.10436,19.10436l76.41738,0l108.74782,0l117.56525,0c10.28698,0 19.10436,-8.81738 19.10436,-19.10436l0,-108.74782c0,-2.93911 0,-4.40871 -1.46955,-5.87827c-1.46955,-1.46955 -1.46955,-1.46955 -1.46955,-2.93911l-0.00005,0l-0.00002,0zm-224.84351,99.93045l-76.41738,0l0,-72.00871l149.89564,0l0,72.00871l-73.47827,0l0.00001,0zm99.93045,-108.74782l-17.6348,-23.51307l70.53916,0l17.6348,23.51307l-70.53916,0zm-64.66089,-23.51307l17.6348,23.51307l-110.21738,0l17.6348,-23.51307l74.94782,0l-0.00005,0l0.00001,0zm74.94782,60.25218l80.82609,0l0,72.00871l-80.82609,0l0,-72.00871z"/>
</g>
</g>
</svg>;
}
export default @connect(mapStateToProps)
@injectIntl
class Compose extends React.PureComponent {
@ -129,7 +148,7 @@ class Compose extends React.PureComponent {
<ComposeFormContainer />
<div className='drawer__inner__mastodon'>
<img alt='' draggable='false' src={mascot || elephantUIPlane} />
{instanceMascot}
</div>
</div>}

View File

@ -71,7 +71,7 @@ class Favourites extends ImmutablePureComponent {
const { intl, shouldUpdateScroll, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
const pinned = !!columnId;
const emptyMessage = <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any favourite toots yet. When you favourite one, it will show up here." />;
const emptyMessage = <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any favourite posts yet. When you favourite one, it will show up here." />;
return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)}>

View File

@ -60,7 +60,7 @@ class Favourites extends ImmutablePureComponent {
);
}
const emptyMessage = <FormattedMessage id='empty_column.favourites' defaultMessage='No one has favourited this toot yet. When someone does, they will show up here.' />;
const emptyMessage = <FormattedMessage id='empty_column.favourites' defaultMessage='No one has favourited this post yet. When someone does, they will show up here.' />;
return (
<Column bindToDocument={!multiColumn}>

View File

@ -29,7 +29,7 @@ const messages = defineMessages({
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' },
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
discover: { id: 'navigation_bar.discover', defaultMessage: 'Discover' },
personal: { id: 'navigation_bar.personal', defaultMessage: 'Personal' },

View File

@ -75,22 +75,22 @@ const FrameInteractions = ({ onNext }) => (
<div className='introduction__text introduction__text--columnized'>
<div>
<h3><FormattedMessage id='introduction.interactions.reply.headline' defaultMessage='Reply' /></h3>
<p><FormattedMessage id='introduction.interactions.reply.text' defaultMessage="You can reply to other people's and your own toots, which will chain them together in a conversation." /></p>
<p><FormattedMessage id='introduction.interactions.reply.text' defaultMessage="You can reply to other people's and your own posts, which will chain them together in a conversation." /></p>
</div>
<div>
<h3><FormattedMessage id='introduction.interactions.reblog.headline' defaultMessage='Boost' /></h3>
<p><FormattedMessage id='introduction.interactions.reblog.text' defaultMessage="You can share other people's toots with your followers by boosting them." /></p>
<p><FormattedMessage id='introduction.interactions.reblog.text' defaultMessage="You can share other people's posts with your followers by boosting them." /></p>
</div>
<div>
<h3><FormattedMessage id='introduction.interactions.favourite.headline' defaultMessage='Favourite' /></h3>
<p><FormattedMessage id='introduction.interactions.favourite.text' defaultMessage='You can save a toot for later, and let the author know that you liked it, by favouriting it.' /></p>
<p><FormattedMessage id='introduction.interactions.favourite.text' defaultMessage='You can save a post for later, and let the author know that you liked it, by favouriting it.' /></p>
</div>
</div>
<div className='introduction__action'>
<button className='button' onClick={onNext}><FormattedMessage id='introduction.interactions.action' defaultMessage='Finish toot-orial!' /></button>
<button className='button' onClick={onNext}><FormattedMessage id='introduction.interactions.action' defaultMessage='Finish tutorial!' /></button>
</div>
</div>
);

View File

@ -86,7 +86,7 @@ class KeyboardShortcuts extends ImmutablePureComponent {
</tr>
<tr>
<td><kbd>alt</kbd>+<kbd>n</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.toot' defaultMessage='to start a brand new toot' /></td>
<td><FormattedMessage id='keyboard_shortcuts.toot' defaultMessage='to start a brand new post' /></td>
</tr>
<tr>
<td><kbd>alt</kbd>+<kbd>x</kbd></td>
@ -134,7 +134,7 @@ class KeyboardShortcuts extends ImmutablePureComponent {
</tr>
<tr>
<td><kbd>g</kbd>+<kbd>p</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.pinned' defaultMessage='to open pinned toots list' /></td>
<td><FormattedMessage id='keyboard_shortcuts.pinned' defaultMessage='to open pinned posts list' /></td>
</tr>
<tr>
<td><kbd>g</kbd>+<kbd>u</kbd></td>

View File

@ -1,9 +1,10 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { changeListEditorTitle, submitListEditor } from '../../../actions/lists';
import { changeListEditorTitle, changeListEditorIsExclusive, submitListEditor } from '../../../actions/lists';
import IconButton from '../../../components/icon_button';
import { defineMessages, injectIntl } from 'react-intl';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import Toggle from 'react-toggle';
const messages = defineMessages({
title: { id: 'lists.edit.submit', defaultMessage: 'Change title' },
@ -17,6 +18,7 @@ const mapStateToProps = state => ({
const mapDispatchToProps = dispatch => ({
onChange: value => dispatch(changeListEditorTitle(value)),
onSubmit: () => dispatch(submitListEditor(false)),
onToggle: value => dispatch(changeListEditorIsExclusive(value)),
});
export default @connect(mapStateToProps, mapDispatchToProps)
@ -26,6 +28,7 @@ class ListForm extends React.PureComponent {
static propTypes = {
value: PropTypes.string.isRequired,
disabled: PropTypes.bool,
isExclusive: PropTypes.bool,
intl: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
@ -44,8 +47,12 @@ class ListForm extends React.PureComponent {
this.props.onSubmit();
}
handleToggle = e => {
this.props.onToggle(e.target.checked);
}
render () {
const { value, disabled, intl } = this.props;
const { value, disabled, intl, isExclusive, hello } = this.props;
const title = intl.formatMessage(messages.title);
@ -57,6 +64,11 @@ class ListForm extends React.PureComponent {
onChange={this.handleChange}
/>
<label htmlFor='is-exclusive-checkbox'>
<Toggle className='is-exclusive-checkbox' defaultChecked={isExclusive} onChange={this.handleToggle}/>
<FormattedMessage id='lists.is-exclusive' defaultMessage='Exclusive?' />
</label>
<IconButton
disabled={disabled}
icon='check'

View File

@ -28,6 +28,7 @@ class ListEditor extends ImmutablePureComponent {
static propTypes = {
listId: PropTypes.string.isRequired,
isExclusive: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
onInitialize: PropTypes.func.isRequired,
@ -53,7 +54,7 @@ class ListEditor extends ImmutablePureComponent {
return (
<div className='modal-root__modal list-editor'>
<EditListForm />
<EditListForm isExclusive={this.props.isExclusive} />
<Search />

View File

@ -113,7 +113,7 @@ class ListTimeline extends React.PureComponent {
}
handleEditClick = () => {
this.props.dispatch(openModal('LIST_EDITOR', { listId: this.props.params.id }));
this.props.dispatch(openModal('LIST_EDITOR', { listId: this.props.params.id, isExclusive: this.props.list.get('is_exclusive') }));
}
handleDeleteClick = () => {

View File

@ -10,7 +10,7 @@ import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
const messages = defineMessages({
heading: { id: 'column.pins', defaultMessage: 'Pinned toot' },
heading: { id: 'column.pins', defaultMessage: 'Pinned post' },
});
const mapStateToProps = state => ({

View File

@ -60,7 +60,7 @@ class Reblogs extends ImmutablePureComponent {
);
}
const emptyMessage = <FormattedMessage id='status.reblogs.empty' defaultMessage='No one has boosted this toot yet. When someone does, they will show up here.' />;
const emptyMessage = <FormattedMessage id='status.reblogs.empty' defaultMessage='No one has boosted this post yet. When someone does, they will show up here.' />;
return (
<Column bindToDocument={!multiColumn}>

View File

@ -18,6 +18,7 @@ const messages = defineMessages({
reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' },
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
local_only: { id: 'status.local_only', defaultMessage: 'This post is only visible by other users of your instance' },
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
more: { id: 'status.more', defaultMessage: 'More' },
@ -189,6 +190,7 @@ class ActionBar extends React.PureComponent {
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
const mutingConversation = status.get('muted');
const federated = !status.get('local_only');
const account = status.get('account');
let menu = [];
@ -247,7 +249,7 @@ class ActionBar extends React.PureComponent {
}
}
const shareButton = ('share' in navigator) && publicStatus && (
const shareButton = ('share' in navigator) && publicStatus && federated && (
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShare} /></div>
);

View File

@ -251,7 +251,7 @@ class DetailedStatus extends ImmutablePureComponent {
<DisplayName account={status.get('account')} localDomain={this.props.domain} />
</a>
<StatusContent status={status} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} />
<StatusContent status={status} expanded={status.get('activity_pub_type') === 'Article' || !status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} />
{media}

View File

@ -50,7 +50,7 @@ const componentMap = {
};
const messages = defineMessages({
publish: { id: 'compose_form.publish', defaultMessage: 'Toot' },
publish: { id: 'compose_form.publish', defaultMessage: 'Post' },
});
const shouldHideFAB = path => path.match(/^\/statuses\/|^\/search|^\/getting-started/);

View File

@ -64,6 +64,11 @@ class LinkFooter extends React.PureComponent {
defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}.'
values={{ github: <span><a href={source_url} rel='noopener noreferrer' target='_blank'>{repository}</a> (v{version})</span> }}
/>
<FormattedMessage
id='getting_started.hometown_open_source_notice'
defaultMessage='Hometown is also open source, at {hometown} (v1.0.5).'
values={{ hometown: <span><a href='https://github.com/hometown-fork/hometown' rel='noopener' target='_blank'>hometown-fork/hometown</a></span> }}
/>
</p>
</div>
);

View File

@ -61,7 +61,7 @@ import { previewState as previewVideoState } from './components/video_modal';
import '../../components/status';
const messages = defineMessages({
beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Mastodon.' },
beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave.' },
});
const mapStateToProps = state => ({

View File

@ -12,6 +12,7 @@ export const boostModal = getMeta('boost_modal');
export const deleteModal = getMeta('delete_modal');
export const me = getMeta('me');
export const searchEnabled = getMeta('search_enabled');
export const maxChars = (initialState && initialState.max_toot_chars) || 500;
export const invitesEnabled = getMeta('invites_enabled');
export const repository = getMeta('repository');
export const source_url = getMeta('source_url');

View File

@ -86,7 +86,7 @@
"community.column_settings.remote_only": "Remote only",
"compose_form.direct_message_warning": "Esti barritu namái va unviase a los usuarios mentaos.",
"compose_form.direct_message_warning_learn_more": "Learn more",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
"compose_form.lock_disclaimer.lock": "locked",
"compose_form.placeholder": "¿En qué pienses?",
@ -219,7 +219,7 @@
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "Pues guardar un barritu pa dempués y facer que l'autor sepa que te prestó marcándolu como favoritu.",
"introduction.interactions.reblog.headline": "Boost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
"introduction.interactions.reblog.text": "You can share other people's posts with your followers by boosting them.",
"introduction.interactions.reply.headline": "Reply",
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
"introduction.welcome.action": "¡Vamos!",

View File

@ -86,7 +86,7 @@
"community.column_settings.remote_only": "Remote only",
"compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.",
"compose_form.direct_message_warning_learn_more": "Learn more",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
"compose_form.lock_disclaimer.lock": "locked",
"compose_form.placeholder": "Какво си мислиш?",
@ -157,8 +157,8 @@
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
"empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
"empty_column.domain_blocks": "There are no hidden domains yet.",
"empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
"empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
"empty_column.favourited_statuses": "You don't have any favourite posts yet. When you favourite one, it will show up here.",
"empty_column.favourites": "No one has favourited this post yet. When someone does, they will show up here.",
"empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
"empty_column.hashtag": "There is nothing in this hashtag yet.",
"empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.",
@ -219,12 +219,12 @@
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.reblog.headline": "Boost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
"introduction.interactions.reblog.text": "You can share other people's posts with your followers by boosting them.",
"introduction.interactions.reply.headline": "Reply",
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
"introduction.interactions.reply.text": "You can reply to other people's and your own posts, which will chain them together in a conversation.",
"introduction.welcome.action": "Let's go!",
"introduction.welcome.headline": "First steps",
"introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
"introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special it hosts your profile, so remember its name.",
"keyboard_shortcuts.back": "to navigate back",
"keyboard_shortcuts.blocked": "to open blocked users list",
"keyboard_shortcuts.boost": "to boost",
@ -247,7 +247,7 @@
"keyboard_shortcuts.my_profile": "to open your profile",
"keyboard_shortcuts.notifications": "to open notifications column",
"keyboard_shortcuts.open_media": "to open media",
"keyboard_shortcuts.pinned": "to open pinned toots list",
"keyboard_shortcuts.pinned": "to open pinned posts list",
"keyboard_shortcuts.profile": "to open author's profile",
"keyboard_shortcuts.reply": "to reply",
"keyboard_shortcuts.requests": "to open follow requests list",
@ -304,7 +304,7 @@
"navigation_bar.logout": "Излизане",
"navigation_bar.mutes": "Muted users",
"navigation_bar.personal": "Personal",
"navigation_bar.pins": "Pinned toots",
"navigation_bar.pins": "Pinned posts",
"navigation_bar.preferences": "Предпочитания",
"navigation_bar.public_timeline": "Публичен канал",
"navigation_bar.security": "Security",

View File

@ -174,6 +174,11 @@
"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": "স্টেকট্রেস ক্লিপবোর্ডে কপি করুন",
"errors.unexpected_crash.report_issue": "সমস্যার প্রতিবেদন করুন",
"federation.change": "Adjust status federation",
"federation.federated.long": "Allow toot to reach other instances",
"federation.federated.short": "Federated",
"federation.local_only.long": "Restrict this toot only to my instance",
"federation.local_only.short": "Local-only",
"follow_request.authorize": "অনুমতি দিন",
"follow_request.reject": "প্রত্যাখ্যান করুন",
"follow_requests.unlocked_explanation": "আপনার অ্যাকাউন্টটি লক না থাকলেও, {domain} কর্মীরা ভেবেছিলেন যে আপনি এই অ্যাকাউন্টগুলি থেকে ম্যানুয়ালি অনুসরণের অনুরোধগুলি পর্যালোচনা করতে চাইতে পারেন।",

View File

@ -174,6 +174,11 @@
"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": "Copïo'r olrhain stac i'r clipfwrdd",
"errors.unexpected_crash.report_issue": "Rhoi gwybod am broblem",
"federation.change": "Adjust status federation",
"federation.federated.long": "Allow toot to reach other instances",
"federation.federated.short": "Federated",
"federation.local_only.long": "Restrict this toot only to my instance",
"federation.local_only.short": "Local-only",
"follow_request.authorize": "Caniatau",
"follow_request.reject": "Gwrthod",
"follow_requests.unlocked_explanation": "Er nid yw eich cyfrif wedi'i gloi, oedd y staff {domain} yn meddwl efallai hoffech adolygu ceisiadau dilyn o'r cyfrifau rhain wrth law.",

View File

@ -174,6 +174,11 @@
"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": "Kopiér stack trace til udklipsholderen",
"errors.unexpected_crash.report_issue": "Rapportér problem",
"federation.change": "Adjust status federation",
"federation.federated.long": "Allow toot to reach other instances",
"federation.federated.short": "Federated",
"federation.local_only.long": "Restrict this toot only to my instance",
"federation.local_only.short": "Local-only",
"follow_request.authorize": "Godkend",
"follow_request.reject": "Afvis",
"follow_requests.unlocked_explanation": "Selvom din konto ikke er låst, troede {domain} -personalet, at du måske vil gennemgå dine anmodninger manuelt.",

View File

@ -568,7 +568,7 @@
"id": "status.filtered"
},
{
"defaultMessage": "Pinned toot",
"defaultMessage": "Pinned post",
"id": "status.pinned"
},
{
@ -670,11 +670,11 @@
{
"descriptors": [
{
"defaultMessage": "Toots",
"defaultMessage": "Posts",
"id": "account.posts"
},
{
"defaultMessage": "Toots and replies",
"defaultMessage": "Posts and replies",
"id": "account.posts_with_replies"
},
{
@ -729,7 +729,7 @@
"id": "empty_column.account_unavailable"
},
{
"defaultMessage": "No toots here!",
"defaultMessage": "No posts here!",
"id": "empty_column.account_timeline"
}
],
@ -843,7 +843,7 @@
"id": "account.disable_notifications"
},
{
"defaultMessage": "Pinned toots",
"defaultMessage": "Pinned posts",
"id": "navigation_bar.pins"
},
{
@ -997,7 +997,7 @@
"id": "account.edit_profile"
},
{
"defaultMessage": "Pinned toots",
"defaultMessage": "Pinned posts",
"id": "navigation_bar.pins"
},
{
@ -1054,7 +1054,7 @@
"id": "compose_form.spoiler_placeholder"
},
{
"defaultMessage": "Toot",
"defaultMessage": "Post",
"id": "compose_form.publish"
},
{
@ -1132,7 +1132,7 @@
"id": "federation.federated.short"
},
{
"defaultMessage": "Allow toot to reach other instances",
"defaultMessage": "Allow post to reach other instances",
"id": "federation.federated.long"
},
{
@ -1140,7 +1140,7 @@
"id": "federation.local_only.short"
},
{
"defaultMessage": "Restrict this toot only to my instance",
"defaultMessage": "Restrict this post only to my instance",
"id": "federation.local_only.long"
},
{
@ -1278,7 +1278,7 @@
"id": "search_results.accounts"
},
{
"defaultMessage": "Toots",
"defaultMessage": "Posts",
"id": "search_results.statuses"
},
{
@ -1414,11 +1414,11 @@
"id": "compose_form.lock_disclaimer.lock"
},
{
"defaultMessage": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"defaultMessage": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
"id": "compose_form.hashtag_warning"
},
{
"defaultMessage": "This toot will only be sent to all the mentioned users.",
"defaultMessage": "This post will only be sent to all the mentioned users.",
"id": "compose_form.direct_message_warning"
},
{
@ -1459,7 +1459,7 @@
"id": "navigation_bar.logout"
},
{
"defaultMessage": "Compose new toot",
"defaultMessage": "Compose new post",
"id": "navigation_bar.compose"
},
{
@ -1634,7 +1634,7 @@
"id": "column.favourites"
},
{
"defaultMessage": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
"defaultMessage": "You don't have any favourite posts yet. When you favourite one, it will show up here.",
"id": "empty_column.favourited_statuses"
}
],
@ -1802,7 +1802,7 @@
"id": "navigation_bar.mutes"
},
{
"defaultMessage": "Pinned toots",
"defaultMessage": "Pinned posts",
"id": "navigation_bar.pins"
},
{
@ -1975,7 +1975,7 @@
"id": "introduction.interactions.reply.headline"
},
{
"defaultMessage": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
"defaultMessage": "You can reply to other people's and your own posts, which will chain them together in a conversation.",
"id": "introduction.interactions.reply.text"
},
{
@ -1983,7 +1983,7 @@
"id": "introduction.interactions.reblog.headline"
},
{
"defaultMessage": "You can share other people's toots with your followers by boosting them.",
"defaultMessage": "You can share other people's posts with your followers by boosting them.",
"id": "introduction.interactions.reblog.text"
},
{
@ -1991,11 +1991,11 @@
"id": "introduction.interactions.favourite.headline"
},
{
"defaultMessage": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"defaultMessage": "You can save a post for later, and let the author know that you liked it, by favouriting it.",
"id": "introduction.interactions.favourite.text"
},
{
"defaultMessage": "Finish toot-orial!",
"defaultMessage": "Finish tutorial!",
"id": "introduction.interactions.action"
}
],
@ -2068,7 +2068,7 @@
"id": "keyboard_shortcuts.compose"
},
{
"defaultMessage": "to start a brand new toot",
"defaultMessage": "to start a brand new post",
"id": "keyboard_shortcuts.toot"
},
{
@ -2116,7 +2116,7 @@
"id": "keyboard_shortcuts.favourites"
},
{
"defaultMessage": "to open pinned toots list",
"defaultMessage": "to open pinned posts list",
"id": "keyboard_shortcuts.pinned"
},
{
@ -2548,7 +2548,7 @@
{
"descriptors": [
{
"defaultMessage": "Pinned toot",
"defaultMessage": "Pinned post",
"id": "column.pins"
}
],
@ -2587,7 +2587,7 @@
"id": "refresh"
},
{
"defaultMessage": "No one has boosted this toot yet. When someone does, they will show up here.",
"defaultMessage": "No one has boosted this post yet. When someone does, they will show up here.",
"id": "status.reblogs.empty"
}
],
@ -2712,6 +2712,10 @@
},
{
"descriptors": [
{
"defaultMessage": "This post is only visible by other users of your instance",
"id": "status.local_only"
},
{
"defaultMessage": "Sensitive content",
"id": "status.sensitive_warning"
@ -2913,7 +2917,7 @@
{
"descriptors": [
{
"defaultMessage": "Toot",
"defaultMessage": "Post",
"id": "compose_form.publish"
}
],

View File

@ -32,13 +32,13 @@
"account.mute_notifications": "Mute notifications from @{name}",
"account.muted": "Muted",
"account.never_active": "Never",
"account.posts": "Toots",
"account.posts_with_replies": "Toots and replies",
"account.posts": "Posts",
"account.posts_with_replies": "Posts and replies",
"account.report": "Report @{name}",
"account.requested": "Awaiting approval. Click to cancel follow request",
"account.share": "Share @{name}'s profile",
"account.show_reblogs": "Show boosts from @{name}",
"account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}",
"account.statuses_counter": "{count, plural, one {{counter} Post} other {{counter} Posts}}",
"account.unblock": "Unblock @{name}",
"account.unblock_domain": "Unblock domain {domain}",
"account.unendorse": "Don't feature on profile",
@ -71,7 +71,7 @@
"column.lists": "Lists",
"column.mutes": "Muted users",
"column.notifications": "Notifications",
"column.pins": "Pinned toots",
"column.pins": "Pinned posts",
"column.public": "Federated timeline",
"column_back_button.label": "Back",
"column_header.hide_settings": "Hide settings",
@ -84,9 +84,9 @@
"community.column_settings.local_only": "Local only",
"community.column_settings.media_only": "Media Only",
"community.column_settings.remote_only": "Remote only",
"compose_form.direct_message_warning": "This toot will only be sent to the mentioned users.",
"compose_form.direct_message_warning": "This post will only be sent to the mentioned users.",
"compose_form.direct_message_warning_learn_more": "Learn more",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
"compose_form.lock_disclaimer.lock": "locked",
"compose_form.placeholder": "What's on your mind?",
@ -96,7 +96,7 @@
"compose_form.poll.remove_option": "Remove this choice",
"compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
"compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
"compose_form.publish": "Toot",
"compose_form.publish": "Post",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive.hide": "{count, plural, one {Mark media as sensitive} other {Mark media as sensitive}}",
"compose_form.sensitive.marked": "{count, plural, one {Media is marked as sensitive} other {Media is marked as sensitive}}",
@ -109,7 +109,7 @@
"confirmations.block.confirm": "Block",
"confirmations.block.message": "Are you sure you want to block {name}?",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.message": "Are you sure you want to delete this toot?",
"confirmations.delete.message": "Are you sure you want to delete this post?",
"confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
"confirmations.domain_block.confirm": "Block entire domain",
@ -120,7 +120,7 @@
"confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
"confirmations.redraft.confirm": "Delete & redraft",
"confirmations.redraft.message": "Are you sure you want to delete this toot and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
"confirmations.redraft.message": "Are you sure you want to delete this post and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
"confirmations.reply.confirm": "Reply",
"confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
"confirmations.unfollow.confirm": "Unfollow",
@ -133,7 +133,7 @@
"directory.local": "From {domain} only",
"directory.new_arrivals": "New arrivals",
"directory.recently_active": "Recently active",
"embed.instructions": "Embed this toot on your website by copying the code below.",
"embed.instructions": "Embed this post on your website by copying the code below.",
"embed.preview": "Here is what it will look like:",
"emoji_button.activity": "Activity",
"emoji_button.custom": "Custom",
@ -150,20 +150,20 @@
"emoji_button.symbols": "Symbols",
"emoji_button.travel": "Travel & Places",
"empty_column.account_suspended": "Account suspended",
"empty_column.account_timeline": "No toots here!",
"empty_column.account_timeline": "No posts here!",
"empty_column.account_unavailable": "Profile unavailable",
"empty_column.blocks": "You haven't blocked any users yet.",
"empty_column.bookmarked_statuses": "You don't have any bookmarked toots yet. When you bookmark one, it will show up here.",
"empty_column.bookmarked_statuses": "You don't have any bookmarked posts yet. When you bookmark one, it will show up here.",
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
"empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
"empty_column.domain_blocks": "There are no blocked domains yet.",
"empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
"empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
"empty_column.favourited_statuses": "You don't have any favourite posts yet. When you favourite one, it will show up here.",
"empty_column.favourites": "No one has favourited this post yet. When someone does, they will show up here.",
"empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
"empty_column.hashtag": "There is nothing in this hashtag yet.",
"empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.",
"empty_column.home.public_timeline": "the public timeline",
"empty_column.list": "There is nothing in this list yet. When members of this list post new toots, they will appear here.",
"empty_column.list": "There is nothing in this list yet. When members of this list post new posts, they will appear here.",
"empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
"empty_column.mutes": "You haven't muted any users yet.",
"empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
@ -175,9 +175,9 @@
"errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
"errors.unexpected_crash.report_issue": "Report issue",
"federation.change": "Adjust status federation",
"federation.federated.long": "Allow toot to reach other instances",
"federation.federated.long": "Allow post to reach other instances",
"federation.federated.short": "Federated",
"federation.local_only.long": "Restrict this toot only to my instance",
"federation.local_only.long": "Restrict this post only to my instance",
"federation.local_only.short": "Local-only",
"follow_request.authorize": "Authorize",
"follow_request.reject": "Reject",
@ -187,6 +187,7 @@
"getting_started.directory": "Profile directory",
"getting_started.documentation": "Documentation",
"getting_started.heading": "Getting started",
"getting_started.hometown_open_source_notice": "Hometown is also open source, at {hometown} (v1.0.5).",
"getting_started.invite": "Invite people",
"getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.",
"getting_started.security": "Account settings",
@ -217,23 +218,23 @@
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish tutorial!",
"introduction.interactions.favourite.headline": "Favourite",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.favourite.text": "You can save a post for later, and let the author know that you liked it, by favouriting it.",
"introduction.interactions.reblog.headline": "Boost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
"introduction.interactions.reblog.text": "You can share other people's posts with your followers by boosting them.",
"introduction.interactions.reply.headline": "Reply",
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
"introduction.interactions.reply.text": "You can reply to other people's and your own posts, which will chain them together in a conversation.",
"introduction.welcome.action": "Let's go!",
"introduction.welcome.headline": "First steps",
"introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
"keyboard_shortcuts.back": "to navigate back",
"keyboard_shortcuts.blocked": "to open blocked users list",
"keyboard_shortcuts.boost": "to boost",
"keyboard_shortcuts.column": "to focus a toot in one of the columns",
"keyboard_shortcuts.column": "to focus a post in one of the columns",
"keyboard_shortcuts.compose": "to focus the compose textarea",
"keyboard_shortcuts.description": "Description",
"keyboard_shortcuts.direct": "to open direct messages column",
"keyboard_shortcuts.down": "to move down in the list",
"keyboard_shortcuts.enter": "to open toot",
"keyboard_shortcuts.enter": "to open post",
"keyboard_shortcuts.favourite": "to favourite",
"keyboard_shortcuts.favourites": "to open favourites list",
"keyboard_shortcuts.federated": "to open federated timeline",
@ -247,7 +248,7 @@
"keyboard_shortcuts.my_profile": "to open your profile",
"keyboard_shortcuts.notifications": "to open notifications column",
"keyboard_shortcuts.open_media": "to open media",
"keyboard_shortcuts.pinned": "to open pinned toots list",
"keyboard_shortcuts.pinned": "to open pinned posts list",
"keyboard_shortcuts.profile": "to open author's profile",
"keyboard_shortcuts.reply": "to reply",
"keyboard_shortcuts.requests": "to open follow requests list",
@ -256,7 +257,7 @@
"keyboard_shortcuts.start": "to open \"get started\" column",
"keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
"keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
"keyboard_shortcuts.toot": "to start a brand new toot",
"keyboard_shortcuts.toot": "to start a brand new post",
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
"keyboard_shortcuts.up": "to move up in the list",
"lightbox.close": "Close",
@ -289,7 +290,7 @@
"navigation_bar.blocks": "Blocked users",
"navigation_bar.bookmarks": "Bookmarks",
"navigation_bar.community_timeline": "Local timeline",
"navigation_bar.compose": "Compose new toot",
"navigation_bar.compose": "Compose new post",
"navigation_bar.direct": "Direct messages",
"navigation_bar.discover": "Discover",
"navigation_bar.domain_blocks": "Blocked domains",
@ -304,17 +305,17 @@
"navigation_bar.logout": "Logout",
"navigation_bar.mutes": "Muted users",
"navigation_bar.personal": "Personal",
"navigation_bar.pins": "Pinned toots",
"navigation_bar.pins": "Pinned posts",
"navigation_bar.preferences": "Preferences",
"navigation_bar.public_timeline": "Federated timeline",
"navigation_bar.security": "Security",
"notification.favourite": "{name} favourited your toot",
"notification.favourite": "{name} favourited your post",
"notification.follow": "{name} followed you",
"notification.follow_request": "{name} has requested to follow you",
"notification.mention": "{name} mentioned you",
"notification.own_poll": "Your poll has ended",
"notification.poll": "A poll you have voted in has ended",
"notification.reblog": "{name} boosted your toot",
"notification.reblog": "{name} boosted your post",
"notification.status": "{name} just posted",
"notifications.clear": "Clear notifications",
"notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
@ -357,7 +358,7 @@
"poll.voted": "You voted for this answer",
"poll_button.add_poll": "Add a poll",
"poll_button.remove_poll": "Remove poll",
"privacy.change": "Adjust toot privacy",
"privacy.change": "Adjust post privacy",
"privacy.direct.long": "Visible for mentioned users only",
"privacy.direct.short": "Direct",
"privacy.private.long": "Visible for followers only",
@ -384,23 +385,23 @@
"report.target": "Reporting {target}",
"search.placeholder": "Search",
"search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns toots you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.full_text": "Simple text returns posts you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "toot",
"search_popout.tips.status": "post",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
"search_results.statuses": "Posts",
"search_results.statuses_fts_disabled": "Searching posts by their content is not enabled on this Mastodon server.",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"status.admin_account": "Open moderation interface for @{name}",
"status.admin_status": "Open this toot in the moderation interface",
"status.admin_status": "Open this post in the moderation interface",
"status.block": "Block @{name}",
"status.bookmark": "Bookmark",
"status.cancel_reblog_private": "Unboost",
"status.cannot_reblog": "This post cannot be boosted",
"status.copy": "Copy link to toot",
"status.copy": "Copy link to post",
"status.delete": "Delete",
"status.detailed_status": "Detailed conversation view",
"status.direct": "Direct message @{name}",
@ -414,14 +415,14 @@
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Mute conversation",
"status.open": "Expand this toot",
"status.open": "Expand this post",
"status.pin": "Pin on profile",
"status.pinned": "Pinned toot",
"status.pinned": "Pinned post",
"status.read_more": "Read more",
"status.reblog": "Boost",
"status.reblog_private": "Boost with original visibility",
"status.reblogged_by": "{name} boosted",
"status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
"status.reblogs.empty": "No one has boosted this post yet. When someone does, they will show up here.",
"status.redraft": "Delete & re-draft",
"status.remove_bookmark": "Remove bookmark",
"status.reply": "Reply",
@ -452,7 +453,7 @@
"timeline_hint.remote_resource_not_displayed": "{resource} from other servers are not displayed.",
"timeline_hint.resources.followers": "Followers",
"timeline_hint.resources.follows": "Follows",
"timeline_hint.resources.statuses": "Older toots",
"timeline_hint.resources.statuses": "Older posts",
"trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} talking",
"trends.trending_now": "Trending now",
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",

View File

@ -174,6 +174,11 @@
"error.unexpected_crash.next_steps_addons": "لطفاً از کارشان انداخته و صفحه را نوسازی کنید. اگر کمکی نکرد، شاید همچنان بتوانید با مرورگری دیگر یا با کاره‌ای بومی از ماستودون استفاده کنید.",
"errors.unexpected_crash.copy_stacktrace": "رونوشت از جزئیات اشکال",
"errors.unexpected_crash.report_issue": "گزارش مشکل",
"federation.change": "Adjust status federation",
"federation.federated.long": "Allow toot to reach other instances",
"federation.federated.short": "Federated",
"federation.local_only.long": "Restrict this toot only to my instance",
"federation.local_only.short": "Local-only",
"follow_request.authorize": "اجازه دهید",
"follow_request.reject": "رد کنید",
"follow_requests.unlocked_explanation": "با این که حسابتان قفل نیست، کارکنان {domain} فکر کردند که ممکن است بخواهید درخواست‌ها از این حساب‌ها را به صورت دستی بازبینی کنید.",

View File

@ -174,6 +174,11 @@
"error.unexpected_crash.next_steps_addons": "Essayez de les désactiver et de rafraîchir la page. Si cela ne vous aide pas, vous pouvez toujours utiliser Mastodon via un autre navigateur ou une application native.",
"errors.unexpected_crash.copy_stacktrace": "Copier la trace d'appels dans le presse-papier",
"errors.unexpected_crash.report_issue": "Signaler le problème",
"federation.change": "Adjust status federation",
"federation.federated.long": "Allow toot to reach other instances",
"federation.federated.short": "Federated",
"federation.local_only.long": "Restrict this toot only to my instance",
"federation.local_only.short": "Local-only",
"follow_request.authorize": "Accepter",
"follow_request.reject": "Rejeter",
"follow_requests.unlocked_explanation": "Même si votre compte nest pas verrouillé, léquipe de {domain} a pensé que vous pourriez vouloir consulter manuellement les demandes de suivi de ces comptes.",

View File

@ -174,6 +174,11 @@
"error.unexpected_crash.next_steps_addons": "Intenta desactivalas e actualiza a páxina. Se isto non funciona, podes seguir usando Mastodon nun navegador diferente ou aplicación nativa.",
"errors.unexpected_crash.copy_stacktrace": "Copiar trazas (stacktrace) ó portapapeis",
"errors.unexpected_crash.report_issue": "Informar sobre un problema",
"federation.change": "Adjust status federation",
"federation.federated.long": "Allow toot to reach other instances",
"federation.federated.short": "Federated",
"federation.local_only.long": "Restrict this toot only to my instance",
"federation.local_only.short": "Local-only",
"follow_request.authorize": "Autorizar",
"follow_request.reject": "Rexeitar",
"follow_requests.unlocked_explanation": "Malia que a túa conta non é privada, a administración de {domain} pensou que quizabes terías que revisar de xeito manual as solicitudes de seguiminto.",

View File

@ -174,6 +174,11 @@
"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": "स्टैकट्रेस को क्लिपबोर्ड पर कॉपी करें",
"errors.unexpected_crash.report_issue": "समस्या सूचित करें",
"federation.change": "Adjust status federation",
"federation.federated.long": "Allow toot to reach other instances",
"federation.federated.short": "Federated",
"federation.local_only.long": "Restrict this toot only to my instance",
"federation.local_only.short": "Local-only",
"follow_request.authorize": "अधिकार दें",
"follow_request.reject": "अस्वीकार करें",
"follow_requests.unlocked_explanation": "हालाँकि आपका खाता लॉक नहीं है, फिर भी {domain} डोमेन स्टाफ ने सोचा कि आप इन खातों के मैन्युअल अनुरोधों की समीक्षा करना चाहते हैं।",

View File

@ -174,6 +174,11 @@
"error.unexpected_crash.next_steps_addons": "Próbáld letiltani őket és frissíteni az oldalt. Ha ez nem segít, egy másik böngészőn vagy appon keresztül még mindig használhatod a Mastodont.",
"errors.unexpected_crash.copy_stacktrace": "Veremkiíratás vágólapra másolása",
"errors.unexpected_crash.report_issue": "Probléma jelentése",
"federation.change": "Adjust status federation",
"federation.federated.long": "Allow toot to reach other instances",
"federation.federated.short": "Federated",
"federation.local_only.long": "Restrict this toot only to my instance",
"federation.local_only.short": "Local-only",
"follow_request.authorize": "Engedélyezés",
"follow_request.reject": "Elutasítás",
"follow_requests.unlocked_explanation": "Bár a fiókod nincs zárolva, a(z) {domain} csapata úgy gondolta, hogy talán kézzel szeretnéd ellenőrizni a fiók követési kéréseit.",

View File

@ -174,6 +174,11 @@
"error.unexpected_crash.next_steps_addons": "Փորձիր անջատել յաւելուածները եւ թարմացնել էջը։ Եթե դա չօգնի, կարող ես օգտուել Մաստադոնից այլ դիտարկիչով կամ յաւելուածով։",
"errors.unexpected_crash.copy_stacktrace": "Պատճենել սթաքթրեյսը սեղմատախտակին",
"errors.unexpected_crash.report_issue": "Զեկուցել խնդրի մասին",
"federation.change": "Adjust status federation",
"federation.federated.long": "Allow toot to reach other instances",
"federation.federated.short": "Federated",
"federation.local_only.long": "Restrict this toot only to my instance",
"federation.local_only.short": "Local-only",
"follow_request.authorize": "Վաւերացնել",
"follow_request.reject": "Մերժել",
"follow_requests.unlocked_explanation": "Այս հարցումը ուղարկուած է հաշուից, որի համար {domain}-ի անձնակազմը միացրել է ձեռքով ստուգում։",
@ -403,6 +408,7 @@
"status.favourite": "Հաւանել",
"status.filtered": "Զտուած",
"status.load_more": "Բեռնել աւելին",
"status.local_only": "This post is only visible by other users of your instance",
"status.media_hidden": "մեդիաբովանդակութիւնը թաքցուած է",
"status.mention": "Նշել @{name}֊ին",
"status.more": "Աւելին",

View File

@ -174,6 +174,11 @@
"error.unexpected_crash.next_steps_addons": "Prova a disabilitarli e ad aggiornare la pagina. Se questo non funziona, potresti ancora essere in grado di utilizzare Mastodon attraverso un browser o un'app diversi.",
"errors.unexpected_crash.copy_stacktrace": "Copia stacktrace negli appunti",
"errors.unexpected_crash.report_issue": "Segnala il problema",
"federation.change": "Adjust status federation",
"federation.federated.long": "Allow toot to reach other instances",
"federation.federated.short": "Federated",
"federation.local_only.long": "Restrict this toot only to my instance",
"federation.local_only.short": "Local-only",
"follow_request.authorize": "Autorizza",
"follow_request.reject": "Rifiuta",
"follow_requests.unlocked_explanation": "Anche se il tuo account non è bloccato, lo staff di {domain} ha pensato che potresti voler esaminare manualmente le richieste di seguirti di questi account.",

View File

@ -174,6 +174,11 @@
"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": "Жиынтықты көшіріп ал клипбордқа",
"errors.unexpected_crash.report_issue": "Мәселені хабарла",
"federation.change": "Adjust status federation",
"federation.federated.long": "Allow toot to reach other instances",
"federation.federated.short": "Federated",
"federation.local_only.long": "Restrict this toot only to my instance",
"federation.local_only.short": "Local-only",
"follow_request.authorize": "Авторизация",
"follow_request.reject": "Қабылдамау",
"follow_requests.unlocked_explanation": "Even though your account is not locked, the {domain} staff thought you might want to review follow requests from these accounts manually.",

View File

@ -174,6 +174,11 @@
"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": "Kopier stacktrace-en til utklippstavlen",
"errors.unexpected_crash.report_issue": "Rapporter en feil",
"federation.change": "Adjust status federation",
"federation.federated.long": "Allow toot to reach other instances",
"federation.federated.short": "Federated",
"federation.local_only.long": "Restrict this toot only to my instance",
"federation.local_only.short": "Local-only",
"follow_request.authorize": "Autorisér",
"follow_request.reject": "Avvis",
"follow_requests.unlocked_explanation": "Selv om kontoen din ikke er låst, tror {domain} ansatte at du kanskje vil gjennomgå forespørsler fra disse kontoene manuelt.",

View File

@ -174,6 +174,11 @@
"error.unexpected_crash.next_steps_addons": "Tente desabilitá-los e atualizar a página. Se isso não ajudar, você ainda poderá usar o Mastodon por meio de um navegador diferente ou de um aplicativo nativo.",
"errors.unexpected_crash.copy_stacktrace": "Copiar stacktrace para área de transferência",
"errors.unexpected_crash.report_issue": "Denunciar problema",
"federation.change": "Ajustar federação do toot",
"federation.federated.long": "Permitir que o toot chegue a outras instâncias",
"federation.federated.short": "Federado",
"federation.local_only.long": "Restringir o toot somente à minha instância",
"federation.local_only.short": "Somente local",
"follow_request.authorize": "Aprovar",
"follow_request.reject": "Vetar",
"follow_requests.unlocked_explanation": "Embora sua conta não esteja trancada, o staff de {domain} achou que você podia querer revisar pedidos para te seguir destas contas manualmente.",

View File

@ -174,6 +174,11 @@
"error.unexpected_crash.next_steps_addons": "Tente desabilitá-los e atualizar a página. Se isso não ajudar, você ainda poderá usar o Mastodon por meio de um navegador diferente ou de um aplicativo nativo.",
"errors.unexpected_crash.copy_stacktrace": "Copiar a stacktrace para o clipboard",
"errors.unexpected_crash.report_issue": "Reportar problema",
"federation.change": "Ajustar federação do toot",
"federation.federated.long": "Permitir que o toot chegue a outras instâncias",
"federation.federated.short": "Federado",
"federation.local_only.long": "Restringir o toot somente à minha instância",
"federation.local_only.short": "Somente local",
"follow_request.authorize": "Autorizar",
"follow_request.reject": "Rejeitar",
"follow_requests.unlocked_explanation": "Apesar de a sua não estar bloqueada, a administração de {domain} pensa que poderá querer rever os pedidos dessas contas manualmente.",

View File

@ -174,6 +174,11 @@
"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": "Copiați stiva în clipboard",
"errors.unexpected_crash.report_issue": "Raportați o problemă",
"federation.change": "Adjust status federation",
"federation.federated.long": "Allow toot to reach other instances",
"federation.federated.short": "Federated",
"federation.local_only.long": "Restrict this toot only to my instance",
"federation.local_only.short": "Local-only",
"follow_request.authorize": "Autorizează",
"follow_request.reject": "Respinge",
"follow_requests.unlocked_explanation": "Chiar dacă contul dvs nu este blocat, personalul {domain} a crezut că ați putea dori să revizuiți cererile de la aceste conturi în mod manual.",

View File

@ -174,6 +174,11 @@
"error.unexpected_crash.next_steps_addons": "Provoni ti çaktivizoni dhe të rifreskoni faqen. Nëse kjo sbën punë, mundeni prapë të jeni në gjendje të përdorni Mastodon-in përmes një shfletuesi tjetër, apo një aplikacioni prej Mastodon-it.",
"errors.unexpected_crash.copy_stacktrace": "Kopjo stacktrace-in në të papastër",
"errors.unexpected_crash.report_issue": "Raportoni problemin",
"federation.change": "Adjust status federation",
"federation.federated.long": "Allow toot to reach other instances",
"federation.federated.short": "Federated",
"federation.local_only.long": "Restrict this toot only to my instance",
"federation.local_only.short": "Local-only",
"follow_request.authorize": "Autorizoje",
"follow_request.reject": "Hidhe tej",
"follow_requests.unlocked_explanation": "Edhe pse llogaria juaj sështë e kyçur, ekipi i {domain} mendoi se mund të donit të shqyrtonit dorazi kërkesa ndjekjeje prej këtyre llogarive.",

View File

@ -174,6 +174,11 @@
"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": "Копирај \"stacktrace\" у клипборд",
"errors.unexpected_crash.report_issue": "Пријави проблем",
"federation.change": "Adjust status federation",
"federation.federated.long": "Allow toot to reach other instances",
"federation.federated.short": "Federated",
"federation.local_only.long": "Restrict this toot only to my instance",
"federation.local_only.short": "Local-only",
"follow_request.authorize": "Одобри",
"follow_request.reject": "Одбиј",
"follow_requests.unlocked_explanation": "Even though your account is not locked, the {domain} staff thought you might want to review follow requests from these accounts manually.",

View File

@ -174,6 +174,11 @@
"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": "Stacktrace-ஐ clipboard-ல் நகலெடு",
"errors.unexpected_crash.report_issue": "புகாரளி",
"federation.change": "Adjust status federation",
"federation.federated.short": "Federated",
"federation.federated.long": "Allow toot to reach other instances",
"federation.local_only.long": "Restrict this toot only to my instance",
"federation.local_only.short": "Local-only",
"follow_request.authorize": "அனுமதியளி",
"follow_request.reject": "நிராகரி",
"follow_requests.unlocked_explanation": "உங்கள் கணக்கு பூட்டப்படவில்லை என்றாலும், இந்தக் கணக்குகளிலிருந்து உங்களைப் பின்தொடர விரும்பும் கோரிக்கைகளை நீங்கள் பரீசீலிப்பது நலம் என்று {domain} ஊழியர் எண்ணுகிறார்.",

View File

@ -174,6 +174,11 @@
"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": "Скопіювати трасування стека у буфер обміну",
"errors.unexpected_crash.report_issue": "Повідомити про проблему",
"federation.change": "Adjust status federation",
"federation.federated.long": "Allow toot to reach other instances",
"federation.federated.short": "Federated",
"federation.local_only.long": "Restrict this toot only to my instance",
"federation.local_only.short": "Local-only",
"follow_request.authorize": "Авторизувати",
"follow_request.reject": "Відмовити",
"follow_requests.unlocked_explanation": "Хоча ваш обліковий запис не заблоковано, працівники {domain} припускають, що, можливо, ви хотіли б переглянути ці запити на підписку.",
@ -403,6 +408,7 @@
"status.favourite": "Подобається",
"status.filtered": "Відфільтровано",
"status.load_more": "Завантажити більше",
"status.local_only": "This post is only visible by other users of your instance",
"status.media_hidden": "Медіа приховано",
"status.mention": "Згадати @{name}",
"status.more": "Більше",

View File

@ -174,6 +174,11 @@
"error.unexpected_crash.next_steps_addons": "请尝试禁用它们并刷新页面。如果没有帮助,你仍可以尝试使用其他浏览器或原生应用来使用 Mastodon。",
"errors.unexpected_crash.copy_stacktrace": "把堆栈跟踪信息复制到剪贴板",
"errors.unexpected_crash.report_issue": "报告问题",
"federation.change": "Adjust status federation",
"federation.federated.long": "Allow toot to reach other instances",
"federation.federated.short": "Federated",
"federation.local_only.long": "Restrict this toot only to my instance",
"federation.local_only.short": "Local-only",
"follow_request.authorize": "同意",
"follow_request.reject": "拒绝",
"follow_requests.unlocked_explanation": "虽说你没有锁嘟,但是 {domain} 的工作人员觉得你可能想手工审核关注请求。",

View File

@ -174,6 +174,11 @@
"error.unexpected_crash.next_steps_addons": "請嘗試停止使用這些附加元件然後重新載入頁面。如果問題沒有解決,你仍然可以使用不同的瀏覽器或 Mastodon 應用程式來檢視。",
"errors.unexpected_crash.copy_stacktrace": "複製 stacktrace 到剪貼簿",
"errors.unexpected_crash.report_issue": "舉報問題",
"federation.change": "Adjust status federation",
"federation.federated.long": "Allow toot to reach other instances",
"federation.federated.short": "Federated",
"federation.local_only.long": "Restrict this toot only to my instance",
"federation.local_only.short": "Local-only",
"follow_request.authorize": "批准",
"follow_request.reject": "拒絕",
"follow_requests.unlocked_explanation": "即使您的帳戶未上鎖,{domain} 的工作人員認為您可能想手動審核來自這些帳戶的關注請求。",

View File

@ -174,6 +174,11 @@
"error.unexpected_crash.next_steps_addons": "請嘗試關閉他們然後重新整理頁面。如果狀況沒有改善,您可以使用不同的瀏覽器或應用程式來檢視來使用 Mastodon。",
"errors.unexpected_crash.copy_stacktrace": "複製 stacktrace 到剪貼簿",
"errors.unexpected_crash.report_issue": "回報問題",
"federation.change": "Adjust status federation",
"federation.federated.long": "Allow toot to reach other instances",
"federation.federated.short": "Federated",
"federation.local_only.long": "Restrict this toot only to my instance",
"federation.local_only.short": "Local-only",
"follow_request.authorize": "授權",
"follow_request.reject": "拒絕",
"follow_requests.unlocked_explanation": "即便您的帳號未被鎖定,{domain} 的員工認為您可能想要自己審核這些帳號的追蹤請求。",

View File

@ -9,6 +9,7 @@ import {
LIST_EDITOR_RESET,
LIST_EDITOR_SETUP,
LIST_EDITOR_TITLE_CHANGE,
LIST_EDITOR_IS_EXCLUSIVE_CHANGE,
LIST_ACCOUNTS_FETCH_REQUEST,
LIST_ACCOUNTS_FETCH_SUCCESS,
LIST_ACCOUNTS_FETCH_FAIL,
@ -52,6 +53,11 @@ export default function listEditorReducer(state = initialState, action) {
map.set('title', action.value);
map.set('isChanged', true);
});
case LIST_EDITOR_IS_EXCLUSIVE_CHANGE:
return state.withMutations(map => {
map.set('isExclusive', action.value);
map.set('isChanged', true);
});
case LIST_CREATE_REQUEST:
case LIST_UPDATE_REQUEST:
return state.withMutations(map => {

View File

@ -0,0 +1,21 @@
export function svgSelect(light, dark) {
var svgbg = window.getComputedStyle(document.getElementsByClassName("drawer__inner")[0], null).getPropertyValue("background-color");
var rgbArray = ((svgbg.replace(/[^0-9,]/g, "")).split(",")).map(Number).map(x => x/255);
for ( var i = 0; i < rgbArray.length; ++i ) {
if ( rgbArray[i] <= 0.03928 ) {
rgbArray[i] = rgbArray[i] / 12.92
} else {
rgbArray[i] = Math.pow( ( rgbArray[i] + 0.055 ) / 1.055, 2.4);
}
}
var luminance = 0.2126 * rgbArray[0] + 0.7152 * rgbArray[1] + 0.0722 * rgbArray[2];
if ( luminance <= 0.179 ) {
return light;
} else {
return dark;
}
}

View File

@ -0,0 +1,3 @@
@import 'fairy-floss/variables';
@import 'application';
@import 'fairy-floss/diff'

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,70 @@
// FAIRY FLOSS VARIABLES
// Purples
$purple1: #2e2b3b;
$purple2: #504b68;
$purple3: #5f5676;
$purple4: #c8c4d4;
$purple5: #474059;
$purpleicon: #ada6bf;
$purpleborder: #5f5676;
$purplescrollbar: #5a5475;
$mint: #c2ffdf;
$pink: #ffb8d1;
$lemon: #fff683;
$coral: #ff857f;
// Commonly used web colors
$black: #000000; // Black
$white: #ffffff; // White
$success-green: #79bd9a !default; // Padua
$error-red: #df405a !default; // Cerise
$warning-red: #ff5050 !default; // Sunset Orange
$gold-star: #ca8f04 !default; // Dark Goldenrod
// Values from the classic Mastodon UI
$classic-base-color: $purple1; // Midnight Express
$classic-primary-color: $purple4; // Echo Blue
$classic-secondary-color: #d9e1e8; // Pattens Blue
$classic-highlight-color: #ff857f; // Summer Sky
// Variables for defaults in UI
$base-shadow-color: $black !default;
$base-overlay-background: $black !default;
$base-border-color: $white !default;
$simple-background-color: $white !default;
$valid-value-color: $success-green !default;
$error-value-color: $error-red !default;
// Tell UI to use selected colors
$ui-base-color: $classic-base-color !default; // Darkest
$ui-base-lighter-color: lighten($ui-base-color, 26%) !default; // Lighter darkest
$ui-primary-color: $classic-primary-color !default; // Lighter
$ui-secondary-color: $classic-secondary-color !default; // Lightest
$ui-highlight-color: $classic-highlight-color !default;
// Variables for texts
$primary-text-color: $white !default;
$darker-text-color: $ui-primary-color !default;
$dark-text-color: $purpleicon !default;
$secondary-text-color: $ui-secondary-color !default;
$highlight-text-color: $ui-highlight-color !default;
$action-button-color: $purpleicon !default;
// For texts on inverted backgrounds
$inverted-text-color: $ui-base-color !default;
$lighter-text-color: $ui-base-lighter-color !default;
$light-text-color: $ui-primary-color !default;
// Language codes that uses CJK fonts
$cjk-langs: ja, ko, zh-CN, zh-HK, zh-TW;
// Variables for components
$media-modal-media-max-width: 100%;
// put margins on top and bottom of image to avoid the screen covered by image.
$media-modal-media-max-height: 80%;
$no-gap-breakpoint: 415px;
$font-sans-serif: 'mastodon-font-sans-serif' !default;
$font-display: 'mastodon-font-display' !default;
$font-monospace: 'mastodon-font-monospace' !default;

View File

@ -0,0 +1,3 @@
@import 'macaron/variables';
@import 'application';
@import 'macaron/diff';

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
// Dependent colors
$black: #000000;
$white: #ffffff;
$purple: #c8c4dd;
$blue: #c8dbee;
$green: #dff6eb;
$pink: #ffcbcb;
$blueberry: #5971ad;
$cream: #fffdef;
$bilberry: #ad599c;
$classic-base-color: #282c37;
$classic-primary-color: #9baec8;
$classic-secondary-color: $green;
$classic-highlight-color: #2b90d9;
// Differences
$success-green: #3c754d;
$base-overlay-background: $white !default;
$valid-value-color: $success-green !default;
$ui-base-color: $classic-secondary-color !default;
$ui-base-lighter-color: $cream;
$ui-primary-color: #9bcbed;
$ui-secondary-color: $classic-base-color !default;
$ui-highlight-color: $blueberry;
$primary-text-color: $black !default;
$darker-text-color: $classic-base-color !default;
$dark-text-color: #444b5d;
$action-button-color: #606984;
$inverted-text-color: $black !default;
$lighter-text-color: $classic-base-color !default;
$light-text-color: #444b5d;
//Newly added colors
$account-background-color: $white !default;
//Invert darkened and lightened colors
@function darken($color, $amount) {
@return hsl(hue($color), saturation($color), lightness($color) + $amount);
}
@function lighten($color, $amount) {
@return hsl(hue($color), saturation($color), lightness($color) - $amount);
}

View File

@ -228,6 +228,11 @@ h1 {
line-height: 36px;
}
.column-cell a h1 {
margin: 0 8px 10px;
line-height: 26px;
}
h2 {
font-size: 23px;
line-height: 30px;

View File

@ -195,7 +195,7 @@ html {
}
.drawer__inner__mastodon {
background: $white url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color($ui-base-color)}"/></svg>') no-repeat bottom / 100% auto;
background: $white;
}
// Change the colors used in compose-form

View File

@ -579,7 +579,7 @@ $small-breakpoint: 960px;
bottom: 25px;
img {
height: 190px;
height: 155px;
width: auto;
}
}
@ -688,6 +688,8 @@ $small-breakpoint: 960px;
justify-content: center;
align-items: center;
padding: 50px;
font-family: $font-display, sans-serif;
font-weight: bold;
svg {
fill: $primary-text-color;
@ -864,6 +866,22 @@ $small-breakpoint: 960px;
.brand {
position: relative;
text-decoration: none;
color: $secondary-text-color;
}
a.brand {
color: $primary-text-color;
}
h1 a.brand {
color: $secondary-text-color;
font-size: 3.8em;
}
.originalmascotimg svg {
width: 200px;
fill: $secondary-text-color;
margin-top: -25px;
}
.brand__tagline {
@ -883,4 +901,3 @@ $small-breakpoint: 960px;
color: $dark-text-color;
}
}

View File

@ -46,6 +46,12 @@ $content-width: 840px;
position: relative;
bottom: -2px;
}
h2 {
height: 20px;
position: relative;
bottom: -2px;
}
}
&__icon {
@ -68,15 +74,32 @@ $content-width: 840px;
.logo {
display: block;
margin: 40px auto;
width: 100px;
margin: 60px 0 0 20px;
height: 100px;
h2 .brand {
color: $primary-text-color;
display: block;
font-size: 24px;
line-height: 28px;
font-weight: 400;
padding-bottom: 40px;
border-bottom: 1px solid lighten($ui-base-color, 8%);
}
}
@media screen and (max-width: $no-columns-breakpoint) {
& > a:first-child {
display: none;
}
.logo,
.logo h2,
.logo h2 .brand {
display: none;
}
}
ul {

View File

@ -138,6 +138,12 @@ body {
height: auto;
margin-top: -120px;
}
h1 {
a.brand {
font-size: 36px;
}
}
}
h1 {

View File

@ -406,12 +406,6 @@
color: $lighter-text-color;
font-weight: 500;
text-decoration: underline;
&:hover,
&:active,
&:focus {
text-decoration: none;
}
}
}
@ -821,10 +815,6 @@
&.status__content--with-spoiler {
white-space: normal;
.status__content__text {
white-space: pre-wrap;
}
}
.emojione {
@ -832,11 +822,82 @@
height: 20px;
margin: -3px 0 0;
}
.status__content__text {
p,
pre,
blockquote {
margin-bottom: 20px;
white-space: pre-wrap;
unicode-bidi: plaintext;
p {
margin-bottom: 20px;
white-space: pre-wrap;
unicode-bidi: plaintext;
&:last-child {
margin-bottom: 2px;
}
}
h1,
h2,
h3,
h4,
h5 {
margin-top: 20px;
margin-bottom: 20px;
}
h1 {
font-weight: 700;
font-size: 22px;
}
h2 {
font-weight: 700;
font-size: 20px;
}
h3,
h4,
h5 {
font-weight: 500;
}
blockquote {
padding-left: 10px;
border-left: 3px solid $darker-text-color;
color: $darker-text-color;
white-space: normal;
p:last-child {
margin-bottom: 0;
}
}
b,
strong {
font-weight: 700;
}
em,
i {
font-style: italic;
}
ul,
ol {
margin-left: 1em;
margin-bottom: 1em;
p {
margin: 0;
}
}
ul {
list-style-type: disc;
}
ol {
list-style-type: decimal;
}
&:last-child {
margin-bottom: 0;
@ -845,7 +906,7 @@
a {
color: $secondary-text-color;
text-decoration: none;
text-decoration: underline;
unicode-bidi: isolate;
&:hover {
@ -857,13 +918,9 @@
}
&.mention {
&:hover {
text-decoration: none;
span {
text-decoration: underline;
}
}
}
.fa {
@ -901,6 +958,10 @@
display: block;
}
}
.article-type img {
max-width: 95%;
}
}
.announcements__item__content {
@ -1177,6 +1238,7 @@
.status__action-bar-dropdown {
height: 23.15px;
width: 23.15px;
margin-right: 18px;
}
.detailed-status__action-bar-dropdown {
@ -2027,6 +2089,13 @@ a.account__display-name {
width: 285px;
pointer-events: auto;
height: 100%;
@media screen and (max-width: 1550px) {
overflow-y: scroll;
overflow-x: hidden;
padding-right: 10px;
}
}
}
@ -2467,6 +2536,12 @@ a.account__display-name {
height: calc(100% - 10px);
overflow-y: hidden;
@media screen and (max-width: 1550px) {
height: auto;
min-height: calc(100% - 10px);
overflow-y: visible;
}
.navigation-bar {
padding-top: 20px;
padding-bottom: 20px;
@ -2493,6 +2568,11 @@ a.account__display-name {
background-color: $white;
border-radius: 4px 4px 0 0;
flex: 0 1 auto;
@media screen and (max-width: 1550px) {
overflow-y: visible;
}
}
.autosuggest-textarea__textarea {
@ -2558,12 +2638,12 @@ a.account__display-name {
}
.drawer__inner__mastodon {
background: lighten($ui-base-color, 13%) url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color($ui-base-color)}"/></svg>') no-repeat bottom / 100% auto;
background: lighten($ui-base-color, 13%);
flex: 1;
min-height: 47px;
display: none;
> img {
> img, svg {
display: block;
object-fit: contain;
object-position: bottom left;
@ -2575,7 +2655,7 @@ a.account__display-name {
}
@media screen and (min-height: 640px) {
display: block;
display: flex;
}
}
@ -2801,6 +2881,15 @@ a.account__display-name {
border-color: $ui-highlight-color;
}
label[for="is-exclusive-checkbox"] {
margin-left: 20px;
}
.is-exclusive-checkbox {
transform: scale(0.7);
transform-origin: bottom;
}
.column-link {
background: lighten($ui-base-color, 8%);
color: $primary-text-color;
@ -3301,7 +3390,7 @@ a.status-card.compact:hover {
}
&__label {
margin-top: 30px;
margin-top: 0px;
strong {
display: block;
@ -7257,3 +7346,21 @@ noscript {
text-align: center;
}
}
.drawer__inner__mastodon svg#hometownlogo {
box-sizing: border-box;
fill: $secondary-text-color;
margin: 0 0 10px 10px;
align-self: flex-end;
}
div.status__content,
div.status__content--with-action,
div.status__content--with-spoiler {
p {
span.show_more_button {
display: block;
margin: 0.25rem 0 0 0;
}
}
}

View File

@ -257,6 +257,29 @@
}
}
.originalhero {
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
align-content: space-between;
svg {
fill: $secondary-text-color;
height: 200px;
}
h1 {
flex-grow: 2;
text-align: center;
white-space: nowrap;
font-family: $font-display, sans-serif;
font-size: 0.6rem;
}
}
.public-layout {
@media screen and (max-width: $no-gap-breakpoint) {
padding-top: 48px;
@ -322,6 +345,7 @@
.brand {
display: block;
padding: 15px;
white-space: nowrap;
svg {
display: block;
@ -476,8 +500,42 @@
border-radius: 4px 4px 0 0;
}
.originalheader {
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
align-content: space-between;
svg {
fill: $secondary-text-color;
height: 300px;
}
h1 {
flex-grow: 2;
text-align: center;
white-space: nowrap;
font-family: $font-display, sans-serif;
}
}
@media screen and (max-width: 600px) {
height: 200px;
.originalheader {
svg {
height: 200px;
}
h1 {
font-size: 0.5rem;
}
}
}
}

View File

@ -15,7 +15,6 @@
> * {
flex: 1;
max-height: 235px;
background: url('../images/elephant_ui_plane.svg') no-repeat left bottom / contain;
}
}

View File

@ -4,8 +4,8 @@ class ActivityPub::Activity
include JsonLdHelper
include Redisable
SUPPORTED_TYPES = %w(Note Question).freeze
CONVERTED_TYPES = %w(Image Audio Video Article Page Event).freeze
SUPPORTED_TYPES = %w(Note Question Article).freeze
CONVERTED_TYPES = %w(Image Audio Video Page Event).freeze
def initialize(json, account, **options)
@json = json

View File

@ -77,6 +77,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
@mentions = []
@params = {}
process_inline_images if @object['content'].present? && @object['type'] == 'Article'
process_status_params
process_tags
process_audience
@ -107,7 +108,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
account: @account,
text: text_from_content || '',
language: detected_language,
spoiler_text: converted_object_type? ? '' : (text_from_summary || ''),
spoiler_text: converted_object_type? ? '' : (text_from_summary || (@object['type'] == 'Article' && text_from_name) || ''),
created_at: @object['published'],
override_timestamps: @options[:override_timestamps],
reply: @object['inReplyTo'].present?,
@ -117,10 +118,61 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
conversation: conversation_from_uri(@object['conversation']),
media_attachment_ids: process_attachments.take(4).map(&:id),
poll: process_poll,
activity_pub_type: @object['type']
}
end
end
class Handler < ::Ox::Sax
attr_reader :srcs
attr_reader :alts
def initialize(block)
@stack = []
@srcs = []
@alts = {}
end
def start_element(element_name)
@stack << [element_name, {}]
end
def end_element(element_name)
self_name, self_attributes = @stack[-1]
if self_name == :img && !self_attributes[:src].nil?
@srcs << self_attributes[:src]
@alts[self_attributes[:src]] = self_attributes[:alt]
end
@stack.pop
end
def attr(attribute_name, attribute_value)
_name, attributes = @stack.last
attributes[attribute_name] = attribute_value
end
end
def process_inline_images
proc = Proc.new { |name| puts name }
handler = Handler.new(proc)
Ox.sax_parse(handler, @object['content'])
handler.srcs.each do |src|
if skip_download?
@object['content'].gsub!(src, '')
next
end
media_attachment = MediaAttachment.create(account: @account, remote_url: src, description: handler.alts[src], focus: nil)
media_attachment.file_remote_url = src
media_attachment.save
if unsupported_media_type?(media_attachment.file.content_type)
@object['content'].gsub!(src, '')
media_attachment.delete
else
@object['content'].gsub!(src, media_attachment.file.url(:small))
end
end
end
def process_audience
(audience_to + audience_cc).uniq.each do |audience|
next if audience == ActivityPub::TagManager::COLLECTIONS[:public]
@ -391,6 +443,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
def text_from_content
return Formatter.instance.linkify([[text_from_name, text_from_summary.presence].compact.join("\n\n"), object_url || object_uri].join(' ')) if converted_object_type?
return Formatter.instance.format_article(@object['content']) if @object['content'].present? && @object['type'] == 'Article'
if @object['content'].present?
@object['content']
elsif content_language_map?

View File

@ -40,9 +40,9 @@ class FeedManager
def filter?(timeline_type, status, receiver)
case timeline_type
when :home
filter_from_home?(status, receiver.id, build_crutches(receiver.id, [status]))
filter_from_home?(status, receiver.id, build_crutches(receiver.id, [status]), :home)
when :list
filter_from_list?(status, receiver) || filter_from_home?(status, receiver.account_id, build_crutches(receiver.account_id, [status]))
filter_from_list?(status, receiver) || filter_from_home?(status, receiver.account_id, build_crutches(receiver.account_id, [status]), :list)
when :mentions
filter_from_mentions?(status, receiver.id)
else
@ -315,10 +315,17 @@ class FeedManager
# @param [Integer] receiver_id
# @param [Hash] crutches
# @return [Boolean]
def filter_from_home?(status, receiver_id, crutches)
def filter_from_home?(status, receiver_id, crutches, timeline_type=:home)
return false if receiver_id == status.account_id
return true if status.reply? && (status.in_reply_to_id.nil? || status.in_reply_to_account_id.nil?)
return true if phrase_filtered?(status, receiver_id, :home)
# hometown: exclusive list rules
unless timeline_type == :list
# find all exclusive lists
@list = List.where(account: Account.find(receiver_id), is_exclusive: true)
# is there a list the receiver owns with this account on it? if so, return true
return true if ListAccount.where(list: @list, account_id: status.account_id).exists?
end
check_for_blocks = crutches[:active_mentions][status.id] || []
check_for_blocks.concat([status.account_id])

View File

@ -91,6 +91,12 @@ class Formatter
html.html_safe # rubocop:disable Rails/OutputSafety
end
def format_article(text)
text = text.gsub(/>\n+</, "><")
text = "<span class='article-type'>#{text}</span>"
text.html_safe # rubocop:disable Rails/OutputSafety
end
def linkify(text)
html = encode_and_link_urls(text)
html = simple_format(html, {}, sanitize: false)

View File

@ -71,11 +71,14 @@ class Sanitize
end
MASTODON_STRICT ||= freeze_config(
elements: %w(p br span a),
elements: %w(p br span a abbr del pre blockquote code b strong i em h1 h2 h3 h4 h5 ul ol li img),
attributes: {
'a' => %w(href rel class),
'span' => %w(class),
'a' => %w(href rel class title),
'span' => %w(class),
'abbr' => %w(title),
'blockquote' => %w(cite),
'img' => %w(src alt),
},
add_attributes: {
@ -83,9 +86,15 @@ class Sanitize
'rel' => 'nofollow noopener noreferrer',
'target' => '_blank',
},
'span' => {
'class' => 'article-type',
},
},
protocols: {},
protocols: {
'a' => { 'href' => HTTP_PROTOCOLS },
'blockquote' => { 'cite' => HTTP_PROTOCOLS },
},
transformers: [
CLASS_WHITELIST_TRANSFORMER,

View File

@ -19,7 +19,7 @@ class UserMailer < Devise::Mailer
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.unconfirmed_email.presence || @resource.email,
subject: I18n.t(@resource.pending_reconfirmation? ? 'devise.mailer.reconfirmation_instructions.subject' : 'devise.mailer.confirmation_instructions.subject', instance: @instance),
subject: I18n.t(@resource.pending_reconfirmation? ? 'devise.mailer.reconfirmation_instructions.subject' : 'devise.mailer.confirmation_instructions.subject', instance: @instance, title: Setting.site_title),
template_name: @resource.pending_reconfirmation? ? 'reconfirmation_instructions' : 'confirmation_instructions'
end
end
@ -32,7 +32,7 @@ class UserMailer < Devise::Mailer
return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.reset_password_instructions.subject')
mail to: @resource.email, subject: I18n.t('devise.mailer.reset_password_instructions.subject', title: Setting.site_title)
end
end
@ -43,7 +43,7 @@ class UserMailer < Devise::Mailer
return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.password_change.subject')
mail to: @resource.email, subject: I18n.t('devise.mailer.password_change.subject', title: Setting.site_title)
end
end
@ -54,7 +54,7 @@ class UserMailer < Devise::Mailer
return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.email_changed.subject')
mail to: @resource.email, subject: I18n.t('devise.mailer.email_changed.subject', title: Setting.site_title)
end
end
@ -144,7 +144,7 @@ class UserMailer < Devise::Mailer
return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('user_mailer.welcome.subject')
mail to: @resource.email, subject: I18n.t('user_mailer.welcome.subject', title: Setting.site_title)
end
end
@ -156,7 +156,7 @@ class UserMailer < Devise::Mailer
return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('user_mailer.backup_ready.subject')
mail to: @resource.email, subject: I18n.t('user_mailer.backup_ready.subject', title: Setting.site_title)
end
end

View File

@ -9,6 +9,7 @@
# created_at :datetime not null
# updated_at :datetime not null
# replies_policy :integer default("list"), not null
# is_exclusive :boolean default(FALSE)
#
class List < ApplicationRecord

View File

@ -24,6 +24,7 @@
# poll_id :bigint(8)
# deleted_at :datetime
# local_only :boolean
# activity_pub_type :string
#
class Status < ApplicationRecord

View File

@ -2,10 +2,15 @@
class InitialStateSerializer < ActiveModel::Serializer
attributes :meta, :compose, :accounts,
:media_attachments, :settings
:media_attachments, :settings,
:max_toot_chars
has_one :push_subscription, serializer: REST::WebPushSubscriptionSerializer
def max_toot_chars
StatusLengthValidator::MAX_CHARS
end
def meta
store = {
streaming_api_base_url: Rails.configuration.x.streaming_api_base_url,

View File

@ -4,7 +4,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
include RoutingHelper
attributes :uri, :title, :short_description, :description, :email,
:version, :urls, :stats, :thumbnail,
:version, :urls, :stats, :thumbnail, :max_toot_chars,
:languages, :registrations, :approval_required, :invites_enabled
has_one :contact_account, serializer: REST::AccountSerializer
@ -39,6 +39,10 @@ class REST::InstanceSerializer < ActiveModel::Serializer
instance_presenter.thumbnail ? full_asset_url(instance_presenter.thumbnail.file.url) : full_pack_url('media/images/preview.jpg')
end
def max_toot_chars
StatusLengthValidator::MAX_CHARS
end
def stats
{
user_count: instance_presenter.user_count,

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
class REST::ListSerializer < ActiveModel::Serializer
attributes :id, :title, :replies_policy
attributes :id, :title, :replies_policy, :is_exclusive
def id
object.id.to_s

View File

@ -4,7 +4,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id,
:sensitive, :spoiler_text, :visibility, :language,
:uri, :url, :replies_count, :reblogs_count,
:favourites_count, :local_only
:favourites_count, :local_only, :activity_pub_type
attribute :favourited, if: :current_user?
attribute :reblogged, if: :current_user?
@ -70,6 +70,10 @@ class REST::StatusSerializer < ActiveModel::Serializer
ActivityPub::TagManager.instance.uri_for(object)
end
def activity_pub_type
object.activity_pub_type.to_s
end
def content
Formatter.instance.format(object)
end

View File

@ -87,9 +87,21 @@ class PostStatusService < BaseService
end
end
def local_only_option(local_only, in_reply_to, federation_setting)
return in_reply_to&.local_only? if local_only.nil? # XXX temporary, just until clients implement to avoid leaking local_only posts
return federation_setting if local_only.nil?
def local_only_option(local_only, in_reply_to, federation_setting, text)
# This is intended for third party clients. The admin can set a custom :local_only:
# emoji that users can append to force a post to be local only.
if text.include? ":local_only:"
return true
end
if local_only.nil?
if in_reply_to && in_reply_to.local_only
return true
end
if in_reply_to && !in_reply_to.local_only
return false
end
return !federation_setting
end
local_only
end
@ -172,7 +184,7 @@ class PostStatusService < BaseService
language: language_from_option(@options[:language]) || @account.user&.setting_default_language&.presence || LanguageDetector.instance.detect(@text, @account),
application: @options[:application],
rate_limit: @options[:with_rate_limit],
local_only: local_only_option(@options[:local_only], @in_reply_to, @account.user&.setting_default_federation),
local_only: local_only_option(@options[:local_only], @in_reply_to, @account.user&.setting_default_federation, @text),
}.compact
end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
class StatusLengthValidator < ActiveModel::Validator
MAX_CHARS = 500
MAX_CHARS = (ENV['MAX_TOOT_CHARS'] || 500).to_i
def validate(status)
return unless status.local? && !status.reblog?

View File

@ -1,6 +1,6 @@
.simple_form__overlay-area{ class: (closed_registrations? && @instance_presenter.closed_registrations_message.present?) ? 'simple_form__overlay-area__blurred' : '' }
= simple_form_for(new_user, url: user_registration_path, namespace: 'registration', html: { novalidate: false }) do |f|
%p.lead= t('about.federation_hint_html', instance: content_tag(:strong, site_hostname))
%p.lead= t('about.federation_hint_html', instance: content_tag(:strong, site_title))
.fields-group
= f.simple_fields_for :account do |account_fields|

View File

@ -9,7 +9,14 @@
.column-0
.public-account-header.public-account-header--no-bar
.public-account-header__image
= image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title, class: 'parallax'
- if @instance_presenter.hero.present? || @instance_presenter.thumbnail.present?
= image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url, alt: @instance_presenter.site_title, class: 'parallax'
- else
%div{:class => ("originalheader")}
= svg_logo
%h1
= link_to root_url, class: 'brand' do
= site_title
.column-1
.landing-page__call-to-action{ dir: 'ltr' }
@ -24,8 +31,13 @@
%strong= number_to_human @instance_presenter.status_count, strip_insignificant_zeros: true
%span= t 'about.status_count_after', count: @instance_presenter.status_count
.row__mascot
.landing-page__mascot
= image_tag @instance_presenter.mascot&.file&.url || asset_pack_path('media/images/elephant_ui_plane.svg'), alt: ''
- if @instance_presenter.mascot&.file&.url
.landing-page__mascot
= image_tag @instance_presenter.mascot&.file&.url
- else
.landing-page__mascot{:class => ("originalmascot")}
%div{:class => ("originalmascotimg")}
= svg_logo
.column-2
.contact-widget

View File

@ -7,9 +7,9 @@
.landing
.landing__brand
= link_to root_url, class: 'brand' do
= svg_logo_full
%span.brand__tagline=t 'about.tagline'
%h1
= link_to root_url, class: 'brand' do
= site_title
.landing__grid
.landing__grid__column.landing__grid__column-registration
@ -35,22 +35,29 @@
%h4
= fa_icon 'globe fw'
= t('about.see_whats_happening')
%small= t('about.browse_public_posts')
%small= t('about.browse_public_posts', title: site_title)
.directory__tag
= link_to 'https://joinmastodon.org/apps', target: '_blank', rel: 'noopener noreferrer' do
%h4
= fa_icon 'tablet fw'
= t('about.get_apps')
%small= t('about.apps_platforms')
%small= t('about.apps_platforms', title: site_title)
.landing__grid__column.landing__grid__column-login
.box-widget
= render 'login'
.hero-widget
.hero-widget__img
= image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title
- if @instance_presenter.hero.present? || @instance_presenter.thumbnail.present?
.hero-widget__img
= image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url, alt: @instance_presenter.site_title
- else
%div{:class => ("originalhero")}
= svg_logo
%h1
= link_to root_url, class: 'brand' do
= site_title
.hero-widget__text
%p

View File

@ -77,7 +77,7 @@
%h4= t 'admin.dashboard.software'
%ul
%li
Mastodon
Hometown
%span.pull-right= @version
%li
Ruby

View File

@ -1,6 +1,7 @@
.hero-widget
.hero-widget__img
= image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title
- if @instance_presenter.hero.present? || @instance_presenter.thumbnail.present?
.hero-widget__img
= image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url, alt: @instance_presenter.site_title
.hero-widget__text
%p= @instance_presenter.site_short_description.html_safe.presence || t('about.about_mastodon_html')

View File

@ -1,5 +1,5 @@
%h3= t 'sessions.title'
%p.muted-hint= t 'sessions.explanation'
%p.muted-hint= t('sessions.explanation', title: site_title)
%hr.spacer/

View File

@ -10,7 +10,7 @@
.app-holder#mastodon{ data: { props: Oj.dump(default_props) } }
%noscript
= image_pack_tag 'logo.svg', alt: 'Mastodon'
= image_pack_tag 'logo.svg', alt: site_title
%div
= t('errors.noscript_html', apps_path: 'https://joinmastodon.org/apps')
= t('errors.noscript_html', apps_path: 'https://joinmastodon.org/apps', title: site_title)

Some files were not shown because too many files have changed in this diff Show More