Подготовка к 3 лабе

This commit is contained in:
Никита Потапов 2023-11-09 09:13:24 +04:00
parent 7f7f47304a
commit 166c1acb42
19 changed files with 3336 additions and 392 deletions

20
.eslintrc.json Normal file
View File

@ -0,0 +1,20 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": "airbnb-base",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"rules": {
"quotes": "off",
"indent": "off",
"no-console": "off",
"no-use-before-define": "off",
"no-alert": "off",
"no-restricted-globals": "off",
"quote-props": "off"
}
}

30
data.json Normal file
View File

@ -0,0 +1,30 @@
{
"users": [
{
"id": 1,
"firstName": "Никита",
"lastName": "Потапов",
"birthday": "17.02.2003",
"address": "г. Ульяновск",
"username": "nspotapov",
"password": "admin",
"avatarImg": "korgi.jpg"
}
],
"posts": [
{
"id": 1,
"userId": 1,
"createdDateTime": "2020-09-17T09:15:27+00:00",
"text": "<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit.</p><p>Perferendis odit quaerat reprehenderit. Ea harum officiis, ipsum numquam quis id eaque ducimus praesentium inventore error, voluptas, aut consequuntur sed libero sit.</p>",
"img": null
},
{
"id": 2,
"userId": 1,
"createdDateTime": "2020-09-20T14:45:27+00:00",
"text": "<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit.</p><p>Perferendis odit <a href='#'>quaerat</a> reprehenderit. Ea harum officiis, ipsum numquam quis id eaque ducimus praesentium inventore error, voluptas, aut consequuntur sed libero sit.</p>",
"img": "./static/img/korgi.jpg"
}
]
}

View File

@ -13,10 +13,11 @@
<link rel="stylesheet" href="./static/css/center.css">
<link rel="stylesheet" href="./static/css/post.css">
<link rel="stylesheet" href="./static/css/avatar.css">
<title>Неконтакте</title>
<link rel="shortcut icon" href="./static/img/favicon.svg" type="image/svg+xml">
<title>Новости</title>
</head>
<body>
<body class="min-vh-100 d-flex flex-column">
<header class="sticky-top">
<nav class="navbar">
<div class="container-fluid d-flex justify-content-between">
@ -49,7 +50,7 @@
</div>
</nav>
</header>
<main class="px-1 px-sm-2 px-lg-4 pt-2 pt-sm-4 d-flex position-relative">
<main class="px-1 px-sm-2 px-lg-4 pt-2 pt-sm-4 d-flex position-relative flex-fill">
<div id="left-menu"
class="p-3 d-none d-sm-flex flex-column justify-content-center align-content-around border border-dark rounded-2 position-fixed">
<a href="/me.html" class="left-menu-item d-flex rounded-2 py-1 px-2">
@ -70,13 +71,18 @@
</a>
</div>
<div class="wrapper d-flex justify-content-center">
<div class="center">
<div class="center d-flex flex-column">
<div id="editorjs" class="border rounded-2 border-dark mb-2 p-2"></div>
<button class="btn btn-primary mb-2">Опубликовать</button>
<div class="posts-wrapper">
<div class="post mb-2 mb-sm-4 w-100 d-flex flex-column border rounded-2 border-dark">
<div
class="post-header py-1 px-2 d-flex border-bottom border-dark justify-content-between align-items-center">
<a class="d-flex justify-content-start align-items-center">
<img src="./static/img/gachi.jpg"
class="post-author-avatar avatar-small img-fluid rounded-circle">
<div class="post-author-avatar-wrapper">
<img src="./static/img/korgi.jpg" class="post-author-avatar">
</div>
<div class="post-meta ms-2 d-flex flex-column justify-content-center">
<div class="post-author-name">
Алексей Смирнов
@ -456,6 +462,8 @@
</div>
</div>
</div>
</div>
</div>
</main>
@ -478,5 +486,6 @@
</footer>
</body>
<script>document.getElementById('current-year').innerHTML = new Date().getFullYear();</script>
<script src="./static/js/feed.js" type="module"></script>
</html>

