Markdownの再実装
This commit is contained in:
parent
4d4f8b5f52
commit
8c9d3e4c2d
@ -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 (
|
||||
<Motion defaultStyle={{ rotate: this.props.active ? 180 : 0 }} style={{ rotate: this.props.animate ? spring(this.props.active ? 0 : 180) : 0 }}>
|
||||
{({ rotate }) =>
|
||||
<button
|
||||
aria-label={this.props.title}
|
||||
title={this.props.title}
|
||||
className={classes.join(' ')}
|
||||
onClick={this.handleClick}
|
||||
style={style}
|
||||
>
|
||||
<i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${this.props.icon}`} aria-hidden='true' />
|
||||
</button>
|
||||
}
|
||||
</Motion>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default IconButton;
|
@ -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 }) => (
|
||||
<Motion defaultStyle={{ height: isVisible ? fullHeight : minHeight }} style={{ height: spring(!isVisible ? minHeight : fullHeight) }}>
|
||||
{({ height }) =>
|
||||
<div style={{ height: `${height}px`, overflow: 'hidden' }}>
|
||||
{children}
|
||||
</div>
|
||||
}
|
||||
</Motion>
|
||||
);
|
||||
|
||||
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 (
|
||||
<ul className='announcements'>
|
||||
<li>
|
||||
<Collapsable isVisible={this.state.showId === 'markdown'} fullHeight={1240} minHeight={20} >
|
||||
<div className='announcements__body'>
|
||||
<p>{ this.nl2br(intl.formatMessage(messages.markdown, { domain: document.title }))}<br />
|
||||
<br />
|
||||
(半角)は半角スペースを入力する必要がある場所です。(半角)だけの列は半角スペースのみが入力された列が必要であるを指します。<br /><br />
|
||||
〜〜〜〜〜〜見出し〜〜〜〜〜〜<br /><br />
|
||||
#(半角)見出しテキスト<br /><br />
|
||||
#は1〜6個重ねることができます。<br /><br />
|
||||
〜〜〜〜コードブロック〜〜〜〜<br /><br />
|
||||
`コード`<br /><br />
|
||||
〜〜〜〜〜〜引用〜〜〜〜〜〜<br /><br />
|
||||
>引用文<br />
|
||||
(半角)<br />
|
||||
ここから先は引用が切れます<br />
|
||||
引用は複数回重ねることが可能です。<br /><br />
|
||||
〜〜〜〜〜〜リスト〜〜〜〜〜〜<br /><br />
|
||||
(半角)<br />
|
||||
+(半角)内容1<br />
|
||||
+(半角)内容2<br />
|
||||
(半角)<br /><br />
|
||||
内容の数に制限はありません。<br />
|
||||
投稿トップにリストを持ってくる場合に限り1行目の(半角)は必要ありません。<br />
|
||||
+(半角)を1.(半角)に置き換えることで数字付きリストになります。<br /><br />
|
||||
〜〜〜〜〜上付き文字〜〜〜〜〜<br /><br />
|
||||
_上付き文字_<br /><br />
|
||||
〜〜〜〜〜下付き文字〜〜〜〜〜<br /><br />
|
||||
__下付き文字__<br /><br />
|
||||
〜〜〜〜〜小さい文字〜〜〜〜〜<br /><br />
|
||||
___小さい文字___<br /><br />
|
||||
〜〜〜〜〜取り消し線〜〜〜〜〜<br /><br />
|
||||
~~取り消したい文字列~~<br /><br />
|
||||
〜〜〜〜〜〜横罫線〜〜〜〜〜〜<br /><br />
|
||||
___<br /><br />
|
||||
〜〜〜〜〜〜リンク〜〜〜〜〜〜<br /><br />
|
||||
[リンク文章](https://・・・)<br /><br />
|
||||
〜〜〜〜〜〜画像〜〜〜〜〜〜<br /><br />
|
||||
<br /><br />
|
||||
リンク、画像ともにURLにはhttps://から始まる物のみご利用可能です。
|
||||
</p>
|
||||
</div>
|
||||
</Collapsable>
|
||||
<div className='announcements__icon'>
|
||||
<IconButton title={intl.formatMessage(messages.toggle_visible)} icon='caret-up' onClick={() => this.onClick('markdown', this.state)} size={20} animate active={this.state.showId === 'markdown'} />
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
@ -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);
|
@ -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 {
|
||||
<NavigationContainer onClose={this.onBlur} />
|
||||
|
||||
<ComposeFormContainer />
|
||||
<AnnouncementsContainer />
|
||||
|
||||
<div className='drawer__inner__mastodon'>
|
||||
<img alt='' draggable='false' src={mascot || elephantUIPlane} />
|
||||
|
@ -28,3 +28,4 @@
|
||||
@import 'mastodon/accessibility';
|
||||
|
||||
@import 'markdown';
|
||||
@import 'astarte';
|
||||
|
59
app/javascript/styles/astarte.scss
Normal file
59
app/javascript/styles/astarte.scss
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -364,4 +364,4 @@ class MDExtractor
|
||||
def htmlTagPatternOuterMostWithItem(tagName, itemTagName)
|
||||
Regexp.compile("<#{tagName} data-md=[^>]*>(?:[^<>]|<#{itemTagName} data-md=[^>]*>|<\\/#{itemTagName}>|(\\g<0>))*<\/#{tagName}>")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user