client - thread page work
BIN
client/app/assets/Tyren.gif
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
client/app/assets/avatars/Blood_elf_female.gif
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
client/app/assets/avatars/Blood_elf_male.gif
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
client/app/assets/avatars/Draenei_female.gif
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
client/app/assets/avatars/Draenei_male.gif
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
client/app/assets/avatars/Dwarf_female.gif
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
client/app/assets/avatars/Dwarf_male.gif
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
client/app/assets/avatars/Gnome_female.gif
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
client/app/assets/avatars/Gnome_male.gif
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
client/app/assets/avatars/Human_female.gif
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
client/app/assets/avatars/Human_male.gif
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
client/app/assets/avatars/Night_elf_female.gif
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
client/app/assets/avatars/Night_elf_male.gif
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
client/app/assets/avatars/Orc_female.gif
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
client/app/assets/avatars/Orc_male.gif
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
client/app/assets/avatars/Tauren_female.gif
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
client/app/assets/avatars/Tauren_male.gif
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
client/app/assets/avatars/Troll_female.gif
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
client/app/assets/avatars/Troll_male.gif
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
client/app/assets/avatars/Undead_female.gif
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
client/app/assets/avatars/Undead_male.gif
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
client/app/assets/blizz.gif
Normal file
|
After Width: | Height: | Size: 314 B |
BIN
client/app/assets/forum-index-bot.gif
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
client/app/assets/forum-index.gif
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
client/app/assets/level-circle.gif
Normal file
|
After Width: | Height: | Size: 371 B |
BIN
client/app/assets/paging-bg.gif
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
client/app/assets/portrait-bot.gif
Normal file
|
After Width: | Height: | Size: 464 B |
BIN
client/app/assets/portrait-left.gif
Normal file
|
After Width: | Height: | Size: 313 B |
BIN
client/app/assets/portrait-right.gif
Normal file
|
After Width: | Height: | Size: 401 B |
BIN
client/app/assets/portrait-top.gif
Normal file
|
After Width: | Height: | Size: 373 B |
BIN
client/app/assets/quote-button.gif
Normal file
|
After Width: | Height: | Size: 749 B |
BIN
client/app/assets/reply-button.gif
Normal file
|
After Width: | Height: | Size: 687 B |
BIN
client/app/assets/topic-bg.gif
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
6
client/app/components/forum-nav/forum-nav.scss
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.forum-nav {
|
||||||
|
display: inline-block;
|
||||||
|
padding-left: 5px;
|
||||||
|
position: relative;
|
||||||
|
bottom: 10;
|
||||||
|
}
|
||||||
20
client/app/components/forum-nav/forum-nav.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import './forum-nav.scss';
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
interface State {}
|
||||||
|
|
||||||
|
export class ForumNav extends React.Component<Props, State> {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<img src={require('../../assets/wow-base-general.gif')}/>
|
||||||
|
<div className="forum-nav">
|
||||||
|
<small>Forum Nav:</small><select style={{ minWidth: '194px' }}></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
export * from './content-container/content-container';
|
export * from './content-container/content-container';
|
||||||
export * from './footer/footer';
|
export * from './footer/footer';
|
||||||
|
export * from './forum-nav/forum-nav';
|
||||||
export * from './header/header';
|
export * from './header/header';
|
||||||
export * from './login-button/login-button';
|
export * from './login-button/login-button';
|
||||||
|
export * from './portrait/portrait';
|
||||||
|
export * from './scroll-to-top/scroll-to-top';
|
||||||
|
|||||||
9
client/app/components/portrait/portrait.scss
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.portrait {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&__level-circle {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
left: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
29
client/app/components/portrait/portrait.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import './portrait.scss';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
imageSrc: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Portrait extends React.Component<Props, State> {
|
||||||
|
|
||||||
|
componentDidMount() {}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="portrait">
|
||||||
|
<img src={require('../../assets/portrait-top.gif')}/>
|
||||||
|
<div>
|
||||||
|
<img src={require('../../assets/level-circle.gif')} className="portrait__level-circle"/>
|
||||||
|
<img src={require('../../assets/portrait-left.gif')}/>
|
||||||
|
<img src={this.props.imageSrc}/>
|
||||||
|
<img src={require('../../assets/portrait-right.gif')}/>
|
||||||
|
</div>
|
||||||
|
<img src={require('../../assets/portrait-bot.gif')}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
client/app/components/scroll-to-top/scroll-to-top.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export class ScrollToTop extends React.Component<any, any> {
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,13 +8,6 @@ $grey2: #161616;
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.forum-nav {
|
|
||||||
display: inline-block;
|
|
||||||
padding-left: 5px;
|
|
||||||
position: relative;
|
|
||||||
bottom: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.forum-body {
|
.forum-body {
|
||||||
min-height: 500px;
|
min-height: 500px;
|
||||||
margin-bottom: 100px;
|
margin-bottom: 100px;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { Link, RouteComponentProps } from 'react-router-dom';
|
import { Link, RouteComponentProps } from 'react-router-dom';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import { ThreadService } from '../../services';
|
import { ThreadService } from '../../services';
|
||||||
import { LoginButton } from '../../components';
|
import { ForumNav, LoginButton, ScrollToTop } from '../../components';
|
||||||
import { ThreadModel } from '../../model';
|
import { ThreadModel } from '../../model';
|
||||||
import './forum.scss';
|
import './forum.scss';
|
||||||
|
|
||||||
@@ -32,12 +32,7 @@ export class Forum extends React.Component<Props, State> {
|
|||||||
renderHeader() {
|
renderHeader() {
|
||||||
return (
|
return (
|
||||||
<div className="forum-header">
|
<div className="forum-header">
|
||||||
<div>
|
<ForumNav />
|
||||||
<img src={require('../../assets/wow-base-general.gif')}/>
|
|
||||||
<div className="forum-nav">
|
|
||||||
<small>Forum Nav:</small><select style={{ minWidth: '194px' }}></select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style={{ height: '100%' }}>
|
<div style={{ height: '100%' }}>
|
||||||
<LoginButton/>
|
<LoginButton/>
|
||||||
</div>
|
</div>
|
||||||
@@ -115,10 +110,10 @@ export class Forum extends React.Component<Props, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<ScrollToTop>
|
||||||
{this.renderHeader()}
|
{this.renderHeader()}
|
||||||
{this.renderBody()}
|
{this.renderBody()}
|
||||||
</div>
|
</ScrollToTop>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link, RouteComponentProps } from 'react-router-dom';
|
import { Link, RouteComponentProps } from 'react-router-dom';
|
||||||
import { ContentContainer } from '../../components';
|
import { ContentContainer, ScrollToTop } from '../../components';
|
||||||
|
|
||||||
import './home.scss';
|
import './home.scss';
|
||||||
|
|
||||||
@@ -87,6 +87,7 @@ export class Home extends React.Component<Props, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
|
<ScrollToTop>
|
||||||
<ContentContainer>
|
<ContentContainer>
|
||||||
<img src={header_forms} />
|
<img src={header_forms} />
|
||||||
<div>
|
<div>
|
||||||
@@ -169,6 +170,7 @@ export class Home extends React.Component<Props, State> {
|
|||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
</ContentContainer>
|
</ContentContainer>
|
||||||
|
</ScrollToTop>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { chain } from 'lodash';
|
|||||||
import { Link, RouteComponentProps } from 'react-router-dom';
|
import { Link, RouteComponentProps } from 'react-router-dom';
|
||||||
import { CategoryService } from '../../services';
|
import { CategoryService } from '../../services';
|
||||||
import { CategoryModel } from '../../model';
|
import { CategoryModel } from '../../model';
|
||||||
import { ContentContainer } from '../../components';
|
import { ContentContainer, ScrollToTop } from '../../components';
|
||||||
import './realms.scss';
|
import './realms.scss';
|
||||||
import header_realmforums from '../../assets/header-realmforums.gif';
|
import header_realmforums from '../../assets/header-realmforums.gif';
|
||||||
import realms_large from '../../assets/realms-large.gif';
|
import realms_large from '../../assets/realms-large.gif';
|
||||||
@@ -49,6 +49,10 @@ export class Realms extends React.Component<Props, State> {
|
|||||||
render() {
|
render() {
|
||||||
const { realms } = this.state;
|
const { realms } = this.state;
|
||||||
|
|
||||||
|
if (realms.length === 0) {
|
||||||
|
return <div></div>;
|
||||||
|
}
|
||||||
|
|
||||||
// copy list so we don't modify state
|
// copy list so we don't modify state
|
||||||
const realmsCopy = realms.slice();
|
const realmsCopy = realms.slice();
|
||||||
// split realms into 3 lists
|
// split realms into 3 lists
|
||||||
@@ -56,7 +60,8 @@ export class Realms extends React.Component<Props, State> {
|
|||||||
const list2 = realmsCopy.splice(0, realmsCopy.length / 2);
|
const list2 = realmsCopy.splice(0, realmsCopy.length / 2);
|
||||||
const list3 = realmsCopy;
|
const list3 = realmsCopy;
|
||||||
|
|
||||||
return realms.length === 0 ? <div></div> : (
|
return (
|
||||||
|
<ScrollToTop>
|
||||||
<ContentContainer>
|
<ContentContainer>
|
||||||
<div className="flex flex--center">
|
<div className="flex flex--center">
|
||||||
<img src={realms_large}/>
|
<img src={realms_large}/>
|
||||||
@@ -82,6 +87,7 @@ export class Realms extends React.Component<Props, State> {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</ContentContainer>
|
</ContentContainer>
|
||||||
|
</ScrollToTop>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
88
client/app/pages/thread/thread.scss
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
.topic-bg {
|
||||||
|
background-image: url('../../assets/topic-bg.gif');
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
height: 41px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.threadTopic-container {
|
||||||
|
background-color: #0C0C0C;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: #8C8E89 #8C8E89 #0C0C0C #0C0C0C;
|
||||||
|
padding: 2px 10px 0 5px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.threadTopic {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paging-bg {
|
||||||
|
background-image: url('../../assets/paging-bg.gif');
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
height: 25px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-body {
|
||||||
|
background-color: #000000;
|
||||||
|
padding: 0 6px 4px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-container {
|
||||||
|
background-color: #000000;
|
||||||
|
padding: 2px;
|
||||||
|
border: 1px solid #343434;
|
||||||
|
|
||||||
|
& + & {
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply {
|
||||||
|
display: flex;
|
||||||
|
background-color: rgb(22, 22, 22);
|
||||||
|
// border: 1px solid #343434;
|
||||||
|
|
||||||
|
&__user-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 4px;
|
||||||
|
width: 150px;
|
||||||
|
border-right: 1px solid #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 26px;
|
||||||
|
border-bottom: 1px solid #000000;
|
||||||
|
padding: 0 6px;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
&__button {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
& + & {
|
||||||
|
margin-left: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.forumliner-bot-bg {
|
||||||
|
background-image: url('../../assets/forumliner-bot-bg.gif');
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
width: 100%;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
@@ -1,25 +1,106 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { RouteComponentProps } from 'react-router-dom';
|
import { RouteComponentProps } from 'react-router-dom';
|
||||||
|
import { get } from 'lodash';
|
||||||
import { ThreadService } from '../../services';
|
import { ThreadService } from '../../services';
|
||||||
|
import { ForumNav, Portrait, ScrollToTop } from '../../components';
|
||||||
|
import { ThreadModel } from '../../model';
|
||||||
|
import './thread.scss';
|
||||||
|
|
||||||
interface Props extends RouteComponentProps<any> {}
|
interface Props extends RouteComponentProps<any> {}
|
||||||
|
|
||||||
interface State {}
|
interface State {
|
||||||
|
thread?: ThreadModel;
|
||||||
|
}
|
||||||
|
|
||||||
export class Thread extends React.Component<Props, State> {
|
export class Thread extends React.Component<Props, State> {
|
||||||
|
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {};
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.getThreads();
|
this.getThreads();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getThreads() {
|
private async getThreads() {
|
||||||
const thread = await ThreadService.getThread(this.props.match.params['id']);
|
const thread = await ThreadService.getThread(this.props.match.params['id']);
|
||||||
console.log(thread);
|
thread.replies = [thread as any, ...thread.replies]; // add the thread topic to the front of the list
|
||||||
|
this.setState({ thread });
|
||||||
|
}
|
||||||
|
|
||||||
|
renderReplies(): any {
|
||||||
|
return this.state.thread!.replies.map((reply, index) => {
|
||||||
|
return (
|
||||||
|
<div className="reply-container" key={index}>
|
||||||
|
<div className="reply">
|
||||||
|
<div className="reply__user-container">
|
||||||
|
<Portrait imageSrc={require('../../assets/Tyren.gif')}/>
|
||||||
|
<div>Tyren</div>
|
||||||
|
<div>Blizzard Poster</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="reply__title">
|
||||||
|
<div>
|
||||||
|
<b>{`${index + 1}. `}{this.state.thread!.title}</b>
|
||||||
|
<small style={{ paddingLeft: '5px' }}>| {reply.inserted_at}</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<img src={require('../../assets/quote-button.gif')} className="reply__title__button"/>
|
||||||
|
<img src={require('../../assets/reply-button.gif')} className="reply__title__button"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* TODO: xss sanitization */}
|
||||||
|
<div className="reply__content">{reply.content}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private navigateForumIndex() {
|
||||||
|
this.props.history.push(`/f/${this.state.thread!.category_id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
|
const replies = get(this.state, 'thread.replies');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div></div>
|
<ScrollToTop {...this.props}>
|
||||||
|
|
||||||
|
<div style={{ padding: '16px 0 12px 0' }}>
|
||||||
|
<ForumNav/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="topic-bg">
|
||||||
|
<div className="threadTopic-container">
|
||||||
|
<div className="threadTopic">
|
||||||
|
<img src={require('../../assets/sticky.gif')} style={{ marginRight: '5px' }}/>
|
||||||
|
<b>Topic: </b><small style={{ paddingLeft: '15px', color: 'white' }}>| 12/20/2005 1:11:44 AM PST</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<img src={require('../../assets/forum-index.gif')}
|
||||||
|
onClick={() => this.navigateForumIndex()}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="paging-bg"/>
|
||||||
|
<div className="reply-body">
|
||||||
|
{replies && this.renderReplies()}
|
||||||
|
</div>
|
||||||
|
<div className="paging-bg"/>
|
||||||
|
<div className="forumliner-bot-bg"/>
|
||||||
|
<img src={require('../../assets/forum-index-bot.gif')}
|
||||||
|
onClick={() => this.navigateForumIndex()}
|
||||||
|
style={{ cursor: 'pointer', float: 'right' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div style={{ height: '200px' }}/>
|
||||||
|
</ScrollToTop>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||