Merge branch 'new_style'

This commit is contained in:
mrfry 2021-04-21 12:12:47 +02:00
commit 21fdbb57a3
72 changed files with 3153 additions and 6934 deletions

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
node_modules/ node_modules/
.next/ .next/
out/ out/
/.vs
public/ public/

5296
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -13,6 +13,7 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"babel-eslint": "^10.1.0",
"eslint-plugin-react": "^7.21.5", "eslint-plugin-react": "^7.21.5",
"next": "^10.0.3", "next": "^10.0.3",
"react": "^16.13.0", "react": "^16.13.0",

View file

@ -1,13 +1,12 @@
import React from 'react'
import styles from './button.module.css' import styles from './button.module.css'
export default function Button (props) { export default function Button(props) {
return ( return (
<div> <div>
<center> <center>
<a href={props.href}> <a href={props.href}>
<div className={styles.ircLink}> <div className={styles.button}>{props.text}</div>
{props.text}
</div>
</a> </a>
</center> </center>
</div> </div>

View file

@ -1,6 +1,6 @@
.load { .load {
border: 4px solid #f3f3f3; border: 4px solid #f3f3f3;
border-top: 4px solid #99f; border-top: 4px solid var(--text-color);
border-radius: 50%; border-radius: 50%;
width: 20px; width: 20px;
height: 20px; height: 20px;

View file

@ -1,9 +1,11 @@
import React, { PureComponent } from 'react' import React from 'react'
class Question extends PureComponent { function highlightText(text, toHighlight) {
render () { const re = new RegExp(toHighlight, 'gi')
const { question } = this.props return text.replace(re, `<mark>${toHighlight}</mark>`)
}
export default function Question({ question, searchTerm }) {
let qdata = question.data let qdata = question.data
if (typeof qdata === 'object' && qdata.type === 'simple') { if (typeof qdata === 'object' && qdata.type === 'simple') {
qdata = '' qdata = ''
@ -11,23 +13,29 @@ class Question extends PureComponent {
if (qdata) { if (qdata) {
try { try {
qdata = JSON.stringify(qdata) qdata = JSON.stringify(qdata)
} catch (e) {} } catch (e) {
//
} }
}
const questionText = searchTerm
? highlightText(question.Q, searchTerm)
: question.Q
const answerText = searchTerm
? highlightText(question.A, searchTerm)
: question.A
return ( return (
<div className='questionContainer'> <div className="questionContainer">
<div className='question'> <div
{question.Q} className="question"
</div> dangerouslySetInnerHTML={{ __html: questionText }}
<div className='answer'> ></div>
{question.A} <div
</div> className="answer"
<div className='data'> dangerouslySetInnerHTML={{ __html: answerText }}
{qdata || null} ></div>
</div> <div className="data">{qdata || null}</div>
</div> </div>
) )
}
} }
export default Question

View file

@ -1,4 +1,4 @@
import React, { PureComponent } from 'react' import React from 'react'
import Questions from './Questions.js' import Questions from './Questions.js'
@ -8,10 +8,7 @@ const countReducer = (acc, subj) => {
return acc + subj.Questions.length return acc + subj.Questions.length
} }
class QuestionSearchResult extends PureComponent { export default function QuestionSearchResult({ data, searchTerm }) {
render() {
const { data, searchTerm } = this.props
let subjs = [] let subjs = []
let results = -1 let results = -1
@ -48,37 +45,37 @@ class QuestionSearchResult extends PureComponent {
const renderCount = () => { const renderCount = () => {
return ( return (
<div> <div className="resultContainer">
<div>
{searchTerm ? '' : 'Kezdj el írni kereséshez! '}
{searchTerm
? `${results} találat, ${subjs.length} tárgyból`
: `Keresés ${results} kérdés és ${data.length} tárgyból`}
</div>
{results === 0 && ( {results === 0 && (
<div> <div>
{ {`${results} találat. Az általad keresett kifejezés nem található,
'Pontos egyezést keres a kereső, próbálj kisebb részletre keresni' próbáld bővíteni a keresési feltételt!`}
}
</div> </div>
)} )}
{results > 0 && <div>{`${results} találat.`}</div>}
</div> </div>
) )
} }
if (results > constants.maxQuestionsToRender) { if (results > constants.maxQuestionsToRender) {
return renderCount() return (
<div>
{searchTerm ? (
<div className="resultContainer">
Szűkítsd a keresési feltételeket a találatok megjelenítéséhez!
</div>
) : null}
<hr />
</div>
)
} else { } else {
return ( return (
<div> <div>
<div>{renderCount()}</div> <div>{renderCount()}</div>
<div> <div>
<Questions subjs={subjs} /> <Questions subjs={subjs} searchTerm={searchTerm} />
</div> </div>
</div> </div>
) )
} }
}
} }
export default QuestionSearchResult

View file

@ -5,22 +5,22 @@ import Question from './Question.js'
import styles from './Questions.module.css' import styles from './Questions.module.css'
class Questions extends PureComponent { class Questions extends PureComponent {
render () { render() {
const { subjs } = this.props const { subjs, searchTerm } = this.props
return ( return (
<div> <div>
<hr />
{subjs.map((subj, i) => { {subjs.map((subj, i) => {
return ( return (
<div key={i}> <div className={styles.questionBg} key={i}>
<div className={styles.subjName}> <div className={styles.subjName}>{subj.Name}</div>
{subj.Name} {subj.Questions.map((question, i) => {
</div>
{ subj.Questions.map((question, i) => {
return ( return (
<Question <Question
key={i} key={i}
question={question} question={question}
searchTerm={searchTerm}
/> />
) )
})} })}

View file

@ -1,7 +1,14 @@
.subjName { .subjName {
font-size: 24px; font-size: 24px;
background-color: #9999ff; font-weight: 600;
background-color: var(--text-color);
color: black; color: black;
margin-top: 25px;
padding: 10px; padding: 10px;
word-wrap: break-word; word-wrap: break-word;
} }
.questionBg {
background-color: #1b1b1c;
padding-bottom: 5px;
}

View file

@ -3,26 +3,19 @@ import React, { PureComponent } from 'react'
import Question from './Question.js' import Question from './Question.js'
class Subject extends PureComponent { class Subject extends PureComponent {
render () { render() {
const { subj } = this.props const { subj } = this.props
if (subj) { if (subj) {
return ( return (
<div > <div>
{subj.Questions.map((question, i) => { {subj.Questions.map((question, i) => {
return ( return <Question key={i} question={question} />
<Question
key={i}
question={question}
/>
)
})} })}
</div> </div>
) )
} else { } else {
return ( return <div />
<div />
)
} }
} }
} }

View file

@ -1,10 +1,10 @@
import styles from './SubjectSelector.module.css' import styles from './SubjectSelector.module.css'
export default function SubjectSelector (props) { export default function SubjectSelector(props) {
const { activeSubjName, searchTerm, data, onSubjSelect } = props const { activeSubjName, searchTerm, data, onSubjSelect } = props
return ( return (
<div className='subjectSelector'> <div className="subjectSelector">
{data.map((subj, i) => { {data.map((subj, i) => {
if (!subj.Name.toLowerCase().includes(searchTerm.toLowerCase())) { if (!subj.Name.toLowerCase().includes(searchTerm.toLowerCase())) {
return null return null
@ -12,16 +12,15 @@ export default function SubjectSelector (props) {
return ( return (
<div <div
className={activeSubjName === subj.Name className={
activeSubjName === subj.Name
? 'subjItem activeSubjItem' ? 'subjItem activeSubjItem'
: 'subjItem' : 'subjItem'
} }
key={i} key={i}
onClick={() => onSubjSelect(subj.Name)} onClick={() => onSubjSelect(subj.Name)}
> >
<span className={styles.subjName}> <span className={styles.subjName}>{subj.Name}</span>
{subj.Name}
</span>
<span className={styles.questionCount}> <span className={styles.questionCount}>
[ {subj.Questions.length} ] [ {subj.Questions.length} ]
</span> </span>

View file

@ -1,4 +1,4 @@
.ircLink { .button {
background-color: #9999ff; background-color: #9999ff;
border: none; border: none;
color: white; color: white;

View file

@ -22,14 +22,14 @@ function CommentInput({ onSubmit, onCancel }) {
onSubmit(val) onSubmit(val)
}} }}
> >
Submit Küldés
</span> </span>
<span <span
onClick={() => { onClick={() => {
onCancel() onCancel()
}} }}
> >
Cancel Bezárás
</span> </span>
</div> </div>
</div> </div>
@ -63,25 +63,27 @@ function Comment({ comment, index, onComment, onDelete, onReact, uid }) {
</div> </div>
<div>User #{user}</div> <div>User #{user}</div>
</div> </div>
<div>{date}</div> <div className={styles.commentDate}>{date}</div>
</div> </div>
<div className={`${!displayed && styles.hidden}`}> <div className={`${!displayed && styles.hidden}`}>
<div className={styles.commentText}> {content}</div> <div className={styles.commentText}> {content}</div>
<div className={'actions'}> <div className={'actions'}>
<span <span
className={styles.reply_bttn}
onClick={() => { onClick={() => {
setCommenting(true) setCommenting(true)
}} }}
> >
Reply... Válasz...
</span> </span>
{own && ( {own && (
<span <span
className={styles.delete_bttn}
onClick={() => { onClick={() => {
onDelete([index]) onDelete([index])
}} }}
> >
Delete Törlés
</span> </span>
)} )}
<ReactButton <ReactButton
@ -179,11 +181,12 @@ export default function Comments({
{commentCount !== 0 ? ( {commentCount !== 0 ? (
<div className={'actions'}> <div className={'actions'}>
<span <span
className={styles.comment_bttn}
onClick={() => { onClick={() => {
setAddingNewComment(true) setAddingNewComment(true)
}} }}
> >
New comment Új komment
</span> </span>
</div> </div>
) : null} ) : null}
@ -191,7 +194,7 @@ export default function Comments({
<CommentInput <CommentInput
onSubmit={(e) => { onSubmit={(e) => {
if (!e) { if (!e) {
alert('Írj be valamit, hogy kommentelhess...') alert('Írj be valamit a szövegdobozba, hogy kommentelhess!')
return return
} }
setAddingNewComment(false) setAddingNewComment(false)
@ -207,8 +210,8 @@ export default function Comments({
) : null} ) : null}
</Modal> </Modal>
) : null} ) : null}
<div className={'actions'}> <div
<span className={'actions'}
onClick={() => { onClick={() => {
setCommentsShowing(true) setCommentsShowing(true)
if (commentCount === 0) { if (commentCount === 0) {
@ -216,9 +219,10 @@ export default function Comments({
} }
}} }}
> >
<span className={styles.comment_bttn}>
{commentCount === 0 {commentCount === 0
? 'New comment' ? 'Új komment'
: `Show ${commentCount} comment${commentCount > 1 ? 's' : ''}`} : `Kommentek mutatása (${commentCount})`}
</span> </span>
</div> </div>
</div> </div>

View file

@ -1,21 +1,58 @@
.comment_bttn {
font-size: 15px;
padding-top: 4px !important;
transition: width 0.5s, height 0.5s, ease-in 0.5s;
}
.comment_bttn:hover {
transition: width 0.5s, height 0.5s, ease-out 0.5s;
color: gainsboro;
}
.reply_bttn {
font-size: 15px;
margin-top: 14.5px !important;
margin-left: 6px !important;
padding-top: 4px !important;
transition: width 0.5s, height 0.5s, ease-in 0.5s;
}
.reply_bttn:hover {
transition: width 0.5s, height 0.5s, ease-out 0.5s;
color: gainsboro;
}
.delete_bttn {
font-size: 15px;
margin-top: 14.5px !important;
margin-left: 6px !important;
padding-top: 4px !important;
transition: width 0.5s, height 0.5s, ease-in 0.5s;
}
.delete_bttn:hover {
transition: width 0.5s, height 0.5s, ease-out 0.5s;
color: gainsboro;
}
.comment { .comment {
margin-left: 25px; margin-left: 10px;
margin-right: 30px;
padding: 8px 0px; padding: 8px 0px;
} }
.commentData { .commentData {
padding: 5px 2px; padding: 5px 2px;
border-left: 2px solid var(--text-color); border-left: 3px solid azure;
border-radius: 3px;
} }
.commentData:hover { .commentDate {
background-color: var(--hoover-color); text-align: right;
} }
.commentHeader { .commentHeader {
display: flex; display: flex;
justify-content: space-between; /*justify-content: space-between;*/
} }
.commentHeader > div { .commentHeader > div {
@ -38,7 +75,7 @@
} }
.ownComment { .ownComment {
border-left: 2px solid green; border-left: 5px dotted azure;
} }
.showHide { .showHide {
@ -58,5 +95,5 @@
} }
.adminComment { .adminComment {
border-left: 2px solid yellow; border-left: 3px solid gold;
} }

View file

@ -4,37 +4,23 @@ import Modal from './modal'
import styles from './composer.module.css' import styles from './composer.module.css'
function FileUploader({ onChange }) {
return (
<div className={styles.inputArea}>
<div className={styles.textTitle}>Fájl csatolása</div>
<input
className={styles.fileInput}
type="file"
name="file"
onChange={onChange}
/>
</div>
)
}
export default function Composer({ onSubmit }) { export default function Composer({ onSubmit }) {
const [editorShowing, setEditorShowing] = useState(false) const [editorShowing, setEditorShowing] = useState(false)
const [val, setVal] = useState('') const [val, setVal] = useState('')
const [type, setType] = useState('public')
const [title, setTitle] = useState('') const [title, setTitle] = useState('')
const [file, setFile] = useState()
return ( return (
<> <>
<center>
<div <div
onClick={() => { onClick={() => {
setEditorShowing(true) setEditorShowing(true)
}} }}
className={styles.new} className={styles.new}
> >
Új bejegyzés / feedback Bejegyzés írása...
</div> </div>
</center>
{editorShowing && ( {editorShowing && (
<Modal <Modal
closeClick={() => { closeClick={() => {
@ -42,73 +28,39 @@ export default function Composer({ onSubmit }) {
}} }}
> >
<div className={styles.container}> <div className={styles.container}>
{type !== 'private' && (
<input <input
placeholder={'Téma'} placeholder={'Téma...'}
required
value={title} value={title}
onChange={(e) => { onChange={(e) => {
setTitle(e.target.value) setTitle(e.target.value)
}} }}
/> />
)}
<textarea <textarea
placeholder={'...'} placeholder={'Írj ide valamit...'}
required
value={val} value={val}
onChange={(e) => { onChange={(e) => {
setVal(e.target.value) setVal(e.target.value)
}} }}
/> />
{type === 'private' && ( <div className={styles.composerAction}>
<FileUploader
onChange={(e) => {
setFile(e.target.files[0])
}}
/>
)}
<div className={styles.typeSelector}>
<div>Post típusa:</div>
<div title="Minden felhasználó látja főoldalon, és tudnak rá válaszolni">
<input
onChange={(e) => {
setType(e.target.value)
}}
type="radio"
name="type"
value="public"
defaultChecked
/>
Publikus
</div>
<div title="Csak a weboldal üzemeltetője látja">
<input
onChange={(e) => {
setType(e.target.value)
}}
type="radio"
name="type"
value="private"
/>
Privát
</div>
<div className={styles.tip}>
(Tartsd egered opciókra több infóért)
</div>
</div>
<div className={'actions'}>
<span <span
onClick={() => { onClick={() => {
onSubmit(type, title, val, file) if (!title) {
alert('Üres a tartalom!')
return
}
if (!val) {
alert('Üres a téma!')
return
}
onSubmit(title, val)
setEditorShowing(false) setEditorShowing(false)
}} }}
> >
Post Posztolás
</span>
<span
onClick={() => {
setEditorShowing(false)
}}
>
Cancel
</span> </span>
</div> </div>
</div> </div>

View file

@ -1,32 +1,97 @@
.container { .container {
display: flex; display: flex;
flex-flow: column; flex-flow: column;
max-width: 400px;
width: 900px;
} }
.container > input, .container > input,
.container > textarea { .container > textarea {
margin: 5px 0px; margin: 5px 0px;
padding: 4px; padding: 4px;
width: 97%;
border-color: #5d5f61;
background-color: #1f2021;
border-radius: 5px;
font-size: 15px;
}
.container > textarea {
margin-left: 1px;
width: 99%;
resize: none;
} }
.typeSelector { .typeSelector {
display: inline-flex;
text-align: center;
margin-left: 6px;
}
.typeSelector > div {
display: flex; display: flex;
align-items: center;
} }
.tip { .tip {
font-size: 10px; font-size: 11px;
margin: 0px 10px; font-style: italic;
text-align: center;
margin: 15px 0px 2px 2px;
} }
.new { .new {
display: flex;
justify-content: center;
margin: 10px 0px;
height: 19px;
width: 30%;
display: flex;
background-color: var(--hoover-color);
border: none;
color: var(--text-color);
padding: 10px; padding: 10px;
text-align: center; font-size: 15px;
border: 2px dashed var(--text-color); margin: 8px;
border-radius: 3px;
cursor: pointer; cursor: pointer;
text-shadow: 1px 1px 8px black;
transition: width 0.5s, height 0.5s, ease-in 0.5s;
text-align: center;
text-decoration: none;
} }
.new:hover { .new:hover {
background-color: var(--hoover-color); text-shadow: 2px 2px 8px black;
transition: width 0.5s, height 0.5s, ease-out 0.5s;
background-color: var(--text-color);
font-weight: bold;
color: black;
}
.composerAction {
display: flex;
align-items: center;
justify-content: center;
width: 36%;
height: 100%;
}
.composerAction > span {
margin: 2px 2px;
padding: 0px 10px;
border: 1px solid #444;
border-radius: 5px;
cursor: pointer;
user-select: none;
font-size: 15px;
margin-top: 14.5px !important;
padding-top: 4px !important;
transform: translate(109%, 10%);
transition: width 0.5s, height 0.5s, ease-in 0.5s;
}
.composerAction > span:hover {
background-color: var(--hoover-color);
transition: width 0.5s, height 0.5s, ease-out 0.5s;
color: var(--text-color);
text-shadow: 2px 1.5px 10px black;
} }

View file

@ -1,23 +1,33 @@
.container { .container {
display: flex; display: flex;
flex-flow: column; flex-flow: column;
margin-top: 8px;
margin-bottom: 0px;
margin-right: 22%;
margin-left: 22%;
padding: 5px; padding: 5px;
} }
.listItem { .listItem {
display: flex; display: flex;
justify-content: center; justify-content: center;
text-align: center;
padding: 5px; padding-top: 10px;
padding-bottom: 10px;
margin: 3px;
cursor: pointer; cursor: pointer;
color: white; color: #a1a1a1;
transition: width 0.5s, height 0.5s, ease-in 0.5s;
} }
.listItem:hover { .listItem:hover {
background-color: var(--hoover-color); background-color: var(--hoover-color);
transition: width 0.5s, height 0.5s, ease-out 0.5s;
text-shadow: 2px 1.5px 10px black;
color: var(--text-color);
} }
.text { .text {
text-align: center; text-align: center;
color: gainsboro;
} }

View file

@ -0,0 +1,22 @@
import React from 'react'
export default function ExternalLinkIcon({ size }) {
return (
<span style={{ margin: '4px', display: 'inline-block' }}>
<svg
width={size}
height={size}
viewBox={`0 0 24 24`}
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
<polyline points="15 3 21 3 21 9"></polyline>
<line x1="10" y1="14" x2="21" y2="3"></line>
</svg>
</span>
)
}

View file

@ -0,0 +1,106 @@
import React, { useState } from 'react'
import styles from './feedbackArea.module.css'
import constants from '../constants.json'
function FileUploader({ onChange }) {
return (
<div className={styles.inputArea}>
<div className={styles.textTitle}>Fájl csatolása</div>
<input
className={styles.fileInput}
type="file"
name="file"
onChange={onChange}
/>
</div>
)
}
function submitFeedback(from, content, file) {
return new Promise((resolve) => {
const promises = [
fetch(constants.apiUrl + 'postfeedback', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
content: content,
from: from,
}),
}).then((res) => {
return res.json()
}),
]
if (file) {
const formData = new FormData() // eslint-disable-line
formData.append('file', file)
promises.push(
fetch(constants.apiUrl + 'postfeedbackfile', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
},
body: formData,
}).then((res) => {
return res.json()
})
)
}
Promise.all(promises).then((res) => {
resolve(res)
})
})
}
export default function FeedbackArea({ from, allowFile }) {
const [feedback, setFeedback] = useState('')
const [file, setFile] = useState()
return (
<div className={styles.inputArea}>
<center>
<textarea
placeholder={'Ide kezdhetsz el írni...'}
onChange={(event) => setFeedback(event.target.value)}
value={feedback}
className={styles.contactFeedback}
/>
{allowFile && (
<div className={styles.fileContainer}>
<FileUploader
onChange={(e) => {
setFile(e.target.files[0])
}}
/>
</div>
)}
</center>
<center>
<div className={`${styles.send} buttonContainer`}>
<div
onClick={() => {
if (feedback) {
submitFeedback(from, feedback, file).then(() => {
alert('Üzenet elküldve!')
setFeedback('')
})
} else {
alert('Üresen hagytad az üzenet szövegének helyét!')
}
}}
>
Küldés
</div>
</div>
</center>
</div>
)
}

View file

@ -0,0 +1,25 @@
.contactFeedback {
color: var(--text-color);
background-color: var(--background-color);
font-size: 14px;
width: 600px;
box-sizing: border-box;
height: 150px;
resize: none;
margin-top: 15px;
padding: 5px;
}
.send {
width: 20%;
margin-bottom: 60px;
}
.inputArea {
display: block;
}
.fileContainer {
width: 400px;
margin: 5px;
}

View file

@ -9,27 +9,120 @@ import tabs from '../data/tabs.json'
import constants from '../constants.json' import constants from '../constants.json'
import BB from './b.js' import BB from './b.js'
import styles from './layout.module.css'
const renderSnow = () => { const renderSnow = () => {
const date = new Date() const date = new Date()
// if its december, and date is more than 5 // if its december, and date is more than 5
return date.getMonth() === 11 && date.getDate() > 5 return date.getMonth() === 11 && date.getDate() > 5
} }
function Donate() {
return (
<div>
<div className={styles.modalHead}>Donate</div>
<div style={{ textAlign: 'justify', margin: '8px' }}>
Paypalen és Patreonon látszódik a neved, de Patreonon könnyen meg lehet
változtatni. Ha név nélkül szeretnél adakozni, akkor írd át egy
nickname-re. De akárhova adakozol, a neved sehova se lesz kiadva, és nem
látja 3. személy.
</div>
<div className={styles.donateLogoContainer}>
<a
href={`${constants.siteUrl}patreon`}
target="_blank"
rel="noreferrer"
>
<img
style={{
border: 'none',
margin: '15px',
}}
src={`${constants.siteUrl}img/patreon-logo.png`}
/>
</a>
<a href={`${constants.siteUrl}donate`} target="_blank" rel="noreferrer">
<img
style={{
border: 'none',
margin: '15px',
}}
src={`${constants.siteUrl}img/paypal-logo.png`}
/>
</a>
</div>
</div>
)
}
function MessageButton({
userSpecificMotd,
setShowMotdModal,
refetchGlobalData,
}) {
return (
<div className="msgs">
<div
onClick={() => {
if (!userSpecificMotd) {
return
}
setShowMotdModal(true)
if (userSpecificMotd.seen) {
return
}
fetch(constants.apiUrl + 'infos', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
userSpecificMotdSeen: true,
}),
})
.then((resp) => {
return resp.json()
})
.then(() => {
refetchGlobalData()
})
}}
style={{ cursor: userSpecificMotd ? 'pointer' : 'default' }}
title={
userSpecificMotd && !userSpecificMotd.seen ? 'Új üzeneted van!' : ''
}
>
{userSpecificMotd && !userSpecificMotd.seen ? '📬' : '📭'}
</div>
</div>
)
}
export default function Layout({ export default function Layout({
children, children,
route, router,
globalData, globalData,
refetchGlobalData, refetchGlobalData,
}) { }) {
let href = route
const [sidebarOpen, setSidebarOpen] = useState(true) const [sidebarOpen, setSidebarOpen] = useState(true)
const [windowSize, setWindowSize] = useState([100, 200]) const [windowSize, setWindowSize] = useState([100, 200])
const [showMotdModal, setShowMotdModal] = useState(false) const [showMotdModal, setShowMotdModal] = useState(false)
const [donateShowing, setDonateShowing] = useState(false)
const [showNewMsgModal, setShowNewMsgModal] = useState(true) const [showNewMsgModal, setShowNewMsgModal] = useState(true)
const userId = globalData.userId const userId = globalData.userId
const userSpecificMotd = globalData.userSpecificMotd const userSpecificMotd = globalData.userSpecificMotd
useEffect(
() => {
setDonateShowing(!!router.query.donate)
},
[router.query.donate]
)
let href = router.route
if (href === '/' || href === '') { if (href === '/' || href === '') {
href = 'index' href = 'index'
} }
@ -80,23 +173,33 @@ export default function Layout({
<div /> <div />
</span> </span>
<div className="sidebarheader"> <div className="sidebarheader">
<Link href="/">
<a>
<img <img
style={{ maxWidth: '100%' }} style={{
src={`${constants.siteUrl}img/frylabs-logo_small_transparent.png`} maxWidth: '100%',
alt="Frylabs" border: 'none',
margin: '1px',
}}
src={`${
constants.siteUrl
}img/frylabs-logo_small_transparent.png`}
alt="FryLabs"
/> />
</a>
</Link>
</div> </div>
</div> </div>
{sidebarOpen ? ( {sidebarOpen ? (
<> <>
<div id="sideBarLinks" className="sidebarLinks"> <div id="sideBarLinks" className={styles.sidebarLinks}>
{Object.keys(tabs).map((key) => { {Object.keys(tabs).map((key) => {
const item = tabs[key] const item = tabs[key]
return ( return (
<Link href={item.href} key={key}> <Link href={item.href} key={key}>
<a <a
onClick={closeSideBar} onClick={closeSideBar}
className={href.includes(key) ? 'active' : undefined} className={href.includes(key) ? styles.active : undefined}
id={item.id || undefined} id={item.id || undefined}
> >
{item.text} {item.text}
@ -105,68 +208,26 @@ export default function Layout({
) )
})} })}
<a <a
href={`/dataeditor`} onClick={() => {
onClick={closeSideBar} closeSideBar()
target="_blank" setDonateShowing(true)
rel="noreferrer" }}
title={
'Kérdések szerkesztése, törlése, beküldése, és kitöltetlen tesztek'
}
>
Kérdés szerkesztő / kitöltetlen tesztek
</a>
<a
href={`/donate`}
className="donate"
onClick={closeSideBar}
target="_blank"
rel="noreferrer"
> >
Donate Donate
</a> </a>
</div> </div>
<div className="userStatus"> <div className={'userStatus'}>
<div className="msgs"> <MessageButton
<div userSpecificMotd={userSpecificMotd}
onClick={() => { setShowMotdModal={setShowMotdModal}
if (!userSpecificMotd) { refetchGlobalData={refetchGlobalData}
return />
} <div className={'uid'} title="User ID">
setShowMotdModal(true) UID: {userId || '...'}
if (userSpecificMotd.seen) {
return
}
fetch(constants.apiUrl + 'infos', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
userSpecificMotdSeen: true,
}),
})
.then((resp) => {
return resp.json()
})
.then(() => {
refetchGlobalData()
})
}}
style={{ cursor: userSpecificMotd ? 'pointer' : 'default' }}
title={
userSpecificMotd && !userSpecificMotd.seen
? "You've got Mail!"
: ''
}
>
{userSpecificMotd && !userSpecificMotd.seen ? '📬' : '📭'}
</div>
<div title="User ID">UID: {userId || '...'}</div>
</div> </div>
<div <div
className="logout" className={'logout'}
title="Kijelentkezés"
onClick={() => { onClick={() => {
fetch(constants.apiUrl + 'logout', { fetch(constants.apiUrl + 'logout', {
method: 'GET', method: 'GET',
@ -182,6 +243,18 @@ export default function Layout({
Logout Logout
</div> </div>
</div> </div>
</>
) : null}
</div>
{donateShowing ? (
<Modal
closeClick={() => {
setDonateShowing(false)
}}
>
<Donate />
</Modal>
) : null}
{showMotdModal ? ( {showMotdModal ? (
<Modal <Modal
closeClick={() => { closeClick={() => {
@ -189,24 +262,19 @@ export default function Layout({
}} }}
> >
<div style={{ textAlign: 'center' }}>Üzenet admintól:</div> <div style={{ textAlign: 'center' }}>Üzenet admintól:</div>
<div <div dangerouslySetInnerHTML={{ __html: userSpecificMotd.msg }} />
dangerouslySetInnerHTML={{ __html: userSpecificMotd.msg }}
/>
</Modal> </Modal>
) : null} ) : null}
</>
) : null}
</div>
<div className="content">{children}</div>
{userSpecificMotd && !userSpecificMotd.seen && showNewMsgModal ? ( {userSpecificMotd && !userSpecificMotd.seen && showNewMsgModal ? (
<Modal <Modal
closeClick={() => { closeClick={() => {
setShowNewMsgModal(false) setShowNewMsgModal(false)
}} }}
> >
Új üzeneted van, kattints 📬-ra bal alul megtekintéséhez! Új üzeneted van, kattints a 📬-ra bal alul a megtekintéséhez!
</Modal> </Modal>
) : null} ) : null}
<div className="content">{children}</div>
<BB /> <BB />
</div> </div>
) )

View file

@ -0,0 +1,49 @@
.modalHead {
text-align: center;
font-size: 22px;
font-weight: 700;
padding-top: 0px;
margin-top: 0px;
padding-bottom: 20px;
letter-spacing: 3px;
}
.donateLogoContainer {
display: flex;
justify-content: center;
}
.donateLogoContainer img {
max-width: 100px;
margin: 5px;
border-radius: 5px;
}
.sidebarLinks > * {
display: block;
text-align: center;
color: black;
font-size: 108%;
padding: 14px;
margin-top: 4px;
margin-bottom: 4px;
text-decoration: none;
color: var(--bright-color);
transition: width 0.5s, height 0.5s, ease-in 0.5s;
cursor: pointer;
}
.sidebarLinks a.active {
border: 0.5px solid var(--text-color);
color: white;
text-shadow: 2px 2px 8px black;
font-weight: bold;
}
.sidebarLinks a:hover:not(.active) {
background-color: var(--text-color);
color: black;
font-weight: bold;
text-shadow: 2px 2px 8px black;
transition: width 0.5s, height 0.5s, ease-out 0.5s;
}

View file

@ -12,7 +12,9 @@
display: flex; display: flex;
align-items: stretch; align-items: stretch;
max-height: 80%; max-height: 80%;
width: 80%; width: 50%;
max-width: 52%;
width: auto;
position: fixed; position: fixed;
background: var(--background-color); background: var(--background-color);
height: auto; height: auto;
@ -20,24 +22,27 @@
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
border-radius: 8px; border-radius: 8px;
padding-top: 38px;
padding: 20px 30px; padding-bottom: 25px;
cursor: auto; padding-right: 14px;
padding-left: 16px;
cursor: default;
} }
.close { .close {
cursor: pointer; cursor: pointer;
font-size: 18px; font-size: 16.5px;
position: absolute; position: fixed;
top: 5px;
top: 10px;
right: 10px; right: 10px;
margin-bottom: 10px;
text-align: right;
display: inline; display: inline;
} }
.children { .children {
max-height: 100%; max-height: 100%;
width: 100%; width: 100%;
overflow-y: scroll; overflow-y: auto;
overflow-x: hidden; overflow-x: auto;
} }

View file

@ -17,13 +17,17 @@ export default function NewsEntry({
const { reacts, title, content, user, comments, date, admin } = newsItem const { reacts, title, content, user, comments, date, admin } = newsItem
return ( return (
<div> <div className={styles.newsRoot}>
<div className={`${styles.newsContainer} ${admin && styles.adminPost}`}> <div
className={`${styles.newsContainer} ${admin &&
styles.adminPost} ${!admin && styles.userPost} ${uid == user &&
styles.ownPost} ${uid == user && admin && styles.adminPost}`}
>
<div className={styles.newsHeader}> <div className={styles.newsHeader}>
<div className={styles.newsTitle}>{title}</div> <div className={styles.newsTitle}>{title}</div>
<div className={styles.user}> <div className={styles.userinfo}>
<div>User #{user}</div> <div>User #{user}</div>
<div className={styles.newsDate}>{date}</div> <div className={styles.newsDate}> @ {date}</div>
</div> </div>
</div> </div>
{admin ? ( {admin ? (
@ -38,11 +42,12 @@ export default function NewsEntry({
<div className={'actions'}> <div className={'actions'}>
{uid === user ? ( {uid === user ? (
<span <span
className={styles.delete_bttn}
onClick={() => { onClick={() => {
onPostDelete() onPostDelete()
}} }}
> >
Delete Törlés
</span> </span>
) : null} ) : null}
<ReactButton <ReactButton
@ -62,8 +67,6 @@ export default function NewsEntry({
onDelete={onDelete} onDelete={onDelete}
comments={comments} comments={comments}
/> />
<hr />
<hr />
</div> </div>
) )
} }

View file

@ -1,32 +1,51 @@
.newsBody { .newsBody {
margin: 0px 5px; margin: 0px 12px;
font-size: 18px;
color: #fff; color: #fff;
white-space: pre-line; white-space: pre-line;
text-align: justify;
} }
.newsTitle { .newsTitle {
font-size: 20px; font-size: 25px;
color: var(--text-color); color: var(--text-color);
margin: 0px 5px; margin: 0px 11px;
padding-bottom: 10px;
} }
.newsDate { .newsDate {
margin: 0px 5px; margin-right: 12px;
margin-left: 14px;
} }
.newsContainer { .newsContainer {
margin: 5px 5px; margin: 5px 5px;
} }
.delete_bttn {
font-size: 15px;
margin-top: 12px !important;
margin-left: 2px !important;
margin-right: 5px !important;
padding-top: 4px !important;
transition: width 0.5s, height 0.5s, ease-in 0.5s;
}
.adminPost { .adminPost {
border-left: 2px solid yellow; border-left: 4px solid var(--text-color);
border-radius: 5px; }
.userPost {
border-left: 4px solid azure;
}
.ownPost {
border-left: 4px dotted azure;
} }
.newsContainer img { .newsContainer img {
max-width: 100%; max-width: 100%;
min-width: 200px; min-width: 200px;
border: 2px solid white;
} }
.newsHeader { .newsHeader {
@ -35,7 +54,17 @@
align-items: center; align-items: center;
} }
.user { .userinfo {
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 12px;
}
.newsRoot {
background-color: var(--dark-color);
margin-left: 8px;
margin-right: 8px;
margin-bottom: 16px;
margin-top: 16px;
padding: 10px;
} }

View file

@ -23,7 +23,7 @@ function useOutsideAlerter(ref, action) {
function ExistingReacts({ existingReacts, onClick, uid }) { function ExistingReacts({ existingReacts, onClick, uid }) {
return ( return (
<div className={styles.reactionContainer}> <div className={styles.reactionContainer}>
<div>React</div> <div className={styles.react_bttn}>Reakció</div>
{existingReacts && {existingReacts &&
Object.keys(existingReacts).map((key) => { Object.keys(existingReacts).map((key) => {
const currReact = existingReacts[key] const currReact = existingReacts[key]
@ -33,7 +33,7 @@ function ExistingReacts({ existingReacts, onClick, uid }) {
} }
return ( return (
<div <div
title={currReact.join(', ')} title={`'${key}': ${currReact.join(', ')}`}
className={`${currReact.includes(uid) && styles.reacted}`} className={`${currReact.includes(uid) && styles.reacted}`}
key={key} key={key}
onClick={(e) => { onClick={(e) => {

View file

@ -1,6 +1,8 @@
.reactionContainer { .reactionContainer {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
margin-top: 15px !important;
margin: 4px 2px;
} }
.reactionContainer > input { .reactionContainer > input {
@ -20,10 +22,13 @@
border-radius: 6px; border-radius: 6px;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
transition: width 0.5s, height 0.5s, ease-in 0.5s;
} }
.reactionContainer > div:hover { .reactionContainer > div:hover {
background-color: var(--hoover-color); background-color: var(--hoover-color);
color: gainsboro;
transition: width 0.5s, height 0.5s, ease-out 0.5s;
} }
.break { .break {
@ -32,9 +37,20 @@
} }
.reacted { .reacted {
color: yellow; background-color: var(--text-color);
color: black;
}
.reacted:hover {
background-color: #96810b !important;
color: #42423e !important;
} }
.reactContainer { .reactContainer {
display: inline-block; display: inline-block;
} }
.react_bttn {
font-size: 15px;
padding-top: 3px !important;
}

View file

@ -1,20 +0,0 @@
import React from 'react'
import styles from './sidebar.module.css'
export default function Sidebar(props) {
const { onClose } = props
return (
<div className={styles.container}>
<div
className={styles.closeBorder}
onClick={() => {
onClose()
}}
>
{'⏩'}
</div>
<div className={styles.content}>{props.children}</div>
</div>
)
}

View file

@ -1,31 +0,0 @@
.container {
background-color: var(--background-color);
border-left: 3px solid #99f;
top: 0;
right: 0px;
height: 100%;
width: 400px;
max-width: 100%;
position: fixed;
display: flex;
}
.closeBorder {
font-size: 14px;
padding: 2px;
cursor: pointer;
display: flex;
flex-flow: column;
justify-content: center;
color: white;
background-color: #222;
}
.closeBorder:hover {
background-color: #333;
}
.content {
width: 100%;
}

View file

@ -1,6 +1,6 @@
import constants from '../constants.json' import constants from '../constants.json'
export default function Sleep (props) { export default function Sleep(props) {
const hours = new Date().getHours() const hours = new Date().getHours()
if (hours < 4 || hours > 23) { if (hours < 4 || hours > 23) {
return ( return (
@ -9,9 +9,11 @@ export default function Sleep (props) {
<img <img
style={{ style={{
margin: '10px', margin: '10px',
width: '300px' width: '300px',
border: '2px solid white',
}} }}
src={constants.siteUrl + 'img/aludni.jpeg'} src={constants.siteUrl + 'img/aludni.jpeg'}
title="Ezt a képet azert látod, mert ilyenkor már igazán nem ezen az oldalon kellene járnod"
/> />
</center> </center>
</div> </div>

View file

@ -3,20 +3,7 @@
padding: 4px; padding: 4px;
} }
.groupContainer { .todoButtons {
display: flex; height: 38px;
} text-wrap: normal;
.group {
padding: 8px 16px;
cursor: pointer;
user-select: none;
}
.group:hover {
background-color: #333;
}
.selectedGroup {
background-color: #444;
} }

View file

@ -11,6 +11,7 @@
.categoryName { .categoryName {
text-align: center; text-align: center;
margin-top: 85px !important;
margin: 5px 0px; margin: 5px 0px;
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;

View file

@ -11,7 +11,7 @@ export default function TodoCard(props) {
<div <div
className={styles.card} className={styles.card}
style={{ style={{
border: `2px solid ${group || '#99f'}`, border: `1px solid ${categories[category].color || '#f2cb05'}`,
}} }}
onClick={() => { onClick={() => {
onClick(props.cardData) onClick(props.cardData)
@ -27,7 +27,7 @@ export default function TodoCard(props) {
backgroundColor: categories[category].color, backgroundColor: categories[category].color,
color: 'white', color: 'white',
borderRadius: '2px', borderRadius: '2px',
padding: '0px 2px', padding: '3px',
}} }}
> >
{categories[category].name} {categories[category].name}
@ -35,9 +35,9 @@ export default function TodoCard(props) {
</div> </div>
<div className={styles.numbers}> <div className={styles.numbers}>
<div className={`${voted && styles.voted}`}> <div className={`${voted && styles.voted}`}>
<div>{`Votes: ${votes.length}`}</div> <div>{`Szavazatok: ${votes.length}`}</div>
</div> </div>
<div>{points}</div> <div>{`Nehézség: ${points}`}</div>
</div> </div>
</div> </div>
) )

View file

@ -1,12 +1,14 @@
.card { .card {
border-radius: 2px; border-radius: 5px;
padding: 5px; padding: 7px;
margin: 6px 3px; margin: 8px 4px;
cursor: pointer; cursor: pointer;
background-color: #171616;
} }
.voted { .voted {
color: #cf9; color: var(--text-color);
font-weight: 600;
} }
.card:hover { .card:hover {
@ -15,7 +17,7 @@
} }
.card > div { .card > div {
margin: 5px 0px; margin: 6px 4px;
} }
.description { .description {
@ -25,7 +27,7 @@
} }
.category { .category {
font-size: 10px; font-size: 12px;
white-space: nowrap; white-space: nowrap;
} }
@ -39,6 +41,6 @@
} }
.id { .id {
margin: 0px 3px; margin: 1px 6px 1px 1px;
color: #999; color: #999;
} }

View file

@ -6,6 +6,8 @@ export default function TodoRow(props) {
const { categories, userId, onClick } = props const { categories, userId, onClick } = props
const { name, category, votes, id, group } = props.rowData const { name, category, votes, id, group } = props.rowData
const voted = votes.includes(userId) const voted = votes.includes(userId)
const borderColor =
categories[category].borderColor || categories[category].color
return ( return (
<div <div
@ -14,7 +16,8 @@ export default function TodoRow(props) {
}} }}
className={styles.row} className={styles.row}
style={{ style={{
border: `2px solid ${group || '#99f'}`, border: `2px dashed ${borderColor || 'white'}`,
borderRadius: '3px',
}} }}
> >
<div className={styles.id}>{`#${id}`}</div> <div className={styles.id}>{`#${id}`}</div>
@ -23,11 +26,11 @@ export default function TodoRow(props) {
<div <div
style={{ style={{
wordBreak: 'break-all', wordBreak: 'break-all',
fontSize: '10px', fontSize: '12px',
backgroundColor: categories[category].color, backgroundColor: categories[category].color,
color: 'white', color: 'white',
borderRadius: '2px', borderRadius: '2px',
padding: '0px 2px', padding: '3px',
}} }}
> >
{categories[category].name} {categories[category].name}
@ -35,7 +38,7 @@ export default function TodoRow(props) {
</div> </div>
<div <div
className={`${styles.votes} ${voted && styles.voted}`} className={`${styles.votes} ${voted && styles.voted}`}
>{`Votes: ${votes.length}`}</div> >{`Szavazatok: ${votes.length}`}</div>
</div> </div>
) )
} }

View file

@ -1,10 +1,9 @@
.row { .row {
display: flex; border-radius: 5px;
border: 2px solid #99f; padding: 7px;
border-radius: 2px; margin: 8px 4px;
padding: 5px;
margin: 6px 3px;
cursor: pointer; cursor: pointer;
background-color: #171616;
} }
.row:hover { .row:hover {
@ -13,12 +12,12 @@
} }
.row > div { .row > div {
margin: 0px 5px; margin: 6px 4px;
} }
.id { .id {
flex: 0 20px; flex: 0 20px;
margin: 0px 3px; margin: 1px 6px 1px 1px;
color: #999; color: #999;
} }
@ -30,10 +29,14 @@
} }
.votes { .votes {
display: flex;
justify-content: space-between;
font-size: 12px;
} }
.voted { .voted {
color: #cf9; color: var(--text-color);
font-weight: 600;
} }
.catName { .catName {

View file

@ -25,18 +25,18 @@ export default function Todos({
// TODO: hide vote button if not voteable // TODO: hide vote button if not voteable
return ( return (
<div className={styles.container}> <div className={styles.container}>
<div className={styles.name}> <div className={styles.title}>
<span className={styles.id}>#{id}</span> <span className={styles.id}>#{id}</span>
<div className={'subtitle'} style={{ textAlign: 'left' }}>
{name} {name}
</div>
<span <span
style={{ style={{
margin: '0px 5px',
fontSize: '10px',
backgroundColor: categories[category].color, backgroundColor: categories[category].color,
color: 'white', color: 'white',
fontSize: '12px',
borderRadius: '2px', borderRadius: '2px',
padding: '0px 2px', padding: '3px',
whiteSpace: 'nowrap',
}} }}
> >
{categories[category].name} {categories[category].name}
@ -45,7 +45,7 @@ export default function Todos({
<hr /> <hr />
{group && ( {group && (
<div className={styles.row}> <div className={styles.row}>
<div>Csoport:</div> <div style={{ color: group }}>Csoport:</div>
<div style={{ color: group }}> <div style={{ color: group }}>
{namedGroups[group] ? namedGroups[group].name : group} {namedGroups[group] ? namedGroups[group].name : group}
</div> </div>
@ -64,22 +64,36 @@ export default function Todos({
<div>{votes.length}</div> <div>{votes.length}</div>
</div> </div>
{gitlink && ( {gitlink && (
<div className={styles.row}> <center>
<a href={gitlink} target={'_blank'} rel={'noreferrer'}> <div>
<a
href={gitlink}
style={{ textDecoration: 'none' }}
target={'_blank'}
rel={'noreferrer'}
>
Git link Git link
</a> </a>
</div> </div>
</center>
)} )}
<hr /> <hr />
{description && ( {description && (
<> <>
<div className={styles.title}>Leírás</div> <div className={'subtitle'} style={{ textAlign: 'left' }}>
<div dangerouslySetInnerHTML={{ __html: description }} /> Leírás
</div>
<div
className={styles.description}
dangerouslySetInnerHTML={{ __html: description }}
/>
<hr /> <hr />
</> </>
)} )}
<div className={styles.category}></div> <div className={styles.category} />
{voteable && ( {voteable && (
<center>
<div className={'buttonContainer'} style={{ width: '40%' }}>
<div <div
className={`${styles.button} ${votes.includes(userId) && className={`${styles.button} ${votes.includes(userId) &&
styles.voted}`} styles.voted}`}
@ -89,6 +103,8 @@ export default function Todos({
> >
{votes.includes(userId) ? 'Szavazat visszavonása' : 'Szavazás'} {votes.includes(userId) ? 'Szavazat visszavonása' : 'Szavazás'}
</div> </div>
</div>
</center>
)} )}
</div> </div>
) )

View file

@ -9,14 +9,17 @@
} }
.title { .title {
color: #99f; color: var(--text-color);
font-size: 18px; font-size: 18px;
} }
.name { .title {
color: #99f; color: var(--text-color);
font-size: 20px; font-size: 20px;
margin: 20px 0px; margin: 5px 0px;
}
.name {
} }
.category { .category {
@ -25,18 +28,26 @@
} }
.id { .id {
font-size: 16px; font-size: 20px;
margin: 0px 3px; margin: 0px 3px;
color: #999; color: #999;
} }
.description {
overflow-x: auto;
overflow-y: auto;
max-height: calc(38vh);
margin: 5px;
}
.row { .row {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin: 2px 10px;
} }
.button { .button {
border: 2px solid #99f; border: 2px solid var(--text-color);
border-radius: 3px; border-radius: 3px;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;

View file

@ -20,6 +20,7 @@ export default function TodoBoard(props) {
return ( return (
<div className={styles.container} key={key}> <div className={styles.container} key={key}>
<div className={styles.title}>{table.name}</div> <div className={styles.title}>{table.name}</div>
<div className={styles.scroll}>
{tableCards.map((card, i) => { {tableCards.map((card, i) => {
const shouldHide = const shouldHide =
card.state !== key || card.state !== key ||
@ -44,6 +45,7 @@ export default function TodoBoard(props) {
) )
})} })}
</div> </div>
</div>
) )
})} })}
</div> </div>

View file

@ -11,6 +11,8 @@
.container { .container {
flex-direction: column; flex-direction: column;
margin-top: 30px;
margin-bottom: 50px;
} }
.title { .title {
@ -18,3 +20,9 @@
font-size: 20px; font-size: 20px;
font-weight: bold; font-weight: bold;
} }
.scroll {
max-height: 500px;
overflow-y: auto;
overflow-x: hidden;
}

View file

@ -4,11 +4,9 @@ import LoadingIndicator from '../LoadingIndicator.js'
import TodoBoard from './todoBoard.js' import TodoBoard from './todoBoard.js'
import TodoTable from './todoTable.js' import TodoTable from './todoTable.js'
import TodoSidebar from './todoSidebar.js' import TodoSidebar from './todoSidebar.js'
import Modal from '../modal.js'
import Sidebar from '../sidebar.js'
import styles from './todo.module.css' import styles from './todo.module.css'
// import styles from './todos.module.css'
import constants from '../../constants.json' import constants from '../../constants.json'
const byVotes = (a, b) => { const byVotes = (a, b) => {
@ -26,7 +24,6 @@ export default function Todos() {
const [selectedGroup, setSelectedGroup] = useState(null) const [selectedGroup, setSelectedGroup] = useState(null)
useEffect(() => { useEffect(() => {
console.info('Fetching todos')
fetch(`${constants.apiUrl}todos`, { fetch(`${constants.apiUrl}todos`, {
credentials: 'include', credentials: 'include',
}) })
@ -96,13 +93,14 @@ export default function Todos() {
const sg = namedGroups[selectedGroup] const sg = namedGroups[selectedGroup]
return ( return (
<> <>
<div className={styles.groupContainer}> <div className={'buttonContainer'}>
{groups.map((group) => { {groups.map((group) => {
const namedGroup = namedGroups[group] const namedGroup = namedGroups[group]
return ( return (
<div <div
className={`${styles.group} ${group === selectedGroup && className={`${'buttonContainer'}, ${
styles.selectedGroup}`} styles.todoButtons
} ${group === selectedGroup && 'activeButton'}`}
key={group} key={group}
onClick={() => { onClick={() => {
setSelectedGroup(group) setSelectedGroup(group)
@ -113,8 +111,9 @@ export default function Todos() {
) )
})} })}
<div <div
className={`${styles.group} ${'uncat' === selectedGroup && className={`${'buttonContainer'}, ${
styles.selectedGroup}`} styles.todoButtons
} ${'uncat' === selectedGroup && 'activeButton'}`}
onClick={() => { onClick={() => {
setSelectedGroup('uncat') setSelectedGroup('uncat')
}} }}
@ -122,7 +121,8 @@ export default function Todos() {
{'Kategorizálatlan'} {'Kategorizálatlan'}
</div> </div>
<div <div
className={styles.group} className={`${'buttonContainer'}, ${styles.todoButtons} ${null ===
selectedGroup && 'activeButton'}`}
onClick={() => { onClick={() => {
setSelectedGroup(null) setSelectedGroup(null)
}} }}
@ -140,8 +140,8 @@ export default function Todos() {
return ( return (
<div> <div>
{sidebarCard && ( {sidebarCard && (
<Sidebar <Modal
onClose={() => { closeClick={() => {
setSidebarCard(null) setSidebarCard(null)
}} }}
> >
@ -165,15 +165,10 @@ export default function Todos() {
}) })
}} }}
/> />
</Sidebar> </Modal>
)} )}
{renderGrouper()} {renderGrouper()}
<div <div>
style={{
// width: '100%',
marginRight: sidebarCard ? 380 : 0,
}}
>
<TodoBoard <TodoBoard
columns={cols} columns={cols}
cards={bCards} cards={bCards}

View file

@ -1,7 +1,6 @@
{ {
"siteUrl": "https://qmining.frylabs.net/", "siteUrl": "https://qmining.frylabs.net/",
"apiUrl": "https://api.frylabs.net/", "apiUrl": "https://api.frylabs.net/",
"devApiUrl": "http://localhost:8080/",
"mobileWindowWidth": 700, "mobileWindowWidth": 700,
"maxQuestionsToRender": 250 "maxQuestionsToRender": 250
} }

View file

@ -1,8 +1,5 @@
{ {
"header": [ "header": ["Miben", "Hogy"],
"Miben",
"Hogy"
],
"rows": { "rows": {
"feedbacker": { "feedbacker": {
"name": "Visszajelzésben", "name": "Visszajelzésben",

View file

@ -1,22 +0,0 @@
{
"install": {
"href": "/install",
"text": "Install"
},
"allqr": {
"href": "/allqr.txt",
"text": "Összes kérdés TXT"
},
"data": {
"href": "/data.json",
"text": "Összes kérdés JSON"
},
"irc": {
"href": "/irc?index",
"text": "IRC chat"
},
"dataeditor": {
"href": "/dataeditor?index",
"text": "Dataeditor"
}
}

View file

@ -1,26 +1,34 @@
{ {
"index": { "index": {
"href": "/", "href": "/",
"text": "Home" "text": "Főoldal"
}, },
"manual": { "script": {
"href": "/manual", "href": "/script",
"text": "Manual" "text": "Script"
}, },
"allQuestions": { "allQuestions": {
"href": "/allQuestions", "href": "/allQuestions",
"text": "Kérdések és tárgyak" "text": "Kérdések és tárgyak"
}, },
"pwRequest": {
"href": "/pwRequest",
"text": "Jelszó kérés"
},
"contribute": { "contribute": {
"href": "/contribute", "href": "/contribute",
"text": "Todos, contribute" "text": "Teendők"
}, },
"ranklist": { "ranklist": {
"href": "/ranklist", "href": "/ranklist",
"text": "Ranklista" "text": "Ranklista"
},
"pwRequest": {
"href": "/pwRequest",
"text": "Jelszó generálás"
},
"faq": {
"href": "/faq",
"text": "GYIK"
},
"contact": {
"href": "/contact",
"text": "Kapcsolat"
} }
} }

View file

@ -1,25 +1,43 @@
:root { :root {
--text-color: #9999ff; --text-color: #f2cb05;
--primary-color: #9999ff; --primary-color: #f2cb05;
--bright-color: #f2f2f2; --bright-color: #f2f2f2;
--background-color: #222426; --background-color: #222426;
--hoover-color: #202020; --hoover-color: #393939;
--dark-color: #191919;
} }
@import url('https://fonts.googleapis.com/css2?family=Kameron&family=Overpass+Mono:wght@300;400&display=swap');
body { body {
font: normal 14px Verdana; font-family: 'Kameron', serif;
font-family: 'Overpass Mono', monospace;
color: #999999; color: #999999;
cursor: default;
}
img {
margin: 10px;
border: 2px solid white;
}
li {
padding: 2px 0px;
} }
a { a {
color: white; color: white;
} }
a:hover {
color: #c1c1c1;
}
textarea { textarea {
color: var(--text-color); color: var(--text-color);
background-color: var(--background-color); background-color: var(--background-color);
box-sizing: border-box; box-sizing: border-box;
height: 120px; height: 140px;
width: 100%; width: 100%;
border: 1px solid #666; border: 1px solid #666;
border-radius: 3px; border-radius: 3px;
@ -28,8 +46,16 @@ textarea {
input { input {
color: var(--text-color); color: var(--text-color);
background-color: var(--background-color); background-color: var(--background-color);
border: 1px solid #666;
border-radius: 5px;
width: 80%;
font-family: inherit;
border: 1px solid #444; border: 1px solid #444;
border-radius: 3px; width: 98%;
}
input:focus {
border: 0px solid #444;
} }
.link { .link {
@ -45,7 +71,6 @@ input {
.sidebar { .sidebar {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin: 0; margin: 0;
padding: 0; padding: 0;
width: 200px; width: 200px;
@ -57,25 +82,7 @@ input {
.content { .content {
margin-left: 200px; margin-left: 200px;
padding: 1px 16px; padding: 1px 15px;
}
.sidebarLinks a {
display: block;
color: black;
padding: 16px;
text-decoration: none;
color: var(--bright-color);
}
.sidebarLinks a.active {
background-color: var(--text-color);
color: black;
}
.sidebarLinks a:hover:not(.active) {
background-color: #555;
color: white;
} }
.menuicon div { .menuicon div {
@ -99,7 +106,11 @@ input {
align-items: center; align-items: center;
flex-wrap: nowrap; flex-wrap: nowrap;
position: relative; position: relative;
margin: 10px; overflow: hidden;
padding-top: 15px;
padding-bottom: 17px;
padding-right: 2px;
padding-left: 2px;
} }
@media screen and (max-width: 700px) { @media screen and (max-width: 700px) {
@ -108,9 +119,11 @@ input {
height: auto; height: auto;
position: relative; position: relative;
} }
.sidebar a { .sidebar a {
float: left; float: left;
} }
div.content { div.content {
margin-left: 0; margin-left: 0;
} }
@ -139,6 +152,7 @@ input {
position: absolute; position: absolute;
left: 50%; left: 50%;
top: 50%; top: 50%;
padding-top: 20px;
transform: translateX(-50%) translateY(-50%); transform: translateX(-50%) translateY(-50%);
} }
@ -153,8 +167,10 @@ input {
} }
.rtfmImage { .rtfmImage {
text-align: center;
display: flex; display: flex;
justify-content: center; justify-content: center;
border: 2px solid white;
width: 100%; width: 100%;
} }
@ -164,19 +180,25 @@ input {
} }
} }
.endofpage {
padding-bottom: 20px;
}
.questionContainer { .questionContainer {
margin: 10px; margin: 6px;
padding: 10px;
} }
.questionContainer:hover { .questionContainer:hover {
background-color: var(--hoover-color); background-color: #141414;
color: var(--text-color);
} }
.question { .question {
word-wrap: break-word; word-wrap: break-word;
font-weight: bold; font-weight: bold;
font-size: 17px; font-size: 17px;
color: #ffffff; color: gainsboro;
} }
.answer { .answer {
@ -193,7 +215,6 @@ input {
.loadingindicator { .loadingindicator {
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
color: #fff; color: #fff;
font-size: 30px; font-size: 30px;
} }
@ -203,16 +224,22 @@ input {
} }
.subjectSelector { .subjectSelector {
overflow: scroll; overflow: auto;
height: 350px; height: auto;
max-height: 250px;
margin: 10px; margin: 10px;
padding: 5px;
padding-right: 10px;
padding-left: 8px;
background-color: #141414;
} }
.subjItem { .subjItem {
font-size: 18px; font-size: 18px;
padding: 3px; padding: 3px;
padding-top: 5px;
margin-top: 4px;
cursor: pointer; cursor: pointer;
float: 1;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
@ -227,47 +254,83 @@ input {
color: white; color: white;
} }
.donate {
background-color: #222a26;
}
.rtfmImage { .rtfmImage {
text-align: center;
justify-content: center; justify-content: center;
margin: 0px 10px; margin: 0px 10px;
} }
.warning {
color: red;
font-weight: 100;
font-size: 17.5px;
}
#manualWarn {
font-size: 14px;
}
.manual_img {
padding: 20px 2px;
}
.subtitle {
color: var(--text-color);
font-size: 23px;
text-align: center;
font-weight: 100;
margin: 0px;
padding-top: 8px;
}
.pageHeader {
background-color: var(--text-color);
height: 45px;
max-width: 100%;
color: black !important;
margin: 5px 0px 0px 0px;
}
.pageHeader > h1 {
padding-top: 6px;
letter-spacing: 7px;
text-align: center;
margin: 0px;
}
.manualUsage { .manualUsage {
margin-top: 5px;
display: flex; display: flex;
} }
select { .manualBody {
cursor: pointer; text-align: justify;
width: 100%;
max-width: 100%;
height: 30px;
}
select:hover {
border: 1px solid #99f;
} }
.userStatus { .userStatus {
display: flex; display: flex;
margin-top: auto; margin-top: auto;
margin-bottom: 20px; margin-bottom: 20px;
background-color: #373737;
align-items: center; align-items: center;
text-align: center;
justify-content: space-between; justify-content: space-between;
} }
.msgs { .msgs {
display: flex; font-size: 15px;
align-items: center; }
.uid {
font-size: 14px;
margin-right: 10px;
} }
.logout { .logout {
padding: 7px; padding: 6px;
margin-right: 7px;
cursor: pointer; cursor: pointer;
font-size: 15.5px;
} }
.logout:hover { .logout:hover {
@ -275,11 +338,13 @@ select:hover {
} }
.msgs :first-child { .msgs :first-child {
font-size: 35px; font-size: 27px;
margin-left: 4px;
} }
.msgs > div { .msgs > div {
margin: 0px 5px; padding: 2px 6px;
font-size: 13.5px;
} }
.actions { .actions {
@ -299,3 +364,114 @@ select:hover {
.actions > span:hover { .actions > span:hover {
background-color: var(--hoover-color); background-color: var(--hoover-color);
} }
.buttonContainer {
display: flex;
align-content: center;
justify-content: center;
margin: 10px 0px;
height: 50px;
}
.buttonContainer > * {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
background-color: var(--hoover-color);
border: none;
color: var(--text-color);
padding: 5px 15px;
font-weight: bold;
font-size: 15px;
margin: 8px 5px 2px 5px;
cursor: pointer;
text-shadow: 1px 1px 8px black;
transition: width 0.5s, height 0.5s, ease-in 0.5s;
user-select: none;
cursor: pointer;
text-align: center;
text-decoration: none;
}
.buttonContainer > *:hover {
text-shadow: 2px 2px 8px black;
transition: width 0.5s, height 0.5s, ease-out 0.5s;
background-color: var(--text-color);
color: black;
}
.buttonContainer > .activeButton {
background-color: var(--text-color);
color: black;
}
select {
cursor: pointer;
width: 100%;
max-width: 100%;
height: 30px;
}
select:hover {
border: 1px solid #f2cb05;
}
.checkbContainer {
display: flex;
justify-content: center;
align-items: center;
margin: 10px 0px 0px 0px;
}
.checkbContainer > input {
width: 5%;
background-color: #9c9c98;
color: azure;
font-family: inherit;
margin-bottom: 5px;
}
.selectContainer {
display: flex;
justify-content: center;
align-items: center;
}
.selectContainer > select,
.selectContainer > input {
width: 20%;
border-radius: 5px;
border: 1.5px solid white;
background-color: #9c9c98;
color: azure;
font-family: inherit;
margin: 6px 0px;
}
.selectContainer > select:hover,
.selectContainer > select:active {
border: 2px solid var(--text-color);
}
.selectContainer > div {
padding: 0px 5px;
margin-right: 8px;
}
.description {
padding-top: 1px;
padding-bottom: 5px;
font-size: inherit;
color: inherit;
text-align: center;
margin: 10px;
}
.resultContainer {
color: gainsboro;
font-size: 18px;
display: flex;
justify-content: center;
margin-top: 8px;
}

View file

@ -1,8 +1,15 @@
export default function Custom404 () { export default function Custom404() {
return ( return (
<center> <center>
<h1>404</h1> <h1>404</h1>
<iframe width='660' height='465' src='https://www.youtube-nocookie.com/embed/GOzwOeONBhQ' frameBorder='0' allow='accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture' allowFullScreen /> <iframe
width="660"
height="465"
src="https://www.youtube-nocookie.com/embed/GOzwOeONBhQ"
frameBorder="0"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
</center> </center>
) )
} }

View file

@ -40,7 +40,7 @@ function MyApp({ Component, pageProps, router }) {
return ( return (
<Layout <Layout
route={router.route} router={router}
globalData={globalData} globalData={globalData}
refetchGlobalData={getGlobalProps} refetchGlobalData={getGlobalProps}
> >

View file

@ -1,16 +1,16 @@
import Document, { Html, Head, Main, NextScript } from 'next/document' import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document { class MyDocument extends Document {
static async getInitialProps (ctx) { static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx) const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps } return { ...initialProps }
} }
render () { render() {
return ( return (
<Html> <Html>
<Head /> <Head />
<body bgcolor='#222426'> <body bgcolor="#222426">
<Main /> <Main />
<NextScript /> <NextScript />
</body> </body>

View file

@ -1,9 +1,16 @@
function Error ({ statusCode }) { function Error({ statusCode }) {
const render404 = () => { const render404 = () => {
return ( return (
<center> <center>
<h1>404</h1> <h1>404</h1>
<iframe width='100%' height='465' src='https://www.youtube-nocookie.com/embed/GOzwOeONBhQ' frameBorder='0' allow='accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture' allowFullScreen /> <iframe
width="100%"
height="465"
src="https://www.youtube-nocookie.com/embed/GOzwOeONBhQ"
frameBorder="0"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
</center> </center>
) )
} }

View file

@ -6,6 +6,7 @@ import QuestionSearchResult from '../components/QuestionSearchResult.js'
import Subject from '../components/Subject.js' import Subject from '../components/Subject.js'
import SubjectSelector from '../components/SubjectSelector.js' import SubjectSelector from '../components/SubjectSelector.js'
import Sleep from '../components/sleep' import Sleep from '../components/sleep'
import ExternalLinkIcon from '../components/externalLinkIcon'
import styles from './allQuestions.module.css' import styles from './allQuestions.module.css'
@ -74,8 +75,10 @@ export default function AllQuestions({ router }) {
const [searchTerm, setSearchTerm] = useState('') const [searchTerm, setSearchTerm] = useState('')
const [activeSubjName, setActiveSubjName] = useState('') const [activeSubjName, setActiveSubjName] = useState('')
const [dbs, setDbs] = useState(null) const [dbs, setDbs] = useState(null)
const [selectedDb, setSelectedDb] = useState('')
const [data, setData] = useState(null) const [data, setData] = useState(null)
const [fetchingData, setFetchingData] = useState(false) const [fetchingData, setFetchingData] = useState(false)
const subjectCount = data ? data.length : 0 const subjectCount = data ? data.length : 0
const questionCount = data ? data.reduce(countReducer, 0) : 0 const questionCount = data ? data.reduce(countReducer, 0) : 0
@ -87,6 +90,7 @@ export default function AllQuestions({ router }) {
const querySearch = router.query.question const querySearch = router.query.question
? decodeURIComponent(router.query.question) ? decodeURIComponent(router.query.question)
: '' : ''
console.log(querySearch) console.log(querySearch)
fetchDbs().then((res) => { fetchDbs().then((res) => {
@ -98,19 +102,43 @@ export default function AllQuestions({ router }) {
if (dbs) { if (dbs) {
return ( return (
<> <>
<div className={'pageHeader'}>
<h1>Kérdések és tárgyak</h1>
</div>
<Sleep />
<div className={'description'}>
Ezen az oldalon tudsz manuálisan keresni a kérdések és a tárgyak
között, vagy ellenőrizni, hogy egy adott tárgy szerepel-e a kérdés-
és tárgyadatbázisban. Ezen kívül a kérdéseket le is töltheted
offline használatra. (txt formátumban)
</div>
<center>
<div className={`buttonContainer ${styles.dataEditor}`}>
<div
onClick={() => {
window.open(`${constants.siteUrl}dataeditor`, '_blank')
}}
>
Kérdés szerkesztő
<ExternalLinkIcon size={15} />
</div>
</div>
</center>
<div className={'selectContainer'}>
<select <select
className={styles.select}
defaultValue={-1} defaultValue={-1}
onChange={(event) => { onChange={(event) => {
const key = event.target.value const key = event.target.value
setData(null) setData(null)
setFetchingData(true) setFetchingData(true)
if (key === 'all') { if (key === 'all') {
setSelectedDb(key)
fetchAllData(dbs).then((res) => { fetchAllData(dbs).then((res) => {
setData(mergeData(res)) setData(mergeData(res))
setFetchingData(false) setFetchingData(false)
}) })
} else { } else {
setSelectedDb(dbs[key].name)
fetchData(dbs[key]).then((res) => { fetchData(dbs[key]).then((res) => {
setData(res.data) setData(res.data)
setFetchingData(false) setFetchingData(false)
@ -119,7 +147,7 @@ export default function AllQuestions({ router }) {
}} }}
> >
<option disabled value={-1}> <option disabled value={-1}>
{' -- Válassz egy kérdés adatbázist -- '} {' Válassz egy adatbázist!'}
</option> </option>
{dbs.map((db, i) => { {dbs.map((db, i) => {
return ( return (
@ -129,9 +157,10 @@ export default function AllQuestions({ router }) {
) )
})} })}
<option value={'all'} key={'all'}> <option value={'all'} key={'all'}>
{'All'} {'Összes kérdés'}
</option> </option>
</select> </select>
</div>
</> </>
) )
} else { } else {
@ -148,16 +177,12 @@ export default function AllQuestions({ router }) {
return ( return (
<div> <div>
<Head>
<title>Tárgyak - Qmining | Frylabs.net</title>
</Head>
{data ? ( {data ? (
<> <>
<center>
<div className={styles.searchContainer}> <div className={styles.searchContainer}>
<input <input
autoFocus placeholder="Kezdj el írni a kereséshez..."
placeholder="Keresés..."
className={styles.searchBar}
type="text" type="text"
value={searchTerm} value={searchTerm}
onChange={(event) => { onChange={(event) => {
@ -173,6 +198,7 @@ export default function AllQuestions({ router }) {
X X
</button> </button>
</div> </div>
</center>
<hr /> <hr />
<SubjectSelector <SubjectSelector
data={data} data={data}
@ -182,10 +208,9 @@ export default function AllQuestions({ router }) {
setActiveSubjName(subjName) setActiveSubjName(subjName)
}} }}
/> />
<hr />
<div>{/*{qCount} kérdés, {sCount} tárgy */}</div>
<Sleep />
<div> <div>
<hr />
<Subject subj={currSubj} /> <Subject subj={currSubj} />
</div> </div>
</> </>
@ -197,16 +222,11 @@ export default function AllQuestions({ router }) {
const renderQuestionBrowser = () => { const renderQuestionBrowser = () => {
return ( return (
<div> <div>
<Head>
<title>Qmining - Kérdés keresés | Frylabs.net</title>
</Head>
{data ? ( {data ? (
<> <>
<div className={styles.searchContainer}> <div className={styles.searchContainer}>
<input <input
autoFocus placeholder="Kezdj el írni a kereséshez..."
placeholder="Keresés..."
className={styles.searchBar}
type="text" type="text"
value={searchTerm} value={searchTerm}
onChange={(event) => { onChange={(event) => {
@ -228,7 +248,6 @@ export default function AllQuestions({ router }) {
X X
</button> </button>
</div> </div>
<hr />
<div> <div>
<QuestionSearchResult data={data} searchTerm={searchTerm} /> <QuestionSearchResult data={data} searchTerm={searchTerm} />
</div> </div>
@ -240,23 +259,43 @@ export default function AllQuestions({ router }) {
return ( return (
<div> <div>
{dbs ? ( <Head>
<title>Kérdések és tárgyak - Qmining | Frylabs.net</title>
</Head>
{dbs ? <>{renderDbSelector()}</> : <LoadingIndicator />}
{dbs && data ? (
<> <>
{renderDbSelector()} <div className={styles.info}>
{data && `${questionCount} kérdés, ${subjectCount} tárgy`} {`Összesen ${questionCount} kérdés, ${subjectCount} tárgyból`}
<div className={styles.typeSelector}> </div>
<div className={'buttonContainer'}>
<div <div
className={!subjectsShowing ? styles.activeTypeSelector : ''} className={!subjectsShowing ? 'activeButton' : ''}
onClick={() => setSubjectsShowing(false)} onClick={() => setSubjectsShowing(false)}
> >
Kérdések Kérdések
</div> </div>
<div <div
className={subjectsShowing ? styles.activeTypeSelector : ''} className={subjectsShowing ? 'activeButton' : ''}
onClick={() => setSubjectsShowing(true)} onClick={() => setSubjectsShowing(true)}
> >
Tárgyak Tárgyak
</div> </div>
<a
onClick={() => {
if (selectedDb === 'all') {
window.open(`${constants.apiUrl}allqr.txt`, '_blank')
} else {
window.open(
`${constants.apiUrl}allqr.txt?db=${selectedDb}`,
'_blank'
)
}
}}
>
{'Kérdések letöltése'}
<ExternalLinkIcon size={15} />
</a>
</div> </div>
{fetchingData ? ( {fetchingData ? (
<LoadingIndicator /> <LoadingIndicator />
@ -268,9 +307,9 @@ export default function AllQuestions({ router }) {
</div> </div>
)} )}
</> </>
) : ( ) : fetchingData ? (
<LoadingIndicator /> <LoadingIndicator />
)} ) : null}
</div> </div>
) )
} }

View file

@ -1,47 +1,29 @@
.searchBar {
margin: 10px;
color: white;
background-color: #222426;
border: none;
font-size: 18px;
flex-grow: 1;
}
.searchContainer { .searchContainer {
width: 100%; width: 100%;
display: flex; display: flex;
justify-content: center;
margin-left: 10px;
padding-top: 10px;
padding-bottom: 2px;
} }
.clearButton { .clearButton {
width: 80px; width: 50px;
background-color: var(--background-color); background-color: var(--background-color);
color: white; color: white;
font-size: 23px; font-size: 18px;
cursor: pointer; cursor: pointer;
border: none; border: none;
margin-left: 10px;
} }
.typeSelector { .info {
margin: 10px 0px; text-align: center;
height: 50px; font-style: italic;
display: flex; padding-top: 5px;
} }
.typeSelector div { .dataEditor {
display: flex; margin-bottom: 25px;
align-items: center; width: 25%;
justify-content: center;
flex: 1;
font-size: 18px;
color: #fff;
cursor: pointer;
}
.typeSelector div:hover {
background-color: #333;
}
.activeTypeSelector {
background-color: #444;
} }

87
src/pages/contact.js Normal file
View file

@ -0,0 +1,87 @@
import React, { useState, useEffect } from 'react'
import Head from 'next/head'
import FeedbackArea from '../components/feedbackArea'
import constants from '../constants.json'
import LoadingIndicator from '../components/LoadingIndicator'
import Sleep from '../components/sleep'
import styles from './contact.module.css'
export default function Contact() {
const [contacts, setContacts] = useState()
useEffect(() => {
fetch(constants.apiUrl + 'contacts.json', {
method: 'GET',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
})
.then((res) => {
return res.json()
})
.then((res) => {
setContacts(res)
})
}, [])
return (
<div>
<Head>
<title>Kapcsolat - Qmining | Frylabs.net</title>
</Head>
<div className={'pageHeader'}>
<h1>Kapcsolat</h1>
</div>
<br />
<Sleep />
<br />
<div>
<div className={'subtitle'}>Üzenet küldése</div>
<div className={styles.text}>
Weboldalon keresztüli üzenetküldés az adminnak (feedback). A válasz a
bal alsó postaládába (📬 ikon) fog érkezni.
</div>
</div>
<FeedbackArea from={'contact'} allowFile />
<div className={styles.container}>
{contacts ? (
<>
<div>
<div className={'subtitle'}>Alternatív módok</div>
<div className={styles.text}>
Az alábbi módokat is nyugodtan használhatod, a nevedet, e-mail
címedet, illetve semmilyen egyéb adatot nem adok ki harmadik fél
számára. (egyedül én fogom látni)
</div>
</div>
<div className={styles.contactsContainer}>
{Object.keys(contacts).map((key) => {
const { description, value, href } = contacts[key]
return (
<div key={key}>
<div>{description}</div>
{href ? (
<a target="blank" rel="noreferrer" href={href}>
{' '}
{value}{' '}
</a>
) : (
<div>{value}</div>
)}
</div>
)
})}
</div>
</>
) : (
<LoadingIndicator />
)}
</div>
</div>
)
}

View file

@ -0,0 +1,48 @@
.container {
display: flex;
flex-flow: column;
align-items: center;
}
.contactsContainer {
margin: 5px;
max-width: 900px;
}
.contactsContainer > * {
text-decoration: none;
color: gainsboro;
padding-top: 10px;
display: flex;
}
.contactsContainer > * > * {
padding: 5px;
}
.contactsContainer > * > *:nth-child(1) {
flex: 1;
}
.contactsContainer > * > *:nth-child(2) {
flex: 0 150px;
}
.text {
display: flex;
flex-flow: column;
align-items: center;
text-align: center;
padding-top: 8px;
padding-right: 100px;
padding-left: 100px;
}
.text > div:nth-child(1) {
font-size: 20px;
color: var(--text-color);
}
.contactTable {
border-spacing: 15px;
}

View file

@ -3,116 +3,98 @@ import Head from 'next/head'
import Sleep from '../components/sleep' import Sleep from '../components/sleep'
import Todos from '../components/todoStuff/todos' import Todos from '../components/todoStuff/todos'
import FeedbackArea from '../components/feedbackArea'
import Modal from '../components/modal'
import constants from '../constants.json' import constants from '../constants.json'
import styles from './contribute.module.css' import styles from './contribute.module.css'
import repos from '../data/repos.json' import repos from '../data/repos.json'
export default function contribute() { export default function contribute() {
const [newTask, setNewTask] = useState('') const [showFeedback, setShowFeedback] = useState(false)
const submitNewTask = async () => {
if (!newTask) {
return
}
fetch(constants.apiUrl + 'postfeedback', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
newTask: newTask,
from: 'contribute',
}),
})
.then((resp) => {
return resp.json()
})
.then((resp) => {
if (resp.success) {
alert('Elküldve')
setNewTask('')
} else {
alert('Hiba küldés közben')
}
})
.catch((err) => {
alert('Hiba küldés közben')
console.error(err)
})
}
const renderNewTaskArea = () => {
return (
<div className={styles.inputArea}>
<textarea
onChange={(event) => setNewTask(event.target.value)}
value={newTask || ''}
className={styles.feedback}
/>
<button className={styles.button} onClick={submitNewTask}>
Küldés
</button>
</div>
)
}
return ( return (
<div> <div className={'endofpage'}>
<Head> <Head>
<title>Qmining - Todos | Frylabs.net</title> <title>Todos - Qmining | Frylabs.net</title>
</Head> </Head>
<div className={styles.description}> <div className={'pageHeader'}>
Egy kártyára kattintva nézheted meg a részleteket, vagy szavazhatsz. <h1>Teendők</h1>
</div>
<Sleep />
<div className={'description'}>
<p>
Ezen az oldalon új ötleteket adhatsz hozzá megvalósításra a teendők
listájához.
</p>
<p>
Emellet ha hozzáértő vagy, adott feladatok megvalósításában is
segíthetsz. (lásd: <a href="#gitrepo">lentebb</a>)
<br /> Ha egy kártyára kattintasz, a megjelenő ablakban láthatod annak
részleteit, illetve{' '}
<b>
<i>szavazhatsz</i>
</b>{' '}
is a feladatra, annak érdekében, hogy minél hamarabb megvalósulhasson.
Minél több szavazat érkezik egy kártyára, annál magasabb lesz a Minél több szavazat érkezik egy kártyára, annál magasabb lesz a
pioritása. Jobb alsó szám minél több, annál nehezebb a feladat. A Done prioritása. (értsd: a legtöbb szavazatot kapó kártya teendője lesz
oszlopban lévő feladatok kész vannak, de különböző okok miat még nem legelőször megvalósítva)
lettek kiadva frissítésként. Ami az In Prod táblázatban van az van kint. <br /> A kurzort az oszlopcímekre mozgatva, további információkat
<br /> olvashatsz a kategóriák tulajdonságairól.
{ </p>
'Ha olyan taskot látsz amiben tudnál és szeretnél segíteni, akkor írj ' </div>
} <center>
<a <div className={`buttonContainer ${styles.newTaskButton}`}>
href="http://qmining.frylabs.net/irc?contribute" <div
target="_blank" onClick={() => {
rel="noreferrer" setShowFeedback(true)
}}
> >
{'IRC'} Új feladat
</a>
-n, és útbaigazítalak.
</div> </div>
<div className={styles.description}>
Itt írhatsz új todo-ra ötleteket, vagy jelezhetsz hogy egyikben
segítenél
</div> </div>
{renderNewTaskArea()} </center>
<br />
<hr /> <hr />
<Todos /> <Todos />
<Sleep />
<hr /> <hr />
<div className={styles.title}>Git repos</div> <div className={'subtitle'}>
<hr /> <b>Git repos</b>
<hr /> </div>
<div className={styles.repos}> <div className={styles.repos} style={{ float: 'left' }} id={'gitrepo'}>
{Object.keys(repos.repos).map((key) => { {Object.keys(repos.repos).map((key) => {
let repo = repos.repos[key] let repo = repos.repos[key]
return ( return (
<div key={key}>
<ul>
<li>
<a key={key} href={repo.href}> <a key={key} href={repo.href}>
{repo.description} {repo.description}
</a> </a>
</li>
</ul>
</div>
) )
})} })}
<hr />
</div> </div>
<div style={{ textAlign: 'center' }}> <div
style={{ textAlign: 'right', marginRight: '100px', marginTop: '25px' }}
>
<img <img
style={{ maxWidth: '100%', width: '400px' }} style={{ maxWidth: '100%', width: '320px' }}
src={`${constants.siteUrl}img/bug.png`} src={`${constants.siteUrl}img/bug.png`}
/> />
</div> </div>
<hr />
{showFeedback && (
<Modal
closeClick={() => {
setShowFeedback(false)
}}
>
<FeedbackArea from={'contribute'} />
</Modal>
)}
</div> </div>
) )
} }

View file

@ -1,10 +1,3 @@
.description {
font-size: 15px;
color: white;
text-align: center;
margin: 10px;
}
.warning { .warning {
color: white; color: white;
padding: 10px; padding: 10px;
@ -12,34 +5,28 @@
text-align: center; text-align: center;
} }
.feedback {
color: var(--text-color);
background-color: var(--background-color);
font-size: 14px;
width: 100%;
box-sizing: border-box;
height: 60px;
}
.button {
background-color: var(--text-color);
border: none;
padding: 5px 15px;
margin: 5px;
color: white;
width: 200px;
}
.inputArea {
display: flex;
}
.title { .title {
color: #9999ff; color: var(--text-color);
font-size: 30px; font-size: 30px;
text-align: center; text-align: center;
} }
.repos {
margin-top: 6%;
margin-left: 10%;
}
.repos a { .repos a {
margin: 0px 5px; margin: 0px 5px;
text-decoration: none;
}
.newTaskButton {
width: 25%;
}
.imgContainer {
float: right;
text-align: right;
padding-right: 50px;
} }

290
src/pages/faq.js Normal file
View file

@ -0,0 +1,290 @@
import React, { useState, useEffect } from 'react'
import Sleep from '../components/sleep'
import Head from 'next/head'
function PasswordSection() {
return (
<>
<div className={'manualBody'}>
<p>
Ha ezt olvasod valszeg már neked is van. Azért lett bevezetve, hogy
nagyjából zárt legyen a felhasználók köre.
</p>
<ul>
<li>Minden felhasználónak más jelszava van.</li>
<li>
Elvileg elég csak 1 szer beírnod, és többet nem kell, de{' '}
<b>mentsd le biztos helyre a jelszót, hogy később is meglegyen</b>!
Ha többször kell megadnod, akkor az bug lesz. Ilyenkor ezt{' '}
<a
href="http://qmining.frylabs.net/feedback?man"
target="_blank"
rel="noreferrer"
>
jelentsd
</a>
.
</li>
<li>
<i>
Jelenleg nincs elfelejtett jelszó funkció, ha elfelejted, akkor az
örökre eltűnik!
</i>
</li>
<li>
Ha van jelszavad akkor <b>bizonyos határok között</b> te is{' '}
<a
href="https://qmining.frylabs.net/pwRequest?man"
target="_blank"
rel="noreferrer"
>
tudsz generálni
</a>{' '}
másoknak (ncore style).
</li>
<li>
Saját jelszavadat ne oszd meg, mivel egyszerre egy helyen lehetsz
belépve, máshol automatikusan ki leszel jelentkeztetve. (meg minek,
ha tudsz adni másoknak az előző pont alapján)
</li>
<li>
Mivel senkinek sincs felhasználóneve, csak egy UserID (amit bal alul
találsz), így az egész teljesen anonim. Emiatt a jelszavakat nem
lehet megváltoztatni, hogy a szükséges komplexitás megmaradjon.
</li>
</ul>
</div>
<hr />
</>
)
}
function FAQSection() {
return (
<>
<div className={'manualBody'}>
<ul>
<li>
<b>
Olyan helyeken fut le a script, ahol nem kellene, vagy
ideiglenesen ki akarod kapcsolni;
</b>
<br />
<i>
Tampermonkey bővitmény ikon -{'>'} click -{'>'} a scriptet
kapcsold ki. Csak ne felejtsd el visszakapcsolni ;)
</i>
</li>
<br />
<li>
<b>
Túl nagy a kérdést és a választ megjelenítő ablak, nem tudok a
válaszra kattintani;
</b>
<br />
<i>
A felugró ablakot ha minden jól megy akkor a szélénél fogva tudod
mozgatni, vagy egeret rajtatartva a görgővel tudod állítani az
áttetszőségét, vagy be tudod zárni jobb felül X-el, vagy egér
középső gombbal.
</i>
</li>
<br />
<li>
<b>Gombok, %-ok, számok;</b>
<br />
<img
style={{ maxWidth: '90%' }}
src="img/6.png"
alt="img"
className={'manual_img'}
/>
</li>
</ul>
</div>
<hr />
</>
)
}
function RiskSection() {
return (
<>
<ul>
<li>
<b>Bármikor észrevehetik hogy használod a scriptet</b>
<br />
A weboldalt már kevésbé, de úgy nem menti el a kérdéseket a script,
mert nem fut. Később manuálisan is be lehet majd küldeni
kérdés-válaszokat.
<p />
A script shadow-root hoz teszi hozzá az összes megjelenített
elementet, így ezeket szinte lehetetlen detektálni. A moodle
semmiféleképpen nem látja, hogy milyen más oldalak vannak megnyitva a
böngésződben. Nem látja az XMLHttp requesteket se, amit a script
végez. Egy Matomo nevű script látja hogy milyen oldalarka navigálsz a
moodle-ről, de a script nem linkekkel irányít át, hanem javascript
eseménnyel, amit nem tud nyomon követni.
<p />
Aztán ki tudja ténylegesen hogy lehet
</li>
<li>
<b>Bármikor leállhat a szerver</b>
<br />
És akkor nem bírod megnézni a válaszokat. Erre van az{' '}
<a
href="http://qmining.frylabs.net/allqr.txt?man"
target="_blank"
rel="noreferrer"
>
{' '}
összes kérdés TXT
</a>
</li>
<li>
<b>Akármelyik válasz rossz lehet</b>
<br />
Pl.: ha a script rosszul menti le, vagy rossz kérdésre ad választ
</li>
</ul>
<hr />
</>
)
}
function WebsiteSaveSection() {
return (
<>
<p>
Ha hibát észlesz, kérlek jelents. Hogy a hibákat a saját gépemen
reprodukálni tudjam, és könnyen ki bírjam javítani, sokszor jól jön, ha
egy lementett weboldalt megkapok, amin a hiba történik. Így lehet
menteni egy oldalt:
</p>
<center>
<img
style={{ maxWidth: '90%' }}
src="img/websitesave.png"
alt="img"
className={'manual_img'}
/>
<br />
<a href="/feedback" rel="noreferrer">
Ide tudod feltölteni
</a>
</center>
<p>
Mivel nincs hozzáférésem semmilyen egyetemi oldalhoz, így csak így tudom
hatékonyan tesztelni a scriptet. Ezért hatalmas segítség ha feltöltöd
azt az oldalt amin hibával találkozol.
</p>
<hr />
</>
)
}
function ScriptReinstallSection() {
return (
<>
<p>
Jelenleg két helyről lehet telepíteni a scriptet: greasyforkról és a
weboldalról. A greasyforkos telepítési lehetőség meg fog szűnni, így ha
onnan telepítetted, akkor nem lesznek frissítések elérhetők (amik nagyon
fontosak (de tényleg)). Ezért a következő rövid manővert kellene
végrehajtani, hogy minden zökkenőmentesen menjen:
</p>
<ul>
<li>Böngésző bővítményeidnél kattints a tampermonkey-ra</li>
<li>Válaszd ki alulról második opciót, ami dashboard néven fut</li>
<li>
Ekkor új tabban felugranak telepített scriptjeid. Keresd meg a
Moodle/Elearning/KMOOC test help-et, és a sor végén kattints a kuka
gombra
</li>
<li>Ha megkérdezi mondd neki, hogy biztos törölni akarod</li>
<li>
Ezután simán kattints{' '}
<a
href="http://qmining.frylabs.net/install?man"
target="_blank"
rel="noreferrer"
>
ide
</a>{' '}
a script újratelepítéséhez a weboldalról.
</li>
<li>
Kész! Lehet megkérdezi újra, hogy elérheti-e a szervert, de azt csak
egyszer. Szokásos módon engedélyezd, hogy le bírja kérni a helyes
válaszokat
</li>
</ul>
Ezzel semmi adat nem vész el, régi jelszó ugyanolyan marad (csak ne
felejtsd azt el)
<hr />
</>
)
}
const pages = {
faq: { name: 'GYIK', component: FAQSection },
pw: { name: 'Jelszavak', component: PasswordSection },
risk: { name: 'Kockázatok', component: RiskSection },
websitedl: { name: 'Weboldal letöltése', component: WebsiteSaveSection },
reinstall: {
name: 'Script újratelepítése',
component: ScriptReinstallSection,
},
}
export default function FAQ({ router }) {
const [currPage, setCurrPage] = useState(pages.faq)
const renderCurrPage = (page) => {
if (page) {
return <page.component />
} else {
return null
}
}
useEffect(() => {
if (router.query.tab) {
setCurrPage(pages[router.query.tab])
}
}, [router.query.tab])
return (
<div>
<Head>
<title>GYIK - Qmining | Frylabs.net</title>
</Head>
<div className={'pageHeader'}>
<h1>Gyakran Ismételt Kérdések</h1>
</div>
<br />
<Sleep />
<br />
<div className={'buttonContainer'}>
{Object.keys(pages).map((key) => {
const page = pages[key]
return (
<div
className={`${page === currPage && 'activeButton'}`}
key={key}
onClick={() => {
setCurrPage(page)
}}
>
{page.name}
</div>
)
})}
</div>
{renderCurrPage(currPage)}
<p className={'endofpage'} />
</div>
)
}

View file

@ -1,33 +1,30 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import fetch from 'unfetch' import fetch from 'unfetch'
import Head from 'next/head' import Head from 'next/head'
import Link from 'next/link'
import LoadingIndicator from '../components/LoadingIndicator' import LoadingIndicator from '../components/LoadingIndicator'
import Sleep from '../components/sleep' import Sleep from '../components/sleep'
import NewsEntry from '../components/newsEntry' import NewsEntry from '../components/newsEntry'
import Composer from '../components/composer' import Composer from '../components/composer'
import DbSelector from '../components/dbSelector.js'
import styles from './index.module.css' import styles from './index.module.css'
import constants from '../constants.json' import constants from '../constants.json'
const links = { const forumPostPerPage = 5
install: { const frontpageForumName = 'frontpage'
href: '/install',
text: 'Install',
},
irc: {
href: '/irc',
text: 'IRC chat',
},
}
function fetchNews() { function fetchForum(from) {
return new Promise((resolve) => { return new Promise((resolve) => {
fetch(`${constants.apiUrl}news.json`, { fetch(
`${
constants.apiUrl
}forumEntries?forumName=${frontpageForumName}&getContent=true${
from ? `&from=${from}` : ''
}&count=${forumPostPerPage}`,
{
credentials: 'include', credentials: 'include',
}) }
)
.then((resp) => { .then((resp) => {
return resp.json() return resp.json()
}) })
@ -47,6 +44,7 @@ function addPost(title, content) {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ body: JSON.stringify({
forumName: frontpageForumName,
title: title, title: title,
content: content, content: content,
}), }),
@ -60,78 +58,42 @@ function addPost(title, content) {
}) })
} }
function postFeedback(content, file) { function updateForumPost(forum, postKey, postData) {
const postText = (file) => { return Object.keys(forum).reduce((acc, key) => {
return new Promise((resolve) => { const entry = forum[key]
fetch(constants.apiUrl + 'postfeedback', { if (key === postKey) {
method: 'POST', acc = {
credentials: 'include', ...acc,
headers: { [key]: postData,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
content: content,
file: file ? file.name : null,
}),
})
.then((res) => {
return res.json()
})
.then((res) => {
resolve(res)
})
})
} }
return new Promise((resolve) => {
if (file) {
const formData = new FormData() // eslint-disable-line
formData.append('file', file)
fetch(constants.apiUrl + 'postfeedbackfile', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
},
body: formData,
})
.then((res) => {
return res.json()
})
.then((fileres) => {
postText(file).then((textres) => {
resolve({ fileres, textres })
})
})
} else { } else {
postText().then((res) => { acc = {
resolve(res) ...acc,
}) [key]: entry,
} }
}) }
return acc
}, {})
} }
export default function Index({ globalData }) { export default function Index({ globalData }) {
const userId = globalData.userId const userId = globalData.userId
const motd = globalData.motd const motd = globalData.motd
const [news, setNews] = useState(null) const [news, setNews] = useState(null)
const [allQrSelector, setAllQrSelector] = useState(null) const [nextEntryKey, setNextEntryKey] = useState()
// const userSpecificMotd = props.globalData.userSpecificMotd
useEffect(() => { useEffect(() => {
console.info('Fetching news.json') fetchForum().then((res) => {
fetchNews().then((res) => { const { entries, nextKey } = res
setNews(res) setNextEntryKey(nextKey)
setNews(entries)
}) })
}, []) }, [])
const renderNews = () => { const renderNews = () => {
if (news) { if (news) {
let newsItems = Object.keys(news) let newsItems = Object.keys(news).map((postKey) => {
.map((key) => { let newsEntryData = news[postKey]
let newsEntryData = news[key]
return ( return (
<NewsEntry <NewsEntry
onPostDelete={() => { onPostDelete={() => {
@ -143,17 +105,30 @@ export default function Index({ globalData }) {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ body: JSON.stringify({
newsKey: key, forumName: frontpageForumName,
postKey: postKey,
}), }),
}) })
.then((res) => { .then((res) => {
return res.json() return res.json()
}) })
.then((res) => { .then((res) => {
if (res.status === 'fail') { const { success, msg } = res
alert(res.msg) if (success) {
setNews(
Object.keys(news).reduce((acc, key) => {
const entry = news[key]
if (key !== postKey) {
acc = {
...acc,
[key]: entry,
}
}
return acc
}, {})
)
} else { } else {
setNews(res.news) alert(msg)
} }
}) })
}} }}
@ -167,15 +142,15 @@ export default function Index({ globalData }) {
}, },
body: JSON.stringify({ body: JSON.stringify({
reaction: reaction, reaction: reaction,
newsKey: key, postKey: postKey,
isDelete: isDelete, isDelete: isDelete,
forumName: frontpageForumName,
}), }),
}) })
.then((res) => { .then((res) => {
return res.json() return res.json()
}) })
.then((res) => { .then((res) => {
console.log(res)
setNews(res.news) setNews(res.news)
}) })
}} }}
@ -189,17 +164,23 @@ export default function Index({ globalData }) {
}, },
body: JSON.stringify({ body: JSON.stringify({
type: 'reaction', type: 'reaction',
newsKey: key, postKey: postKey,
path: path, path: path,
reaction: reaction, reaction: reaction,
isDelete: isDelete, isDelete: isDelete,
forumName: frontpageForumName,
}), }),
}) })
.then((res) => { .then((res) => {
return res.json() return res.json()
}) })
.then((res) => { .then((res) => {
setNews(res.news) const { success, postData, msg } = res
if (success) {
setNews(updateForumPost(news, postKey, postData))
} else {
alert(msg)
}
}) })
}} }}
onDelete={(path) => { onDelete={(path) => {
@ -213,17 +194,19 @@ export default function Index({ globalData }) {
body: JSON.stringify({ body: JSON.stringify({
type: 'delete', type: 'delete',
path: path, path: path,
newsKey: key, postKey: postKey,
forumName: frontpageForumName,
}), }),
}) })
.then((res) => { .then((res) => {
return res.json() return res.json()
}) })
.then((res) => { .then((res) => {
if (res.status === 'fail') { const { success, postData, msg } = res
alert(res.msg) if (success) {
setNews(updateForumPost(news, postKey, postData))
} else { } else {
setNews(res.news) alert(msg)
} }
}) })
}} }}
@ -239,55 +222,62 @@ export default function Index({ globalData }) {
type: 'add', type: 'add',
path: path, path: path,
content: content, content: content,
newsKey: key, postKey: postKey,
forumName: frontpageForumName,
}), }),
}) })
.then((res) => { .then((res) => {
return res.json() return res.json()
}) })
.then((res) => { .then((res) => {
setNews(res.news) const { success, postData, msg } = res
if (success) {
setNews(updateForumPost(news, postKey, postData))
} else {
alert(msg)
}
}) })
}} }}
uid={userId} uid={userId}
key={key} key={postKey}
newsKey={key} newsKey={postKey}
newsItem={newsEntryData} newsItem={newsEntryData}
/> />
) )
}) })
.reverse()
return ( return (
<div> <div>
<div className={styles.title}>Forum</div> <div className={styles.title}>Fórum/Hírek</div>
<hr /> <hr />
<Composer <Composer
onSubmit={(type, title, content, file) => { onSubmit={(title, content) => {
if (!content) {
alert('Üres a tartalom!')
return
}
console.log(type, title, content, file)
if (type === 'private') {
postFeedback(content, file).then(
(/* { fileres, textres } */) => {
alert('Privát visszajelzés elküldve!')
}
)
} else {
if (!title) {
alert('Üres a téma!')
return
}
addPost(title, content).then((res) => { addPost(title, content).then((res) => {
setNews(res.news) const { success, newPostKey, newEntry, msg } = res
}) if (success) {
setNews({ [newPostKey]: newEntry, ...news })
} else {
alert(msg)
} }
})
}} }}
/> />
<hr />
<div>{newsItems}</div> <div>{newsItems}</div>
{nextEntryKey && (
<div
className={styles.loadMoreButton}
onClick={() => {
fetchForum(nextEntryKey).then((res) => {
console.log(res)
const { entries, nextKey } = res
setNextEntryKey(nextKey)
setNews({ ...news, ...entries })
})
}}
>
Több bejegyzés betöltése
</div>
)}
</div> </div>
) )
} else { } else {
@ -297,9 +287,8 @@ export default function Index({ globalData }) {
const renderMotd = () => { const renderMotd = () => {
return ( return (
<div> <div className={styles.motd_body}>
<div className={styles.title}>MOTD</div> <div className={styles.title}>MOTD</div>
<hr />
{motd ? ( {motd ? (
<div <div
className={styles.motd} className={styles.motd}
@ -312,94 +301,15 @@ export default function Index({ globalData }) {
) )
} }
// const renderUserSpecificMotd = () => {
// return (
// <div>
// <hr />
// <div className={styles.subtitle}>Üzenet admintól:</div>
// {userSpecificMotd ? (
// <div
// className={styles.motd}
// dangerouslySetInnerHTML={{ __html: userSpecificMotd.msg }}
// />
// ) : (
// <div>...</div>
// )}
// </div>
// )
// }
const renderDbSelector = () => {
if (allQrSelector) {
return (
<DbSelector
text={`Válaszd ki melyik adatbázist szeretnéd letölteni (${allQrSelector}):`}
showAll={allQrSelector === 'txt'}
closeClick={() => {
setAllQrSelector(null)
}}
onDbSelect={(selectedDb) => {
if (allQrSelector === 'txt') {
if (selectedDb === 'all') {
window.open(`${constants.apiUrl}allqr.txt`, '_blank')
} else {
window.open(
`${constants.apiUrl}allqr.txt?db=${selectedDb.name}`,
'_blank'
)
}
} else if (allQrSelector === 'json') {
window.open(`${constants.apiUrl}${selectedDb.path}`, '_blank')
}
}}
/>
)
} else {
return null
}
}
return ( return (
<div> <div>
<Head> <Head>
<title>Qmining | Frylabs.net</title> <title>Qmining | Frylabs.net</title>
</Head> </Head>
<div className={styles.buttonContainer}>
{Object.keys(links).map((key) => {
let link = links[key]
return (
<Link key={key} href={link.href}>
<a className={styles.button} target="_blank">
{link.text}
</a>
</Link>
)
})}
<a
onClick={() => {
setAllQrSelector('txt')
}}
className={styles.button}
>
{'Összes kérdés TXT'}
</a>
<a
onClick={() => {
setAllQrSelector('json')
}}
className={styles.button}
>
{'Összes kérdés JSON'}
</a>
</div>
<hr />
{renderMotd()} {renderMotd()}
{/*{userSpecificMotd && renderUserSpecificMotd()} */} {/*{userSpecificMotd && renderUserSpecificMotd()} */}
<hr />
<hr />
<Sleep /> <Sleep />
{renderNews()} {renderNews()}
{renderDbSelector()}
</div> </div>
) )
} }

View file

@ -1,27 +1,5 @@
.buttonContainer { .hr {
display: flex; width: 100%;
}
.button {
color: white;
background-color: #303030;
margin: 2px;
padding: 10px 5px;
text-align: center;
font-size: 16px;
cursor: pointer;
word-wrap: break-word;
text-decoration: none;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
user-select: none;
}
.button:hover {
background-color: #555;
} }
.motd { .motd {
@ -30,20 +8,54 @@
margin: 5px; margin: 5px;
} }
.itemContainer {
width: 100%;
margin: 20px 5px;
background-color: var(--hoover-color);
}
.newsBody {
margin: 0px 5px;
padding: 10px 14px;
font-size: 17px;
color: #fff;
text-align: justify;
}
.motd_body {
border: 2px dashed var(--text-color);
padding-top: 13px;
padding-bottom: 15px;
padding-left: 5px;
padding-right: 5px;
margin-top: 18px;
margin-bottom: 30px;
margin-left: 5px;
margin-right: 5px;
font-size: 8px;
}
.title { .title {
color: #9999ff; color: var(--text-color);
font-size: 30px; font-size: 32px;
text-align: center; text-align: center;
letter-spacing: 2.5px;
} }
.subtitle { .subtitle {
color: #9999ff; color: var(--text-color);
font-size: 20px; font-size: 20px;
text-align: center; text-align: center;
} }
.newsTitle {
color: var(--text-color);
font-size: 28px;
padding-left: 17px;
}
.question { .question {
font-weight: 'bold'; font-weight: bold;
font-size: 16px; font-size: 16px;
color: #fff; color: #fff;
margin: 0px 5px; margin: 0px 5px;
@ -53,7 +65,32 @@
margin: 0px 5px; margin: 0px 5px;
} }
.itemNumber {
color: #a7a7a7;
margin: 0px 5px;
font-size: 22px;
padding-top: 12px;
padding-left: 13px;
padding-bottom: 3px;
}
.repos { .repos {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.loadMoreButton {
text-align: center;
background-color: var(--dark-color);
margin-left: 8px;
margin-right: 8px;
margin-bottom: 16px;
margin-top: 16px;
padding: 10px;
cursor: pointer;
}
.loadMoreButton:hover {
background-color: var(--hoover-color);
}

View file

@ -1,320 +0,0 @@
import React from 'react'
import Sleep from '../components/sleep'
import Head from 'next/head'
export default function Manual() {
return <div>{renderMaual()}</div>
}
function renderMaual() {
return (
<div>
<Head>
<title>Manual - Qmining | Frylabs.net</title>
</Head>
<center>
<h1>Manual</h1>
</center>
<hr />
<hr />
<Sleep />
<center>
Ez a userscript Moodle/Elearnig/KMOOC tesztek megoldása során segítséget
jelenít meg.
<h2>
Ha az oldalt vagy a scriptet használod: akármikor észrevehetik,
leállhat a szerver, és rossz lehet az összes válasz
</h2>
Valószínűleg semmi baj nem lesz, de én szóltam. Ha ez iránt aggódsz,
olvasd el a kockázatok részt
</center>
<hr />
<hr />
<center>
<h1>Userscript használata</h1>
</center>
<hr />
<hr />
<div className={'manualUsage'}>
<div>
<ul>
<li>
Tölts le egy userscript futtató kiegészítőt a böngésződhöz:{' '}
<a
href="https://www.tampermonkey.net/"
target="_blank"
rel="noreferrer"
>
Tampermonkey
</a>
</li>
<li>
<a
href="http://qmining.frylabs.net/install?man"
target="_blank"
rel="noreferrer"
>
Weboldalról
</a>{' '}
rakd fel a scriptet
</li>
<li>
Script majd udvariasan megkéri, hogy hagy beszélgessen a
szerverrel, mert mással nem tud, ezt engedélyezd.
</li>
<li>
Ezután:
<ul>
<li>
Teszt oldalon a kérdésre a választ kell látnod felül egy
felugró ablakszerűben
</li>
<li>
Teszt ellenőrzés oldalon a script beküldi a szervernek a
helyes válaszokat, az lementi az új kérdéseket, amik ezután
azonnal elérhetők lesznek
</li>
</ul>
</li>
</ul>
Egyéb fontos tudnivalók:
<ul>
<li>
Ezt ments sokszor akár minden nap:{' '}
<a
href="http://qmining.frylabs.net/allqr.txt?man"
target="_blank"
rel="noreferrer"
>
{' '}
Összes kérdés TXT
</a>{' '}
(ha elszállna a szerver)
</li>
<li>
<a
href="https://qmining.frylabs.net/allQuestions.html"
target="_blank"
rel="noreferrer"
>
Összes kérdés oldal
</a>
, ahol manuál tudsz keresni ha valami gáz lenne a scriptel
</li>
</ul>
<p /> Egyéb funkciók:
<ul>
<li>
Ha esetleg videókat nézel, akkor spaceval lehet play/pausolni, és
jobbra/balra gombbal ugrani a videóban.
</li>
</ul>
</div>
<div className={'rtfmImage'}>
<img
style={{ maxWidth: '100%', minWidth: '200px' }}
src="img/rtfm.jpg"
alt="img"
/>
</div>
</div>
<hr />
<hr />
<center>
<h1>Jelszavak</h1>
</center>
<hr />
<hr />
Ha ezt olvasod valszeg már van neked egy. Azért lett bevezetve, hogy
nagyjából zárt legyen a felhasználók köre
<ul>
<li>Minden felhasználónak más jelszava van</li>
<li>
Elvileg elég csak 1 szer beírnod, és nem kell többet, de{' '}
<b>mentsd le biztos helyre a jelszót, hogy később is meglegyen!</b> Ha
többször kell akkor az bug,{' '}
<a
href="http://qmining.frylabs.net/feedback?man"
target="_blank"
rel="noreferrer"
>
és szólj
</a>
</li>
<li>
<b>
Jelenleg nincs elfelejtett jelszó funkció, ha elfelejted akkor az
örökre eltűnik!
</b>
</li>
<li>
Ha van jelszavad akkor bizonyos határok között{' '}
<a
href="https://qmining.frylabs.net/pwRequest?man"
target="_blank"
rel="noreferrer"
>
te is tudsz generálni másoknak
</a>
(ncore style).
</li>
<li>
Saját jelszavad ne oszd meg, belépésnél máshonnan azonnal ki leszel
jelentkeztetve, és minek ha tudsz adni amúgy is
</li>
<li>
Mivel felhasználóneved nincs, így teljesen anoním az egész. Ez miatt
jelszót nem lehet megváltoztatni, hogy a szükséges komplexitás
megmaradjon
</li>
</ul>
<hr />
<hr />
<center>
<h1>Gyakran előforduló kérdések</h1>
</center>
<hr />
<hr />
<ul>
<li>
<b>
Olyan helyeken fut le a script, ahol nem kellene, vagy ideiglenesen
ki akarod kapcsolni
</b>
<br /> Tampermonkey bővitmény ikon -{'>'} click -{'>'} scriptet
kapcsold ki. Csak ne felejtsd visszakapcsolni ;)
</li>
<p />
<li>
<b>
Túl nagy a kérdést és a választ megjelenítő ablak, nem tudok a
válaszra kattintani
</b>
<br /> Zommolj ki egy kicsit, vagy kapcsold ki addig a scriptet.
Továbbá középső egérgombra kattintva el bírod tüntetni az ablakot,
amíg újra nem töltöd az oldalt, vagy másikra ugrasz.
</li>
<p />
<li>
<b>Gombok, %-ok, számok</b>
<br />
<img style={{ maxWidth: '100%' }} src="img/6.png" alt="img" />
</li>
<p />
</ul>
<hr />
<hr />
<center>
<h1>Kockázatok</h1>
</center>
<hr />
<hr />
<ul>
<li>
<b>Bármikor észrevehetik hogy használod a scriptet</b>
<br />
A weboldalt már kevésbé, de úgy nem menti el a kérdéseket a script,
mert nem fut. Később manuálisan is be lehet majd küldeni
kérdés-válaszokat.
<p />
Ha arra nem veszik a fáradságot, hogy a kérdéseket lecseréljék akkor
valószínűleg arra se hogy userscript futását detektáló kódot rakjanak
a weboldalra. A{' '}
<a href="https://moodle.org/" target="_blank" rel="noreferrer">
Moodle
</a>{' '}
egy nyílt forráskódú, valószínűleg self-hosted rendszer. Valószínűleg
az egyetem egy ezer éves debian szerverén fut, amihez senki se mer
nyúlni, nemhogy a moodle-t frissítse valaki.
<p />
A script shadow-root hoz teszi hozzá az összes megjelenített
elementet, így ezeket szinte lehetetlen detektálni. A moodle
semmiféleképpen nem látja, hogy milyen más oldalak vannak megnyitva a
böngésződben. Nem látja az XMLHttp requesteket se, amit a script
végez. Egy Matomo nevű script látja hogy milyen oldalarka navigálsz a
moodle-ről, de a script nem linkekkel irányít át, hanem javascript
eseménnyel, amit nem tud nyomon követni.
<p />
Aztán ki tudja ténylegesen hogy lehet
</li>
<li>
<b>Bármikor leállhat a szerver</b>
<br />
És akkor nem bírod megnézni a válaszokat. Erre van az{' '}
<a
href="http://qmining.frylabs.net/allqr.txt?man"
target="_blank"
rel="noreferrer"
>
{' '}
összes kérdés TXT
</a>
</li>
<li>
<b>Akármelyik válasz rossz lehet</b>
<br />
Pl.: ha a script rosszul menti le, vagy rossz kérdésre ad választ
</li>
</ul>
<hr />
<hr />
<center>
<h1>Egyéb</h1>
</center>
<hr />
<hr />
<h2 id="sitesave">Weboldal lementése</h2>
Hogy a hibákat a saját gépemen reprodukálni tudjam, és könnyen ki bírjam
javítani sokszor jól jön ha egy lementett weboldalt megkapok. Így lehet
letölteni egy oldalt:
<img style={{ maxWidth: '100%' }} src="img/websitesave.png" alt="img" />
<br />
<a
href="http://qmining.frylabs.net/feedback?man"
target="_blank"
rel="noreferrer"
>
Ide tudod feltölteni
</a>
<br />
Mivel nincs hozzáférésem semmilyen egyetemi oldalhoz, így csak így tudom
hatékonyan tesztelni a scriptet. Ezért hatalmas segítség ha feltöltöd azt
az oldalt amin hibával találkozol.
<hr />
<h2 id="scriptreinstall">Script újratelepítése</h2>
Jelenleg két helyről lehet telepíteni a scriptet: greasyforkról és a
weboldalról. A greasyforkos telepítési lehetőség meg fog szűnni, így ha
onnan telepítetted, akkor nem lesznek frissítések elérhetők (amik nagyon
fontosak (de tényleg)). Ezért a következő rövid manővert kellene
végrehajtani, hogy minden zökkenőmentesen menjen:
<ul>
<li>Böngésző bővítményeidnél kattints a tampermonkey-ra</li>
<li>Válaszd ki alulról második opciót, ami dashboard néven fut</li>
<li>
Ekkor új tabban felugranak telepített scriptjeid. Keresd meg a
Moodle/Elearning/KMOOC test help-et, és a sor végén kattints a kuka
gombra
</li>
<li>Ha megkérdezi mondd neki, hogy biztos törölni akarod</li>
<li>
Ezután simán kattints{' '}
<a
href="http://qmining.frylabs.net/install?man"
target="_blank"
rel="noreferrer"
>
ide a script újratelepítéséhez a weboldalról.
</a>
</li>
<li>
Kész! Lehet megkérdezi újra, hogy elérheti-e a szervert, de azt csak
egyszer. Szokásos módon engedélyezd, hogy le bírja kérni a helyes
válaszokat
</li>
</ul>
Ezzel semmi adat nem vész el, régi jelszó ugyanolyan marad (csak ne
felejtsd azt el)
</div>
)
}

View file

@ -1,45 +1,29 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import fetch from 'unfetch' import fetch from 'unfetch'
import Head from 'next/head' import Head from 'next/head'
import Link from 'next/link'
import Sleep from '../components/sleep'
import styles from './pwRequest.module.css' import styles from './pwRequest.module.css'
import constants from '../constants.json' import constants from '../constants.json'
export default function PwRequest(props) { function fetchAvailablePWS() {
const [result, setResult] = useState([]) return new Promise((resolve) => {
const [remaining, setRemaining] = useState('...')
const [requestedPWS, setRequestedPWS] = useState('...')
const [createDate, setCreateDate] = useState('...')
const [addPwPerDay, setAddPwPerDay] = useState('...')
const [daysAfterUserGetsPWs, setDaysAfterUserGetsPWs] = useState('...')
const [maxPWCount, setMaxPWCount] = useState('...')
const [addPWCount, setAddPWCount] = useState('...')
const [dayDiff, setDayDiff] = useState('...')
useEffect(() => {
console.info('Fetching avaible pws')
fetch(`${constants.apiUrl}avaiblePWS`, { fetch(`${constants.apiUrl}avaiblePWS`, {
credentials: 'include', credentials: 'include',
}) })
.then((resp) => { .then((resp) => {
return resp.json() return resp.json()
}) })
.then((data) => { .then((res) => {
setRemaining(data.avaiblePWS) resolve(res)
setCreateDate(data.userCreated)
setRequestedPWS(data.requestedPWS)
setAddPwPerDay(data.addPWPerDay)
setAddPWCount(data.addPWCount)
setDaysAfterUserGetsPWs(data.daysAfterUserGetsPWs)
setMaxPWCount(data.maxPWCount)
setDayDiff(data.dayDiff)
}) })
}, []) })
}
const handleSubmit = async () => { function requestPw() {
const rawResponse = await fetch(constants.apiUrl + 'getpw', { return new Promise((resolve) => {
fetch(constants.apiUrl + 'getpw', {
method: 'POST', method: 'POST',
credentials: 'include', credentials: 'include',
headers: { headers: {
@ -48,85 +32,135 @@ export default function PwRequest(props) {
}, },
body: JSON.stringify({}), body: JSON.stringify({}),
}) })
try { .then((res) => {
rawResponse return res.json()
.json() })
.then((resp) => { .then((res) => {
if (resp.result === 'success') { resolve(res)
setResult([...result, resp.pw]) })
setRemaining(resp.remaining) })
setRequestedPWS(resp.requestedPWS) }
} else if (resp.result === 'success') {
setResult(['Nem vagy bejelentkezve!']) // this should never happpen export default function PwRequest({ globalData }) {
const userId = globalData.userId || '...'
const [result, setResult] = useState([])
const [data, setData] = useState({
userCreated: '...',
availablePWS: '...',
requestedPWS: '...',
maxPWCount: '...',
daysAfterUserGetsPWs: '...',
addPWPerDay: '...',
addPWCount: '...',
dayDiff: '...',
userCount: '...',
})
const {
userCreated,
availablePWS,
requestedPWS,
maxPWCount,
daysAfterUserGetsPWs,
addPWPerDay,
addPWCount,
dayDiff,
userCount,
} = data
useEffect(() => {
fetchAvailablePWS().then((data) => {
setData(data)
})
}, [])
return (
<div>
<Head>
<title>Jelszó generálás - Qmining | Frylabs.net</title>
</Head>
<div className={'pageHeader'}>
<h1>Jelszó generálás</h1>
</div>
<center>
<Sleep />
<div id="form">
<div className={styles.text}>
<p className={styles.descrip}>
Minden felhasználó egyedi jelszót kap. Ne használjatok többen egy
jelszót, mert egy idő után -biztonsági okokból- kidob a rendszer,
ha ugyan az a felhasználó több helyen is belépve marad. A
jelszavakról bővebben a{' '}
<Link href="/faq?tab=pw">
<a>GYIK</a>
</Link>{' '}
vonatkozó részében olvashatsz.
</p>
</div>
<div className={styles.text}>
Minden <span>{' ' + daysAfterUserGetsPWs + ' '}</span> napnál
régebbi felhasználó generálhat magának{' '}
<span>{' ' + maxPWCount + 'db '}</span> jelszót. Miután lekértél{' '}
<span>{' ' + maxPWCount + 'db'}</span>-ot, a lehetőségeid nem
fogytak el, ismét lesz módod további{' '}
<span>{' ' + maxPWCount + 'db'}</span>-ot kérni, összességében
korlátlan mennyiségben. Az egyszerre rendelkezésre álló lehetőségeid
számát lentebb látod, ez maximum egyszerre{' '}
<span>{' ' + maxPWCount + 'db '}</span> lehet. Ezt követően{' '}
<span>{' ' + addPWPerDay + ' '}</span>
naponta kapsz további <span>{' ' + addPWCount + ' '}</span> új
lehetőséget generálásra, amíg a lehetőségeid száma ismét el nem éri
a <span>{' ' + maxPWCount + 'db'}</span>-ot. Eddig
<span>{' ' + requestedPWS + ' '}</span>
jelszót kértél le, vagyis jelen pillanatban
<span>{' ' + (availablePWS - requestedPWS) + 'db '}</span>
jelszót generálhatsz még.
<br />
<br />A jelenleg bejelentkezett felhasználó{' '}
<span>{' ' + dayDiff + ' '}</span>
napos,{' '}
<span>
{userCreated ? new Date(userCreated).toLocaleString() : '...'}
</span>
-kor lett létrehozva.
<br />
Az oldalnak jelenleg
<span>{' ' + userCount + ' '}</span>
felhasználója van.
</div>
<div className={`buttonContainer ${styles.pwButton}`}>
<div
onClick={() => {
requestPw().then((res) => {
setData(res)
if (res.success) {
setResult([...result, res.pw])
} else { } else {
setResult([ setResult([
...result, ...result,
'Jelszó kérési lehetőségeid elfogytak, nézz vissza később', 'Jelszó kérési lehetőségeid elfogytak, nézz vissza később',
]) ])
setRemaining(0)
} }
}) })
.catch((e) => { }}
setResult([...result, 'Szerver oldali hiba!']) >
console.error(e)
})
} catch (e) {
setResult([...result, 'Szerver oldali hiba!'])
console.error(e)
}
}
return (
// TODO: újrafogalmazás, remove hány nap után kapnak új jelszót először
<div>
<Head>
<title>Jelszó kérés - Qmining | Frylabs.net</title>
</Head>
<div id="form">
<div className={styles.text}>
Itt új jelszavakat tudsz kérni új felhasználóknak. Közös
jelszóhasználat nem ajánlott, mert ha valaki belép azzal a jelszóval
amit te használsz akkor téged kiléptet mindenhonnan. Szerintem van
elég jelszó hogy ne kelljen közös
</div>
<div className={styles.text}>
Új felhasználóknak
<span>{' ' + daysAfterUserGetsPWs + ' '}</span>
napot kell várni, míg kapnak lehetőséget jelszó generálásra.
</div>
<div className={styles.text}>
<span>{' ' + addPwPerDay + ' '}</span>
naponta
<span>{' ' + addPWCount + ' '}</span>
új lehetőség van jelszót generálni, maximum
<span>{' ' + maxPWCount + ' '}</span>
lehetőség gyűlhet össze
</div>
<div className={styles.text}>
Még kérhető jelszavak:
<span>{' ' + remaining}</span>. Felhasználó létrehozva:
<span>{' ' + createDate}</span>,<span>{' ' + dayDiff + ' '}</span>
napja. Eddig
<span>{' ' + requestedPWS + ' '}</span>
jelszót kértél.
</div>
<div className={styles.buttonContainer}>
<div onClick={handleSubmit} className={styles.button}>
Jelszó kérése Jelszó kérése
</div> </div>
</div> </div>
{result ? ( {result ? (
<div className={styles.pwContainer}> <div className={styles.pwContainer}>
{result.map((r, i) => { {result.map((res, i) => {
return ( return (
<div key={i} className={styles.pw}> <div key={i} className={styles.pw}>
{i + 1}.: {r} {i + 1}.: {res}
</div> </div>
) )
})} })}
</div> </div>
) : null} ) : null}
</div> </div>
</center>
</div> </div>
) )
} }

View file

@ -1,33 +1,23 @@
.buttonContainer {
padding: 10px;
display: flex;
align-items: center;
justify-content: center;
}
.button {
cursor: pointer;
text-align: center;
background-color: var(--text-color);
border: none;
padding: 10px 30px;
color: white;
width: 200px;
}
.text { .text {
text-align: center;
font-size: 18px; font-size: 18px;
color: white; color: white;
padding: 10px; padding: 10px;
} }
.descrip {
color: #acabab;
font-weight: bold;
padding-top: 4%;
padding-bottom: 4%;
font-size: 20px;
}
.pw { .pw {
margin: 10px; margin: 10px;
} }
.pwContainer { .pwContainer {
font-family: "Courier New", Courier, monospace; font-family: 'Courier New', Courier, monospace;
text-align: center; text-align: center;
font-size: 24px; font-size: 24px;
color: white; color: white;
@ -42,3 +32,7 @@
.text span { .text span {
color: var(--text-color); color: var(--text-color);
} }
.pwButton {
width: 20% !important;
}

View file

@ -85,20 +85,22 @@ function sortDataBy(data, key) {
}) })
} }
export default function RankList() { export default function RankList({ globalData }) {
const userId = globalData.userId
const [ranklist, setRanklist] = useState(null) const [ranklist, setRanklist] = useState(null)
const [selfUserId, setSelfUserId] = useState('...') const [selfUserId, setSelfUserId] = useState('...')
const [key, setKey] = useState('newQuestions') const [key, setKey] = useState('newQuestions')
const [since, setSince] = useState('all') const [since, setSince] = useState('all')
const [sum, setSum] = useState({}) const [sum, setSum] = useState()
const [ownEntryOnTop, setOwnEntryOnTop] = useState(false)
const getList = () => { const getList = () => {
setSum({}) setSum()
setRanklist(null) setRanklist(null)
getListFromServer(since) getListFromServer(since)
.then((data) => { .then((data) => {
setRanklist(data.list || []) setRanklist(data.list || [])
setSum(data.sum || {}) setSum(data.sum)
if (data.selfuserId) { if (data.selfuserId) {
setSelfUserId(data.selfuserId) setSelfUserId(data.selfuserId)
} }
@ -112,11 +114,22 @@ export default function RankList() {
getList() getList()
}, []) }, [])
useEffect(() => { useEffect(
() => {
getList() getList()
}, [since]) },
[since]
)
const list = ranklist && sortDataBy(ranklist, key) const list =
ranklist &&
sortDataBy(ranklist, key).reduce((acc, entry, i) => {
if (entry.userId === userId && ownEntryOnTop) {
return [{ rank: i, ...entry }, ...acc]
} else {
return [...acc, { rank: i, ...entry }]
}
}, [])
const updateSince = (keyword) => { const updateSince = (keyword) => {
setSince(keyword) setSince(keyword)
@ -127,70 +140,106 @@ export default function RankList() {
<Head> <Head>
<title>Ranklista - Qmining | Frylabs.net</title> <title>Ranklista - Qmining | Frylabs.net</title>
</Head> </Head>
<div className={styles.container}>
<div> <div>
<center>
<div className={'pageHeader'}>
<h1>Ranklista</h1>
</div>
</center>
<div <div
className={styles.infoText} className={styles.infoText}
>{`A felhasználó ID-d: #${selfUserId}`}</div> >{`A felhasználó ID-d: #${selfUserId}`}</div>
<div className={styles.text}> <div className={styles.text}>
<p>
Az adatok kb 2020. április eleje óta vannak gyűjtve, és azonnal Az adatok kb 2020. április eleje óta vannak gyűjtve, és azonnal
frissülnek. frissülnek.
</p>
</div> </div>
<Sleep /> <Sleep />
<div className={styles.sinceTable}> <div className={'selectContainer'}>
<div className={styles.sinceHeaderRow}> <div>Megjelenítés: </div>
<select
onChange={(e) => {
updateSince(e.target.value)
}}
>
{Object.keys(sinceOptions).map((key) => { {Object.keys(sinceOptions).map((key) => {
const val = sinceOptions[key] const val = sinceOptions[key]
return ( return (
<div <option key={key} value={key}>
key={key}
className={`${styles.clickable} ${key === since &&
styles.selected}`}
onClick={() => {
updateSince(key)
}}
>
{val.name} {val.name}
</div> </option>
) )
})} })}
</select>
</div>
<div className={'selectContainer'}>
<div>Rendezés: </div>
<select
value={key}
onChange={(e) => {
setKey(e.target.value)
}}
>
{Object.keys(selectOptions).map((key) => {
const val = selectOptions[key]
return (
<option key={key} value={key}>
{val.name}
</option>
)
})}
</select>
</div>
<div className={'checkbContainer'}>
<div>Saját hely mutatása felül:</div>
<input
type="checkbox"
checked={ownEntryOnTop}
onChange={(e) => {
setOwnEntryOnTop(e.target.checked)
}}
/>
</div> </div>
</div> </div>
<div className={styles.table}> {sum && list ? (
<>
<div className={`${styles.sumRow}`}>
<div />
<div>
<b>{'Összesen: '}</b>
</div>
<div>{sum.newQuestions.toLocaleString('hu')}</div>
<div>{sum.allQuestions.toLocaleString('hu')}</div>
<div>{sum.count.toLocaleString('hu')}</div>
</div>
<div className={styles.headerRow}> <div className={styles.headerRow}>
<div>{'Rank'}</div> <div>
<b>{'Rank'}</b>
</div>
<div>{'Felhasználó ID'}</div> <div>{'Felhasználó ID'}</div>
{Object.keys(selectOptions).map((listKey) => { {Object.keys(selectOptions).map((listKey) => {
const val = selectOptions[listKey] const val = selectOptions[listKey]
return ( return (
<div <div
className={`${styles.clickable} ${listKey === key && className={`${listKey === key && styles.selected}`}
styles.selected}`}
key={listKey} key={listKey}
onClick={() => {
setKey(listKey)
}}
> >
{val.name} {val.name}
</div> </div>
) )
})} })}
</div> </div>
<div className={`${styles.row} ${styles.sumrow}`}> <div className={styles.table}>
<div /> {list.map((listItem, i) => {
<div>{'Összesen'}</div>
<div>{sum.newQuestions}</div>
<div>{sum.allQuestions}</div>
<div>{sum.count}</div>
</div>
{list ? (
list.map((listItem, i) => {
return ( return (
<div <div
className={`${styles.row} ${listItem.userId === selfUserId && className={`${styles.row} ${listItem.userId ===
styles.selfRow}`} selfUserId && styles.selfRow}`}
key={i} key={i}
> >
<div>{i + 1}</div> <div>{listItem.rank + 1}</div>
<div>{'#' + listItem.userId}</div> <div>{'#' + listItem.userId}</div>
{Object.keys(selectOptions).map((listKey) => { {Object.keys(selectOptions).map((listKey) => {
const val = listItem[listKey] const val = listItem[listKey]
@ -198,12 +247,13 @@ export default function RankList() {
})} })}
</div> </div>
) )
}) })}
</div>
</>
) : ( ) : (
<LoadingIndicator /> <LoadingIndicator />
)} )}
</div> </div>
</div> </div>
</div>
) )
} }

View file

@ -1,47 +1,72 @@
.table, .container {
.sinceTable {
display: flex; display: flex;
flex-direction: column; flex-flow: column;
padding: 0px 10px; height: calc(98vh);
cursor: default;
} }
.table :nth-child(1), .container:first-child {
.sinceTable :nth-child(1) { flex: 0 1 auto;
color: white;
} }
.row, .table {
.headerRow, flex: 1 1 auto;
.sinceHeaderRow { overflow-y: scroll;
overflow-x: hidden;
background-color: #1b1b1c;
padding-top: 5px;
padding-bottom: 10px;
}
.sumRow {
font-size: 16px;
font-weight: 500;
display: flex;
padding-top: 5px;
padding-right: 16px;
margin-top: 30px;
background-color: var(--hoover-color);
}
.headerRow {
font-size: 15px;
display: flex; display: flex;
padding: 1px; padding: 1px;
padding-right: 15px;
background-color: var(--hoover-color);
}
.row {
display: flex;
padding: 1.5px;
} }
.row:hover { .row:hover {
background-color: #333333; background-color: #242323;
cursor: default;
text-shadow: 1px 1px 6px #969696;
color: var(--text-color);
} }
.row div, .row div,
.headerRow div, .headerRow div, .sumRow div {
.sinceHeaderRow div {
flex: 1; flex: 1;
padding: 0px 5px; padding: 0px 5px;
text-align: center; text-align: center;
} }
.headerRow div, .headerRow > div, .sumRow > div {
.sinceHeaderRow div { padding: 5px;
padding: 15px 5px;
} }
.row :nth-child(1), .row :nth-child(1),
.headerRow :nth-child(1) { .headerRow :nth-child(1), .sumRow :nth-child(1) {
flex: 0 30px; flex: 0 50px;
} }
.row :nth-child(2), .row :nth-child(2),
.headerRow :nth-child(2) { .headerRow :nth-child(2), .sumRow :nth-child(2) {
flex: 0 100px; flex: 0 130px;
text-align: left; text-align: left;
} }
@ -54,26 +79,27 @@
} }
.selfRow { .selfRow {
background-color: #9999ff; background-color: var(--text-color);
color: black; color: black;
} }
.selfRow:hover { .selfRow:hover {
background-color: #9999ee; background-color: #96810b;
color: gainsboro;
} }
.text { .text {
text-align: center; text-align: center;
font-size: 12px; font-size: 12px;
font-style: italic;
color: var(--text-color);
padding-bottom: 10px;
} }
.infoText { .infoText {
text-align: center; text-align: center;
font-size: 20px; font-size: 20px;
} padding-top: 20px;
.selected {
background-color: #9999ff;
} }
.sumrow { .sumrow {

113
src/pages/script.js Normal file
View file

@ -0,0 +1,113 @@
import React from 'react'
import Sleep from '../components/sleep'
import Head from 'next/head'
import ExternalLinkIcon from '../components/externalLinkIcon'
export default function Script() {
return (
<div>
<Head>
<title>Script - Qmining | Frylabs.net</title>
</Head>
<div className={'pageHeader'}>
<h1>Script</h1>
</div>
<div className={'buttonContainer'}>
<a href="https://www.tampermonkey.net/">
Ajánlott userscript kezelő bővítmény
<ExternalLinkIcon size={15} />
</a>
<a href="/install">
Script telepítése
<ExternalLinkIcon size={15} />
</a>
</div>
<Sleep />
{renderManual()}
</div>
)
}
function renderManual() {
return (
<>
<center>
<h2 className={'subtitle'}>A userscript telepítése, és használata</h2>
</center>
<div>
<p>
Ez a userscript Moodle/Elearnig/KMOOC tesztek megoldása során
segítséget jelenít meg.
</p>
<ol>
<li>
Tölts le egy userscript futtató kiegészítőt a böngésződhöz: pl. a{' '}
<a
href="https://www.tampermonkey.net/"
target="_blank"
rel="noreferrer"
>
Tampermonkey
</a>
-t.
</li>
<li>
<a
href="http://qmining.frylabs.net/install"
target="_blank"
rel="noreferrer"
>
Kattints ide hogy felrakd a scriptet
</a>{' '}
</li>
<li>
A script ezt követően udvariasan megkér, hogy hadd beszélgessen a
szerverrel, ezt engedélyezd neki. (Always allow domain)
</li>
<li>
A támogatott oldalakon a script egy apró menü ablakot jelenít meg a
weboldal bal alsó részén
</li>
<li>
Ezután a kitöltendő teszt oldalán a kérdésre a választ kell látnod
felül egy lebegő ablakban.
</li>
<li>
Teszt ellenőrzés oldalon a script beküldi a szervernek a helyes
válaszokat, az lementi az új kérdéseket, amik ezután azonnal
elérhetők lesznek (neked, és másoknak is)
</li>
</ol>
Egyéb fontos tudnivalók:
<ul>
<li>
Ezt ments sokszor akár minden nap:{' '}
<a
href="http://qmining.frylabs.net/allqr.txt"
target="_blank"
rel="noreferrer"
>
{' '}
Összes kérdés TXT
</a>{' '}
(az összes összegyűjtött kérdés, ha elszállna a szerver)
</li>
<li>
Az{' '}
<a href="/allQuestions" rel="noreferrer">
összes kérdés oldal
</a>{' '}
az oldal, ahol manuálisan tudsz keresni, ha valami gáz lenne a
scripttel.
</li>
</ul>
<div>
Ha útközben elakadsz, vagy hibát észlelsz, akkor oldalt a Kapcsolat
résznél sok elérhetőséget találsz, amin segítséget kérhetsz.
</div>
</div>
</>
)
}

View file

View file

@ -3,13 +3,13 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://fonts.googleapis.com/css?family=Quicksand&display=swap" rel="stylesheet"> <title>Thank you! - Qmining | Frylabs.net</title>
<title>Thank you!</title>
<style> <style>
@import url('https://fonts.googleapis.com/css2?family=Kameron&family=Overpass+Mono:wght@300;400&display=swap');
:root{ :root{
--size: 100px; --size: 100px;
--bgcolor: #222426; --bgcolor: #222426;
--color: #fcff4f; --color: #f2f2f2;
--shadow: rgba(30,2,5,.2); --shadow: rgba(30,2,5,.2);
} }
.surface { .surface {
@ -51,21 +51,28 @@
html,body{ html,body{
height:100vh; height: 95vh;
overflow: hidden; overflow: hidden;
} }
body { body {
font-family: 'Kameron', serif;
font-family: 'Overpass Mono', monospace;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background-color: var(--bgcolor); background-color: var(--bgcolor);
cursor: default;
}
#qminer {
color: #f2cb05;
} }
#text{ #text{
font-family: 'Quicksand', sans-serif; font-weight: 100;
color: white; font-size: 25px;
font-size: 24px; color: #f2f2f2;
text-shadow: 1px 1px 2px rgba(0,0,0,0.6); text-shadow: 1px 1px 2px black;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
@ -74,16 +81,19 @@
} }
#backbutton{ #backbutton{
text-decoration: none; text-decoration: none;
color: white; color: azure;
cursor: pointer; cursor: pointer;
margin: 10px; margin: 25px;
background-color: #4bb9bd; background-color: #f2cb05;
box-shadow: 0px 1px 3px rgba(0,0,0,0.6);
border-radius: 3px; border-radius: 3px;
padding: 3px 10px; padding: 3px 10px;
padding-top: 4px;
transition: width 0.5s, height 0.5s, ease-in 0.5s;
text-shadow: 1px 1px 2px black;
} }
#backbutton:hover{ #backbutton:hover{
box-shadow: 0px 1px 2px rgba(0,0,0,0.6); transition: width 0.5s, height 0.5s, ease-out 0.5s;
background-color: #96810b;
} }
.coincontainer { .coincontainer {
position: relative; position: relative;
@ -103,12 +113,11 @@
<div class="shadow"></div> <div class="shadow"></div>
</div></div> </div></div>
<center> <center>
<div id="text"><span>Thanks for the gold, kind question miner!</span><a id="backbutton" href="<%= siteurl %>">Return</a></div> <div id="text"><span>Thanks for the gold, kind <span id="qminer"><b>question miner</b></span>!</span><a id="backbutton" href="<%= siteurl %>">Return</a></div>
</center> </center>
<div class="coincontainer"><div class="surface"> <div class="coincontainer"><div class="surface">
<div class="coin"></div> <div class="coin"></div>
<div class="shadow"></div> <div class="shadow"></div>
</div></div> </div></div>
</body> </body>
</html> </html>

View file

@ -2,7 +2,7 @@ import styles from './thanks.module.css'
import constants from '../constants.json' import constants from '../constants.json'
import Head from 'next/head' import Head from 'next/head'
export default function Thanks () { export default function Thanks() {
return ( return (
<div> <div>
<Head> <Head>

View file

@ -1,83 +0,0 @@
import React, { useState } from 'react'
import Head from 'next/head'
import Sleep from '../components/sleep'
import Todos from '../components/todoStuff/todos'
import constants from '../constants.json'
import styles from './todos.module.css'
export default function contribute() {
const [newTask, setNewTask] = useState('')
const submitNewTask = async () => {
if (!newTask) {
return
}
fetch(constants.apiUrl + 'postfeedback', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
newTask: newTask,
from: 'contribute',
}),
})
.then((resp) => {
return resp.json()
})
.then((resp) => {
if (resp.success) {
alert('Elküldve')
setNewTask('')
} else {
alert('Hiba küldés közben')
}
})
.catch((err) => {
alert('Hiba küldés közben')
console.error(err)
})
}
const renderNewTaskArea = () => {
return (
<div className={styles.inputArea}>
<textarea
onChange={(event) => setNewTask(event.target.value)}
value={newTask || ''}
className={styles.feedback}
/>
<button className={styles.button} onClick={submitNewTask}>
Küldés
</button>
</div>
)
}
return (
<div>
<Head>
<title>Todos - Qmining | Frylabs.net</title>
</Head>
<div className={styles.description}>
Egy kártyára kattintva nézheted meg a részleteket, vagy szavazhatsz.
Minél több szavazat érkezik egy kártyára, annál magasabb lesz a
pioritása. Jobb alsó szám minél több, annál nehezebb a feladat. A Done
oszlopban lévő feladatok kész vannak, de különböző okok miat még nem
lettek kiadva frissítésként. Ami az In Prod táblázatban van az van kint.
</div>
<Todos />
<div className={styles.description}>
Itt írhatsz új todo-ra ötleteket, vagy jelezhetsz hogy egyikben
segítenél
</div>
{renderNewTaskArea()}
<Sleep />
</div>
)
}

View file

@ -1,41 +0,0 @@
.description {
font-size: 15px;
color: white;
text-align: center;
margin: 10px;
}
.warning {
color: white;
padding: 10px;
font-size: 26px;
text-align: center;
}
.feedback {
color: var(--text-color);
background-color: var(--background-color);
font-size: 14px;
width: 100%;
box-sizing: border-box;
height: 60px;
}
.button {
background-color: var(--text-color);
border: none;
padding: 5px 15px;
margin: 5px;
color: white;
width: 200px;
}
.inputArea {
display: flex;
}
.title {
color: #9999ff;
font-size: 30px;
text-align: center;
}

330
src/pages/userFiles.js Normal file
View file

@ -0,0 +1,330 @@
import React, { useState, useEffect } from 'react'
import Head from 'next/head'
import LoadingIndicator from '../components/LoadingIndicator'
import Modal from '../components/modal'
import styles from './userFiles.module.css'
import constants from '../constants.json'
function listUserDir(subdir) {
return new Promise((resolve) => {
fetch(
`${constants.apiUrl}listUserDir${subdir ? `?subdir=${subdir}` : ''}`,
{
credentials: 'include',
}
)
.then((resp) => {
return resp.json()
})
.then((res) => {
if (res.success) {
resolve(res)
} else {
alert(res.msg)
}
})
})
}
function FileUploader({ onChange }) {
return (
<div>
<div>Fájl csatolása</div>
<input type="file" name="file" onChange={onChange} />
</div>
)
}
function deleteFile(currDir, name) {
return new Promise((resolve) => {
fetch(constants.apiUrl + 'deleteUserFile', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
dir: currDir,
fname: name,
}),
})
.then((res) => {
return res.json()
})
.then((res) => {
if (res.success) {
resolve(res)
} else {
alert(res.msg)
}
})
})
}
function newSubj(name) {
return new Promise((resolve) => {
fetch(constants.apiUrl + 'newUserDir', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: name,
}),
})
.then((res) => {
return res.json()
})
.then((res) => {
if (res.success) {
resolve(res)
} else {
alert(res.msg)
}
})
})
}
function uploadFile(dir, file) {
return new Promise((resolve) => {
const formData = new FormData() // eslint-disable-line
formData.append('file', file)
formData.append('dir', dir)
fetch(constants.apiUrl + 'uploadUserFile', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
},
body: formData,
})
.then((res) => {
return res.json()
})
.then((res) => {
resolve(res)
})
})
}
export default function UserFiles({ router, globalData }) {
const userId = globalData.userId
const [dirs, setDirs] = useState()
const [sortBy, setSortBy] = useState('name')
const [sortDirection, setSortDirection] = useState(true)
const [addingNew, setAddingNew] = useState()
const [newSubjName, setNewSubjName] = useState()
const [file, setFile] = useState()
const currDir = router.query.dir ? decodeURIComponent(router.query.dir) : ''
useEffect(() => {
const dir = router.query.dir ? decodeURIComponent(router.query.dir) : ''
setDirs(null)
if (router.query.dir) {
listUserDir(dir).then((res) => {
setDirs(res.files)
})
} else {
listUserDir().then((res) => {
setDirs(res.dirs)
})
}
}, [router.query.dir])
const dirSorter = (a, b) => {
if (a[sortBy] < b[sortBy]) {
return sortDirection ? -1 : 1
} else if (a[sortBy] > b[sortBy]) {
return sortDirection ? 1 : -1
} else {
return 0
}
}
const renderDirList = (dirs) => {
return (
<div>
{currDir && (
<div className={`buttonContainer ${styles.backButton}`}>
<div
onClick={() => {
router.back()
// FIXME: consistend going back with browser back button
// back button works like broser back button, unless it would result in site leave
// history: nothing > opened site/usrFiles?dir=...
// history: site > site/userFiles > site/usrFiles?dir=...
router.push(`${router.pathname}`, undefined, { shallow: true })
}}
>
Vissza
</div>
</div>
)}
<div className={`${styles.tableContainer} ${styles.header}`}>
<div
onClick={(e) => {
const name = e.target.getAttribute('name')
if (name) {
if (sortBy === name) {
setSortDirection(!sortDirection)
} else {
setSortDirection(true)
}
setSortBy(name)
}
}}
>
<div name="name">Fájl név</div>
<div name="date">Feltöltés dátuma</div>
<div name="size">Méret</div>
<div name="user">Feltöltő user</div>
</div>
</div>
<div className={`${styles.tableContainer} ${styles.rows}`}>
<div
onClick={() => {
setAddingNew(currDir ? 'file' : 'dir')
}}
>
<div>{currDir ? 'Új fájl feltöltése...' : 'Új tárgy...'}</div>
</div>
{dirs.length !== 0 && (
<>
{dirs.sort(dirSorter).map((dir) => {
const { name, date, path, size, user } = dir
return (
<div
title={name}
key={name}
onClick={() => {
if (path) {
window.open(`${constants.apiUrl}${path}`, '_blank')
} else {
router.push(
`${router.pathname}?dir=${encodeURIComponent(name)}`,
undefined,
{ shallow: true }
)
}
}}
>
<div>{name}</div>
<div>{new Date(date).toDateString()}</div>
<div>
{currDir
? (size / 1000000).toFixed(2).toString() + ' MB'
: size + ' fájl'}
</div>
<div>
{user &&
user !== -1 &&
(userId === user ? (
<div
className={styles.deleteButton}
onClick={(e) => {
e.stopPropagation()
if (confirm(`Biztos törlöd '${name}'-t ?`)) {
deleteFile(currDir, name).then(() => {
listUserDir(currDir).then((res) => {
setDirs(res.files)
})
})
}
}}
>
Törlés
</div>
) : (
`#${user}`
))}
</div>
</div>
)
})}
</>
)}
</div>
</div>
)
}
return (
<div>
<Head>
<title>Study Docs - Qmining | Frylabs.net</title>
</Head>
<div className="pageHeader">
<h1>Study Docs</h1>
</div>
<div className={styles.description}>
Ide tárgyanként lehet feltölteni
</div>
{dirs ? renderDirList(dirs) : <LoadingIndicator />}
{addingNew && (
<Modal
closeClick={() => {
setAddingNew(null)
}}
>
<div className={styles.uploadContainer}>
{addingNew === 'file' ? (
<>
<FileUploader
onChange={(e) => {
setFile(e.target.files[0])
}}
/>
<div
className="buttonContainer"
onClick={() => {
uploadFile(currDir, file).then(() => {
listUserDir(currDir).then((res) => {
setDirs(res.files)
setAddingNew(null)
})
})
}}
>
<div>Feltöltés</div>
</div>
</>
) : (
<>
<div>Új tárgy neve</div>
<div>
<input
type="text"
onChange={(e) => {
setNewSubjName(e.target.value)
}}
/>
</div>
<div
className="buttonContainer"
onClick={() => {
newSubj(newSubjName).then(() => {
listUserDir().then((res) => {
setDirs(res.dirs)
setAddingNew(null)
})
})
}}
>
<div>OK</div>
</div>
</>
)}
</div>
</Modal>
)}
</div>
)
}

View file

@ -0,0 +1,78 @@
.tableContainer {
user-select: none;
display: flex;
flex-flow: column;
}
.tableContainer > div {
padding: 15px 5px;
display: flex;
align-items: stretch;
}
.tableContainer > div > div {
padding: 5px;
}
.tableContainer > div > div:nth-child(1) {
flex: 1;
}
.tableContainer > div > div:nth-child(2) {
flex: 0 240px;
}
.tableContainer > div > div:nth-child(3) {
flex: 0 160px;
}
.tableContainer > div > div:nth-child(4) {
flex: 0 100px;
}
.rows > div {
cursor: pointer;
}
.rows > div:nth-child(2n + 1) {
background-color: var(--dark-color);
}
.rows > div:hover {
background-color: var(--hoover-color);
}
.header > div > div {
display: flex;
align-items: center;
cursor: pointer;
}
.header > div > div:hover {
background-color: var(--hoover-color);
}
.uploadContainer > div {
padding: 5px;
text-align: center;
}
.deleteButton {
text-align: center;
padding: 2px;
border: 1px solid var(--hoover-color);
border-radius: 3px;
cursor: pointer;
}
.deleteButton:hover {
background-color: var(--dark-color);
}
.backButton {
width: 30%;
}
.description {
padding: 5px;
text-align: center;
}