View File

@ -13,7 +13,8 @@
<link rel="stylesheet" href="./static/css/center.css">
<link rel="stylesheet" href="./static/css/post.css">
<link rel="stylesheet" href="./static/css/avatar.css">
<title>Неконтакте</title>
<link rel="shortcut icon" href="./static/img/favicon.svg" type="image/svg+xml">
<title>Друзья</title>
</head>
<body class="vh-100 d-flex flex-column">

14
jsconfig.json Normal file
View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "Node",
"target": "ES2020",
"jsx": "preserve",
"strictNullChecks": true,
"strictFunctionTypes": true
},
"exclude": [
"node_modules",
"**/node_modules/*"
]
}

View File

@ -13,7 +13,8 @@
<link rel="stylesheet" href="./static/css/center.css">
<link rel="stylesheet" href="./static/css/post.css">
<link rel="stylesheet" href="./static/css/avatar.css">
<title>Неконтакте</title>
<link rel="shortcut icon" href="./static/img/favicon.svg" type="image/svg+xml">
<title>Вход</title>
</head>
<body class="vh-100">

View File

@ -13,7 +13,8 @@
<link rel="stylesheet" href="./static/css/center.css">
<link rel="stylesheet" href="./static/css/post.css">
<link rel="stylesheet" href="./static/css/avatar.css">
<title>Неконтакте</title>
<link rel="shortcut icon" href="./static/img/favicon.svg" type="image/svg+xml">
<title>Профиль</title>
</head>
<body>

View File

@ -14,7 +14,8 @@
<link rel="stylesheet" href="./static/css/post.css">
<link rel="stylesheet" href="./static/css/avatar.css">
<link rel="stylesheet" href="./static/css/messages.css">
<title>Неконтакте</title>
<link rel="shortcut icon" href="./static/img/favicon.svg" type="image/svg+xml">
<title>Сообщения</title>
</head>
<body class="vh-100 d-flex flex-column">

2575
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,18 +3,31 @@
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "vite --host 0.0.0.0 --port 80",
"serve": "http-server -p 3000 ./dist/",
"start": "vite --port 80 --host 0.0.0.0",
"serve": "http-server -p 80 ./dist/",
"build": "vite build",
"prod": "npm-run-all build serve"
"rest": "json-server --watch data.json -p 8081",
"dev": "npm-run-all --parallel rest start",
"prod": "npm-run-all build --parallel serve rest"
},
"dependencies": {
"@editorjs/editorjs": "^2.28.2",
"@editorjs/embed": "^2.6.0",
"@editorjs/header": "^2.7.0",
"@editorjs/list": "^1.8.0",
"bootstrap": "5.3.2",
"bootstrap-icons": "1.11.1"
},
"devDependencies": {
"@editorjs/link": "^2.5.0",
"@editorjs/quote": "^2.5.0",
"@editorjs/simple-image": "^1.5.1",
"eslint": "8.50.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-plugin-import": "2.28.1",
"http-server": "14.1.1",
"vite": "4.4.9",
"npm-run-all": "4.1.5"
"json-server": "0.17.4",
"npm-run-all": "4.1.5",
"vite": "4.4.9"
}
}

View File

@ -1,4 +1,5 @@
.avatar-small {
width: 35px;
height: 35px;
width: auto;
object-fit: cover;
}

View File

@ -31,6 +31,7 @@ a:hover {
header {
background-color: #235D70;
z-index: 99999999;
}
.navbar {
@ -67,3 +68,12 @@ header {
width: 75%;
}
}
.btn-primary {
--bs-btn-bg: #235D70;
--bs-btn-hover-bg: none;
--bs-btn-hover-color: black;
--bs-btn-border-color: #235D70;
--bs-btn-hover-border-color: #235D70;
--bs-btn-active-bg: #235D70;
}

View File

@ -4,6 +4,7 @@
.center {
width: 70%;
z-index: 0;
}
.user-message-trucated-text {
@ -29,3 +30,8 @@
width: 100%;
}
}
#editorjs {
z-index: 9999;
min-height: 100px;
}

