diff --git a/app/javascript/mastodon/components/announcement_icon_button.js b/app/javascript/mastodon/components/announcement_icon_button.js
new file mode 100644
index 000000000..0de58c6f7
--- /dev/null
+++ b/app/javascript/mastodon/components/announcement_icon_button.js
@@ -0,0 +1,90 @@
+import React from 'react';
+import Motion from 'react-motion/lib/Motion';
+import spring from 'react-motion/lib/spring';
+import PropTypes from 'prop-types';
+
+class IconButton extends React.PureComponent {
+
+ static propTypes = {
+ className: PropTypes.string,
+ title: PropTypes.string.isRequired,
+ icon: PropTypes.string.isRequired,
+ onClick: PropTypes.func,
+ size: PropTypes.number,
+ active: PropTypes.bool,
+ style: PropTypes.object,
+ activeStyle: PropTypes.object,
+ disabled: PropTypes.bool,
+ inverted: PropTypes.bool,
+ animate: PropTypes.bool,
+ overlay: PropTypes.bool,
+ };
+
+ static defaultProps = {
+ size: 18,
+ active: false,
+ disabled: false,
+ animate: false,
+ overlay: false,
+ };
+
+ handleClick = (e) => {
+ e.preventDefault();
+
+ if (!this.props.disabled) {
+ this.props.onClick(e);
+ }
+ }
+
+ render () {
+ const style = {
+ fontSize: `${this.props.size}px`,
+ width: `${this.props.size * 1.28571429}px`,
+ height: `${this.props.size * 1.28571429}px`,
+ lineHeight: `${this.props.size}px`,
+ ...this.props.style,
+ ...(this.props.active ? this.props.activeStyle : {}),
+ };
+
+ const classes = ['icon-button'];
+
+ if (this.props.active) {
+ classes.push('active');
+ }
+
+ if (this.props.disabled) {
+ classes.push('disabled');
+ }
+
+ if (this.props.inverted) {
+ classes.push('inverted');
+ }
+
+ if (this.props.overlay) {
+ classes.push('overlayed');
+ }
+
+ if (this.props.className) {
+ classes.push(this.props.className);
+ }
+
+ return (
+
+ {({ rotate }) =>
+
+ }
+
+ );
+ }
+
+}
+
+export default IconButton;
diff --git a/app/javascript/mastodon/features/compose/components/announcements.js b/app/javascript/mastodon/features/compose/components/announcements.js
new file mode 100644
index 000000000..ad7043e89
--- /dev/null
+++ b/app/javascript/mastodon/features/compose/components/announcements.js
@@ -0,0 +1,127 @@
+import React from 'react';
+import Immutable from 'immutable';
+import PropTypes from 'prop-types';
+import Link from 'react-router-dom/Link';
+import { defineMessages, injectIntl } from 'react-intl';
+import IconButton from '../../../components/announcement_icon_button';
+import Motion from 'react-motion/lib/Motion';
+import spring from 'react-motion/lib/spring';
+
+const Collapsable = ({ fullHeight, minHeight, isVisible, children }) => (
+
+ {({ height }) =>
+
+ {children}
+
+ }
+
+);
+
+Collapsable.propTypes = {
+ fullHeight: PropTypes.number.isRequired,
+ minHeight: PropTypes.number.isRequired,
+ isVisible: PropTypes.bool.isRequired,
+ children: PropTypes.node.isRequired,
+};
+
+const messages = defineMessages({
+ toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
+ welcome: { id: 'welcome.message', defaultMessage: '{domain}へようこそ!' },
+ markdown: { id: 'markdown.list', defaultMessage: 'markdown一覧' },
+});
+
+const hashtags = Immutable.fromJS([
+ '神崎ドン自己紹介',
+]);
+
+class Announcements extends React.PureComponent {
+
+ static propTypes = {
+ intl: PropTypes.object.isRequired,
+ homeSize: PropTypes.number,
+ isLoading: PropTypes.bool,
+ };
+
+ state = {
+ showId: null,
+ isLoaded: false,
+ };
+
+ onClick = (announcementId, currentState) => {
+ this.setState({ showId: currentState.showId === announcementId ? null : announcementId });
+ }
+ nl2br (text) {
+ return text.split(/(\n)/g).map((line, i) => {
+ if (line.match(/(\n)/g)) {
+ return React.createElement('br', { key: i });
+ }
+ return line;
+ });
+ }
+
+ render () {
+ const { intl } = this.props;
+
+ return (
+
+ );
+ }
+
+ componentWillReceiveProps (nextProps) {
+ if (!this.state.isLoaded) {
+ if (!nextProps.isLoading && (nextProps.homeSize === 0 || this.props.homeSize !== nextProps.homeSize)) {
+ this.setState({ isLoaded: true });
+ }
+ }
+ }
+
+}
+
+export default injectIntl(Announcements);
diff --git a/app/javascript/mastodon/features/compose/containers/announcements_container.js b/app/javascript/mastodon/features/compose/containers/announcements_container.js
new file mode 100644
index 000000000..e1dbb29b9
--- /dev/null
+++ b/app/javascript/mastodon/features/compose/containers/announcements_container.js
@@ -0,0 +1,11 @@
+import { connect } from 'react-redux';
+import Announcements from '../components/announcements';
+
+const mapStateToProps = state => {
+ return {
+ homeSize: state.getIn(['timelines', 'home', 'items']).size,
+ isLoading: state.getIn(['timelines', 'home', 'isLoading']),
+ };
+};
+
+export default connect(mapStateToProps)(Announcements);
diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js
index 0731abcf4..b19b1b2e4 100644
--- a/app/javascript/mastodon/features/compose/index.js
+++ b/app/javascript/mastodon/features/compose/index.js
@@ -15,6 +15,7 @@ import { changeComposing } from '../../actions/compose';
import elephantUIPlane from '../../../images/elephant_ui_plane.svg';
import { mascot } from '../../initial_state';
import Icon from 'mastodon/components/icon';
+import AnnouncementsContainer from './containers/announcements_container';
const messages = defineMessages({
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
@@ -108,6 +109,7 @@ class Compose extends React.PureComponent {
+

diff --git a/app/javascript/styles/application.scss b/app/javascript/styles/application.scss
index cf32a70b0..4a4975a21 100644
--- a/app/javascript/styles/application.scss
+++ b/app/javascript/styles/application.scss
@@ -28,3 +28,4 @@
@import 'mastodon/accessibility';
@import 'markdown';
+@import 'astarte';
diff --git a/app/javascript/styles/astarte.scss b/app/javascript/styles/astarte.scss
new file mode 100644
index 000000000..66507b944
--- /dev/null
+++ b/app/javascript/styles/astarte.scss
@@ -0,0 +1,59 @@
+
+ .announcements {
+ padding: 0 10px;
+
+ li {
+ display: flex;
+ padding: 10px;
+ color: #282c37;
+ background: darken($white, 10%);
+ border-radius: 4px;
+
+ & + li {
+ margin-top: 10px;
+ }
+ }
+ }
+
+ .announcements__admin {
+ width: 100%;
+ position: relative;
+
+ p {
+ padding: 0 5px;
+ font-size: 14px;
+ }
+ }
+
+ .announcements__icon {
+ display: inline-block;
+ position: absolute;
+ margin: -5px 5px;
+ right: 10px;
+ }
+
+ .announcements__body {
+ width: 100%;
+ position: relative;
+
+ p {
+ padding: 0 5px;
+ font-size: 14px;
+ }
+
+ a {
+ display: inline-block;
+ float: right;
+ clear: both;
+ color: #282c37;
+ background: darken($white, 5%);
+ text-decoration: none;
+ padding: 1px 10px 0;
+ border: solid 1px #282c37;
+ font-size: 10px;
+ font-weight: 600;
+ border-radius: 4px;
+ margin-top: 5px;
+ }
+ }
+
diff --git a/app/lib/formatter_markdown.rb b/app/lib/formatter_markdown.rb
index 0ee01b972..13399069a 100644
--- a/app/lib/formatter_markdown.rb
+++ b/app/lib/formatter_markdown.rb
@@ -364,4 +364,4 @@ class MDExtractor
def htmlTagPatternOuterMostWithItem(tagName, itemTagName)
Regexp.compile("<#{tagName} data-md=[^>]*>(?:[^<>]|<#{itemTagName} data-md=[^>]*>|<\\/#{itemTagName}>|(\\g<0>))*<\/#{tagName}>")
end
-end
\ No newline at end of file
+end