Commit 822f8ca3 authored by Even Gultvedt's avatar Even Gultvedt
Browse files

commit

parents
Pipeline #58473 failed with stage
in 9 seconds
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
.gitignore
.idea/
\ No newline at end of file
image: node:8
# Set up MySQL test-database
services:
- mysql:5.5
variables:
MYSQL_DATABASE: supertestdb
MYSQL_ROOT_PASSWORD: secret
#install node libraries
before_script:
- cd server
- npm install
- cd ../client_
- npm install
# Run JEST tests
run_tests:
stage: test
script:
- cd ../server
- npm test
- cd ../client_
- npm test
artifacts:
paths:
- coverage/
This diff is collapsed.
{
"name": "client",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^16.11.0",
"react-bootstrap": "^1.0.0-beta.14",
"react-dom": "^16.11.0",
"react-scripts": "3.2.0",
"react-simplified": "^1.6.1",
"react-ticker": "^1.2.1",
"react-native-text-ticker": "^1.0.0",
"mysql": "^2.17.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "jest --detectOpenHandles --forceExit",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"axios": "^0.19.0",
"history": "^4.10.1",
"react-native-text-ticker": "^1.0.0",
"react-router-dom": "^5.1.2"
}
}
This diff is collapsed.
This diff is collapsed.
/*Ticker CSS: https://codepen.io/lewismcarey/pen/GJZVoG*/
@-webkit-keyframes ticker {
0% {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
visibility: visible;
}
100% {
-webkit-transform: translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0);
}
}
@keyframes ticker {
0% {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
visibility: visible;
}
100% {
-webkit-transform: translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0);
}
}
.ticker-wrap {
width: 100%;
overflow: hidden;
height: 4rem;
background-color: rgba(0, 0, 0, 0.9);
padding-left: 100%;
box-sizing: content-box;
}
.ticker {
display: inline-block;
height: 4rem;
line-height: 4rem;
white-space: nowrap;
padding-right: 100%;
box-sizing: content-box;
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
-webkit-animation-timing-function: linear;
animation-timing-function: linear;
-webkit-animation-name: ticker;
animation-name: ticker;
-webkit-animation-duration: 20s;
animation-duration: 20s;
}
.ticker-item{
display: inline-block;
padding: 0 2rem;
font-size: 2rem;
color: white;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="bootstrap.min.css"/>
<link rel="stylesheet" href="bootstrap-grid.css" />
<link rel="stylesheet" href="index.css"/>
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
[ignore]
[include]
[libs]
[lints]
[options]
[strict]
module.exports = class Article{
static constantURL:string = 'https://png.pngtree.com/png-clipart/20190515/original/pngtree-coffee-time-png-image_3626459.jpg';
id: number;
author: string;
title: string;
imageURL: string;
time: string;
content: string;
category: number;
important: boolean;
constantURL: string = 'https://png.pngtree.com/png-clipart/20190515/original/pngtree-coffee-time-png-image_3626459.jpg';
comments: Comment[];
constructor(id: number, author: string, title: string, imageURL: string, time: string, content: string, category: number, important: boolean){
this.id = id;
this.author = author;
this.title = title;
if (imageURL == null){
this.imageURL = this.constantURL;
}
else{
this.imageURL = imageURL;
}
this.time = time;
this.content = content;
this.category = category;
this.important = important;
this.comments = [];
}
addComments(comments: Comment[]){
if (comments){
comments.map(element => this.comments.push(element));
}
}
};
\ No newline at end of file
import {Component} from "react-simplified";
import {articleStore} from "./stores";
import {NavLink} from "react-router-dom";
import {Button, Card, Column, Row} from "./widgets";
import Image from "react-bootstrap/Image";
import * as React from "react";
import {Header} from "./myWidgets";
import {AddComment, SingleComment} from "./CommentWidgets";
import {history} from "./myWidgets";
const Article = require('../src/Article');
export class ArticleGrid extends Component<{ category?: number }> {
mounted() {
articleStore.getAllArticles().catch(error => console.log(error));
}
render() {
if (articleStore.articles != null) {
return (
<div className='container' key={1}>
{
articleStore.articles.filter(a => ((!this.props.category && a.important) || (a.category === this.props.category))).map(article => (
<div className='container m-4 article-box' key={article.id}>
<NavLink className='card-link' exact to={'/articles/article/' + article.id}>
<Card title={article.title}>
<Image alt={article.title} src={article.imageURL} style={{width: '200px'}}
fluid/>
</Card>
</NavLink>
</div>
)
)
}
</div>
);
} else {
return null;
}
}
}
export class SingleArticle extends Component<{ match: { params: { id: number } } }> {
constructor(props) {
super(props);
if (this.props.match != null && this.props.match.params.id) {
console.log("Fetching " + this.props.match.params.id);
articleStore.getArticle(this.props.match.params.id).catch(error => console.log(error));
} else if (this.props.id != null && this.props.id) {
console.log("Fetching " + this.props.id);
articleStore.getArticle(this.props.id).catch(error => console.log(error));
}
}
render() {
if (articleStore.currentArticle != null) {
return (
<div>
<Header/>
<Card title={articleStore.currentArticle.title}>
<Image alt={articleStore.currentArticle.title} src={articleStore.currentArticle.imageURL}
style={{width: '200px'}} fluid/>
</Card>
<Card title="">{articleStore.currentArticle.content}</Card>
<Row>
<Column><Button.Info
onClick={() => (history.push("/articles/edit/" + articleStore.currentArticle.id))}>Edit</Button.Info></Column>
<Column><Button.Danger
onClick={() => articleStore.deleteCurrentArticle()}>Delete</Button.Danger></Column>
</Row>
{
articleStore.currentArticle.comments.map(comment => <SingleComment key={comment.id}
comment={comment}/>)
}
<AddComment article_id={articleStore.currentArticle.id}/>
</div>
);
} else {
return null;
}
}
}
class AddOrEditArticle extends Component {
author: string;
title: string;
imageURL: string;
content: string;
category: number;
important: boolean;
time: string;
validateURL(URL: string) {
return (URL.match(/\.(jpeg|jpg|gif|png)$/) != null);
}
constructor(props) {
super(props);
this.authorRef = React.createRef();
this.titleRef = React.createRef();
this.imageRef = React.createRef();
this.contentRef = React.createRef();
this.categoryRef = React.createRef();
this.importantRef = React.createRef();
}
render() {
return (
<div>
<Header/>
<Card title='Add article' id='title'>
<form>
<Row>
<Column width={2}>
<span className='input-group-text'>Author</span>
</Column>
<Column>
<input type='text' id='authorValue' ref={this.authorRef}
value={this.author}
onChange={(event: SyntheticInputEvent<HTMLInputElement>) => {
this.author = event.target.value
}}
className="form-control" aria-label="Large"
aria-describedby="inputGroup-sizing-sm"/>
</Column>
</Row>
<Row>
<Column width={2}>
<span className='input-group-text'>Title</span>
</Column>
<Column>
<input type='text' id='titleValue' ref={this.titleRef}
value={this.title}
onChange={(event: SyntheticInputEvent<HTMLInputElement>) => {
this.title = event.target.value
}}
className="form-control" aria-label="Large"
aria-describedby="inputGroup-sizing-sm"/>
</Column>
</Row>
<Row>
<Column width={2}>
<span className='input-group-text'>Image</span>
</Column>
<Column>
<input type="text" className='form-control' id="imageURL" ref={this.imageRef}
onChange={event => {
if (this.validateURL(event.target.value)) {
this.imageURL = event.target.value;
} else {
this.imageURL = null;
}
}}/>
</Column>
<Column>
<div className="input-group-append">
<button className="btn btn-outline-secondary" type="button"
onClick={(event: SyntheticInputEvent<HTMLButtonElement>) => this.imageURL = document.getElementById("imageURL").value}>Upload
</button>
</div>
</Column>
<Column>
<Image alt={this.title} src={this.imageURL === null ? null : this.imageURL}/>
</Column>
</Row>
<Row>
<Column width={2}>
<div className='input-group-prepend'>
<span className='input-group-text'>Content</span>
</div>
</Column>
<Column>
<textarea id='contentValue' ref={this.contentRef} value={this.content} className='form-control'
aria-label="With textarea" rows={15}
onChange={(event: SyntheticInputEvent<HTMLInputElement>) => {
this.content = event.target.value
}}/></Column>
</Row>
<Row>
<Column width={2}>
Category
</Column>
<Column>
<select id='categorySelection' ref={this.categoryRef} defaultValue={1}
className="custom-select"
onChange={event => {
this.category = event.target.options[event.target.selectedIndex].value
}}>
<option value={1}>News</option>
<option value={2}>Sports</option>
<option value={3}>Culture</option>
</select>
</Column>
</Row>
<Row>
<Column width={2}>
Important
</Column>
<Column>
<input id='importantValue' ref={this.importantRef} type='checkbox' onClick={event => {
this.important = event.target.checked
}}/>
</Column>
</Row>
<button className="btn btn-outline-secondary" type="button"
onClick={this.submitArticle}>Submit
</button>
</form>
</Card>
</div>
);
}
updateInternalValues() {
this.author = this.authorRef.current.value;
this.title = this.titleRef.current.value;
this.imageURL = (this.imageRef.current.value.trim().length > 0 && this.validateURL(this.imageRef.current.value) ? this.imageRef.current.value : null);
this.content = this.contentRef.current.value;
this.important = this.importantRef.current.checked;
let categorySelection = this.categoryRef.current;
this.category = categorySelection.options[categorySelection.selectedIndex].value;
}
updateInputValues(author: string, title: string, imageURL: string, content: string, important: boolean, category: number) {
this.authorRef.current.value = author;
this.titleRef.current.value = title;
this.imageRef.current.value = (imageURL === Article.constantURL ? null : imageURL);
this.contentRef.current.value = content;
this.importantRef.current.checked = important;
this.categoryRef.current.value = category;
this.updateInternalValues();
}
createArticle(id: number) {
let today = new Date();
this.time = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate() + " " +
(today.getHours() > 9 ? today.getHours() : '0' + today.getHours()) + ":" +
(today.getMinutes() > 9 ? today.getMinutes() : '0' + today.getMinutes());
console.log(this.imageURL);
return new Article(id, this.author, this.title, this.imageURL, this.time, this.content, this.category, this.important);
}
submitArticle() {
}
}
export class AddArticle extends AddOrEditArticle {
submitArticle() {
articleStore.getIds().then(ids => {
console.log(ids);
let id: number;
if (ids.length === 0) {
id = 0;
} else {
id = Math.max(...ids) + 1;
}
super.updateInternalValues();
console.log(id);
let newArticle = super.createArticle(id);
console.log(newArticle);
articleStore.articles.push(newArticle);
articleStore.postArticle(newArticle).then(response => {
if (response.statusText === 'OK') {
history.push("/articles/article/" + newArticle.id);
}
}).catch(error => console.log("Error:" + error));
});
}
}
export class EditArticle extends AddOrEditArticle<{ match: { params: { article_id: number } } }> {
componentDidMount() {
articleStore.getArticle(this.props.match.params.article_id).then(() => {
super.updateInputValues(articleStore.currentArticle.author,
articleStore.currentArticle.title,
articleStore.currentArticle.imageURL,
articleStore.currentArticle.content,
articleStore.currentArticle.important,
articleStore.currentArticle.category);
}).catch(error => {
console.log(error)
});
}
submitArticle() {
super.updateInternalValues();
let editedArticle = super.createArticle(this.props.match.params.article_id);
articleStore.editArticle(editedArticle).then(response => {
if (response.statusText === 'OK') {