View File

@ -14,3 +14,17 @@
.post-body-img {
width: 100%;
}
.post-body a {
color: #4a9cb7;
text-decoration: none;
background-image: linear-gradient(currentColor, currentColor);
background-position: 0% 100%;
background-repeat: no-repeat;
background-size: 0% 2px;
transition: background-size .3s;
}
.post-body a:hover {
background-size: 100% 2px;
}

13
static/img/favicon.svg Normal file
View File

@ -0,0 +1,13 @@
<svg width="142" height="72" viewBox="0 0 142 72" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.955078 72V65.8477L8.91406 64.4805V8.47461L0.955078 7.10742V0.90625H26.4922V7.10742L18.5332 8.47461V33.0352H52.5176V8.47461L44.5586 7.10742V0.90625H52.5176H62.1367H70.0957V7.10742L62.1367 8.47461V64.4805L70.0957 65.8477V72H44.5586V65.8477L52.5176 64.4805V40.6035H18.5332V64.4805L26.4922 65.8477V72H0.955078ZM77.5176 72V65.8477L85.4766 64.4805V8.47461L77.5176 7.10742V0.90625H103.055V7.10742L95.0957 8.47461V32.9375H102.859L118.924 9.79297C121.561 6.14714 123.855 3.67318 125.809 2.37109C127.762 1.03646 130.024 0.369141 132.596 0.369141C134.126 0.369141 135.411 0.564453 136.453 0.955078C137.527 1.31315 138.65 1.85026 139.822 2.56641L138.406 9.35352C137.527 9.19076 136.746 9.06055 136.062 8.96289C135.411 8.83268 134.76 8.76758 134.109 8.76758C132.579 8.76758 131.277 9.17448 130.203 9.98828C129.161 10.8021 127.941 12.1855 126.541 14.1387L111.258 35.4277L134.891 64.627L141.58 65.8477V72H118.24V65.8477L123.66 65.1641L123.465 64.9199L104.275 41.043H95.0957V64.4805L103.055 65.8477V72H77.5176Z"/>
<style>
path {
fill: black;
}
@media (prefers-color-scheme: dark) {
path {
fill: white;
}
}
</style>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

110
static/js/apiendpoint.js Normal file
View File

@ -0,0 +1,110 @@
const serverUrl = "http://localhost:8081";
export function createUserObject(
firstName,
lastName,
birthday,
address,
username,
password,
avatarImg,
) {
return {
firstName,
lastName,
birthday,
address,
username,
password,
avatarImg,
};
}
export function createPostObject(
userId,
createdDateTime,
text,
img,
) {
return {
userId,
createdDateTime,
text,
img,
};
}
export class ApiEndpoint {
constructor(endpoint) {
this.endpoint = endpoint;
}
async getObjects() {
const response = await fetch(`${serverUrl}/${this.endpoint}`);
if (!response.ok) {
throw response.statusText;
}
return response.json();
}
async getObject(id) {
const response = await fetch(`${serverUrl}/${this.endpoint}/${id}`);
if (!response.ok) {
throw response.statusText;
}
return response.json();
}
async createObject(obj) {
const options = {
method: "POST",
body: JSON.stringify(obj),
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
},
};
const response = await fetch(`${serverUrl}/${this.endpoint}`, options);
if (!response.ok) {
throw response.statusText;
}
return response.json();
}
async updateObject(obj) {
const options = {
method: "PUT",
body: JSON.stringify(obj),
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
},
};
const response = await fetch(
`${serverUrl}/${this.endpoint}/${obj.id}`,
options,
);
if (!response.ok) {
throw response.statusText;
}
return response.json();
}
async deleteObject(id) {
const options = {
method: "DELETE",
};
const response = await fetch(
`${serverUrl}/${this.endpoint}/${id}`,
options,
);
if (!response.ok) {
throw response.statusText;
}
return response.json();
}
}

