REF: React eslint (#2432)

This commit is contained in:
Tim Lange 2019-04-16 23:28:19 +02:00 committed by Ben Halpern
parent c6ee9fe3b4
commit bf68782374
34 changed files with 586 additions and 372 deletions

1
.eslintignore Normal file
View file

@ -0,0 +1 @@
package.json

View file

@ -2,34 +2,54 @@ import { h } from 'preact';
import PropTypes from 'prop-types';
const BodyPreview = ({ previewHTML, version, articleState }) => (
<div className="container" style={{marginTop: "10px",minHeight:"508px", overflow:"hidden", boxShadow: "0px 0px 0px #fff",border:"0px"}}>
<div
className="container"
style={{
marginTop: '10px',
minHeight: '508px',
overflow: 'hidden',
boxShadow: '0px 0px 0px #fff',
border: '0px',
}}
>
{titleArea(version, articleState)}
<div className="body" dangerouslySetInnerHTML={{__html: previewHTML}} style={{width: "90%"}}>
</div>
<div
className="body"
dangerouslySetInnerHTML={{ __html: previewHTML }}
style={{ width: '90%' }}
/>
</div>
);
function titleArea(version, articleState) {
if(version === 'help'){
//possibly something different here in future.
return ''
} else {
const tags = articleState.tagList.split(", ").map(tag => {
return <span><div class="tag">{tag}</div> </span>
})
return <div class="title" style={{width: "90%", maxWidth:"1000px"}}>
<h1>{articleState.title}</h1>
<h3>
<img class="profile-pic" src={window.currentUser.profile_image_90} alt="image" />&nbsp;
<span>{window.currentUser.name}</span>
</h3>
<div class="tags">
{tags}
</div>
</div>
if (version === 'help') {
// possibly something different here in future.
return '';
}
const tags = articleState.tagList.split(', ').map(tag => {
return (
<span>
<div className="tag">{tag}</div>
{' '}
</span>
);
});
return (
<div className="title" style={{ width: '90%', maxWidth: '1000px' }}>
<h1>{articleState.title}</h1>
<h3>
<img
className="profile-pic"
src={window.currentUser.profile_image_90}
alt="image"
/>
&nbsp;
<span>{window.currentUser.name}</span>
</h3>
<div className="tags">{tags}</div>
</div>
);
}
};
BodyPreview.propTypes = {
previewHTML: PropTypes.string.isRequired,
@ -37,8 +57,3 @@ BodyPreview.propTypes = {
};
export default BodyPreview;

View file

@ -2,11 +2,19 @@ import { h } from 'preact';
import PropTypes from 'prop-types';
const Errors = ({ errorsList }) => (
<div className='articleform__errors'>
<div className="articleform__errors">
<h2>😱 Heads up:</h2>
<ul>{Object.keys(errorsList).map((key) => {
return <li>{key}: {errorsList[key]}</li>
})}</ul>
<ul>
{Object.keys(errorsList).map(key => {
return (
<li>
{key}
:
{errorsList[key]}
</li>
);
})}
</ul>
</div>
);

View file

@ -1,8 +1,6 @@
import { h, Component } from 'preact';
import PropTypes from 'prop-types';
import { generateMainImage } from '../actions'
import { generateMainImage } from '../actions';
// const ImageManagement = ({ onExit }) => (
export default class MoreConfig extends Component {
@ -14,50 +12,91 @@ export default class MoreConfig extends Component {
handleSeriesButtonClick = e => {
e.preventDefault();
this.props.onConfigChange(e);
}
};
render() {
const { onExit, passedData, onSaveDraft } = this.props;
let publishedField = '';
let seriesTip = <small>Will this post be part of a series? Give the series a unique name. (Series visible once it has multiple posts)</small>
let seriesTip = (
<small>
Will this post be part of a series? Give the series a unique name.
(Series visible once it has multiple posts)
</small>
);
if (passedData.allSeries.length > 0) {
const seriesNames = passedData.allSeries.map( name => {
return <button name='series' onClick={this.props.onConfigChange} value={name}>{name}</button>
})
seriesTip = <small>Existing series: {seriesNames}</small>
const seriesNames = passedData.allSeries.map(name => {
return (
<button
name="series"
onClick={this.props.onConfigChange}
value={name}
>
{name}
</button>
);
});
seriesTip = (
<small>
Existing series:
{seriesNames}
</small>
);
}
if (passedData.published) {
publishedField = <div>
<h4>Danger Zone</h4>
<button onClick={onSaveDraft}>Unpublish Post</button>
</div>
publishedField = (
<div>
<h4>Danger Zone</h4>
<button onClick={onSaveDraft}>Unpublish Post</button>
</div>
);
}
return <div
className="articleform__overlay"
>
<h3>Additional Config/Settings</h3>
<button
class="articleform__exitbutton"
data-content="exit"
onClick={onExit}
>×</button>
<div>
<label>Canonical URL</label>
<input type="text" value={passedData.canonicalUrl} name="canonicalUrl" onKeyUp={this.props.onConfigChange}/>
</div>
<small>Change meta tag <code>canonical_url</code> if this post was first published elsewhere (like your own blog)</small>
<div>
<label>Series Name</label>
<input type="text" value={passedData.series} name="series" onKeyUp={this.props.onConfigChange}/>
</div>
{seriesTip}
<div><button class="articleform__donebutton" onClick={onExit}>Done</button></div>
{publishedField}
</div>
return (
<div className="articleform__overlay">
<h3>Additional Config/Settings</h3>
<button
className="articleform__exitbutton"
data-content="exit"
onClick={onExit}
>
×
</button>
<div>
<label>Canonical URL</label>
<input
type="text"
value={passedData.canonicalUrl}
name="canonicalUrl"
onKeyUp={this.props.onConfigChange}
/>
</div>
<small>
Change meta tag
<code>canonical_url</code>
{' '}
if this post was first published elsewhere
(like your own blog)
</small>
<div>
<label>Series Name</label>
<input
type="text"
value={passedData.series}
name="series"
onKeyUp={this.props.onConfigChange}
/>
</div>
{seriesTip}
<div>
<button className="articleform__donebutton" onClick={onExit}>
Done
</button>
</div>
{publishedField}
</div>
);
}
}
MoreConfig.propTypes = {
onExit: PropTypes.func.isRequired,
};
};

View file

@ -2,7 +2,13 @@ import { h } from 'preact';
import PropTypes from 'prop-types';
const Notice = ({ published }) => (
<div className={'articleform__notice articleform__notice--'+(published ? "publishing" : "draft")}>{published ? "Publishing..." : "Saving Draft..."}</div>
<div
className={`articleform__notice articleform__notice--${
published ? 'publishing' : 'draft'
}`}
>
{published ? 'Publishing...' : 'Saving Draft...'}
</div>
);
Notice.propTypes = {

View file

@ -2,11 +2,17 @@ import { h } from 'preact';
import PropTypes from 'prop-types';
const OrgSettings = ({ organization, postUnderOrg, onToggle }) => (
<div
className='articleform__orgsettings'
onClick={onToggle}
>
<img src={organization.profile_image_90} style={{opacity: postUnderOrg ? '1' : '0.7' }} /> {organization.name} <button class={postUnderOrg ? 'yes' : 'no'}>{postUnderOrg ? '✅ YES' : '◻️ NO'}</button>
<div className="articleform__orgsettings" onClick={onToggle}>
<img
src={organization.profile_image_90}
style={{ opacity: postUnderOrg ? '1' : '0.7' }}
/>
{' '}
{organization.name}
{' '}
<button className={postUnderOrg ? 'yes' : 'no'}>
{postUnderOrg ? '✅ YES' : '◻️ NO'}
</button>
</div>
);

View file

@ -100,7 +100,7 @@ class Tags extends Component {
};
handleInput = e => {
let value = e.target.value;
let { value } = e.target;
// If we start typing immediately after a comma, add a space
// before what we typed.
// e.g. If value = "javascript," and we type a "p",

View file

@ -199,16 +199,19 @@ describe('<ChannelDetails />', () => {
let inviteAttrAns;
const included = (list, el) => {
const keys = Object.keys(list);
for (var key of keys) {
for (const key of keys) {
if (list[key].id === el.id) {
return true;
}
}
};
for (let i = 0; i < searchedusersdivs.length; i += 1) {
if (!included(
if (
!included(
moddetails.pending_users_select_fields,
searchedusers.searchedUsers[i])) {
searchedusers.searchedUsers[i],
)
) {
expect(
searchedusersdivs
.at(i)
@ -226,7 +229,9 @@ describe('<ChannelDetails />', () => {
}`,
);
if (included(moddetails.channel_users, searchedusers.searchedUsers[i])) {
if (
included(moddetails.channel_users, searchedusers.searchedUsers[i])
) {
inviteMessage = `is already in ${moddetails.channel_name}`;
inviteAttr = 'className';
inviteAttrAns = 'channel__member';
@ -247,8 +252,8 @@ describe('<ChannelDetails />', () => {
.childAt(2)
.attr(inviteAttr),
).toEqual(inviteAttrAns);
}
}
}
const tree = render(context);
expect(tree).toMatchSnapshot();
});

View file

@ -1,6 +1,6 @@
import { h } from 'preact';
import render from 'preact-render-to-json';
import { shallow } from 'preact-render-spy';
import { deep } from 'preact-render-spy';
import View from '../view';
let exited = false;
@ -43,12 +43,12 @@ describe('<View />', () => {
});
it('should render and test snapshot (with channel)', () => {
const tree = shallow(getView(sampleChannel));
const tree = deep(getView(sampleChannel), { depth: 2 });
expect(tree).toMatchSnapshot();
});
it('should have the proper attributes and text values (no channel provided)', () => {
const context = shallow(getView([]));
const context = deep(getView([]), { depth: 2 });
expect(context.find('.chatNonChatView').exists()).toEqual(true);
expect(context.find('.container').exists()).toEqual(true);
@ -64,7 +64,7 @@ describe('<View />', () => {
});
it('should have the proper attributes and text values (with channel provided)', () => {
const context = shallow(getView(sampleChannel));
const context = deep(getView(sampleChannel), { depth: 2 });
expect(context.find('.chatNonChatView_contentblock').exists()).toEqual(
true,
);
@ -103,14 +103,14 @@ describe('<View />', () => {
});
it('should trigger exit', () => {
const context = shallow(getView([]));
const context = deep(getView([]), { depth: 2 });
context.find('.chatNonChatView_exitbutton').simulate('click');
expect(exited).toEqual(true);
exited = false;
});
it('should trigger accept', () => {
const context = shallow(getView(sampleChannel));
const context = deep(getView(sampleChannel), { depth: 2 });
context
.find('.cta')
.at(0)
@ -124,7 +124,7 @@ describe('<View />', () => {
});
it('should trigger decline', () => {
const context = shallow(getView(sampleChannel));
const context = deep(getView(sampleChannel), { depth: 2 });
context
.find('.cta')
.at(1)

View file

@ -72,18 +72,31 @@ export function conductModeration(
.catch(failureCb);
}
export function getChannels(query,retrievalID, props, paginationNumber, additionalFilters, successCb, failureCb) {
export function getChannels(
query,
retrievalID,
props,
paginationNumber,
additionalFilters,
successCb,
failureCb,
) {
const client = algoliasearch(props.algoliaId, props.algoliaKey);
const index = client.initIndex(props.algoliaIndex);
let filters = {...{
hitsPerPage: 30 + paginationNumber,
page: paginationNumber
}, ...additionalFilters};
index.search(query, filters)
.then(function(content) {
let channels = content.hits
if (retrievalID === null || content.hits.filter(e => e.id === retrievalID).length === 1) {
successCb(channels, query)
const filters = {
...{
hitsPerPage: 30 + paginationNumber,
page: paginationNumber,
},
...additionalFilters,
};
index.search(query, filters).then(function(content) {
const channels = content.hits;
if (
retrievalID === null ||
content.hits.filter(e => e.id === retrievalID).length === 1
) {
successCb(channels, query);
} else {
index.getObjects([`${retrievalID}`], function(err, content) {
channels.unshift(content.results[0]);
@ -102,7 +115,7 @@ export function sendKeys(subscription, successCb, failureCb) {
'Content-Type': 'application/json',
},
body: JSON.stringify({
subscription: subscription
subscription,
}),
credentials: 'same-origin',
})
@ -153,10 +166,10 @@ export function getChannelInvites(successCb, failureCb) {
.then(response => response.json())
.then(successCb)
.catch(failureCb);
};
}
export function sendChannelInviteAction(id, action, successCb, failureCb) {
fetch('/chat_channel_memberships/'+id, {
fetch(`/chat_channel_memberships/${id}`, {
method: 'PUT',
headers: {
Accept: 'application/json',
@ -166,11 +179,11 @@ export function sendChannelInviteAction(id, action, successCb, failureCb) {
body: JSON.stringify({
chat_channel_membership: {
user_action: action,
}
},
}),
credentials: 'same-origin',
})
.then(response => response.json())
.then(successCb)
.catch(failureCb);
}
}

View file

@ -93,7 +93,7 @@ class ChannelDetails extends Component {
};
userInList = (list, user) => {
const keys = Object.keys(list)
const keys = Object.keys(list);
for (const key of keys) {
if (user.id === list[key].id) {
return true;
@ -144,7 +144,7 @@ class ChannelDetails extends Component {
if (this.userInList(channel.channel_users, user)) {
invite = (
<span className="channel__member">
is already in
is already in
{' '}
<em>{channel.channel_name}</em>
</span>
@ -153,11 +153,11 @@ class ChannelDetails extends Component {
return (
<div className="channeldetails__searchedusers">
<a href={user.path} target="_blank" rel="noopener noreferrer">
<img src={user.user.profile_image_90} alt="profile_image"/>
<img src={user.user.profile_image_90} alt="profile_image" />
@
{user.user.username}
{' '}
-
-
{' '}
{user.title}
</a>
@ -178,7 +178,7 @@ class ChannelDetails extends Component {
@
{user.username}
{' '}
-
-
{' '}
{user.name}
</a>
@ -188,9 +188,7 @@ class ChannelDetails extends Component {
<div className="channeldetails__inviteusers">
<h2>Invite Members</h2>
<input onKeyUp={this.triggerUserSearch} placeholder="Find users" />
<div className="channeldetails__searchresults">
{searchedUsers}
</div>
<div className="channeldetails__searchresults">{searchedUsers}</div>
<h2>Pending Invites:</h2>
{pendingInvites}
<div style={{ marginTop: '10px' }}>
@ -212,12 +210,11 @@ class ChannelDetails extends Component {
</h3>
<h4>It may not be immediately in the sidebar</h4>
<p>
Contact the admins at
Contact the admins at
{' '}
<a href="mailto:yo@dev.to">yo@dev.to</a>
{' '}
if
this was a mistake
if this was a mistake
</p>
</div>
);

View file

@ -2,30 +2,30 @@ import { h, Component } from 'preact';
import PropTypes from 'prop-types';
export default class CodeEditor extends Component {
componentDidMount() {
import('codemirror')
.then(CodeMirror => {
const editor = document.getElementById("codeeditor");
import('codemirror').then(CodeMirror => {
const editor = document.getElementById('codeeditor');
const myCodeMirror = CodeMirror(editor, {
mode: "javascript",
theme: "material",
mode: 'javascript',
theme: 'material',
autofocus: true,
});
myCodeMirror.setSize("100%", "100%");
//Initial trigger:
const channel = window.pusher.channel(`presence-channel-${this.props.activeChannelId}`)
myCodeMirror.setSize('100%', '100%');
// Initial trigger:
const channel = window.pusher.channel(
`presence-channel-${this.props.activeChannelId}`,
);
channel.trigger('client-livecode', {
context: 'initializing-live-code-channel',
channel: `presence-channel-${this.props.activeChannelId}`
channel: `presence-channel-${this.props.activeChannelId}`,
});
//Coding trigger:
// Coding trigger:
myCodeMirror.on('keyup', cm => {
channel.trigger('client-livecode', {
keyPressed: true,
value: cm.getValue(),
cursorPos: cm.getCursor(),
});
channel.trigger('client-livecode', {
keyPressed: true,
value: cm.getValue(),
cursorPos: cm.getCursor(),
});
});
});
}
@ -35,11 +35,10 @@ export default class CodeEditor extends Component {
}
render() {
return <div id="codeeditor" className="chatcodeeditor">
<div className="chatcodeeditor__header">Experimental (WIP)</div>
</div>
return (
<div id="codeeditor" className="chatcodeeditor">
<div className="chatcodeeditor__header">Experimental (WIP)</div>
</div>
);
}
}
}

View file

@ -5,7 +5,7 @@ export default class Chat extends Component {
static propTypes = {
handleKeyDown: PropTypes.func.isRequired,
handleSubmitOnClick: PropTypes.func.isRequired,
activeChannelId: PropTypes.number
activeChannelId: PropTypes.number,
};
shouldComponentUpdate(nextProps) {

View file

@ -12,109 +12,128 @@ export default class Video extends Component {
pageY: null,
token: null,
room: null,
participants: []
}
participants: [],
};
}
componentDidMount() {
getTwilioToken('private-video-channel-'+this.props.activeChannelId, this.setupCallChannel)
getTwilioToken(
`private-video-channel-${this.props.activeChannelId}`,
this.setupCallChannel,
);
}
componentWillUnmount() {
this.state.room.disconnect();
}
setupCallChannel = (response) => {
setupCallChannel = response => {
const component = this;
import('twilio-video')
.then(({ connect, createLocalVideoTrack }) => {
connect(response.token,
import('twilio-video').then(({ connect, createLocalVideoTrack }) => {
connect(
response.token,
{
name:`private-video-channel-${this.props.activeChannelId}`,
name: `private-video-channel-${this.props.activeChannelId}`,
audio: true,
type: 'peer-to-peer',
video: { width: 640 }
}).then(function(room) {
component.setState({token: response.token, room: room})
createLocalVideoTrack().then(track => {
let localMediaContainer = document.getElementById('videolocalscreen');
localMediaContainer.appendChild(track.attach());
});
let roomParticipants = []
room.participants.forEach(participant => {
component.triggerRemoteJoin(participant);
roomParticipants.push(participant);
});
component.setState({participants: roomParticipants})
room.on('participantConnected', function(participant) {
component.triggerRemoteJoin(participant);
let roomParticipants = []
video: { width: 640 },
},
).then(
function(room) {
component.setState({ token: response.token, room });
createLocalVideoTrack().then(track => {
const localMediaContainer = document.getElementById(
'videolocalscreen',
);
localMediaContainer.appendChild(track.attach());
});
const roomParticipants = [];
room.participants.forEach(participant => {
component.triggerRemoteJoin(participant);
roomParticipants.push(participant);
});
component.setState({participants: roomParticipants})
room.on('participantDisconnected', function(participant) {
component.props.onExit()
component.setState({ participants: roomParticipants });
room.on('participantConnected', function(participant) {
component.triggerRemoteJoin(participant);
const roomParticipants = [];
room.participants.forEach(participant => {
roomParticipants.push(participant);
});
component.setState({ participants: roomParticipants });
room.on('participantDisconnected', function(participant) {
component.props.onExit();
});
});
})
}, function(error) {
document.getElementById('videoremotescreen').innerHTML = "";
console.error('Unable to connect to Room: ' + error.message);
});
},
function(error) {
document.getElementById('videoremotescreen').innerHTML = '';
console.error(`Unable to connect to Room: ${error.message}`);
},
);
});
}
};
triggerRemoteJoin = (participant) => {
triggerRemoteJoin = participant => {
// document.getElementById('videoremotescreen').innerHTML = ""
participant.on('trackAdded', track => {
document.getElementById('videoremotescreen').appendChild(track.attach());
});
}
};
handleDrag = e => {
if (!this.state.pageX) {
this.setState({pageX:e.pageX,
this.setState({
pageX: e.pageX,
pageY: e.pageY,
offsetDiffX: e.pageX - e.target.offsetLeft,
offsetDiffY: e.pageY - e.target.offsetTop,
})
} else if (e.pageX != 0){
});
} else if (e.pageX != 0) {
this.setState({
leftPx: this.state.pageX + e.pageX - this.state.pageX - this.state.offsetDiffX,
topPx: this.state.pageY + e.pageY - this.state.pageY - this.state.offsetDiffY
})
leftPx:
this.state.pageX +
e.pageX -
this.state.pageX -
this.state.offsetDiffX,
topPx:
this.state.pageY +
e.pageY -
this.state.pageY -
this.state.offsetDiffY,
});
} else if (e.pageX === 0) {
this.setState({pageX:null,
this.setState({
pageX: null,
pageY: null,
offsetDiffX: null,
offsetDiffY: null,
})
});
}
}
};
render() {
return (
<div
className="chat__videocall"
id="chat__videocall"
draggable="true"
onDrag={this.handleDrag}
style={{left:this.state.leftPx+'px', top: this.state.topPx+'px'}}
>
<div id="videoremotescreen" class={'chat__remotevideoscreen-'+this.state.participants.length}></div>
<div className="chat__localvideoscren" id="videolocalscreen"></div>
<button className="chat__videocallexitbutton" onClick={this.props.onExit}>
id="chat__videocall"
draggable="true"
onDrag={this.handleDrag}
style={{ left: `${this.state.leftPx}px`, top: `${this.state.topPx}px` }}
>
<div
id="videoremotescreen"
className={`chat__remotevideoscreen-${
this.state.participants.length
}`}
/>
<div className="chat__localvideoscren" id="videolocalscreen" />
<button
className="chat__videocallexitbutton"
onClick={this.props.onExit}
>
×
</button>
</div>
)
);
}
}

View file

@ -1,29 +1,49 @@
import { h, Component } from 'preact';
export default class View extends Component {
channel = (props) => {
return (
<div className="chatNonChatView_contentblock">
<h2>{props.channel.channel_name}</h2>
<div>
<em>{props.channel.description}</em>
</div>
<button
className="cta"
onClick={this.props.onAcceptInvitation}
data-content={props.channel.membership_id}
>
Accept
</button>
<button
className="cta"
onClick={this.props.onDeclineInvitation}
data-content={props.channel.membership_id}
>
Decline
</button>
</div>
);
};
render() {
const channels = this.props.channels.map((channel) => {
return <div className='chatNonChatView_contentblock'>
<h2>{channel.channel_name}</h2>
<div><em>{channel.description}</em></div>
<button className='cta' onClick={this.props.onAcceptInvitation} data-content={channel.membership_id}>Accept</button>
<button className='cta' onClick={this.props.onDeclineInvitation} data-content={channel.membership_id}>Decline</button>
</div>
const channels = this.props.channels.map(channel => {
return <this.channel channel={channel} />
});
return <div className="chatNonChatView">
<div className="container">
<button
class="chatNonChatView_exitbutton"
data-content="exit"
onClick={this.props.onViewExit}
>×</button>
<h1>Channel Invitations 🤗</h1>
{channels}
</div>
</div>
return (
<div className="chatNonChatView">
<div className="container">
<button
className="chatNonChatView_exitbutton"
data-content="exit"
onClick={this.props.onViewExit}
>
×
</button>
<h1>Channel Invitations 🤗</h1>
{channels}
</div>
</div>
);
}
}
}

View file

@ -21,12 +21,12 @@ function isUserSignedIn() {
function renderPage() {
import('../src/Onboarding')
.then(({ default: Onboarding }) => {
let waitingForOnboarding = setInterval(function(){
if (document.getElementById("main-head-stylesheet")) {
render(<Onboarding />, document.getElementById('top-bar'))
const waitingForOnboarding = setInterval(function() {
if (document.getElementById('main-head-stylesheet')) {
render(<Onboarding />, document.getElementById('top-bar'));
clearInterval(waitingForOnboarding);
}
}, 3)
}, 3);
})
.catch(error => {
// eslint-disable-next-line no-console

View file

@ -10,16 +10,16 @@ HTMLDocument.prototype.ready = new Promise(resolve => {
return null;
});
document.ready.then(function(){
document.ready.then(function() {
loadForm();
window.InstantClick.on('change', () => {
if (document.getElementById('article-form')){
if (document.getElementById('article-form')) {
loadForm();
}
});
});
function loadForm(){
function loadForm() {
getUserDataAndCsrfToken().then(({ currentUser, csrfToken }) => {
window.currentUser = currentUser;
window.csrfToken = csrfToken;
@ -32,5 +32,5 @@ function loadForm(){
root,
root.firstElementChild,
);
})
}
});
}

View file

@ -3,7 +3,7 @@ import { GithubRepos } from '../githubRepos/githubRepos';
function loadElement() {
const root = document.getElementById('github-repos-container');
if (root){
if (root) {
render(<GithubRepos />, root, root.firstElementChild);
}
}

View file

@ -79,7 +79,9 @@ class SidebarWidget extends Component {
return (
<div className="widget" id="widget-00001">
<div className="widget-suggested-follows-container">
<header><h4>who to follow</h4></header>
<header>
<h4>who to follow</h4>
</header>
<div className="widget-body">{users}</div>
</div>
</div>

View file

@ -222,8 +222,8 @@ class Onboarding extends Component {
const newCheckedUsers = checkedUsers.slice();
const index = checkedUsers.indexOf(user);
if(index > -1){
newCheckedUsers.splice(index,1);
if (index > -1) {
newCheckedUsers.splice(index, 1);
} else {
newCheckedUsers.push(user);
}
@ -259,12 +259,7 @@ class Onboarding extends Component {
}
handleNextButton() {
const {
users,
articles,
checkedUsers,
profileInfo,
} = this.state;
const { users, articles, checkedUsers, profileInfo } = this.state;
let { pageNumber } = this.state;
if (pageNumber === 2 && users.length === 0 && articles.length === 0) {
this.getUsersToFollow();

View file

@ -30,12 +30,14 @@ const OnboardingArticle = ({ article, isSaved, onSaveArticle }) => (
<img
src="https://practicaldev-herokuapp-com.freetls.fastly.net/assets/reactions-stack-4bb9c1e4b3e71b7aa135d6f9a5ef29a6494141da882edd4fa971a77abe13dbe7.png"
alt="Reactions"
/>{' '}
/>
{' '}
{article.positive_reactions_count}
<img
src="https://practicaldev-herokuapp-com.freetls.fastly.net/assets/comments-bubble-7448082accd39cfe9db9b977f38fa6e8f8d26dc43e142c5d160400d6f952ee47.png"
alt="Comments"
/>{' '}
/>
{' '}
{article.comments_count}
</div>
</div>

View file

@ -27,8 +27,11 @@ class OnboardingArticles extends Component {
return (
<div className="onboarding-user-container">
<div className="onboarding-user-cta">
When you see an interesting post, you can{' '}
<strong className="purple">SAVE</strong> it. To get started, here are
When you see an interesting post, you can
{' '}
<strong className="purple">SAVE</strong>
{' '}
it. To get started, here are
pre-selected suggestions.
</div>
<div className="onboarding-user-list">

View file

@ -1,24 +1,58 @@
import { h, render, Component } from 'preact';
// page 1
const OnboardingProfile = ({onChange}) => {
const OnboardingProfile = ({ onChange }) => {
return (
<div class="onboarding-profile-page">
<div className="onboarding-profile-page">
<div className="onboarding-user-cta">
Tell the us a bit about yourself
</div>
<div class="onboarding-profile-question">What's your quick bio?</div>
<input name="summary" placeholder="e.g. I'm a passionate hacker wizard unicorn ninja" maxLength="120" onChange={onChange} />
<div class="onboarding-profile-question">Where are you located?</div>
<input name="location" placeholder="e.g. New York City" maxLength="60" onChange={onChange} />
<div class="onboarding-profile-question">What is your title?</div>
<input name="employment_title" placeholder="e.g. Frontend developer" maxLength="60" onChange={onChange} />
<div class="onboarding-profile-question">Where do you work?</div>
<input name="employer_name" placeholder="e.g. Google" maxLength="60" onChange={onChange} />
<div class="onboarding-profile-question">What are your core skills/languages?</div>
<input name="mostly_work_with" placeholder="e.g. JavaScript and MongoDB" maxLength="200" onChange={onChange} />
<div class="onboarding-profile-question">What are you currently learning/playing with?</div>
<input name="currently_learning" placeholder="e.g. Rust and Docker" maxLength="200" onChange={onChange} />
<div className="onboarding-profile-question">What's your quick bio?</div>
<input
name="summary"
placeholder="e.g. I'm a passionate hacker wizard unicorn ninja"
maxLength="120"
onChange={onChange}
/>
<div className="onboarding-profile-question">Where are you located?</div>
<input
name="location"
placeholder="e.g. New York City"
maxLength="60"
onChange={onChange}
/>
<div className="onboarding-profile-question">What is your title?</div>
<input
name="employment_title"
placeholder="e.g. Frontend developer"
maxLength="60"
onChange={onChange}
/>
<div className="onboarding-profile-question">Where do you work?</div>
<input
name="employer_name"
placeholder="e.g. Google"
maxLength="60"
onChange={onChange}
/>
<div className="onboarding-profile-question">
What are your core skills/languages?
</div>
<input
name="mostly_work_with"
placeholder="e.g. JavaScript and MongoDB"
maxLength="200"
onChange={onChange}
/>
<div className="onboarding-profile-question">
What are you currently learning/playing with?
</div>
<input
name="currently_learning"
placeholder="e.g. Rust and Docker"
maxLength="200"
onChange={onChange}
/>
</div>
);
};

View file

@ -12,17 +12,28 @@ class OnboardingSingleTag extends Component {
}
render() {
const backgroundColor = this.props.tag.following ? this.props.tag.bg_color_hex : ''
const textroundColor = this.props.tag.following ? this.props.tag.text_color_hex : ''
const backgroundColor = this.props.tag.following
? this.props.tag.bg_color_hex
: '';
const textroundColor = this.props.tag.following
? this.props.tag.text_color_hex
: '';
return (
<div className={`onboarding-tag-container${this.props.tag.following ? ' followed-tag' : ''}`} id={`onboarding-tag-container-${this.props.tag.name}`} style={`background: ${backgroundColor}`}>
<div
className={`onboarding-tag-container${
this.props.tag.following ? ' followed-tag' : ''
}`}
id={`onboarding-tag-container-${this.props.tag.name}`}
style={`background: ${backgroundColor}`}
>
<a
className="onboarding-tag-link"
href="#"
style={`color:${textroundColor}`}
onClick={this.onClick}
>
#{this.props.tag.name}
#
{this.props.tag.name}
</a>
<a
className="onboarding-tag-link-follow"

View file

@ -6,21 +6,22 @@ class OnboardingTags extends Component {
constructor(props) {
super(props);
}
render() {
const tags = this.props.allTags.map((tag) => {
const tags = this.props.allTags.map(tag => {
return (
<OnboardingSingleTag key={tag.id} tag={tag} onTagClick={this.props.handleFollowTag.bind(this, tag)} />
<OnboardingSingleTag
key={tag.id}
tag={tag}
onTagClick={this.props.handleFollowTag.bind(this, tag)}
/>
);
});
return (
<div className="tags-slide">
<div className="onboarding-user-cta">
Personalize your home feed
</div>
<div className="tags-col-container">
{tags}
</div>
<div className="onboarding-user-cta">Personalize your home feed</div>
<div className="tags-col-container">{tags}</div>
</div>
);
}

View file

@ -12,19 +12,23 @@ class OnboardingUsers extends Component {
}
render() {
const followList = this.props.users.map((user) => {
const followList = this.props.users.map(user => {
return (
<div className="onboarding-user-list-row" key={user.id} >
<div className="onboarding-user-list-row" key={user.id}>
<div className="onboarding-user-list-key">
<img
src={user.profile_image_url}
alt={user.name}
/>
<img src={user.profile_image_url} alt={user.name} />
<div>{user.name}</div>
<div className="onboarding-user-list-row__summary"><em>{user.summary}</em></div>
<div className="onboarding-user-list-row__summary">
<em>{user.summary}</em>
</div>
</div>
<div className="onboarding-user-list-checkbox">
<button onClick={this.props.handleCheckUser.bind(this, user)} className={this.props.checkedUsers.indexOf(user) > -1 ? 'checked' : ''}>
<button
onClick={this.props.handleCheckUser.bind(this, user)}
className={
this.props.checkedUsers.indexOf(user) > -1 ? 'checked' : ''
}
>
{this.props.checkedUsers.indexOf(user) > -1 ? '✓' : '+'}
</button>
</div>
@ -33,11 +37,7 @@ class OnboardingUsers extends Component {
});
const renderLoadingOrList = () => {
if (this.props.users.length === 0) {
return (
<div className="onboarding-user-loading">
Loading...
</div>
);
return <div className="onboarding-user-loading">Loading...</div>;
}
return followList;
};
@ -49,12 +49,20 @@ class OnboardingUsers extends Component {
</div>
<div className="onboarding-user-list">
<div className="onboarding-user-list-header">
<div className="onboarding-user-list-key">
Follow All
</div>
<div className="onboarding-user-list-key">Follow All</div>
<div className="onboarding-user-list-checkbox">
<button id="onboarding-user-follow-all-btn" onClick={this.handleAllClick} className={this.props.checkedUsers.length === this.props.users.length ? 'checked' : ''}>
{this.props.checkedUsers.length === this.props.users.length ? '✓' : '+'}
<button
id="onboarding-user-follow-all-btn"
onClick={this.handleAllClick}
className={
this.props.checkedUsers.length === this.props.users.length
? 'checked'
: ''
}
>
{this.props.checkedUsers.length === this.props.users.length
? '✓'
: '+'}
</button>
</div>
</div>
@ -72,5 +80,4 @@ OnboardingUsers.propTypes = {
handleCheckAllUsers: PropTypes.func.isRequired,
};
export default OnboardingUsers;

View file

@ -3,16 +3,22 @@ import { h, render, Component } from 'preact';
// page 1
const OnboardingWelcome = () => {
const messages = [
"Thank you for joining the DEV Community.",
"Keep up with the people and software trends you care about. ❤️",
'Thank you for joining the DEV Community.',
'Keep up with the people and software trends you care about. ❤️',
];
const specialMessage = "Let's get started!";
return (
<div class="onboarding-initial-welcome">
{messages.map(item => (<p>{item}</p>))}
<p><em><strong className="green">{specialMessage}</strong></em></p>
<div className="onboarding-initial-welcome">
{messages.map(item => (
<p>{item}</p>
))}
<p>
<em>
<strong className="green">{specialMessage}</strong>
</em>
</p>
</div>
);
};

View file

@ -1,15 +1,15 @@
import { h } from 'preact';
const OnboardingWelcomeThread = () => {
const wrapInYellow = message => (
<strong className="yellow">{message}</strong>
);
const wrapInYellow = message => <strong className="yellow">{message}</strong>;
return (
<div class="onboarding-final-message">
<div className="onboarding-final-message">
<p>Software is driven by community.</p>
<p>Don't hesitate to find a discussion and jump right in.</p>
<p><em class="green">Everyone is welcome!</em></p>
<p>
<em className="green">Everyone is welcome!</em>
</p>
</div>
);
};

View file

@ -48,7 +48,9 @@ export class Search extends Component {
}
enableSearchPageChecker = true;
globalSearchKeyListener;
enableSearchPageListener = () => {
this.enableSearchPageChecker = true;
};
@ -76,7 +78,10 @@ export class Search extends Component {
}
search = event => {
const { keyCode, target: { value } } = event;
const {
keyCode,
target: { value },
} = event;
this.enableSearchPageChecker = false;

View file

@ -1,10 +1,7 @@
import { h } from 'preact';
const UnopenedChannelNotice = () => {
return (
<div style={{position: 'absolute'}}>
</div>
);
return <div style={{ position: 'absolute' }} />;
};
export default UnopenedChannelNotice;

View file

@ -1,6 +1,6 @@
import { h } from 'preact';
import { storiesOf } from '@storybook/react';
import { globalModalDecorator } from '../__stories__/story-decorators';
import { globalModalDecorator } from './story-decorators';
import OnboardingWelcome from '../OnboardingWelcome';
storiesOf('OnboardingWelcome', module)

View file

@ -1,6 +1,6 @@
import { h } from 'preact';
import { storiesOf } from '@storybook/react';
import { globalModalDecorator } from '../__stories__/story-decorators';
import { globalModalDecorator } from './story-decorators';
import OnboardingWelcomeThread from '../OnboardingWelcomeThread';
storiesOf('OnboardingWelcomeThread', module)

View file

@ -1,16 +1,15 @@
import { h, render, Component } from 'preact';
import setupPusher from './pusher';
class UnopenedChannelNotice extends Component {
constructor(props) {
super(props);
const unopenedChannels = this.props.unopenedChannels;
const visible = unopenedChannels.length > 0 ? true : false;
const { unopenedChannels } = this.props;
const visible = unopenedChannels.length > 0;
this.state = {
visible: visible,
unopenedChannels }
visible,
unopenedChannels,
};
}
componentDidMount() {
@ -19,79 +18,95 @@ class UnopenedChannelNotice extends Component {
messageCreated: this.receiveNewMessage,
});
const component = this;
document.getElementById("connect-link").onclick = function(){
//Hack, should probably be its own component in future
document.getElementById("connect-number").classList.remove("showing");
component.setState({visible: false});
}
document.getElementById('connect-link').onclick = function() {
// Hack, should probably be its own component in future
document.getElementById('connect-number').classList.remove('showing');
component.setState({ visible: false });
};
}
receiveNewMessage = e => {
if (location.pathname.startsWith("/connect")) {
return
if (location.pathname.startsWith('/connect')) {
return;
}
let channels = this.state.unopenedChannels;
const newObj = {adjusted_slug: e.chat_channel_adjusted_slug}
if(channels.filter(obj => obj.adjusted_slug === newObj.adjusted_slug).length === 0 &&
newObj.adjusted_slug != `@${window.currentUser.username}`) {
const channels = this.state.unopenedChannels;
const newObj = { adjusted_slug: e.chat_channel_adjusted_slug };
if (
channels.filter(obj => obj.adjusted_slug === newObj.adjusted_slug)
.length === 0 &&
newObj.adjusted_slug != `@${window.currentUser.username}`
) {
channels.push(newObj);
}
this.setState({
visible: (channels.length > 0 && e.user_id != window.currentUser.id),
unopenedChannels: channels
})
visible: channels.length > 0 && e.user_id != window.currentUser.id,
unopenedChannels: channels,
});
const number = document.getElementById("connect-number")
number.classList.add("showing")
number.innerHTML = channels.length
const number = document.getElementById('connect-number');
number.classList.add('showing');
number.innerHTML = channels.length;
const component = this;
if (channels.length === 0) {
number.classList.remove("showing")
number.classList.remove('showing');
} else {
document.getElementById("connect-link").href = `/connect/${channels[0].adjusted_slug}`
document.getElementById('connect-link').href = `/connect/${
channels[0].adjusted_slug
}`;
}
setTimeout(function(){
component.setState({visible: false});
}, 7500)
}
setTimeout(function() {
component.setState({ visible: false });
}, 7500);
};
handleClick = e => {
document.getElementById("connect-number").classList.remove("showing");
this.setState({visible: false})
}
document.getElementById('connect-number').classList.remove('showing');
this.setState({ visible: false });
};
render() {
if (this.state.visible) {
const channels = this.state.unopenedChannels.map(channel => {
return <a
href={"/connect/"+channel.adjusted_slug}
style={{
background: "#66e2d5",
color: "black",
border: "1px solid black",
padding: "2px 7px",
display: "inline-block",
margin: "3px 6px",
borderRadius: "3px"}}>{channel.adjusted_slug}</a>
return (
<a
href={`/connect/${channel.adjusted_slug}`}
style={{
background: '#66e2d5',
color: 'black',
border: '1px solid black',
padding: '2px 7px',
display: 'inline-block',
margin: '3px 6px',
borderRadius: '3px',
}}
>
{channel.adjusted_slug}
</a>
);
});
return (
<a
onClick={this.handleClick}
href={"/connect/"+this.state.unopenedChannels[0].adjusted_slug}
href={`/connect/${this.state.unopenedChannels[0].adjusted_slug}`}
style={{
position: 'fixed',
zIndex: '200',
top: '44px',
right: 0,
left: 0,
background: '#66e2d5',
borderBottom: '1px solid black',
color: 'black',
fontWeight: 'bold',
textAlign: 'center',
fontSize: '17px',
opacity: '0.94',
padding: '19px 5px 14px'}}>
New Message from {channels}
position: 'fixed',
zIndex: '200',
top: '44px',
right: 0,
left: 0,
background: '#66e2d5',
borderBottom: '1px solid black',
color: 'black',
fontWeight: 'bold',
textAlign: 'center',
fontSize: '17px',
opacity: '0.94',
padding: '19px 5px 14px',
}}
>
New Message from
{' '}
{channels}
</a>
);
}
@ -99,9 +114,15 @@ class UnopenedChannelNotice extends Component {
}
export default function getUnopenedChannels(user, successCb) {
render(<UnopenedChannelNotice unopenedChannels={[]} pusherKey={document.body.dataset.pusherKey} />, document.getElementById('message-notice'));
if (location.pathname.startsWith("/connect")) {
return
render(
<UnopenedChannelNotice
unopenedChannels={[]}
pusherKey={document.body.dataset.pusherKey}
/>,
document.getElementById('message-notice'),
);
if (location.pathname.startsWith('/connect')) {
return;
}
fetch('/chat_channels?state=unopened', {
method: 'GET',
@ -109,13 +130,15 @@ export default function getUnopenedChannels(user, successCb) {
})
.then(response => response.json())
.then(json => {
const number = document.getElementById("connect-number")
const number = document.getElementById('connect-number');
if (json.length > 0) {
number.classList.add("showing")
number.innerHTML = json.length
document.getElementById("connect-link").href = `/connect/${json[0].adjusted_slug}` // Jump the user directly to the channel where appropriate
number.classList.add('showing');
number.innerHTML = json.length;
document.getElementById('connect-link').href = `/connect/${
json[0].adjusted_slug
}`; // Jump the user directly to the channel where appropriate
} else {
number.classList.remove("showing")
number.classList.remove('showing');
}
})
.catch(error => {

View file

@ -21,8 +21,8 @@
},
"lint-staged": {
"*.{js,jsx}": [
"prettier --write",
"eslint --fix",
"prettier --write",
"git add"
],
"{app,spec}/**/*.rb": [