Changed button layout on main page /IRC button removed temporarily/

Remade design on passwordgen page
This commit is contained in:
ndaniel1102 2021-03-09 00:43:18 +01:00
commit 49eae83f81
42 changed files with 6682 additions and 605 deletions

View file

@ -18,7 +18,7 @@ module.exports = {
'no-prototype-builtins': 'off',
'id-length': [
'warn',
{ exceptions: ['x', 'i', 'j', 't', 'Q', 'A', 'C', 'q', 'a', 'b'] },
{ exceptions: ['x', 'i', 'j', 't', 'Q', 'A', 'C', 'q', 'a', 'b', 'e'] },
],
},
root: true,

2
.gitignore vendored
View file

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

View file

@ -5,9 +5,7 @@ export default function Button (props) {
<div>
<center>
<a href={props.href}>
<div className={styles.ircLink}>
{props.text}
</div>
<div className={styles.ircLink}>{props.text}</div>
</a>
</center>
</div>

View file

@ -15,16 +15,10 @@ class Question extends PureComponent {
}
return (
<div className='questionContainer'>
<div className='question'>
{question.Q}
</div>
<div className='answer'>
{question.A}
</div>
<div className='data'>
{qdata || null}
</div>
<div className="questionContainer">
<div className="question">{question.Q}</div>
<div className="answer">{question.A}</div>
<div className="data">{qdata || null}</div>
</div>
)
}

View file