49
static/js/feed.js Normal file
View File

@ -0,0 +1,49 @@
import { ApiEndpoint } from "./apiendpoint";
import { generatePostHtml } from "./posts";
import EditorJS from "@editorjs/editorjs";
import Header from "@editorjs/header";
import List from "@editorjs/list";
import Link from "@editorjs/link";
import Quote from "@editorjs/quote";
import SimpleImage from "@editorjs/simple-image"
import Embed from "@editorjs/embed";
const editor = new EditorJS({
/**
* Id of Element that should contain the Editor
*/
holder: "editorjs",
/**
* Available Tools list.
* Pass Tool's class or Settings object for each Tool you want to use
*/
tools: {
header: Header,
list: List,
quote: Quote,
link: Link,
image: SimpleImage,
embed: Embed
},
});
const postApiEndpoint = new ApiEndpoint("posts");
const userApiEndpoint = new ApiEndpoint("users");
document.addEventListener("DOMContentLoaded", loadPosts);
async function loadPosts() {
const posts = await postApiEndpoint.getObjects();
const center = document.getElementsByClassName("posts-wrapper")[0];
center.innerHTML = "";
for (let i = 0; i < posts.length; i++) {
const post = posts[i];
const postOwner = await userApiEndpoint.getObject(post.userId);
for (let j = 0; j < 3; j++)
center.innerHTML += generatePostHtml(post, postOwner);
}
}

65
static/js/posts.js Normal file
View File

