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 elephantUIPlane from '../../../images/elephant_ui_plane.svg';
|
||||||
import { mascot } from '../../initial_state';
|
import { mascot } from '../../initial_state';
|
||||||
import Icon from 'mastodon/components/icon';
|
import Icon from 'mastodon/components/icon';
|
||||||
|
import AnnouncementsContainer from './containers/announcements_container';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
|
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
|
||||||
@ -108,6 +109,7 @@ class Compose extends React.PureComponent {
|
|||||||
<NavigationContainer onClose={this.onBlur} />
|
<NavigationContainer onClose={this.onBlur} />
|
||||||
|
|
||||||
<ComposeFormContainer />
|
<ComposeFormContainer />
|
||||||
|
<AnnouncementsContainer />
|
||||||
|
|
||||||
<div className='drawer__inner__mastodon'>
|
<div className='drawer__inner__mastodon'>
|
||||||
<img alt='' draggable='false' src={mascot || elephantUIPlane} />
|
<img alt='' draggable='false' src={mascot || elephantUIPlane} />
|
||||||
|
@ -28,3 +28,4 @@
|
|||||||
@import 'mastodon/accessibility';
|
@import 'mastodon/accessibility';
|
||||||
|
|
||||||
@import 'markdown';
|
@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)
|
def htmlTagPatternOuterMostWithItem(tagName, itemTagName)
|
||||||
Regexp.compile("<#{tagName} data-md=[^>]*>(?:[^<>]|<#{itemTagName} data-md=[^>]*>|<\\/#{itemTagName}>|(\\g<0>))*<\/#{tagName}>")
|
Regexp.compile("<#{tagName} data-md=[^>]*>(?:[^<>]|<#{itemTagName} data-md=[^>]*>|<\\/#{itemTagName}>|(\\g<0>))*<\/#{tagName}>")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user