@ -13,16 +13,9 @@ class Questions extends PureComponent {
{subjs.map((subj, i) => {
return (
<div key={i}>
<div className={styles.subjName}>
{subj.Name}
</div>
<div className={styles.subjName}>{subj.Name}</div>
{subj.Questions.map((question, i) => {
return (
<Question
key={i}
question={question}
/>
)
return <Question key={i} question={question} />
})}
</div>
)

View file

@ -10,19 +10,12 @@ class Subject extends PureComponent {
return (
<div>
{subj.Questions.map((question, i) => {
return (
<Question
key={i}
question={question}
/>
)
return <Question key={i} question={question} />
})}
</div>
)
} else {
return (
<div />
)
return <div />
}
}
}

View file

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

227
src/components/comments.js Normal file
View file

@ -0,0 +1,227 @@
import React, { useState } from 'react'
import ReactButton from './reactButton.js'
import Modal from './modal.js'
import styles from './comments.module.css'
function CommentInput({ onSubmit, onCancel }) {
const [val, setVal] = useState('')
return (
<div className={styles.commentAreaContainer}>
<textarea
autoFocus
value={val}
onChange={(e) => {
setVal(e.target.value)
}}
/>
<div className={'actions'}>
<span
onClick={() => {
onSubmit(val)
}}
>
Submit
</span>
<span
onClick={() => {
onCancel()
}}
>
Cancel
</span>
</div>
</div>
)
}
function Comment({ comment, index, onComment, onDelete, onReact, uid }) {
const [displayed, setDisplayed] = useState(true)
const [commenting, setCommenting] = useState(false)
const { content, subComments, date, user, reacts, admin } = comment
const own = uid === user
const commentStyle = admin
? styles.adminComment
: own
? styles.ownComment
: ''
return (
<div className={styles.comment}>
<div className={`${styles.commentData} ${commentStyle}`}>
<div className={styles.commentHeader}>
<div className={styles.userContainer}>
<div
className={styles.showHide}
onClick={() => {
setDisplayed(!displayed)
}}
>
{displayed ? '[-]' : '[+]'}
</div>
<div>User #{user}</div>
</div>
<div>{date}</div>
</div>
<div className={`${!displayed && styles.hidden}`}>
<div className={styles.commentText}> {content}</div>
<div className={'actions'}>
<span
onClick={() => {
setCommenting(true)
}}
>
Reply...
</span>
{own && (
<span
onClick={() => {
onDelete([index])
}}
>
Delete
</span>
)}
<ReactButton
onClick={(reaction, isDelete) => {
onReact([index], reaction, isDelete)
}}
uid={uid}
existingReacts={reacts}
/>
</div>
{commenting && (
<CommentInput
onCancel={() => {
setCommenting(false)
}}
onSubmit={(e) => {
onComment([index], e)
setCommenting(false)
}}
/>
)}
</div>
</div>
<div className={`${!displayed && styles.hidden}`}>
{subComments &&
subComments.map((sc, i) => {
return (
<Comment
comment={sc}
onReact={(path, reaction, isDelete) => {
onReact([...path, index], reaction, isDelete)
}}
onDelete={(path) => {
onDelete([...path, index])
}}
onComment={(path, content) => {
onComment([...path, index], content)
}}
index={i}
key={i}
uid={uid}
/>
)
})}
</div>
</div>
)
}
function countComments(comments) {
return comments.reduce((acc, comment) => {
if (comment.subComments) {
acc += countComments(comment.subComments) + 1
} else {
acc += 1
}
return acc
}, 0)
}
export default function Comments({
comments,
onComment,
onDelete,
onReact,
uid,
}) {
const [addingNewComment, setAddingNewComment] = useState(false)
const [commentsShowing, setCommentsShowing] = useState(false)
const commentCount = comments ? countComments(comments) : 0
return (
<div>
{commentsShowing ? (
<Modal
closeClick={() => {
setCommentsShowing(false)
}}
>
{comments && comments.length > 0
? comments.map((comment, i) => {
return (
<Comment
onReact={onReact}
onComment={onComment}
onDelete={onDelete}
comment={comment}
index={i}
key={i}
uid={uid}
/>
)
})
: null}
{commentCount !== 0 ? (
<div className={'actions'}>
<span
onClick={() => {
setAddingNewComment(true)
}}
>
New comment
</span>
</div>
) : null}
{addingNewComment ? (
<CommentInput
onSubmit={(e) => {
if (!e) {
alert('Írj be valamit, hogy kommentelhess...')
return
}
setAddingNewComment(false)
onComment([], e)
}}
onCancel={() => {
setAddingNewComment(false)
if (commentCount === 0) {
setCommentsShowing(false)
}
}}
/>
) : null}
</Modal>
) : null}
<div
className={'actions'}
onClick={() => {
setCommentsShowing(true)
if (commentCount === 0) {
setAddingNewComment(true)
}
}}
>
<span>
{commentCount === 0
? 'New comment'
: `Show ${commentCount} comment${commentCount > 1 ? 's' : ''}`}
</span>
</div>
</div>
)
}

View file

@ -0,0 +1,57 @@
.comment {
margin-left: 25px;
padding: 8px 0px;
}
.commentData {
padding: 5px 2px;
border-left: 2px solid var(--text-color);
}
.commentHeader {
display: flex;
justify-content: space-between;
}
.commentHeader > div {
font-weight: bold;
margin: 2px;
}
.userContainer {
display: flex;
flex-direction: row;
}
.userContainer > div {
padding: 0px 3px;
}
.commentText {
margin: 2px;
padding: 10px 5px;
}
.ownComment {
border-left: 2px solid green;
}
.showHide {
cursor: pointer;
}
.hidden {
display: none;
}
.commentAreaContainer {
margin: 0px 8px 3px 8px;
}
.commentAreaContainer > div {
margin: 5px 0px;
}
.adminComment {
border-left: 2px solid yellow;
}

119
src/components/composer.js Normal file
View file

@ -0,0 +1,119 @@
import React, { useState } from 'react'
import Modal from './modal'
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 }) {
const [editorShowing, setEditorShowing] = useState(false)
const [val, setVal] = useState('')
const [type, setType] = useState('public')
const [title, setTitle] = useState('')
const [file, setFile] = useState()
return (
<>
<div
onClick={() => {
setEditorShowing(true)
}}
className={styles.new}
>
Új bejegyzés / feedback
</div>
{editorShowing && (
<Modal
closeClick={() => {
setEditorShowing(false)
}}
>
<div className={styles.container}>
{type !== 'private' && (
<input
placeholder={'Téma'}
value={title}
onChange={(e) => {
setTitle(e.target.value)
}}
/>
)}
<textarea
placeholder={'...'}
value={val}
onChange={(e) => {
setVal(e.target.value)
}}
/>
{type === 'private' && (
<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
onClick={() => {
onSubmit(type, title, val, file)
setEditorShowing(false)
}}
>
Post
</span>
<span
onClick={() => {
setEditorShowing(false)
}}
>
Cancel
</span>
</div>
</div>
</Modal>
)}
</>
)
}

View file

@ -0,0 +1,33 @@
.container {
display: flex;
flex-flow: column;
}
.container > input,
.container > textarea {
margin: 5px 0px;
padding: 4px;
}
.typeSelector {
display: flex;
align-items: center;
}
.tip {
font-size: 10px;
margin: 0px 10px;
}
.new {
text-align: center;
background-color: #191919;
border-radius: 3px;
cursor: pointer;
margin: 8px;
padding: 8px;
}
.new:hover {
background-color: var(--hoover-color);
}

View file

@ -15,7 +15,7 @@
}
.listItem:hover {
background-color: #666;
background-color: var(--hoover-color);
}
.text {

View file

@ -80,11 +80,15 @@ export default function Layout({
<div />
</span>
<div className="sidebarheader">
<Link href="/"><a><img
<Link href="/">
<a>
<img
style={{ maxWidth: '100%' }}
src={`${constants.siteUrl}img/frylabs-logo_small_transparent.png`}
alt="Frylabs"
/></a></Link>
/>
</a>
</Link>
</div>
</div>
{sidebarOpen ? (

View file

@ -46,7 +46,7 @@ export default function Modal(props) {
</div>
)}
{props.children}
<div className={styles.children}>{props.children}</div>
</div>
</div>
)

View file

@ -1,4 +1,5 @@
.modal {
z-index: 99;
position: fixed;
top: 0;
left: 0;
@ -8,16 +9,19 @@
}
.modalContent {
display: flex;
align-items: stretch;
max-height: 80%;
width: 80%;
position: fixed;
background: var(--background-color);
width: 70%;
height: auto;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 5px;
border-radius: 8px;
padding: 20px;
padding: 20px 30px;
cursor: auto;
}
@ -26,8 +30,14 @@
font-size: 18px;
position: absolute;
position: absolute;
top: 10px;
right: 10px;
display: inline;
}
.children {
max-height: 100%;
width: 100%;
overflow-y: scroll;
overflow-x: hidden;
}

View file

@ -0,0 +1,67 @@
import React from 'react'
import ReactButton from './reactButton.js'
import Comments from './comments.js'
import styles from './newsEntry.module.css'
export default function NewsEntry({
newsItem,
uid,
onCommentReact,
onNewsReact,
onComment,
onDelete,
onPostDelete,
}) {
const { reacts, title, content, user, comments, date, admin } = newsItem
return (
<div className={styles.newsRoot}>
<div className={`${styles.newsContainer} ${admin && styles.adminPost}`}>
<div className={styles.newsHeader}>
<div className={styles.newsTitle}>{title}</div>
<div className={styles.user}>
<div>User #{user}</div>
<div className={styles.newsDate}>{date}</div>
</div>
</div>
{admin ? (
<div
className={styles.newsBody}
dangerouslySetInnerHTML={{ __html: content }}
/>
) : (
<div className={styles.newsBody}>{content}</div>
)}
</div>
<div className={'actions'}>
{uid === user ? (
<span
onClick={() => {
onPostDelete()
}}
>
Delete
</span>
) : null}
<ReactButton
existingReacts={reacts}
uid={uid}
onClick={(reaction, isDelete) => {
onNewsReact({ reaction, isDelete })
}}
/>
</div>
<Comments
uid={uid}
onReact={(path, reaction, isDelete) => {
onCommentReact({ path, reaction, isDelete })
}}
onComment={onComment}
onDelete={onDelete}
comments={comments}
/>
</div>
)
}

View file

@ -0,0 +1,45 @@
.newsBody {
margin: 0px 5px;
color: #fff;
white-space: pre-line;
}
.newsTitle {
font-size: 20px;
color: var(--text-color);
margin: 0px 5px;
}
.newsDate {
margin: 0px 5px;
}
.newsContainer {
margin: 5px 5px;
}
.adminPost {
border-left: 2px solid yellow;
}
.newsContainer img {
max-width: 100%;
min-width: 200px;
}
.newsHeader {
display: flex;
justify-content: space-between;
align-items: center;
}
.user {
display: flex;
align-items: center;
}
.newsRoot {
background-color: #191919;
margin: 8px;
padding: 8px;
}

View file

@ -0,0 +1,107 @@
import React, { useState } from 'react'
import Tooltip from './tooltip.js'
import styles from './reactButton.module.css'
import reactions from '../data/reactions.json'
function ExistingReacts({ existingReacts, onClick, uid }) {
return (
<div className={styles.reactionContainer}>
<div>React</div>
{existingReacts &&
Object.keys(existingReacts).map((key) => {
const currReact = existingReacts[key]
const react = reactions[key]
if (!react) {
return null
}
return (
<div
title={currReact.join(', ')}
className={`${currReact.includes(uid) && styles.reacted}`}
key={key}
onClick={(e) => {
e.stopPropagation()
onClick(key, currReact.includes(uid))
}}
>
{react.emoji} {currReact.length}
</div>
)
})}
</div>
)
}
function RenderEmojis({ onClick }) {
const [search, setSearch] = useState('')
return (
<>
<input
type="text"
placeholder="Keresés..."
onChange={(event) => {
setSearch(event.target.value)
}}
/>
{Object.keys(reactions).map((key) => {
const reaction = reactions[key]
if (!key.includes(search.toLowerCase())) {
return null
}
return (
<div
title={key}
key={key}
onClick={() => {
onClick(key)
}}
>
{reaction.emoji}
</div>
)
})}
</>
)
}
export default function ReactButton({ onClick, existingReacts, uid }) {
const [opened, setOpened] = useState(false)
return (
<div
className={styles.reactContainer}
onClick={() => {
setOpened(true)
}}
onMouseLeave={() => {
setOpened(false)
}}
>
<Tooltip
opened={opened}
text={() => (
<ExistingReacts
uid={uid}
onClick={(key, isDelete) => {
onClick(key, isDelete)
setOpened(false)
}}
existingReacts={existingReacts}
/>
)}
>
<div className={styles.reactionContainer}>
<RenderEmojis
onClick={(e) => {
// setOpened(false)
onClick(e)
}}
/>
</div>
</Tooltip>
</div>
)
}

View file

@ -0,0 +1,40 @@
.reactionContainer {
display: flex;
flex-wrap: wrap;
}
.reactionContainer > input {
width: 100%;
background-color: var(--background-color);
color: var(--text-color);
border: none;
border-radius: 3px;
margin: 2px 5px;
font-size: 16px;
}
.reactionContainer > div {
margin: 2px 2px;
padding: 0px 10px;
border: 1px solid #444;
border-radius: 6px;
cursor: pointer;
user-select: none;
}
.reactionContainer > div:hover {
background-color: var(--hoover-color);
}
.break {
flex-basis: 100%;
height: 0;
}
.reacted {
color: yellow;
}
.reactContainer {
display: inline-block;
}

View file

@ -1,6 +1,6 @@
.container {
background-color: var(--background-color);
border-left: 3px solid #99f;
border-left: 3px solid var(--text-color);
top: 0;
right: 0px;
height: 100%;

View file

@ -10,9 +10,10 @@ export default function Sleep (props) {
style={{
margin: '10px',
width: '300px',
border: '2px solid white'
border: '2px solid white',
}}
src={constants.siteUrl + 'img/aludni.jpeg'} title="Ezt a kepet azert latod, mert ilyenkor mar igazan nem ezen az oldalon kellene jarnod"
src={constants.siteUrl + 'img/aludni.jpeg'}
title="Ezt a kepet azert latod, mert ilyenkor mar igazan nem ezen az oldalon kellene jarnod"
/>
</center>
</div>

View file

@ -9,12 +9,12 @@
}
.title {
color: #99f;
color: var(--text-color);
font-size: 18px;
}
.name {
color: #99f;
color: var(--text-color);
font-size: 20px;
margin: 20px 0px;
}
@ -36,7 +36,7 @@
}
.button {
border: 2px solid #99f;
border: 2px solid var(--text-color);
border-radius: 3px;
text-align: center;
cursor: pointer;

11
src/components/tooltip.js Normal file
View file

@ -0,0 +1,11 @@
import React from 'react'
import styles from './tooltip.module.css'
export default function Tooltip({ children, text, opened }) {
return (
<div className={styles.tooltip}>
{text()}
{opened && <span className={styles.tooltiptext}>{children}</span>}
</div>
)
}

View file

@ -0,0 +1,28 @@
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
width: 300px;
max-width: 300px;
height: 250px;
max-height: 250px;
background-color: var(--background-color);
border-radius: 5px;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 9999;
bottom: 100%;
left: 0%;
margin-left: -60px;
transition: opacity 0.3s;
overflow-y: scroll;
}
.container {
}

View file

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

5417
src/data/reactions.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -17,11 +17,6 @@
},
"ranklist": {
"href": "/ranklist",
"text": "Ranglista"
},
"feedback": {
"href": "/feedback",
"text": "Feedback",
"id": "feedback"
"text": "Ranklista"
}
}

View file

@ -1,8 +1,9 @@
:root {
--text-color: #F2CB05;
--text-color: #f2cb05;
--primary-color: #9999ff;
--bright-color: #f2f2f2;
--background-color: #222426;
--hoover-color: #191919;
--hoover-color: #393939;
}
@import url('https://fonts.googleapis.com/css2?family=Kameron&family=Overpass+Mono:wght@300;400&display=swap');
@ -22,7 +23,24 @@ a {
}
a:hover {
color: #C1C1C1;
color: #c1c1c1;
}
textarea {
color: var(--text-color);
background-color: var(--background-color);
box-sizing: border-box;
height: 120px;
width: 100%;
border: 1px solid #666;
border-radius: 3px;
}
input {
color: var(--text-color);
background-color: var(--background-color);
border: 1px solid #444;
width: 100%;
}
.link {
@ -283,7 +301,6 @@ a:hover {
.pageHeader h1 {
padding-top: 6px;
letter-spacing: 7px;
text-align: center;
margin: 0px;
}
@ -341,3 +358,21 @@ select:hover {
padding: 2px 6px;
font-size: 13.5px;
}
.actions {
display: flex;
align-items: center;
}
.actions > span {
margin: 2px 2px;
padding: 0px 10px;
border: 1px solid #444;
border-radius: 6px;
cursor: pointer;
user-select: none;
}
.actions > span:hover {
background-color: var(--hoover-color);
}

View file

@ -2,7 +2,14 @@ export default function Custom404 () {
return (
<center>
<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>
)
}

View file

@ -10,7 +10,7 @@ class MyDocument extends Document {
return (
<Html>
<Head />
<body bgcolor='#222426'>
<body bgcolor="#222426">
<Main />
<NextScript />
</body>

View file

@ -3,7 +3,14 @@ function Error ({ statusCode }) {
return (
<center>
<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>
)
}

View file

@ -157,7 +157,6 @@ export default function AllQuestions({ router }) {
<input
autoFocus
placeholder="Keresés..."
className={styles.searchBar}
type="text"
value={searchTerm}
onChange={(event) => {
@ -206,7 +205,6 @@ export default function AllQuestions({ router }) {
<input
autoFocus
placeholder="Keresés..."
className={styles.searchBar}
type="text"
value={searchTerm}
onChange={(event) => {

View file

@ -1,12 +1,3 @@
.searchBar {
margin: 10px;
color: white;
background-color: #222426;
border: none;
font-size: 18px;
flex-grow: 1;
}
.searchContainer {
width: 100%;
display: flex;

View file

@ -35,7 +35,7 @@
}
.title {
color: #9999ff;
color: var(--text-color);
font-size: 30px;
text-align: center;
}

View file

@ -1,214 +0,0 @@
import React, { useState } from 'react'
import fetch from 'unfetch'
import Head from 'next/head'
import Button from '../components/Button.js'
import styles from './feedback.module.css'
import constants from '../constants.json'
const results = {
success: 'SUCCESS',
error: 'ERROR',
notSent: 'NOTSENT',
invalid: 'INVALID',
}
export default function Feedback() {
const [form, setForm] = useState({})
const [file, setFile] = useState(undefined)
const [result, setResult] = useState(results.notSent)
const [fileResult, setFileResult] = useState(results.notSent)
const onChange = (event) => {
setForm({
...form,
[event.target.name]: event.target.value,
})
}
const renderTextInputArea = (params) => {
return (
<div className={styles.inputArea}>
<div className={styles.textTitle}>{params.text}</div>
<textarea
autoFocus={params.autoFocus}
onChange={(event) => params.onChange(event)}
value={form[params.name] || ''}
className={styles.feedback}
name={params.name}
/>
</div>
)
}
const onFileChangeHandler = (event) => {
setForm({
...form,
file: event.target.files[0].name,
})
setFile(event.target.files[0])
}
const renderFileUploader = () => {
return (
<div className={styles.inputArea}>
<div className={styles.textTitle}>Fájl csatolása</div>
<input
className={styles.fileInput}
type="file"
name="file"
onChange={onFileChangeHandler}
/>
</div>
)
}
const handleSubmit = async () => {
if (!form.description) {
setResult(results.invalid)
}
const t = document.getElementById('cid').value
let cid = ''
let version = ''
if (t) {
cid = t.split('|')[0]
version = t.split('|')[1]
}
const rawResponse = await fetch(constants.apiUrl + 'postfeedback', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
...form,
cid: cid,
version: version,
}),
})
rawResponse
.json()
.then((resp) => {
if (resp.success) {
setResult(results.success)
} else {
setResult(results.error)
}
})
.catch((err) => {
setResult(results.error)
console.error(err)
})
if (file) {
const formData = new FormData() // eslint-disable-line
formData.append('file', file)
const rawFileResponse = await fetch(
constants.apiUrl + 'postfeedbackfile',
{
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
},
body: formData,
}
)
rawFileResponse
.json()
.then((resp) => {
if (resp.success) {
setFileResult(results.success)
} else {
setFileResult(results.error)
}
})
.catch((err) => {
setFileResult(results.error)
console.error('FILE error', err)
})
}
}
const renderResult = () => {
if (results === result.success) {
return <div>sucess</div>
} else if (results === result.error) {
return <div>error</div>
} else if (results === result.invalid) {
return <div>invalid</div>
} else {
return null
}
}
// action={constants.apiUrl + 'badtestsender'} encType='multipart/form-data' method='post'
const renderForm = (props) => {
return (
<div className={styles.feedback}>
{props.noDesc ? (
<div className={styles.errorMsg}>Mező kitöltése kötelező!</div>
) : null}
{renderTextInputArea({
text: 'Rövid leírás',
name: 'description',
onChange: onChange,
autoFocus: true,
})}
<div className={styles.desc}>
Bal aluli levelesláda ikonnál keresd majd a választ
</div>
{renderFileUploader()}
<div className={styles.buttonContainer}>
<button className={styles.button} onClick={handleSubmit}>
Küldés
</button>
</div>
<input type="text" id="cid" name="cid" hidden />
{renderResult()}
</div>
)
}
const renderStuff = () => {
if (result === results.notSent && fileResult === results.notSent) {
return <div className={styles.textTitle}>{renderForm({})}</div>
} else if (result === results.invalid) {
return (
<div className={styles.textTitle}>{renderForm({ noDesc: true })}</div>
)
} else if (result === results.success && !file) {
return <div className={styles.textTitle}>Visszajelzés elküldve c:</div>
} else if (result === results.error && fileResult === results.success) {
return <div className={styles.textTitle}>Hiba küldés közben :c</div>
} else if (result === results.success && fileResult === results.error) {
return (
<div className={styles.textTitle}>
Visszajelzés elküldve, de fájlt nem sikerült elküldeni :c
</div>
)
} else if (result === results.success && fileResult === results.success) {
return <div className={styles.textTitle}>Visszajelzés elküldve c:</div>
} else {
return <div className={styles.textTitle}>Bit of a fuckup here</div>
}
}
return (
<div>
<Head>
<title>Feedback - Qmining | Frylabs.net</title>
</Head>
<Button text="IRC chat" href="/irc" />
<p />
<hr />
<p />
{renderStuff()}
</div>
)
}

View file

@ -1,46 +0,0 @@
.feedback {
color: var(--text-color);
background-color: var(--background-color);
font-size: 16px;
width: 100%;
box-sizing: border-box;
height: 120px;
}
.buttonContainer {
text-align: 'center';
width: 200px;
margin: 0 auto;
padding: 10px;
}
.desc {
font-size: 16px;
color: white;
}
.textTitle {
color: var(--text-color);
font-size: 20px;
}
.button {
background-color: var(--text-color);
border: none;
padding: 10px 30px;
color: white;
width: 200px;
}
.textInputArea {
padding: 20px 0px;
}
.fileInput {
margin: 10px;
color: var(--text-color);
}
.errorMsg {
color: red;
}

View file

@ -5,6 +5,8 @@ import Link from 'next/link'
import LoadingIndicator from '../components/LoadingIndicator'
import Sleep from '../components/sleep'
import NewsEntry from '../components/newsEntry'
import Composer from '../components/composer'
import DbSelector from '../components/dbSelector.js'
import styles from './index.module.css'
@ -25,83 +27,251 @@ const links = {
},
}
export default function Index(props) {
const [news, setNews] = useState(null)
const [allQrSelector, setAllQrSelector] = useState(null)
const motd = props.globalData.motd
// const userSpecificMotd = props.globalData.userSpecificMotd
useEffect(() => {
console.info('Fetching news.json')
function fetchNews() {
return new Promise((resolve) => {
fetch(`${constants.apiUrl}news.json`, {
credentials: 'include',
})
.then((resp) => {
return resp.json()
})
.then((data) => {
setNews(data)
.then((res) => {
resolve(res)
})
})
}
function addPost(title, content) {
return new Promise((resolve) => {
fetch(constants.apiUrl + 'addPost', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: title,
content: content,
}),
})
.then((res) => {
return res.json()
})
.then((res) => {
resolve(res)
})
})
}
function postFeedback(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,
}),
}).then((res) => {
return res.json()
}),
]
if (file) {
console.log('FIEEEEEEEEEELE')
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 Index({ globalData }) {
const userId = globalData.userId
const motd = globalData.motd
const [news, setNews] = useState(null)
const [allQrSelector, setAllQrSelector] = useState(null)
// const userSpecificMotd = props.globalData.userSpecificMotd
useEffect(() => {
console.info('Fetching news.json')
fetchNews().then((res) => {
setNews(res)
})
}, [])
const renderQAItem = (newsItem, key) => {
return (
<div key={key} className={styles.itemContainer}>
<div className={styles.itemNumber}>{key} :</div>
<div
className={styles.question}
dangerouslySetInnerHTML={{ __html: newsItem.q }}
/>
<div
className={styles.answer}
dangerouslySetInnerHTML={{ __html: newsItem.a }}
/>
</div>
)
}
const renderNewsItem = (newsItem, key) => {
return (
<div key={key} className={styles.itemContainer}>
<div className={styles.itemNumber}>{key} :</div>
<div
className={styles.newsTitle}
dangerouslySetInnerHTML={{ __html: newsItem.title }}
/>
<div
className={styles.newsBody}
dangerouslySetInnerHTML={{ __html: newsItem.body }}
/>
</div>
)
}
const renderNews = () => {
if (news) {
let questions = Object.keys(news)
let newsItems = Object.keys(news)
.map((key) => {
let newsItem = news[key]
if (newsItem.q) {
let newsEntryData = news[key]
return (
<div key={key}>
{renderQAItem(newsItem, key)}
</div>
<NewsEntry
onPostDelete={() => {
fetch(constants.apiUrl + 'rmPost', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
newsKey: key,
}),
})
.then((res) => {
return res.json()
})
.then((res) => {
setNews(res.news)
})
}}
onNewsReact={({ reaction, isDelete }) => {
fetch(constants.apiUrl + 'react', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
reaction: reaction,
newsKey: key,
isDelete: isDelete,
}),
})
.then((res) => {
return res.json()
})
.then((res) => {
console.log(res)
setNews(res.news)
})
}}
onCommentReact={({ path, reaction, isDelete }) => {
fetch(constants.apiUrl + 'react', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'reaction',
newsKey: key,
path: path,
reaction: reaction,
isDelete: isDelete,
}),
})
.then((res) => {
return res.json()
})
.then((res) => {
setNews(res.news)
})
}}
onDelete={(path) => {
fetch(constants.apiUrl + 'comment', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'delete',
path: path,
newsKey: key,
}),
})
.then((res) => {
return res.json()
})
.then((res) => {
setNews(res.news)
})
}}
onComment={(path, content) => {
fetch(constants.apiUrl + 'comment', {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'add',
path: path,
content: content,
newsKey: key,
}),
})
.then((res) => {
return res.json()
})
.then((res) => {
setNews(res.news)
})
}}
uid={userId}
key={key}
newsKey={key}
newsItem={newsEntryData}
/>
)
} else {
return (
<div key={key}>
{renderNewsItem(newsItem, key)}
</div>
)
}
})
.reverse()
return (
<div>
<div className={styles.title}>Hírek</div>
<div className={styles.title}>Fórum</div>
<hr />
<div className={styles.questionscontainer}>{questions}</div>
<Composer
onSubmit={(type, title, content, file) => {
if (!content) {
alert('Üres a tartalom!')
return
}
console.log(type, title, content, file)
if (type === 'private') {
postFeedback(content, file).then((res) => {
console.log(res)
alert('Privát visszajelzés elküldve!')
})
} else {
if (!title) {
alert('Üres a téma!')
return
}
addPost(title, content).then((res) => {
setNews(res.news)
})
}
}}
/>
<div>{newsItems}</div>
</div>
)
} else {
@ -125,23 +295,6 @@ export default function Index(props) {
)
}
// 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 (
@ -196,15 +349,6 @@ export default function Index(props) {
>
{'Összes kérdés TXT'}
</a>
<a
/*onClick={() => {
setAllQrSelector('json')
}}
className={styles.button}*/
>
{/*'Összes kérdés JSON'*/}
</a>
</div>
{renderMotd()}
{/*{userSpecificMotd && renderUserSpecificMotd()} */}

View file

@ -21,20 +21,23 @@ function renderMaual() {
Ha az oldalt vagy a scriptet használod: akármikor észrevehetik,
leállhat a szerver, és rossz lehet az összes válasz!
</h3>
<p id="manualWarn">Valószínűleg semmi baj nem lesz, de én szóltam. Ha emiatt aggódsz,
olvasd el a <a href="#risk">kockázatok részt</a>.</p>
<p id="manualWarn">
Valószínűleg semmi baj nem lesz, de én szóltam. Ha emiatt aggódsz,
olvasd el a <a href="#risk">kockázatok részt</a>.
</p>
</center>
<Sleep />
<center>
</center>
<center></center>
<hr />
<center>
<h2 className={'subtitle'}>A userscript használata</h2>
</center>
<div className={'manualUsage manualBody'}>
<div>
<p>Ez a userscript Moodle/Elearnig/KMOOC tesztek megoldása során segítséget
jelenít meg.</p>
<p>
Ez a userscript Moodle/Elearnig/KMOOC tesztek megoldása során
segítséget jelenít meg.
</p>
<ul>
<li>
Tölts le egy userscript futtató kiegészítőt a böngésződhöz: pl. a{' '}
@ -44,35 +47,30 @@ function renderMaual() {
rel="noreferrer"
>
Tampermonkey
</a>-t.
</a>
-t.
</li>
<li>
A <a
<a
href="http://qmining.frylabs.net/install?man"
target="_blank"
rel="noreferrer"
>
weboldalról
Kattints ide hogy felrakd a scriptet
</a>{' '}
rakd fel a scriptet.
</li>
<li>
A script ezt követően udvariasan megkér, hogy hadd beszélgessen a
szerverrel, mert mással nem tud, ezért ezt engedélyezd neki.
</li>
<li>
Ezután:
<ul>
<li>
A kitöltendő teszt oldalán a kérdésre a választ kell látnod felül egy
lebegő ablakban.
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
</li>
</ul>
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>
</ul>
Egyéb fontos tudnivalók:
@ -87,15 +85,15 @@ function renderMaual() {
{' '}
Összes kérdés TXT
</a>{' '}
(ha elszállna a szerver)
(az összes összegyűjtött kérdés, ha elszállna a szerver)
</li>
<li>
Az <a
href="/allQuestions"
rel="noreferrer"
>
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.
</a>{' '}
az oldal, ahol manuálisan tudsz keresni, ha valami gáz lenne a
scripttel.
</li>
</ul>
</div>
@ -112,21 +110,24 @@ function renderMaual() {
<h2 id="pw" className={'subtitle'}>Jelszavak</h2>
</center>
<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>
<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 {' '}
<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>.
</a>
.
</li>
<li>
<i>
@ -135,23 +136,25 @@ function renderMaual() {
</i>
</li>
<li>
Ha van jelszavad akkor {' '}
<b>bizonyos határok között</b>{' '}
te is <a
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).
</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)
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.
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>
@ -163,11 +166,14 @@ function renderMaual() {
<ul>
<li>
<b>
Olyan helyeken fut le a script, ahol nem kellene, vagy ideiglenesen
ki akarod kapcsolni;
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>
<br />
<i>
Tampermonkey bővitmény ikon -{'>'} click -{'>'} a scriptet
kapcsold ki. Csak ne felejtsd el visszakapcsolni ;)
</i>
</li>
<br />
<li>
@ -175,22 +181,32 @@ function renderMaual() {
Túl nagy a kérdést és a választ megjelenítő ablak, nem tudok a
válaszra kattintani;
</b>
<br /><i>Zoomolj ki egy kicsit az oldalon, kapcsold ki addig a scriptet,
vagy zárd be a script ablakát. Illetve a középső egérgombbal kattintva a
script abalkon el bírod tüntetni, amíg újra nem töltöd az oldalt, vagy görgetéssel
állíthatsz az átlátszóságán.</i>
<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: '100%' }} src="img/6.png" alt="img" className={'manual_img'} />
<img
style={{ maxWidth: '100%' }}
src="img/6.png"
alt="img"
className={'manual_img'}
/>
</li>
</ul>
</div>
<hr />
<center>
<h2 id="risk" className={'subtitle'}>Kockázatok</h2>
<h2 id="risk" className={'subtitle'}>
Kockázatok
</h2>
</center>
<ul>
<li>
@ -200,16 +216,6 @@ function renderMaual() {
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
@ -240,30 +246,43 @@ function renderMaual() {
</li>
</ul>
<hr />
<h2 id="sitesave" className={'subtitle'}>Weboldal lementése</h2>
<p>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
menteni egy oldalt:</p>
<h2 id="sitesave" className={'subtitle'}>
Weboldal lementése
</h2>
<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: '100%' }} src="img/websitesave.png" alt="img" className={'manual_img'} />
<img
style={{ maxWidth: '100%' }}
src="img/websitesave.png"
alt="img"
className={'manual_img'}
/>
<br />
<a
href="/feedback"
rel="noreferrer"
>
<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>
<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 />
<h2 id="scriptreinstall" className={'subtitle'}>Script újratelepítése</h2>
<p>Jelenleg két helyről lehet telepíteni a scriptet: greasyforkról és a
<h2 id="scriptreinstall" className={'subtitle'}>
Script újratelepítése
</h2>
<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>
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>
@ -281,7 +300,8 @@ function renderMaual() {
rel="noreferrer"
>
ide
</a> a script újratelepítéséhez a weboldalról.
</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

View file

@ -48,7 +48,7 @@
}
.pwContainer {
font-family: "Courier New", Courier, monospace;
font-family: 'Courier New', Courier, monospace;
text-align: center;
font-size: 24px;
color: white;

View file

@ -46,7 +46,7 @@
}
.clickable:hover {
background-color: #666666;
background-color: var(--hoover-color);
}
.clickable {