Progress
This commit is contained in:
@@ -3,6 +3,7 @@ import { CancelToken, isCancel } from 'axios';
|
||||
import { throttle } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { search as emojiSearch } from '../components/emoji/emoji_mart_search_light';
|
||||
import { urlRegex } from '../features/compose/util/url_regex'
|
||||
import { tagHistory } from '../settings';
|
||||
import { useEmoji } from './emojis';
|
||||
import resizeImage from '../utils/resize_image';
|
||||
@@ -62,6 +63,8 @@ export const COMPOSE_POLL_SETTINGS_CHANGE = 'COMPOSE_POLL_SETTINGS_CHANGE';
|
||||
|
||||
export const COMPOSE_SCHEDULED_AT_CHANGE = 'COMPOSE_SCHEDULED_AT_CHANGE';
|
||||
|
||||
export const COMPOSE_RICH_TEXT_EDITOR_CONTROLS_VISIBILITY = 'COMPOSE_RICH_TEXT_EDITOR_CONTROLS_VISIBILITY'
|
||||
|
||||
const messages = defineMessages({
|
||||
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
|
||||
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
|
||||
@@ -170,12 +173,20 @@ export function submitCompose(routerHistory, group) {
|
||||
return function (dispatch, getState) {
|
||||
if (!me) return;
|
||||
|
||||
const status = getState().getIn(['compose', 'text'], '');
|
||||
let status = getState().getIn(['compose', 'text'], '');
|
||||
let statusMarkdown = getState().getIn(['compose', 'text_markdown'], '');
|
||||
const media = getState().getIn(['compose', 'media_attachments']);
|
||||
|
||||
if ((!status || !status.length) && media.size === 0) {
|
||||
return;
|
||||
}
|
||||
// : hack :
|
||||
//Prepend http:// to urls in status that don't have protocol
|
||||
status = status.replace(urlRegex, (match) =>{
|
||||
const hasProtocol = match.startsWith('https://') || match.startsWith('http://')
|
||||
return hasProtocol ? match : `http://${match}`
|
||||
})
|
||||
statusMarkdown = statusMarkdown.replace(urlRegex, (match) =>{
|
||||
const hasProtocol = match.startsWith('https://') || match.startsWith('http://')
|
||||
return hasProtocol ? match : `http://${match}`
|
||||
})
|
||||
|
||||
dispatch(submitComposeRequest());
|
||||
dispatch(closeModal());
|
||||
@@ -191,6 +202,7 @@ export function submitCompose(routerHistory, group) {
|
||||
|
||||
api(getState)[method](endpoint, {
|
||||
status,
|
||||
statusMarkdown,
|
||||
scheduled_at,
|
||||
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
|
||||
quote_of_id: getState().getIn(['compose', 'quote_of_id'], null),
|
||||
@@ -593,4 +605,10 @@ export function changeScheduledAt(date) {
|
||||
type: COMPOSE_SCHEDULED_AT_CHANGE,
|
||||
date,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export function changeRichTextEditorControlsVisibility() {
|
||||
return {
|
||||
type: COMPOSE_RICH_TEXT_EDITOR_CONTROLS_VISIBILITY,
|
||||
}
|
||||
}
|
||||
26
app/javascript/gabsocial/assets/donor_icon.js
Normal file
26
app/javascript/gabsocial/assets/donor_icon.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const DonorIcon = ({
|
||||
className = '',
|
||||
width = '24px',
|
||||
height = '24px',
|
||||
viewBox = '0 0 24 24',
|
||||
title = 'Gab.com Donor',
|
||||
}) => (
|
||||
<svg
|
||||
className={className}
|
||||
version='1.1'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
x='0px'
|
||||
y='0px'
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox={viewBox}
|
||||
xmlSpace='preserve'
|
||||
aria-label={title}
|
||||
>
|
||||
<g>
|
||||
<path fill='purple' d='M22.5 12.5c0-1.58-.875-2.95-2.148-3.6.154-.435.238-.905.238-1.4 0-2.21-1.71-3.998-3.818-3.998-.47 0-.92.084-1.336.25C14.818 2.415 13.51 1.5 12 1.5s-2.816.917-3.437 2.25c-.415-.165-.866-.25-1.336-.25-2.11 0-3.818 1.79-3.818 4 0 .494.083.964.237 1.4-1.272.65-2.147 2.018-2.147 3.6 0 1.495.782 2.798 1.942 3.486-.02.17-.032.34-.032.514 0 2.21 1.708 4 3.818 4 .47 0 .92-.086 1.335-.25.62 1.334 1.926 2.25 3.437 2.25 1.512 0 2.818-.916 3.437-2.25.415.163.865.248 1.336.248 2.11 0 3.818-1.79 3.818-4 0-.174-.012-.344-.033-.513 1.158-.687 1.943-1.99 1.943-3.484zm-6.616-3.334l-4.334 6.5c-.145.217-.382.334-.625.334-.143 0-.288-.04-.416-.126l-.115-.094-2.415-2.415c-.293-.293-.293-.768 0-1.06s.768-.294 1.06 0l1.77 1.767 3.825-5.74c.23-.345.696-.436 1.04-.207.346.23.44.696.21 1.04z' />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default DonorIcon
|
||||
@@ -1,9 +1,11 @@
|
||||
const GabLogo = ({
|
||||
className = _s.fillColorBrand,
|
||||
width = '50px',
|
||||
height = '30px',
|
||||
viewBox = '0 0 50 30'
|
||||
}) => (
|
||||
<svg
|
||||
className={className}
|
||||
version='1.1'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
x='0px'
|
||||
@@ -14,14 +16,14 @@ const GabLogo = ({
|
||||
xmlSpace='preserve'
|
||||
>
|
||||
<g>
|
||||
<path className={_s.fillColorBrand} d='M13.8,7.6h-2.4v0.7V9l-0.4-0.3C10.2,7.8,9,7.2,7.7,7.2c-0.2,0-0.4,0-0.4,0c-0.1,0-0.3,0-0.5,0
|
||||
<path className={_s.inheritFill} d='M13.8,7.6h-2.4v0.7V9l-0.4-0.3C10.2,7.8,9,7.2,7.7,7.2c-0.2,0-0.4,0-0.4,0c-0.1,0-0.3,0-0.5,0
|
||||
c-5.6,0.3-8.7,7.2-5.4,12.1c2.3,3.4,7.1,4.1,9.7,1.5l0.3-0.3l0,0.7c0,1-0.1,1.5-0.4,2.2c-1,2.4-4.1,3-6.8,1.3
|
||||
c-0.2-0.1-0.4-0.2-0.4-0.2c-0.1,0.1-1.9,3.5-1.9,3.6c0,0.1,0.5,0.4,0.8,0.6c2.2,1.4,5.6,1.7,8.3,0.8c2.7-0.9,4.5-3.2,5-6.4
|
||||
c0.2-1.1,0.2-0.8,0.2-8.4l0-7.1H13.8z M9.7,17.6c-2.2,1.2-4.9-0.4-4.9-2.9C4.8,12.6,7,11,9,11.6C11.8,12.4,12.3,16.1,9.7,17.6z'/>
|
||||
<path className={_s.fillColorBrand} d='M45.6,7.7c-2.4-1-5-0.6-6.7,1L38.6,9V4.5V0h-2.4h-2.4v11v11h2.4h2.4v-0.7v-0.7l0.3,0.3
|
||||
<path className={_s.inheritFill} d='M45.6,7.7c-2.4-1-5-0.6-6.7,1L38.6,9V4.5V0h-2.4h-2.4v11v11h2.4h2.4v-0.7v-0.7l0.3,0.3
|
||||
c2.4,2.4,6.9,1.9,9.3-0.9C51.5,15.9,50.1,9.6,45.6,7.7z M43.7,17.5c-2.1,1.4-5.1-0.2-5.1-2.8c0-2.1,1.9-3.7,3.9-3.3
|
||||
C45.4,12.1,46.2,15.8,43.7,17.5z'/>
|
||||
<path className={_s.fillColorBrand} d='M31.5,12.5c-0.7-3.7-3.1-5.5-7.1-5.3c-1.7,0.1-4,0.7-4.8,1.4l-0.1,0.1l0.7,1.7c0.4,0.9,0.7,1.7,0.7,1.7
|
||||
<path className={_s.inheritFill} d='M31.5,12.5c-0.7-3.7-3.1-5.5-7.1-5.3c-1.7,0.1-4,0.7-4.8,1.4l-0.1,0.1l0.7,1.7c0.4,0.9,0.7,1.7,0.7,1.7
|
||||
c0,0,0.1,0,0.2-0.1c2.7-1.4,5.4-0.9,5.6,1.1l0,0.2l-0.4-0.1c-3-0.8-6.3-0.1-7.7,1.6c-1.8,2.2-0.9,5.8,1.7,7.1
|
||||
c2.1,1,4.6,0.6,6.1-0.8l0.2-0.2v0.6v0.6h2.4h2.4v-4C31.7,13.7,31.7,13.3,31.5,12.5z M26.9,16.4c-0.1,0.7-0.5,1.5-1,2
|
||||
c-1.2,1.1-3,0.7-3.2-0.7c-0.2-1,0.6-1.7,2-1.8c0.1,0,0.2,0,0.2,0c0,0,0.2,0,0.4,0c0.5,0,1,0.1,1.4,0.2l0.3,0.1L26.9,16.4z'/>
|
||||
|
||||
26
app/javascript/gabsocial/assets/investor_icon.js
Normal file
26
app/javascript/gabsocial/assets/investor_icon.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const InvestorIcon = ({
|
||||
className = '',
|
||||
width = '24px',
|
||||
height = '24px',
|
||||
viewBox = '0 0 24 24',
|
||||
title = 'Gab.com Investor',
|
||||
}) => (
|
||||
<svg
|
||||
className={className}
|
||||
version='1.1'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
x='0px'
|
||||
y='0px'
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox={viewBox}
|
||||
xmlSpace='preserve'
|
||||
aria-label={title}
|
||||
>
|
||||
<g>
|
||||
<path fill='red' d='M22.5 12.5c0-1.58-.875-2.95-2.148-3.6.154-.435.238-.905.238-1.4 0-2.21-1.71-3.998-3.818-3.998-.47 0-.92.084-1.336.25C14.818 2.415 13.51 1.5 12 1.5s-2.816.917-3.437 2.25c-.415-.165-.866-.25-1.336-.25-2.11 0-3.818 1.79-3.818 4 0 .494.083.964.237 1.4-1.272.65-2.147 2.018-2.147 3.6 0 1.495.782 2.798 1.942 3.486-.02.17-.032.34-.032.514 0 2.21 1.708 4 3.818 4 .47 0 .92-.086 1.335-.25.62 1.334 1.926 2.25 3.437 2.25 1.512 0 2.818-.916 3.437-2.25.415.163.865.248 1.336.248 2.11 0 3.818-1.79 3.818-4 0-.174-.012-.344-.033-.513 1.158-.687 1.943-1.99 1.943-3.484zm-6.616-3.334l-4.334 6.5c-.145.217-.382.334-.625.334-.143 0-.288-.04-.416-.126l-.115-.094-2.415-2.415c-.293-.293-.293-.768 0-1.06s.768-.294 1.06 0l1.77 1.767 3.825-5.74c.23-.345.696-.436 1.04-.207.346.23.44.696.21 1.04z' />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default InvestorIcon
|
||||
26
app/javascript/gabsocial/assets/pro_icon.js
Normal file
26
app/javascript/gabsocial/assets/pro_icon.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const ProIcon = ({
|
||||
className = '',
|
||||
width = '24px',
|
||||
height = '24px',
|
||||
viewBox = '0 0 24 24',
|
||||
title = 'GabPRO Account',
|
||||
}) => (
|
||||
<svg
|
||||
className={className}
|
||||
version='1.1'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
x='0px'
|
||||
y='0px'
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox={viewBox}
|
||||
xmlSpace='preserve'
|
||||
aria-label={title}
|
||||
>
|
||||
<g>
|
||||
<path fill='green' d='M22.5 12.5c0-1.58-.875-2.95-2.148-3.6.154-.435.238-.905.238-1.4 0-2.21-1.71-3.998-3.818-3.998-.47 0-.92.084-1.336.25C14.818 2.415 13.51 1.5 12 1.5s-2.816.917-3.437 2.25c-.415-.165-.866-.25-1.336-.25-2.11 0-3.818 1.79-3.818 4 0 .494.083.964.237 1.4-1.272.65-2.147 2.018-2.147 3.6 0 1.495.782 2.798 1.942 3.486-.02.17-.032.34-.032.514 0 2.21 1.708 4 3.818 4 .47 0 .92-.086 1.335-.25.62 1.334 1.926 2.25 3.437 2.25 1.512 0 2.818-.916 3.437-2.25.415.163.865.248 1.336.248 2.11 0 3.818-1.79 3.818-4 0-.174-.012-.344-.033-.513 1.158-.687 1.943-1.99 1.943-3.484zm-6.616-3.334l-4.334 6.5c-.145.217-.382.334-.625.334-.143 0-.288-.04-.416-.126l-.115-.094-2.415-2.415c-.293-.293-.293-.768 0-1.06s.768-.294 1.06 0l1.77 1.767 3.825-5.74c.23-.345.696-.436 1.04-.207.346.23.44.696.21 1.04z' />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default ProIcon
|
||||
@@ -253,10 +253,21 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
||||
<div className={[_s.default, _s.flexGrow1].join(' ')}>
|
||||
<div className={[_s.default, _s.ml5].join(' ')}>
|
||||
|
||||
{/*<Composer
|
||||
/>*/}
|
||||
<Composer
|
||||
inputRef={this.setTextbox}
|
||||
disabled={disabled}
|
||||
placeholder={placeholder}
|
||||
autoFocus={autoFocus}
|
||||
value={value}
|
||||
onChange={this.onChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
onKeyUp={onKeyUp}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
onPaste={this.onPaste}
|
||||
/>
|
||||
|
||||
<Textarea
|
||||
{ /* <Textarea
|
||||
className={_s.default}
|
||||
inputRef={this.setTextbox}
|
||||
disabled={disabled}
|
||||
@@ -270,8 +281,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
||||
onBlur={this.onBlur}
|
||||
onPaste={this.onPaste}
|
||||
aria-autocomplete='list'
|
||||
/>
|
||||
|
||||
/> */ }
|
||||
|
||||
{ /*
|
||||
<Textarea
|
||||
|
||||
@@ -1,36 +1,315 @@
|
||||
import {
|
||||
Editor,
|
||||
EditorState,
|
||||
CompositeDecorator,
|
||||
RichUtils
|
||||
} from 'draft-js'
|
||||
import { urlRegex } from '../features/compose/util/url_regex'
|
||||
import classNames from 'classnames/bind'
|
||||
import Button from './button'
|
||||
|
||||
const cx = classNames.bind(_s)
|
||||
|
||||
const getBlockStyle = (block) => {
|
||||
switch (block.getType()) {
|
||||
case 'blockquote':
|
||||
return 'RichEditor-blockquote'
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function handleStrategy(contentBlock, callback, contentState) {
|
||||
findWithRegex(HANDLE_REGEX, contentBlock, callback)
|
||||
}
|
||||
|
||||
function hashtagStrategy (contentBlock, callback, contentState) {
|
||||
findWithRegex(HASHTAG_REGEX, contentBlock, callback)
|
||||
}
|
||||
|
||||
function urlStrategy (contentBlock, callback, contentState) {
|
||||
findWithRegex(urlRegex, contentBlock, callback)
|
||||
}
|
||||
|
||||
const findWithRegex = (regex, contentBlock, callback) => {
|
||||
const text = contentBlock.getText()
|
||||
let matchArr, start
|
||||
while ((matchArr = regex.exec(text)) !== null) {
|
||||
start = matchArr.index
|
||||
callback(start, start + matchArr[0].length)
|
||||
}
|
||||
}
|
||||
|
||||
const HighlightedSpan = (props) => {
|
||||
console.log("HighlightedSpan:", props)
|
||||
return (
|
||||
<span
|
||||
className={_s.colorBrand}
|
||||
data-offset-key={props.offsetKey}
|
||||
>
|
||||
{props.children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const RTE_ITEMS = [
|
||||
{
|
||||
label: 'Bold',
|
||||
style: 'BOLD',
|
||||
type: 'style',
|
||||
icon: 'circle',
|
||||
},
|
||||
{
|
||||
label: 'Italic',
|
||||
style: 'ITALIC',
|
||||
type: 'style',
|
||||
icon: 'circle',
|
||||
},
|
||||
{
|
||||
label: 'Underline',
|
||||
style: 'UNDERLINE',
|
||||
type: 'style',
|
||||
icon: 'circle',
|
||||
},
|
||||
{
|
||||
label: 'Monospace',
|
||||
style: 'CODE',
|
||||
type: 'style',
|
||||
icon: 'circle',
|
||||
},
|
||||
{
|
||||
label: 'H1',
|
||||
style: 'header-one',
|
||||
type: 'block',
|
||||
icon: 'circle',
|
||||
},
|
||||
{
|
||||
label: 'Blockquote',
|
||||
style: 'blockquote',
|
||||
type: 'block',
|
||||
icon: 'circle',
|
||||
},
|
||||
{
|
||||
label: 'UL',
|
||||
style: 'unordered-list-item',
|
||||
type: 'block',
|
||||
icon: 'circle',
|
||||
},
|
||||
{
|
||||
label: 'OL',
|
||||
style: 'ordered-list-item',
|
||||
type: 'block',
|
||||
icon: 'circle',
|
||||
},
|
||||
{
|
||||
label: 'Code Block',
|
||||
style: 'code-block',
|
||||
type: 'block',
|
||||
icon: 'circle',
|
||||
},
|
||||
]
|
||||
|
||||
const compositeDecorator = new CompositeDecorator([
|
||||
{
|
||||
strategy: handleStrategy,
|
||||
component: HighlightedSpan,
|
||||
},
|
||||
{
|
||||
strategy: hashtagStrategy,
|
||||
component: HighlightedSpan,
|
||||
},
|
||||
{
|
||||
strategy: urlStrategy,
|
||||
component: HighlightedSpan,
|
||||
}
|
||||
])
|
||||
|
||||
const HANDLE_REGEX = /\@[\w]+/g;
|
||||
const HASHTAG_REGEX = /\#[\w\u0590-\u05ff]+/g;
|
||||
|
||||
export default class Composer extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
inputRef: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
placeholder: PropTypes.string,
|
||||
autoFocus: PropTypes.bool,
|
||||
value: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
onKeyDown: PropTypes.func,
|
||||
onKeyUp: PropTypes.func,
|
||||
onFocus: PropTypes.func,
|
||||
onBlur: PropTypes.func,
|
||||
onPaste: PropTypes.func,
|
||||
}
|
||||
|
||||
state = {
|
||||
editorState: EditorState.createEmpty(),
|
||||
editorState: EditorState.createEmpty(compositeDecorator),
|
||||
}
|
||||
|
||||
onChange = (editorState) => {
|
||||
this.setState({ editorState })
|
||||
}
|
||||
|
||||
onBoldClick() {
|
||||
this.onChange(RichUtils.toggleInlineStyle(this.state.editorState, 'BOLD'));
|
||||
onToggleInlineStyle = (style) => {
|
||||
this.onChange(RichUtils.toggleInlineStyle(this.state.editorState, style))
|
||||
}
|
||||
|
||||
focus = () => {
|
||||
this.textbox.editor.focus()
|
||||
}
|
||||
|
||||
handleKeyCommand = (command) => {
|
||||
const { editorState } = this.state
|
||||
const newState = RichUtils.handleKeyCommand(editorState, command)
|
||||
|
||||
if (newState) {
|
||||
this.onChange(newState)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
onTab = (e) => {
|
||||
const maxDepth = 4
|
||||
this.onChange(RichUtils.onTab(e, this.state.editorState, maxDepth))
|
||||
}
|
||||
|
||||
toggleBlockType = (blockType) => {
|
||||
this.onChange(
|
||||
RichUtils.toggleBlockType(
|
||||
this.state.editorState,
|
||||
blockType
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
toggleInlineStyle = (inlineStyle) => {
|
||||
this.onChange(
|
||||
RichUtils.toggleInlineStyle(
|
||||
this.state.editorState,
|
||||
inlineStyle
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
setRef = (n) => {
|
||||
this.textbox = n
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
inputRef,
|
||||
disabled,
|
||||
placeholder,
|
||||
autoFocus,
|
||||
// value,
|
||||
onChange,
|
||||
onKeyDown,
|
||||
onKeyUp,
|
||||
onFocus,
|
||||
onBlur,
|
||||
onPaste
|
||||
} = this.props
|
||||
const { editorState } = this.state
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={[_s.default].join(' ')}>
|
||||
|
||||
{/*<button onClick={this.onBoldClick.bind(this)}>Bold</button>*/}
|
||||
|
||||
<Editor
|
||||
editorState={this.state.editorState}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
<div className={[_s.default, _s.backgroundColorPrimary, _s.borderBottom1PX, _s.borderColorSecondary, _s.py5, _s.px15, _s.alignItemsCenter, _s.flexRow].join(' ')}>
|
||||
{
|
||||
RTE_ITEMS.map((item, i) => (
|
||||
<StyleButton
|
||||
key={`rte-button-${i}`}
|
||||
editorState={editorState}
|
||||
{...item}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
<div
|
||||
onClick={this.focus}
|
||||
className={[_s.text, _s.fontSize16PX].join(' ')}
|
||||
>
|
||||
<Editor
|
||||
blockStyleFn={getBlockStyle}
|
||||
// customStyleMap={styleMap}
|
||||
editorState={editorState}
|
||||
handleKeyCommand={this.handleKeyCommand}
|
||||
onChange={this.onChange}
|
||||
onTab={this.onTab}
|
||||
placeholder={placeholder}
|
||||
ref={this.setRef}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class StyleButton extends PureComponent {
|
||||
static propTypes = {
|
||||
onToggle: PropTypes.func,
|
||||
label: PropTypes.string,
|
||||
style: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
}
|
||||
|
||||
handleOnToggle = (e) => {
|
||||
const { onToggle, style } = this.props
|
||||
e.preventDefault()
|
||||
onToggle(style)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
label,
|
||||
style,
|
||||
type,
|
||||
icon,
|
||||
editorState
|
||||
} = this.props
|
||||
|
||||
const selection = editorState.getSelection()
|
||||
|
||||
const currentStyle = editorState.getCurrentInlineStyle()
|
||||
const blockType = editorState.getCurrentContent().getBlockForKey(selection.getStartKey()).getType()
|
||||
|
||||
let active
|
||||
// active={type.style === blockType}
|
||||
// active={currentStyle.has(type.style)}
|
||||
|
||||
const btnClasses = cx({
|
||||
px10: 1,
|
||||
mr5: 1,
|
||||
backgroundSubtle2Dark_onHover: 1,
|
||||
backgroundColorBrandLight: active,
|
||||
// py10: !small,
|
||||
// py5: small,
|
||||
// px5: small,
|
||||
})
|
||||
|
||||
const iconClasses = cx({
|
||||
fillColorSecondary: !active,
|
||||
fillColorWhite: active,
|
||||
})
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={btnClasses}
|
||||
backgroundColor='none'
|
||||
onClick={this.handleOnToggle}
|
||||
title={label}
|
||||
icon={'rich-text'}
|
||||
iconClassName={iconClasses}
|
||||
iconWidth='10px'
|
||||
iconHeight='10px'
|
||||
radiusSmall
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -105,6 +105,11 @@ class DisplayName extends ImmutablePureComponent {
|
||||
!!large ? '19px' :
|
||||
!!small ? '14px' : '16px'
|
||||
|
||||
const domain = account.get('acct').split('@')[1];
|
||||
const isRemoteUser = !!domain
|
||||
|
||||
console.log("domain, isRemoteUser:", domain, isRemoteUser)
|
||||
|
||||
// : todo :
|
||||
return (
|
||||
<span {...containerOptions} ref={this.setRef}>
|
||||
@@ -117,20 +122,20 @@ class DisplayName extends ImmutablePureComponent {
|
||||
</bdi>
|
||||
{
|
||||
account.get('is_verified') &&
|
||||
<Icon id='verified' width={iconSize} height={iconSize} className={_s.default} title='Verified Account' />
|
||||
<Icon id='verified' width={iconSize} height={iconSize} className={_s.default} />
|
||||
}
|
||||
{ /*
|
||||
{
|
||||
account.get('is_pro') &&
|
||||
<Icon id='verified' width='16px' height='16px' className={_s.default} title='Gab PRO' />
|
||||
*/ }
|
||||
{ /*
|
||||
<Icon id='pro' width='16px' height='16px' className={_s.default} />
|
||||
}
|
||||
{
|
||||
account.get('is_donor') &&
|
||||
<Icon id='verified' width='16px' height='16px' className={_s.default} title='Gab Donor' />
|
||||
*/ }
|
||||
{ /*
|
||||
<Icon id='donor' width='16px' height='16px' className={_s.default} />
|
||||
}
|
||||
{
|
||||
account.get('is_investor') &&
|
||||
<Icon id='verified' width='16px' height='16px' className={_s.default} title='Gab Investor' />
|
||||
*/ }
|
||||
<Icon id='investor' width='16px' height='16px' className={_s.default} />
|
||||
}
|
||||
</div>
|
||||
{
|
||||
!noUsername &&
|
||||
|
||||
@@ -10,15 +10,18 @@ import CircleIcon from '../assets/circle_icon'
|
||||
import CloseIcon from '../assets/close_icon'
|
||||
import CommentIcon from '../assets/comment_icon'
|
||||
import DissenterIcon from '../assets/dissenter_icon'
|
||||
import DonorIcon from '../assets/donor_icon'
|
||||
import EllipsisIcon from '../assets/ellipsis_icon'
|
||||
import ErrorIcon from '../assets/error_icon'
|
||||
import FullscreenIcon from '../assets/fullscreen_icon'
|
||||
import GabLogoIcon from '../assets/gab_logo'
|
||||
import GifIcon from '../assets/gif_icon'
|
||||
import GlobeIcon from '../assets/globe_icon'
|
||||
import GroupIcon from '../assets/group_icon'
|
||||
import GroupAddIcon from '../assets/group_add_icon'
|
||||
import HappyIcon from '../assets/happy_icon'
|
||||
import HomeIcon from '../assets/home_icon'
|
||||
import InvestorIcon from '../assets/investor_icon'
|
||||
import LikeIcon from '../assets/like_icon'
|
||||
import LinkIcon from '../assets/link_icon'
|
||||
import ListIcon from '../assets/list_icon'
|
||||
@@ -33,6 +36,7 @@ import PauseIcon from '../assets/pause_icon'
|
||||
import PinIcon from '../assets/pin_icon'
|
||||
import PlayIcon from '../assets/play_icon'
|
||||
import PollIcon from '../assets/poll_icon'
|
||||
import ProIcon from '../assets/pro_icon'
|
||||
import RepostIcon from '../assets/repost_icon'
|
||||
import RichTextIcon from '../assets/rich_text_icon'
|
||||
import SearchIcon from '../assets/search_icon'
|
||||
@@ -56,15 +60,18 @@ const ICONS = {
|
||||
'close': CloseIcon,
|
||||
'comment': CommentIcon,
|
||||
'dissenter': DissenterIcon,
|
||||
'donor': DonorIcon,
|
||||
'ellipsis': EllipsisIcon,
|
||||
'error': ErrorIcon,
|
||||
'fullscreen': FullscreenIcon,
|
||||
'gab-logo': GabLogoIcon,
|
||||
'gif': GifIcon,
|
||||
'globe': GlobeIcon,
|
||||
'group': GroupIcon,
|
||||
'group-add': GroupAddIcon,
|
||||
'happy': HappyIcon,
|
||||
'home': HomeIcon,
|
||||
'investor': InvestorIcon,
|
||||
'like': LikeIcon,
|
||||
'link': LinkIcon,
|
||||
'list': ListIcon,
|
||||
@@ -79,6 +86,7 @@ const ICONS = {
|
||||
'pin': PinIcon,
|
||||
'play': PlayIcon,
|
||||
'poll': PollIcon,
|
||||
'pro': ProIcon,
|
||||
'repost': RepostIcon,
|
||||
'rich-text': RichTextIcon,
|
||||
'search': SearchIcon,
|
||||
|
||||
@@ -134,7 +134,7 @@ class LinkFooter extends PureComponent {
|
||||
defaultMessage='Gab Social is open source software. You can contribute or report issues on our self-hosted GitLab at {gitlab}.'
|
||||
values={{
|
||||
gitlab: (
|
||||
<a href={source_url} className={_s.inherit} rel='noopener' target='_blank'>
|
||||
<a href={source_url} className={_s.inherit} rel='noopener noreferrer' target='_blank'>
|
||||
{repository}
|
||||
</a>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import Button from '../button'
|
||||
import Text from '../text'
|
||||
import ModalLayout from './modal_layout'
|
||||
|
||||
const messages = defineMessages({
|
||||
|
||||
})
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
class LeavingGabModal extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl } = this.props
|
||||
|
||||
return (
|
||||
<ModalLayout>
|
||||
</ModalLayout>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -96,12 +96,12 @@ class ProfileInfoPanel extends ImmutablePureComponent {
|
||||
|
||||
{ /* : todo : */}
|
||||
<dd className='verified'>
|
||||
<a href={proof.get('proof_url')} target='_blank' rel='noopener'>
|
||||
<a href={proof.get('proof_url')} target='_blank' rel='noopener noreferrer'>
|
||||
<span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(proof.get('updated_at'), dateFormatOptions) })}>
|
||||
<Icon id='check' className='verified__mark' />
|
||||
</span>
|
||||
</a>
|
||||
<a href={proof.get('profile_url')} target='_blank' rel='noopener'>
|
||||
<a href={proof.get('profile_url')} target='_blank' rel='noopener noreferrer'>
|
||||
<span
|
||||
className={_s.dangerousContent}
|
||||
dangerouslySetInnerHTML={{ __html: ' ' + proof.get('provider_username') }}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { shortNumberFormat } from '../../utils/numbers'
|
||||
import PopoverLayout from './popover_layout'
|
||||
import Avatar from '../avatar'
|
||||
import Button from '../button'
|
||||
import DisplayName from '../display_name'
|
||||
import Text from '../text'
|
||||
|
||||
export default class RemoteUserInfoPopover extends ImmutablePureComponent {
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
}
|
||||
|
||||
render() {
|
||||
const { account } = this.props
|
||||
|
||||
const content = !account ? null : { __html: account.get('note_emojified') }
|
||||
|
||||
return (
|
||||
<PopoverLayout>
|
||||
<div className={[_s.default, _s.px15, _s.py15].join(' ')} style={{width: '260px'}}>
|
||||
|
||||
<div className={[_s.default, _s.flexRow].join(' ')}>
|
||||
<div className={[_s.default, _s.flexGrow1].join(' ')}>
|
||||
<Avatar account={account} size={42} />
|
||||
<DisplayName account={account} multiline noHover />
|
||||
</div>
|
||||
<div className={[_s.default, _s.marginLeftAuto].join(' ')}>
|
||||
<Button
|
||||
color='white'
|
||||
>
|
||||
<Text size='medium' weight='bold' color='inherit'>
|
||||
Following
|
||||
</Text>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={[_s.default, _s.my10].join(' ')}>
|
||||
<div className={_s.dangerousContent} dangerouslySetInnerHTML={content} />
|
||||
</div>
|
||||
|
||||
<div className={[_s.default, _s.flexRow].join(' ')}>
|
||||
<div>
|
||||
<Text size='small'>
|
||||
{shortNumberFormat(account.get('following_count'))}
|
||||
Following
|
||||
</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text size='small'>
|
||||
{shortNumberFormat(account.get('followers_count'))}
|
||||
Followers
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</PopoverLayout>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ class StatusOptionsPopover extends ImmutablePureComponent {
|
||||
menu.push({
|
||||
icon: 'circle',
|
||||
hideArrow: true,
|
||||
title: formatMessage(messages.open),
|
||||
title: intl.formatMessage(messages.open),
|
||||
onClick: this.handleOpen
|
||||
});
|
||||
|
||||
@@ -76,13 +76,13 @@ class StatusOptionsPopover extends ImmutablePureComponent {
|
||||
menu.push({
|
||||
icon: 'circle',
|
||||
hideArrow: true,
|
||||
title: formatMessage(messages.copy),
|
||||
title: intl.formatMessage(messages.copy),
|
||||
onClick: this.handleCopy,
|
||||
})
|
||||
menu.push({
|
||||
icon: 'circle',
|
||||
hideArrow: true,
|
||||
title: formatMessage(messages.embed),
|
||||
title: intl.formatMessage(messages.embed),
|
||||
onClick: this.handleEmbed,
|
||||
})
|
||||
}
|
||||
@@ -93,7 +93,7 @@ class StatusOptionsPopover extends ImmutablePureComponent {
|
||||
menu.push({
|
||||
icon: 'circle',
|
||||
hideArrow: true,
|
||||
title: formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation),
|
||||
title: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation),
|
||||
onClick: this.handleConversationMuteClick,
|
||||
})
|
||||
}
|
||||
@@ -103,33 +103,33 @@ class StatusOptionsPopover extends ImmutablePureComponent {
|
||||
menu.push({
|
||||
icon: 'circle',
|
||||
hideArrow: true,
|
||||
title: formatMessage(status.get('pinned') ? messages.unpin : messages.pin),
|
||||
title: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin),
|
||||
onClick: this.handlePinClick,
|
||||
})
|
||||
} else {
|
||||
if (status.get('visibility') === 'private') {
|
||||
menu.push({
|
||||
title: formatMessage(status.get('reblogged') ? messages.cancel_repost_private : messages.repost_private),
|
||||
title: intl.formatMessage(status.get('reblogged') ? messages.cancel_repost_private : messages.repost_private),
|
||||
onClick: this.handleRepostClick
|
||||
})
|
||||
}
|
||||
}
|
||||
menu.push({ text: formatMessage(messages.delete), action: this.handleDeleteClick });
|
||||
menu.push({ text: formatMessage(messages.edit), action: this.handleEditClick });
|
||||
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
|
||||
menu.push({ text: intl.formatMessage(messages.edit), action: this.handleEditClick });
|
||||
} else {
|
||||
menu.push({ text: formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
|
||||
menu.push({ text: formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick });
|
||||
menu.push({ text: formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick });
|
||||
menu.push({ text: formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
|
||||
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
|
||||
menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick });
|
||||
menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick });
|
||||
menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
|
||||
|
||||
if (isStaff) {
|
||||
menu.push({ text: formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
|
||||
menu.push({ text: formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
|
||||
menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
|
||||
menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
|
||||
}
|
||||
|
||||
if (withGroupAdmin) {
|
||||
menu.push({ text: formatMessage(messages.group_remove_account), action: this.handleGroupRemoveAccount });
|
||||
menu.push({ text: formatMessage(messages.group_remove_post), action: this.handleGroupRemovePost });
|
||||
menu.push({ text: intl.formatMessage(messages.group_remove_account), action: this.handleGroupRemoveAccount });
|
||||
menu.push({ text: intl.formatMessage(messages.group_remove_post), action: this.handleGroupRemovePost });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ class Sidebar extends ImmutablePureComponent {
|
||||
{
|
||||
title: 'Home',
|
||||
icon: 'home',
|
||||
to: '/',
|
||||
to: '/home',
|
||||
count: homeItemsQueueCount,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { me } from '../initial_state'
|
||||
import { makeGetAccount } from '../selectors'
|
||||
import GabLogo from '../assets/gab_logo'
|
||||
import Icon from './icon'
|
||||
import SidebarSectionItem from './sidebar_section_item'
|
||||
import Text from './text'
|
||||
|
||||
@@ -27,14 +27,20 @@ class SidebarHeader extends ImmutablePureComponent {
|
||||
render() {
|
||||
const { account } = this.props
|
||||
|
||||
const isPro = account.get('is_pro')
|
||||
const gabLogoClasses = isPro ? _s.fillColorGabPro : _s.fillColorBrand
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<h1 className={[_s.default].join(' ')}>
|
||||
<NavLink to='/' aria-label='Gab' className={[_s.default, _s.flexRow, _s.noSelect, _s.noUnderline, _s.height50PX, _s.cursorPointer, _s.px10].join(' ')}>
|
||||
<GabLogo />
|
||||
<Text weight='bold' color='brand' size='extraSmall' className={[_s.pb5].join(' ')}>
|
||||
BETA
|
||||
</Text>
|
||||
<Icon id='gab-logo' className={gabLogoClasses} />
|
||||
{
|
||||
isPro &&
|
||||
<Text weight='bold' color='pro' size='extraSmall' className={[_s.pb5].join(' ')}>
|
||||
PRO
|
||||
</Text>
|
||||
}
|
||||
</NavLink>
|
||||
</h1>
|
||||
|
||||
|
||||
@@ -5,6 +5,10 @@ import Icon from './icon'
|
||||
const cx = classNames.bind(_s)
|
||||
|
||||
export default class SidebarSectionItem extends PureComponent {
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
to: PropTypes.string,
|
||||
href: PropTypes.string,
|
||||
@@ -46,7 +50,8 @@ export default class SidebarSectionItem extends PureComponent {
|
||||
const { hovering } = this.state
|
||||
|
||||
const iconSize = '16px'
|
||||
const shouldShowActive = hovering || active
|
||||
const currentPathname = this.context.router.route.location.pathname
|
||||
const shouldShowActive = hovering || active || currentPathname === to || currentPathname === href
|
||||
const isNotifications = to === '/notifications'
|
||||
|
||||
const containerClasses = cx({
|
||||
@@ -58,10 +63,12 @@ export default class SidebarSectionItem extends PureComponent {
|
||||
px10: 1,
|
||||
alignItemsCenter: 1,
|
||||
radiusSmall: 1,
|
||||
// border1PX: shouldShowActive,
|
||||
// borderColorSecondary: shouldShowActive,
|
||||
backgroundSubtle2: shouldShowActive,
|
||||
backgroundTransparent: 1,
|
||||
mt2: 1,
|
||||
border1PX: 1,
|
||||
borderColorTransparent: !shouldShowActive,
|
||||
borderColorSecondary: shouldShowActive,
|
||||
backgroundTransparent: !shouldShowActive,
|
||||
backgroundColorPrimary: shouldShowActive,
|
||||
})
|
||||
|
||||
const textClasses = cx({
|
||||
@@ -70,13 +77,13 @@ export default class SidebarSectionItem extends PureComponent {
|
||||
fontSize15PX: 1,
|
||||
text: 1,
|
||||
textOverflowEllipsis: 1,
|
||||
colorSecondary: !hovering && !active && !me && !shouldShowActive,
|
||||
colorPrimary: shouldShowActive || me,
|
||||
colorSecondary: !hovering && !active && !me,
|
||||
})
|
||||
|
||||
const iconClasses = cx({
|
||||
fillColorSecondary: !hovering && !active && !shouldShowActive,
|
||||
fillColorBlack: shouldShowActive,
|
||||
fillColorSecondary: !hovering && !active,
|
||||
})
|
||||
|
||||
const countClasses = cx({
|
||||
|
||||
@@ -44,7 +44,7 @@ export default class StatusActionBarItem extends PureComponent {
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.flexGrow1, _s.px10].join(' ')}>
|
||||
<div className={[_s.default, _s.flexGrow1, _s.px5].join(' ')}>
|
||||
<button
|
||||
ref={buttonRef}
|
||||
className={btnClasses}
|
||||
|
||||
@@ -144,7 +144,7 @@ export default class Card extends ImmutablePureComponent {
|
||||
className={[_s.default, _s.displayFlex, _s.text, _s.noUnderline, _s.overflowWrapBreakWord, _s.colorPrimary, _s.fontSize15PX, _s.fontWeightMedium].join(' ')}
|
||||
href={card.get('url')}
|
||||
title={card.get('title')}
|
||||
rel='noopener'
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
{card.get('title')}
|
||||
@@ -228,7 +228,7 @@ export default class Card extends ImmutablePureComponent {
|
||||
<a
|
||||
href={card.get('url')}
|
||||
className={[_s.default, _s.cursorPointer, _s.flexRow, _s.overflowHidden, _s.noUnderline, _s.width100PC, _s.backgroundSubtle_onHover, _s.borderColorSecondary2, _s.border1PX, _s.radiusSmall].join(' ')}
|
||||
rel='noopener'
|
||||
rel='noopener noreferrer'
|
||||
ref={this.setRef}
|
||||
>
|
||||
{embed}
|
||||
|
||||
@@ -49,14 +49,15 @@ class StatusContent extends ImmutablePureComponent {
|
||||
|
||||
const links = node.querySelectorAll('a')
|
||||
|
||||
for (var i = 0; i < links.length; ++i) {
|
||||
let link = links[i]
|
||||
if (link.classList.contains('status-link')) {
|
||||
for (let i = 0; i < links.length; ++i) {
|
||||
const link = links[i]
|
||||
if (link.classList.contains('linked')) {
|
||||
continue
|
||||
}
|
||||
link.classList.add('status-link')
|
||||
link.classList.add('linked')
|
||||
link.classList.add(_s.text, _s.colorBrand, _s.cursorPointer, _s.inherit)
|
||||
|
||||
let mention = this.props.status.get('mentions').find(item => link.href === `${item.get('url')}`)
|
||||
const mention = this.props.status.get('mentions').find(item => link.href === `${item.get('url')}`)
|
||||
|
||||
if (mention) {
|
||||
link.addEventListener('click', this.onMentionClick.bind(this, mention), false)
|
||||
@@ -66,6 +67,20 @@ class StatusContent extends ImmutablePureComponent {
|
||||
} else {
|
||||
link.setAttribute('title', link.href)
|
||||
}
|
||||
|
||||
const descendents = link.getElementsByTagName('*')
|
||||
for (let j = 0; j < descendents.length; j++) {
|
||||
const descendent = descendents[j];
|
||||
|
||||
if (descendent.classList.contains('invisible')) {
|
||||
descendent.classList.remove('invisible')
|
||||
descendent.classList.add(_s.fontSize0, _s.text, _s.inherit)
|
||||
}
|
||||
if (descendent.classList.contains('ellipsis')) {
|
||||
descendent.classList.remove('ellipsis')
|
||||
descendent.classList.add(_s.noSelect, _s.text, _s.inherit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -89,8 +104,8 @@ class StatusContent extends ImmutablePureComponent {
|
||||
|
||||
onMentionClick = (mention, e) => {
|
||||
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault()
|
||||
this.context.router.history.push(`/${mention.get('acct')}`)
|
||||
// e.preventDefault()
|
||||
// this.context.router.history.push(`/${mention.get('acct')}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ const COLORS = {
|
||||
error: 'error',
|
||||
white: 'white',
|
||||
inherit: 'inherit',
|
||||
pro: 'pro',
|
||||
}
|
||||
|
||||
const SIZES = {
|
||||
@@ -73,6 +74,7 @@ export default class Text extends PureComponent {
|
||||
colorSecondary: color === COLORS.secondary,
|
||||
colorBrand: color === COLORS.brand,
|
||||
colorWhite: color === COLORS.white,
|
||||
colorGabPro: color === COLORS.pro,
|
||||
inherit: color === COLORS.inherit,
|
||||
|
||||
fontSize19PX: size === SIZES.large,
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import { injectIntl, defineMessages } from 'react-intl'
|
||||
import classNames from 'classnames/bind'
|
||||
import { me } from '../initial_state'
|
||||
import ComposeFormContainer from '../features/compose/containers/compose_form_container'
|
||||
import Avatar from './avatar'
|
||||
import Block from './block'
|
||||
import Heading from './heading'
|
||||
|
||||
const cx = classNames.bind(_s)
|
||||
|
||||
const messages = defineMessages({
|
||||
createPost: { id: 'column_header.create_post', defaultMessage: 'Create Post' },
|
||||
})
|
||||
@@ -57,12 +55,15 @@ class TimelineComposeBlock extends ImmutablePureComponent {
|
||||
return (
|
||||
<section className={[_s.default, _s.mb15].join(' ')}>
|
||||
<Block>
|
||||
<div className={[_s.default, _s.backgroundSubtle, _s.borderBottom1PX, _s.borderColorSecondary, _s.px15, _s.py2].join(' ')}>
|
||||
<div className={[_s.default, _s.backgroundSubtle, _s.borderBottom1PX, _s.borderColorSecondary, _s.px15, _s.py2, _s.alignItemsCenter, _s.flexRow].join(' ')}>
|
||||
<div className={_s.mr10}>
|
||||
<Avatar account={account} size='20' />
|
||||
</div>
|
||||
<Heading size='h5'>
|
||||
{intl.formatMessage(messages.createPost)}
|
||||
</Heading>
|
||||
</div>
|
||||
<div className={[_s.default, _s.flexRow, _s.px15, _s.py15].join(' ')}>
|
||||
<div className={[_s.default, _s.flexRow, _s.px15, _s.pt15, _s.pb10].join(' ')}>
|
||||
<ComposeFormContainer {...rest} />
|
||||
</div>
|
||||
</Block>
|
||||
|
||||
@@ -210,7 +210,7 @@ class Video extends PureComponent {
|
||||
const y = 1 - ((e.clientY - rect.top) / this.volHeight)
|
||||
|
||||
if (!isNaN(y)) {
|
||||
var slideamt = y
|
||||
const slideamt = y
|
||||
if (y > 1) {
|
||||
slideamt = 1
|
||||
} else if (y < 0) {
|
||||
|
||||
@@ -5,7 +5,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import classNames from 'classnames/bind'
|
||||
import CharacterCounter from '../../../../components/character_counter'
|
||||
import UploadForm from '../upload_form'
|
||||
import ReplyIndicatorContainer from '../../containers/reply_indicator_container'
|
||||
import AutosuggestTextbox from '../../../../components/autosuggest_textbox'
|
||||
import PollButton from '../../components/poll_button'
|
||||
import UploadButton from '../media_upload_button'
|
||||
@@ -255,12 +254,6 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
mt10: !shouldCondense,
|
||||
})
|
||||
|
||||
const avatarContainerClasses = cx({
|
||||
default: 1,
|
||||
mr10: 1,
|
||||
mt5: shouldCondense,
|
||||
})
|
||||
|
||||
const contentWarningClasses = cx({
|
||||
default: 1,
|
||||
pt5: 1,
|
||||
@@ -271,13 +264,14 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
displayNone: !spoiler
|
||||
})
|
||||
|
||||
const avatarSize = shouldCondense ? 28 : 46
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.flexRow, _s.width100PC].join(' ')}>
|
||||
<div className={avatarContainerClasses}>
|
||||
<Avatar account={account} size={avatarSize} />
|
||||
</div>
|
||||
{
|
||||
shouldCondense &&
|
||||
<div className={[_s.default, _s.mr10, _s.mt5]}>
|
||||
<Avatar account={account} size='28' />
|
||||
</div>
|
||||
}
|
||||
|
||||
<div
|
||||
className={containerClasses}
|
||||
@@ -285,8 +279,6 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
|
||||
{ /* !shouldCondense && <ReplyIndicatorContainer /> */}
|
||||
|
||||
<div className={contentWarningClasses}>
|
||||
<AutosuggestTextbox
|
||||
placeholder={intl.formatMessage(messages.spoiler_placeholder)}
|
||||
@@ -301,7 +293,6 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
onSuggestionSelected={this.onSpoilerSuggestionSelected}
|
||||
searchTokens={[':']}
|
||||
id='cw-spoiler-input'
|
||||
className='spoiler-input__input'
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './reply_indicator'
|
||||
@@ -1,64 +0,0 @@
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import Avatar from '../../../../components/avatar';
|
||||
import Button from '../../../../components/button';
|
||||
import DisplayName from '../../../../components/display_name';
|
||||
import { isRtl } from '../../../../utils/rtl';
|
||||
|
||||
const messages = defineMessages({
|
||||
cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' },
|
||||
});
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
class ReplyIndicator extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
this.props.onCancel();
|
||||
}
|
||||
|
||||
render () {
|
||||
const { status, intl } = this.props;
|
||||
|
||||
if (!status) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const content = { __html: status.get('contentHtml') };
|
||||
const style = {
|
||||
direction: isRtl(status.get('search_index')) ? 'rtl' : 'ltr',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='reply-indicator'>
|
||||
<div className='reply-indicator__header'>
|
||||
<div className='reply-indicator__cancel'>
|
||||
<Button title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} inverted />
|
||||
</div>
|
||||
|
||||
<NavLink to={`/${status.getIn(['account', 'acct'])}`} className='reply-indicator__display-name'>
|
||||
<div className='reply-indicator__display-avatar'>
|
||||
<Avatar account={status.get('account')} size={24} />
|
||||
</div>
|
||||
<DisplayName account={status.get('account')} />
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<div className='reply-indicator-content' style={style} dangerouslySetInnerHTML={content} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
.reply-indicator {
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
background: $gab-background-base-light;
|
||||
padding: 10px;
|
||||
min-height: 23px;
|
||||
overflow-y: auto;
|
||||
flex: 0 2 auto;
|
||||
max-height: 500px;
|
||||
|
||||
@media screen and (min-width: 320px) and (max-width: 375px) {
|
||||
max-height: 220px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 320px) {
|
||||
max-height: 130px;
|
||||
}
|
||||
|
||||
&__header {
|
||||
margin-bottom: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__cancel {
|
||||
float: right;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
&__display-name {
|
||||
color: $inverted-text-color;
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
line-height: 24px;
|
||||
overflow: hidden;
|
||||
padding-right: 25px;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover strong {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
&__display-avatar {
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.reply-indicator-content {
|
||||
position: relative;
|
||||
padding-top: 2px;
|
||||
color: $primary-text-color;
|
||||
|
||||
@include text-sizing(15px, 400, 20px);
|
||||
@include text-overflow(normal, break-word);
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&.status__content--with-spoiler {
|
||||
white-space: normal;
|
||||
|
||||
.status__content__text {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.emojione {
|
||||
margin: -3px 0 0;
|
||||
|
||||
@include size(20px);
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 20px;
|
||||
white-space: pre-wrap;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: $gab-brand-default;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
|
||||
.fa {
|
||||
color: lighten($dark-text-color, 7%);
|
||||
}
|
||||
}
|
||||
|
||||
&.mention {
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
|
||||
span {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fa {
|
||||
color: $dark-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.status__content__spoiler-link {
|
||||
background: $action-button-color;
|
||||
|
||||
&:hover {
|
||||
background: lighten($action-button-color, 7%);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&::-moz-focus-inner,
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.status__content__text {
|
||||
display: none;
|
||||
|
||||
&.status__content__text--visible {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { cancelReplyCompose } from '../../../actions/compose';
|
||||
import { makeGetStatus } from '../../../selectors';
|
||||
import ReplyIndicator from '../components/reply_indicator';
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getStatus = makeGetStatus();
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
status: getStatus(state, { id: state.getIn(['compose', 'in_reply_to']) }),
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
onCancel () {
|
||||
dispatch(cancelReplyCompose());
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(ReplyIndicator);
|
||||
@@ -16,7 +16,7 @@ const regexSupplant = function(regex, flags) {
|
||||
regex = regex.source;
|
||||
}
|
||||
return new RegExp(regex.replace(/#\{(\w+)\}/g, function(match, name) {
|
||||
var newRegex = regexen[name] || '';
|
||||
let newRegex = regexen[name] || '';
|
||||
if (typeof newRegex !== 'string') {
|
||||
newRegex = newRegex.source;
|
||||
}
|
||||
@@ -185,7 +185,7 @@ export const urlRegex = (function() {
|
||||
regexen.validUrlQueryEndingChars = /[a-z0-9_&=#\/]/i;
|
||||
regexen.validUrl = regexSupplant(
|
||||
'(' + // $1 URL
|
||||
'(https?:\\/\\/)' + // $2 Protocol
|
||||
'(https?:\\/\\/)?' + // $2 Protocol
|
||||
'(#{validDomain})' + // $3 Domain(s)
|
||||
'(?::(#{validPortNumber}))?' + // $4 Port number (optional)
|
||||
'(\\/#{validUrlPath}*)?' + // $5 URL Path
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
COMPOSE_POLL_OPTION_REMOVE,
|
||||
COMPOSE_POLL_SETTINGS_CHANGE,
|
||||
COMPOSE_SCHEDULED_AT_CHANGE,
|
||||
COMPOSE_RICH_TEXT_EDITOR_CONTROLS_VISIBILITY
|
||||
} from '../actions/compose';
|
||||
import { TIMELINE_DELETE } from '../actions/timelines';
|
||||
import { STORE_HYDRATE } from '../actions/store';
|
||||
@@ -73,6 +74,7 @@ const initialState = ImmutableMap({
|
||||
idempotencyKey: null,
|
||||
tagHistory: ImmutableList(),
|
||||
scheduled_at: null,
|
||||
rte_controls_visible: false,
|
||||
});
|
||||
|
||||
const initialPoll = ImmutableMap({
|
||||
@@ -386,6 +388,8 @@ export default function compose(state = initialState, action) {
|
||||
return state.update('poll', poll => poll.set('expires_in', action.expiresIn).set('multiple', action.isMultiple));
|
||||
case COMPOSE_SCHEDULED_AT_CHANGE:
|
||||
return state.set('scheduled_at', action.date);
|
||||
case COMPOSE_RICH_TEXT_EDITOR_CONTROLS_VISIBILITY:
|
||||
return ''
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -368,6 +368,10 @@ body {
|
||||
color: #21cf7a
|
||||
}
|
||||
|
||||
.colorGabPro {
|
||||
color: #ffd700;
|
||||
}
|
||||
|
||||
.fillColorBlack {
|
||||
fill: #000;
|
||||
}
|
||||
@@ -388,6 +392,10 @@ body {
|
||||
fill: #666;
|
||||
}
|
||||
|
||||
.fillColorGabPro {
|
||||
fill: #ffd700;
|
||||
}
|
||||
|
||||
.bottom0 {
|
||||
bottom: 0;
|
||||
}
|
||||
@@ -716,6 +724,10 @@ body {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.fontSize0 {
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.fontWeightNormal {
|
||||
font-weight: 400;
|
||||
}
|
||||
@@ -858,6 +870,10 @@ body {
|
||||
padding-top: 25%;
|
||||
}
|
||||
|
||||
.pt15 {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.pt10 {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ class Formatter
|
||||
|
||||
def reformat(html)
|
||||
sanitize(html, Sanitize::Config::GABSOCIAL_STRICT)
|
||||
rescue ArgumentError
|
||||
''
|
||||
end
|
||||
|
||||
def plaintext(status)
|
||||
@@ -246,7 +248,7 @@ class Formatter
|
||||
|
||||
def link_to_url(entity, options = {})
|
||||
url = Addressable::URI.parse(entity[:url])
|
||||
html_attrs = { target: '_blank', rel: 'nofollow noopener' }
|
||||
html_attrs = { target: '_blank', rel: 'nofollow noopener noreferrer' }
|
||||
|
||||
html_attrs[:rel] = "me #{html_attrs[:rel]}" if options[:me]
|
||||
|
||||
@@ -284,14 +286,14 @@ class Formatter
|
||||
suffix = url[prefix.length + 30..-1]
|
||||
cutoff = url[prefix.length..-1].length > 30
|
||||
|
||||
"<span class=\"invisible\">#{encode(prefix)}</span><span class=\"#{cutoff ? 'ellipsis' : ''}\">#{encode(text)}</span><span class=\"invisible\">#{encode(suffix)}</span>"
|
||||
"<span aria-hidden=\"true\" class=\"invisible\">#{encode(prefix)}</span>#{encode(text)}<span aria-hidden=\"true\" class=\"invisible\">#{encode(suffix)}</span>" + (cutoff ? "<span aria-hidden=\"true\" class=\"ellipsis\">…</span>" : "")
|
||||
end
|
||||
|
||||
def hashtag_html(tag)
|
||||
"<a href=\"#{encode(tag_url(tag.downcase))}\" class=\"mention hashtag\" rel=\"tag\">#<span>#{encode(tag)}</span></a>"
|
||||
"<a data-focusable=\"true\" role=\"link\" href=\"#{encode(tag_url(tag.downcase))}\" class=\"mention hashtag\" rel=\"tag\">##{encode(tag)}</a>"
|
||||
end
|
||||
|
||||
def mention_html(account)
|
||||
"<span class=\"h-card\"><a href=\"#{encode(TagManager.instance.url_for(account))}\" class=\"u-url mention\">@<span>#{encode(account.username)}</span></a></span>"
|
||||
"<a data-focusable=\"true\" role=\"link\" href=\"#{encode(TagManager.instance.url_for(account))}\" class=\"u-url mention\">@#{encode(account.acct)}</a>"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -29,7 +29,7 @@ class Sanitize
|
||||
|
||||
add_attributes: {
|
||||
'a' => {
|
||||
'rel' => 'nofollow noopener',
|
||||
'rel' => 'nofollow noopener noreferrer',
|
||||
'target' => '_blank',
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user