-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Chrome: Adding the Permalink popup #1042
Changes from all commits
6be3634
e180b88
385110b
4f882b1
6434533
88ba957
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import Clipboard from 'clipboard'; | ||
import classnames from 'classnames'; | ||
import { noop } from 'lodash'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { findDOMNode, Component } from 'element'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { Button } from '../'; | ||
|
||
class ClipboardButton extends Component { | ||
componentDidMount() { | ||
const { text, onCopy = noop } = this.props; | ||
const button = findDOMNode( this.button ); | ||
this.clipboard = new Clipboard( button, { | ||
text: () => text, | ||
} ); | ||
this.clipboard.on( 'success', onCopy ); | ||
} | ||
|
||
componentWillUnmount() { | ||
this.clipboard.destroy(); | ||
delete this.clipboard; | ||
} | ||
|
||
render() { | ||
const { className, children } = this.props; | ||
const classes = classnames( 'components-clipboard-button', className ); | ||
|
||
return ( | ||
<Button | ||
ref={ ref => this.button = ref } | ||
className={ classes } | ||
> | ||
{ children } | ||
</Button> | ||
); | ||
} | ||
} | ||
|
||
export default ClipboardButton; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { connect } from 'react-redux'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { Component } from 'element'; | ||
import { __ } from 'i18n'; | ||
import { Dashicon, ClipboardButton, Button } from 'components'; | ||
|
||
/** | ||
* Internal Dependencies | ||
*/ | ||
import './style.scss'; | ||
import { getEditedPostAttribute } from '../selectors'; | ||
|
||
class PostPermalink extends Component { | ||
constructor() { | ||
super( ...arguments ); | ||
this.state = { | ||
showCopyConfirmation: false, | ||
}; | ||
this.onCopy = this.onCopy.bind( this ); | ||
} | ||
|
||
componentWillUnmout() { | ||
clearTimeout( this.dismissCopyConfirmation ); | ||
} | ||
|
||
onCopy() { | ||
this.setState( { | ||
showCopyConfirmation: true, | ||
} ); | ||
|
||
clearTimeout( this.dismissCopyConfirmation ); | ||
this.dismissCopyConfirmation = setTimeout( () => { | ||
this.setState( { | ||
showCopyConfirmation: false, | ||
} ); | ||
}, 4000 ); | ||
} | ||
|
||
render() { | ||
const { link } = this.props; | ||
if ( ! link ) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<div className="editor-post-permalink"> | ||
<Dashicon icon="admin-links" /> | ||
<span className="editor-post-permalink__label">{ __( 'Permalink:' ) }</span> | ||
<Button className="editor-post-permalink__link" href={ link } target="_blank"> | ||
{ link } | ||
</Button> | ||
<ClipboardButton className="button" text={ link } onCopy={ this.onCopy }> | ||
{ this.state.showCopyConfirmation ? __( 'Copied!' ) : __( 'Copy' ) } | ||
</ClipboardButton> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
export default connect( | ||
( state ) => { | ||
return { | ||
link: getEditedPostAttribute( state, 'link' ), | ||
}; | ||
} | ||
)( PostPermalink ); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
.editor-post-permalink { | ||
display: inline-flex; | ||
align-items: center; | ||
position: absolute; | ||
top: -36px; | ||
left: 15px; | ||
box-shadow: $shadow-popover; | ||
border: 1px solid $light-gray-500; | ||
background: $white; | ||
padding: 5px; | ||
font-family: $default-font; | ||
font-size: 13px; | ||
} | ||
|
||
.editor-post-permalink__label { | ||
margin: 0 10px; | ||
} | ||
|
||
.editor-post-permalink__link { | ||
color: $dark-gray-200; | ||
text-decoration: underline; | ||
margin-right: 10px; | ||
width: 300px; | ||
overflow: hidden; | ||
position: relative; | ||
white-space: nowrap; | ||
|
||
&:after { | ||
@include long-content-fade( $size: 20% ); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,47 +3,111 @@ | |
*/ | ||
import { connect } from 'react-redux'; | ||
import Textarea from 'react-autosize-textarea'; | ||
import clickOutside from 'react-click-outside'; | ||
import classnames from 'classnames'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { Component } from 'element'; | ||
import { ENTER } from 'utils/keycodes'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import './style.scss'; | ||
import { getEditedPostTitle } from '../selectors'; | ||
import { editPost } from '../actions'; | ||
import { editPost, clearSelectedBlock } from '../actions'; | ||
import PostPermalink from '../post-permalink'; | ||
|
||
/** | ||
* Constants | ||
*/ | ||
const REGEXP_NEWLINES = /[\r\n]+/g; | ||
|
||
function PostTitle( { title, onUpdate } ) { | ||
const onChange = ( event ) => { | ||
class PostTitle extends Component { | ||
constructor() { | ||
super( ...arguments ); | ||
this.bindTextarea = this.bindTextarea.bind( this ); | ||
this.onChange = this.onChange.bind( this ); | ||
this.onSelect = this.onSelect.bind( this ); | ||
this.onUnselect = this.onUnselect.bind( this ); | ||
this.onSelectionChange = this.onSelectionChange.bind( this ); | ||
this.state = { | ||
isSelected: false, | ||
}; | ||
} | ||
|
||
componentDidMount() { | ||
document.addEventListener( 'selectionchange', this.onSelectionChange ); | ||
} | ||
|
||
componentWillUnmount() { | ||
document.removeEventListener( 'selectionchange', this.onSelectionChange ); | ||
} | ||
|
||
bindTextarea( ref ) { | ||
this.textareaContainer = ref; | ||
} | ||
|
||
onSelectionChange() { | ||
const textarea = this.textareaContainer.textarea; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this doing? Can we add an inline comment? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See #5422 |
||
if ( | ||
document.activeElement === textarea && | ||
textarea.selectionStart !== textarea.selectionEnd | ||
) { | ||
this.onSelect(); | ||
} | ||
} | ||
|
||
onChange( event ) { | ||
const newTitle = event.target.value.replace( REGEXP_NEWLINES, ' ' ); | ||
onUpdate( newTitle ); | ||
}; | ||
this.props.onUpdate( newTitle ); | ||
} | ||
|
||
onSelect() { | ||
this.setState( { isSelected: true } ); | ||
this.props.clearSelectedBlock(); | ||
} | ||
|
||
onUnselect() { | ||
this.setState( { isSelected: false } ); | ||
} | ||
|
||
handleClickOutside() { | ||
this.setState( { isSelected: false } ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't because of the popup |
||
} | ||
|
||
const onKeyDown = ( event ) => { | ||
onKeyDown( event ) { | ||
if ( event.keyCode === ENTER ) { | ||
event.preventDefault(); | ||
} | ||
}; | ||
|
||
return ( | ||
<h1 className="editor-post-title"> | ||
<Textarea | ||
className="editor-post-title__input" | ||
value={ title } | ||
onChange={ onChange } | ||
placeholder={ wp.i18n.__( 'Enter title here' ) } | ||
onKeyDown={ onKeyDown } | ||
/> | ||
</h1> | ||
); | ||
} | ||
|
||
render() { | ||
const { title } = this.props; | ||
const { isSelected } = this.state; | ||
const className = classnames( 'editor-post-title', { 'is-selected': isSelected } ); | ||
|
||
return ( | ||
<div className={ className }> | ||
{ isSelected && <PostPermalink /> } | ||
<h1> | ||
<Textarea | ||
ref={ this.bindTextarea } | ||
className="editor-post-title__input" | ||
value={ title } | ||
onChange={ this.onChange } | ||
placeholder={ wp.i18n.__( 'Enter title here' ) } | ||
onFocus={ this.onSelect } | ||
onClick={ this.onSelect } | ||
onKeyDown={ this.onKeyDown } | ||
onKeyPress={ this.onUnselect } | ||
/> | ||
</h1> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
export default connect( | ||
|
@@ -55,6 +119,9 @@ export default connect( | |
onUpdate( title ) { | ||
dispatch( editPost( { title } ) ); | ||
}, | ||
clearSelectedBlock() { | ||
dispatch( clearSelectedBlock() ); | ||
}, | ||
}; | ||
} | ||
)( PostTitle ); | ||
)( clickOutside( PostTitle ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bit confusing that that there's no click handler here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, that's how the
clipboard
library works.