@ -0,0 +1,65 @@
export function generatePostHtml(postObject, userObject) {
const postText = postObject.text !== null ? `<div class="post-body-text m-2">${postObject.text}</div>` : "";
const postImg = postObject.img !== null ? `<img src="${postObject.img}" class="post-body-img img-fluid"></img>` : "";
var options = {
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric"
};
const postCreatedDateTime = new Date(postObject.createdDateTime);
const html = `<div class="post mb-2 mb-sm-4 w-100 d-flex flex-column border rounded-2 border-dark">
<div class="post-header py-1 px-2 d-flex border-bottom border-dark justify-content-between align-items-center">
<a class="d-flex justify-content-start align-items-center">
<img src="./static/img/${userObject.avatarImg}" class="post-author-avatar avatar-small rounded-circle">
<div class="post-meta ms-2 d-flex flex-column justify-content-center">
<div class="post-author-name">
${userObject.firstName} ${userObject.lastName}
</div>
<div class="post-publication-datetime">
${postCreatedDateTime.toLocaleDateString("ru-RU", options)}
</div>
</div>
</a>
<a>
<i class="bi bi-three-dots fs-4"></i>
</a>
</div>
<div class="post-body">
${postText}
${postImg}
</div>
<div class="post-footer py-1 px-2 border-top border-dark d-flex">
<div class="counter-block likes-block px-2 me-1 d-flex align-items-center rounded-4">
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd">
<path d="M0 0h24v24H0z"></path>
<path d="M16 4a5.95 5.95 0 0 0-3.89 1.7l-.12.11-.12-.11A5.96 5.96 0 0 0 7.73 4 5.73 5.73 0 0 0 2 9.72c0 3.08 1.13 4.55 6.18 8.54l2.69 2.1c.66.52 1.6.52 2.26 0l2.36-1.84.94-.74c4.53-3.64 5.57-5.1 5.57-8.06A5.73 5.73 0 0 0 16.27 4zm.27 1.8a3.93 3.93 0 0 1 3.93 3.92v.3c-.08 2.15-1.07 3.33-5.51 6.84l-2.67 2.08a.04.04 0 0 1-.04 0L9.6 17.1l-.87-.7C4.6 13.1 3.8 11.98 3.8 9.73A3.93 3.93 0 0 1 7.73 5.8c1.34 0 2.51.62 3.57 1.92a.9.9 0 0 0 1.4-.01c1.04-1.3 2.2-1.91 3.57-1.91z" fill="currentColor" fill-rule="nonzero"></path>
</g>
</svg>
<span class="count ms-1">0</span>
</div>
<div class="counter-block comments-block px-2 me-1 d-flex align-items-center rounded-4">
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd">
<path d="M0 0h24v24H0z"></path>
<path d="M16.9 4H7.1c-1.15 0-1.73.11-2.35.44-.56.3-1 .75-1.31 1.31C3.11 6.37 3 6.95 3 8.1v5.8c0 1.15.11 1.73.44 2.35.3.56.75 1 1.31 1.31l.15.07c.51.25 1.04.35 1.95.37h.25v2.21c0 .44.17.85.47 1.16l.12.1c.64.55 1.6.52 2.21-.08L13.37 18h3.53c1.15 0 1.73-.11 2.35-.44.56-.3 1-.75 1.31-1.31.33-.62.44-1.2.44-2.35V8.1c0-1.15-.11-1.73-.44-2.35a3.17 3.17 0 0 0-1.31-1.31A4.51 4.51 0 0 0 16.9 4zM6.9 5.8h9.99c.88 0 1.18.06 1.5.23.25.13.44.32.57.57.17.32.23.62.23 1.5v6.16c-.02.61-.09.87-.23 1.14-.13.25-.32.44-.57.57-.32.17-.62.23-1.5.23h-4.02a.9.9 0 0 0-.51.26l-3.47 3.4V17.1c0-.5-.4-.9-.9-.9H6.74a2.3 2.3 0 0 1-1.14-.23 1.37 1.37 0 0 1-.57-.57c-.17-.32-.23-.62-.23-1.5V7.74c.02-.61.09-.87.23-1.14.13-.25.32-.44.57-.57.3-.16.58-.22 1.31-.23z" fill="currentColor" fill-rule="nonzero"></path>
</g>
</svg>
<span class="count ms-1">0</span>
</div>
<div class="counter-block replies-block px-2 d-flex align-items-center rounded-4">
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd">
<path d="M0 0h24v24H0z"></path>
<path d="M12 3.73c-1.12.07-2 1-2 2.14v2.12h-.02a9.9 9.9 0 0 0-7.83 10.72.9.9 0 0 0 1.61.46l.19-.24a9.08 9.08 0 0 1 5.84-3.26l.2-.03.01 2.5a2.15 2.15 0 0 0 3.48 1.69l7.82-6.14a2.15 2.15 0 0 0 0-3.38l-7.82-6.13c-.38-.3-.85-.46-1.33-.46zm.15 1.79c.08 0 .15.03.22.07l7.82 6.14a.35.35 0 0 1 0 .55l-7.82 6.13a.35.35 0 0 1-.57-.28V14.7a.9.9 0 0 0-.92-.9h-.23l-.34.02c-2.28.14-4.4.98-6.12 2.36l-.17.15.02-.14a8.1 8.1 0 0 1 6.97-6.53.9.9 0 0 0 .79-.9V5.87c0-.2.16-.35.35-.35z" fill="currentColor" fill-rule="nonzero"></path>
</g>
</svg>
<span class="count ms-1">0</span>
</div>
</div>
</div>`;
return html;
}

19
vite.config.js Normal file
View File

@ -0,0 +1,19 @@
import { resolve } from "path";
// eslint-disable-next-line import/no-extraneous-dependencies
import { defineConfig } from "vite";
export default defineConfig({
build: {
sourcemap: true,
emptyOutDir: true,
rollupOptions: {
input: {
feed: resolve(__dirname, "feed.html"),
friends: resolve(__dirname, "friends.html"),
login: resolve(__dirname, "login.html"),
me: resolve(__dirname, "me.html"),
messages: resolve(__dirname, "messages.html"),
},
},
},
});