Compare commits
10 Commits
d4a18b0759
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9094f5e278 | ||
|
|
bb987b1f8b | ||
|
|
3fcaefba1b | ||
|
|
5a375d67c7 | ||
|
|
81446e56f5 | ||
|
|
e14450f373 | ||
|
|
0eca2b1e68 | ||
| 2c3e6578e9 | |||
|
|
69db666a4b | ||
| a4cffbed23 |
6
backend/.dockerignore
Normal file
6
backend/.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.git
|
||||||
|
docker-compose.yml
|
||||||
|
data/
|
||||||
|
Makefile
|
||||||
|
.env
|
||||||
|
docs/
|
||||||
@@ -3,18 +3,21 @@ FROM golang:1.25.6 AS builder
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY . .
|
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
ENV GIN_MODE=release
|
ENV GIN_MODE=release
|
||||||
RUN go mod tidy
|
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
RUN go build -ldflags "-s -w" -o backend ./cmd/main.go
|
RUN go build -ldflags "-s -w" -o backend ./cmd/main.go
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3.23.0
|
FROM alpine:3.23.0
|
||||||
|
RUN adduser -D appuser && apk add --no-cache curl
|
||||||
COPY --from=builder /app/backend .
|
COPY --from=builder /app/backend .
|
||||||
|
RUN chown appuser:appuser ./backend
|
||||||
|
USER appuser
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
RUN apk add --no-cache curl
|
|
||||||
HEALTHCHECK CMD curl --fail http://localhost:8080/health || exit 1
|
HEALTHCHECK CMD curl --fail http://localhost:8080/health || exit 1
|
||||||
CMD ["./backend"]
|
CMD ["./backend"]
|
||||||
|
|||||||
72
backend/internal/handlers/comments_handlers.go
Normal file
72
backend/internal/handlers/comments_handlers.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/logger"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CommentsHandlers struct {
|
||||||
|
logger *logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCommentsHandlers() *CommentsHandlers {
|
||||||
|
return &CommentsHandlers{logger: logger.New(false)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllComments godoc
|
||||||
|
// @Summary Get all comments
|
||||||
|
// @Description Get all comments
|
||||||
|
// @Tags comments
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} models.SuccessResponse{data=[]storage.Comment}
|
||||||
|
// @Failure 404 {object} models.ErrorResponse "No Comment found"
|
||||||
|
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
||||||
|
// @Router /comments [get]
|
||||||
|
func (h *CommentsHandlers) GetAllComments(c *gin.Context) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCommentsOfPost godoc
|
||||||
|
// @Summary Get comments of post
|
||||||
|
// @Description Get comments of post
|
||||||
|
// @Tags comments
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "Post ID"
|
||||||
|
// @Success 200 {object} models.SuccessResponse{data=[]storage.Comment}
|
||||||
|
// @Failure 404 {object} models.ErrorResponse "No Comment found"
|
||||||
|
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
||||||
|
// @Router /posts/{id}/comments [get]
|
||||||
|
func (h *CommentsHandlers) GetCommentsOfPost(c *gin.Context) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateComment godoc
|
||||||
|
// @Summary Create comment
|
||||||
|
// @Description Create new comment
|
||||||
|
// @Tags comments
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param comment body storage.CommentCreate true "Comment data"
|
||||||
|
// @Security Bearer
|
||||||
|
// @Success 200 {object} models.SuccessResponse{data=storage.Comment}
|
||||||
|
// @Failure 400 {object} models.ErrorResponse "Invalid request"
|
||||||
|
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
||||||
|
// @Router /comments [post]
|
||||||
|
func (h *CommentsHandlers) CreateComment(c *gin.Context) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteComment godoc
|
||||||
|
// @Summary Delete comment
|
||||||
|
// @Description Delete comment
|
||||||
|
// @Tags admin
|
||||||
|
// @Security Bearer
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "Comment ID"
|
||||||
|
// @Success 200 {object} models.SuccessResponse{data=storage.Comment}
|
||||||
|
// @Failure 400 {object} models.ErrorResponse "Invalid ID format"
|
||||||
|
// @Failure 404 {object} models.ErrorResponse "Comment not found"
|
||||||
|
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
||||||
|
// @Router admin/comments/{id} [delete]
|
||||||
|
func (h *CommentsHandlers) DeleteComment(c *gin.Context) {
|
||||||
|
}
|
||||||
@@ -110,6 +110,7 @@ func (h *PostHandlers) CreatePost(c *gin.Context) {
|
|||||||
post := storage.Post{
|
post := storage.Post{
|
||||||
Title: req.Title,
|
Title: req.Title,
|
||||||
Content: req.Content,
|
Content: req.Content,
|
||||||
|
Tags: req.Tags,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.repo.Create(c.Request.Context(), post); err != nil {
|
if err := h.repo.Create(c.Request.Context(), post); err != nil {
|
||||||
|
|||||||
86
backend/internal/repositories/comments_repository.go
Normal file
86
backend/internal/repositories/comments_repository.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/logger"
|
||||||
|
|
||||||
|
"gitea.d3m0k1d.ru/d3m0k1d/d3m0k1d.ru/backend/internal/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type commentsRepository struct {
|
||||||
|
db *sql.DB
|
||||||
|
logger *logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCommentsRepository(db *sql.DB) CommentRepository {
|
||||||
|
return &commentsRepository{
|
||||||
|
db: db,
|
||||||
|
logger: logger.New(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commentsRepository) CreateComment(
|
||||||
|
ctx context.Context,
|
||||||
|
comment *storage.CommentCreate,
|
||||||
|
) error {
|
||||||
|
_, err := c.db.Exec(
|
||||||
|
"INSERT INTO comments(content, post_id) VALUES(?, ?, ?)",
|
||||||
|
comment.Content,
|
||||||
|
comment.PostID,
|
||||||
|
comment.UserID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("error insert: " + err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commentsRepository) GetAllComments(ctx context.Context) ([]storage.Comment, error) {
|
||||||
|
var result []storage.Comment
|
||||||
|
rows, err := c.db.Query("SELECT id, content, post_id, user_id, created_at FROM comments")
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("error scan " + err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
var id int
|
||||||
|
var content string
|
||||||
|
var postID int
|
||||||
|
var userID int
|
||||||
|
var createdAt string
|
||||||
|
err := rows.Scan(&id, &content, &postID, &userID, &createdAt)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("error scan: " + err.Error())
|
||||||
|
}
|
||||||
|
result = append(result, storage.Comment{
|
||||||
|
ID: id,
|
||||||
|
Content: content,
|
||||||
|
PostID: postID,
|
||||||
|
UserID: userID,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commentsRepository) GetCommentsOfPost(
|
||||||
|
ctx context.Context,
|
||||||
|
id int,
|
||||||
|
) ([]storage.Comment, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commentsRepository) DeleteComment(ctx context.Context, id int) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *commentsRepository) UpdateComment(
|
||||||
|
ctx context.Context,
|
||||||
|
id int,
|
||||||
|
comment *storage.Comment,
|
||||||
|
) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -22,3 +22,11 @@ type AuthRepository interface {
|
|||||||
IsRegistered(ctx context.Context, github_id int) (bool, error)
|
IsRegistered(ctx context.Context, github_id int) (bool, error)
|
||||||
GetUserByGithubID(ctx context.Context, githubID int) (*storage.User, error)
|
GetUserByGithubID(ctx context.Context, githubID int) (*storage.User, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CommentRepository interface {
|
||||||
|
CreateComment(ctx context.Context, comment *storage.CommentCreate) error
|
||||||
|
GetAllComments(ctx context.Context) ([]storage.Comment, error)
|
||||||
|
GetCommentsOfPost(ctx context.Context, id int) ([]storage.Comment, error)
|
||||||
|
DeleteComment(ctx context.Context, id int) error
|
||||||
|
UpdateComment(ctx context.Context, id int, comment *storage.Comment) error
|
||||||
|
}
|
||||||
|
|||||||
@@ -70,9 +70,10 @@ func (p *postRepository) GetByID(ctx context.Context, id int) (storage.PostReq,
|
|||||||
|
|
||||||
func (p *postRepository) Create(ctx context.Context, post storage.Post) error {
|
func (p *postRepository) Create(ctx context.Context, post storage.Post) error {
|
||||||
query, err := p.db.Exec(
|
query, err := p.db.Exec(
|
||||||
"INSERT INTO posts(title, content) VALUES(?, ?)",
|
"INSERT INTO posts(title, content) VALUES(?, ?, ?)",
|
||||||
post.Title,
|
post.Title,
|
||||||
post.Content,
|
post.Content,
|
||||||
|
post.Tags,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -94,7 +95,10 @@ func (p *postRepository) Update(ctx context.Context, id int, post storage.Post)
|
|||||||
updates = append(updates, "title = ?")
|
updates = append(updates, "title = ?")
|
||||||
args = append(args, post.Title)
|
args = append(args, post.Title)
|
||||||
}
|
}
|
||||||
|
if post.Tags != "" {
|
||||||
|
updates = append(updates, "tags = ?")
|
||||||
|
args = append(args, post.Tags)
|
||||||
|
}
|
||||||
if post.Content != "" {
|
if post.Content != "" {
|
||||||
updates = append(updates, "content = ?")
|
updates = append(updates, "content = ?")
|
||||||
args = append(args, post.Content)
|
args = append(args, post.Content)
|
||||||
@@ -145,7 +149,7 @@ func (p *postRepository) IsExist(ctx context.Context, id int) bool {
|
|||||||
|
|
||||||
func (p *postRepository) GetAllAdmin(ctx context.Context) ([]storage.PostReq, error) {
|
func (p *postRepository) GetAllAdmin(ctx context.Context) ([]storage.PostReq, error) {
|
||||||
result := []storage.PostReq{}
|
result := []storage.PostReq{}
|
||||||
rows, err := p.db.Query("SELECT id, title, content FROM posts")
|
rows, err := p.db.Query("SELECT id, title, content, tags, CREATED_AT FROM posts")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.logger.Error(err.Error())
|
p.logger.Error(err.Error())
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -154,14 +158,18 @@ func (p *postRepository) GetAllAdmin(ctx context.Context) ([]storage.PostReq, er
|
|||||||
var title string
|
var title string
|
||||||
var content string
|
var content string
|
||||||
var id int
|
var id int
|
||||||
err := rows.Scan(&id, &title, &content)
|
var tags string
|
||||||
|
var createdAt string
|
||||||
|
err := rows.Scan(&id, &title, &content, &tags, &createdAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.logger.Error("error scan: " + err.Error())
|
p.logger.Error("error scan: " + err.Error())
|
||||||
}
|
}
|
||||||
result = append(result, storage.PostReq{
|
result = append(result, storage.PostReq{
|
||||||
ID: id,
|
ID: id,
|
||||||
Title: title,
|
Title: title,
|
||||||
Content: content,
|
Content: content,
|
||||||
|
Tags: tags,
|
||||||
|
CreatedAt: createdAt,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ CREATE TABLE IF NOT EXISTS posts(
|
|||||||
published BOOLEAN DEFAULT 0,
|
published BOOLEAN DEFAULT 0,
|
||||||
content TEXT NOT NULL,
|
content TEXT NOT NULL,
|
||||||
CREATED_AT DATETIME DEFAULT CURRENT_TIMESTAMP,
|
CREATED_AT DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at DATETIME
|
updated_at DATETIME,
|
||||||
|
tags TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS users(
|
CREATE TABLE IF NOT EXISTS users(
|
||||||
@@ -17,4 +18,14 @@ CREATE TABLE IF NOT EXISTS users(
|
|||||||
github_login TEXT,
|
github_login TEXT,
|
||||||
avatar_url TEXT
|
avatar_url TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS comments(
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
post_id INTEGER NOT NULL,
|
||||||
|
user_id INTEGER,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (post_id) REFERENCES posts(id),
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
|
);
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package storage
|
package storage
|
||||||
|
|
||||||
|
// Post
|
||||||
type Post struct {
|
type Post struct {
|
||||||
ID int `db:"id" json:"id"`
|
ID int `db:"id" json:"id"`
|
||||||
Title string `db:"title" json:"title"`
|
Title string `db:"title" json:"title"`
|
||||||
Published bool `db:"published" json:"published"`
|
Published bool `db:"published" json:"published"`
|
||||||
Content string `db:"content" json:"content"`
|
Content string `db:"content" json:"content"`
|
||||||
CreatedAt string `db:"created_at" json:"created_at"`
|
CreatedAt string `db:"created_at" json:"created_at"`
|
||||||
|
Tags string `db:"tags" json:"tags"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PostReq struct {
|
type PostReq struct {
|
||||||
@@ -13,14 +15,17 @@ type PostReq struct {
|
|||||||
Title string `db:"title" json:"title"`
|
Title string `db:"title" json:"title"`
|
||||||
Content string `db:"content" json:"content"`
|
Content string `db:"content" json:"content"`
|
||||||
CreatedAt string `db:"created" json:"created_at"`
|
CreatedAt string `db:"created" json:"created_at"`
|
||||||
|
Tags string `db:"tags" json:"tags"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PostCreate struct {
|
type PostCreate struct {
|
||||||
Title string `db:"title" json:"title"`
|
Title string `db:"title" json:"title"`
|
||||||
Published bool `db:"published" json:"published"`
|
Published bool `db:"published" json:"published"`
|
||||||
Content string `db:"content" json:"content"`
|
Content string `db:"content" json:"content"`
|
||||||
|
Tags string `db:"tags" json:"tags"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// User
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int `db:"id" json:"id"`
|
ID int `db:"id" json:"id"`
|
||||||
Email string `db:"email" json:"email"`
|
Email string `db:"email" json:"email"`
|
||||||
@@ -35,3 +40,26 @@ type UserReg struct {
|
|||||||
GithubLogin string `json:"login"`
|
GithubLogin string `json:"login"`
|
||||||
AvatarURL string `json:"avatar_url"`
|
AvatarURL string `json:"avatar_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Comment
|
||||||
|
type Comment struct {
|
||||||
|
ID int `db:"id" json:"id"`
|
||||||
|
PostID int `db:"post_id" json:"post_id"`
|
||||||
|
UserID int `db:"user_id" json:"user_id"`
|
||||||
|
Content string `db:"content" json:"content"`
|
||||||
|
CreatedAt string `db:"created_at" json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommentReq struct {
|
||||||
|
ID int `db:"id" json:"id"`
|
||||||
|
PostID int `db:"post_id" json:"post_id"`
|
||||||
|
UserID int `db:"user_id" json:"user_id"`
|
||||||
|
Content string `db:"content" json:"content"`
|
||||||
|
CreatedAt string `db:"created_at" json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommentCreate struct {
|
||||||
|
PostID int `db:"post_id" json:"post_id"`
|
||||||
|
UserID int `db:"user_id" json:"user_id"`
|
||||||
|
Content string `db:"content" json:"content"`
|
||||||
|
}
|
||||||
|
|||||||
4
frontend/.dockerignore
Normal file
4
frontend/.dockerignore
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Makefile
|
||||||
|
dist
|
||||||
|
node_modules
|
||||||
|
|
||||||
@@ -1,7 +1,16 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@plugin "daisyui";
|
@plugin "daisyui";
|
||||||
|
|
||||||
#root {
|
#root {
|
||||||
max-width: 1280px;
|
max-width: 1280px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
|
color: oklch(var(--bc));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: oklch(var(--bc));
|
||||||
|
background-color: oklch(var(--b1));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,9 +52,7 @@ function BlogPost() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const copyCode = useCallback((code: string) => {
|
const copyCode = useCallback((code: string) => {
|
||||||
navigator.clipboard.writeText(code).then(() => {
|
navigator.clipboard.writeText(code);
|
||||||
// Можно добавить toast позже
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
@@ -118,14 +116,15 @@ function BlogPost() {
|
|||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={[remarkGfm]}
|
remarkPlugins={[remarkGfm]}
|
||||||
components={{
|
components={{
|
||||||
code({ node, inline, className, children, ...props }) {
|
code(props) {
|
||||||
|
const { children, className, ...rest } = props;
|
||||||
const match = /language-(\w+)/.exec(className || "");
|
const match = /language-(\w+)/.exec(className || "");
|
||||||
const language = match ? match[1] : "text";
|
const language = match ? match[1] : "text";
|
||||||
|
|
||||||
if (inline) {
|
if (!match) {
|
||||||
return (
|
return (
|
||||||
<code className="inline-code">
|
<code {...rest} className="inline-code">
|
||||||
{String(children).replace(/\n$/, "")}
|
{children}
|
||||||
</code>
|
</code>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -142,21 +141,17 @@ function BlogPost() {
|
|||||||
<span>Copy</span>
|
<span>Copy</span>
|
||||||
</button>
|
</button>
|
||||||
<SyntaxHighlighter
|
<SyntaxHighlighter
|
||||||
{...props}
|
style={theme}
|
||||||
style={{
|
|
||||||
...theme,
|
|
||||||
'pre[class*="language-"]': {
|
|
||||||
...theme['pre[class*="language-"]'],
|
|
||||||
background: "rgb(15, 15, 25) !important",
|
|
||||||
marginBottom: "0 !important",
|
|
||||||
borderRadius: "0 0 12px 12px !important",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
language={language}
|
language={language}
|
||||||
PreTag="div"
|
PreTag="div"
|
||||||
showLineNumbers
|
customStyle={{
|
||||||
wrapLines
|
background: "rgb(12, 12, 22)",
|
||||||
className="code-highlighter"
|
margin: 0,
|
||||||
|
padding: "1.75rem 1.5rem 1.5rem",
|
||||||
|
borderRadius: "0 0 12px 12px",
|
||||||
|
fontSize: "0.875rem",
|
||||||
|
lineHeight: "1.65",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{String(children).replace(/\n$/, "")}
|
{String(children).replace(/\n$/, "")}
|
||||||
</SyntaxHighlighter>
|
</SyntaxHighlighter>
|
||||||
@@ -198,6 +193,10 @@ function BlogPost() {
|
|||||||
padding-left: 1.5em;
|
padding-left: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blog-content li {
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Inline код */
|
/* Inline код */
|
||||||
.inline-code {
|
.inline-code {
|
||||||
color: #ffffff !important;
|
color: #ffffff !important;
|
||||||
@@ -205,28 +204,28 @@ function BlogPost() {
|
|||||||
padding: 0.2em 0.4em !important;
|
padding: 0.2em 0.4em !important;
|
||||||
border-radius: 6px !important;
|
border-radius: 6px !important;
|
||||||
font-size: 0.875em !important;
|
font-size: 0.875em !important;
|
||||||
|
font-family: 'Commit_Mono', monospace !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 🔥 КОНТЕЙНЕР КОДА */
|
/* Контейнер кода */
|
||||||
.code-container {
|
.code-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 2em 0;
|
margin: 2em 0;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: linear-gradient(145deg, rgb(10,10,20) 0%, rgb(20,20,35) 100%);
|
background: linear-gradient(145deg, rgb(8,8,18) 0%, rgb(18,18,32) 100%);
|
||||||
border: 1px solid rgba(255,255,255,0.08);
|
border: 1px solid rgba(255,255,255,0.06);
|
||||||
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
|
box-shadow: 0 12px 40px rgba(0,0,0,0.5);
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ✨ АНИМИРОВАННАЯ КНОПКА COPY */
|
/* Анимированная кнопка */
|
||||||
.copy-button {
|
.copy-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 14px;
|
top: 14px;
|
||||||
right: 14px;
|
right: 14px;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
background: linear-gradient(135deg, rgba(117,43,255,0.2) 0%, rgba(117,43,255,0.1) 100%);
|
background: linear-gradient(135deg, rgba(117,43,255,0.25) 0%, rgba(117,43,255,0.15) 100%);
|
||||||
border: 1px solid rgba(117,43,255,0.4);
|
border: 1px solid rgba(117,43,255,0.5);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -236,53 +235,30 @@ function BlogPost() {
|
|||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
font-family: 'Commit_Mono', monospace;
|
font-family: 'Commit_Mono', monospace;
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
box-shadow: 0 4px 14px rgba(117,43,255,0.2);
|
box-shadow: 0 4px 16px rgba(117,43,255,0.25);
|
||||||
transform: translateY(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy-button:hover {
|
.copy-button:hover {
|
||||||
background: linear-gradient(135deg, hsl(270,73%,63%) 0%, hsl(270,73%,55%) 100%);
|
background: linear-gradient(135deg, hsl(270,73%,65%) 0%, hsl(270,73%,55%) 100%);
|
||||||
transform: translateY(-2px) scale(1.02);
|
transform: translateY(-3px) scale(1.03);
|
||||||
box-shadow: 0 8px 25px rgba(117,43,255,0.4);
|
box-shadow: 0 12px 30px rgba(117,43,255,0.45);
|
||||||
border-color: hsl(270,73%,70%);
|
border-color: hsl(270,73%,75%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy-button:active {
|
.copy-button:active {
|
||||||
transform: translateY(0) scale(0.98);
|
transform: translateY(-1px) scale(0.98);
|
||||||
transition: all 0.1s;
|
transition: all 0.1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy-button span {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* SyntaxHighlighter */
|
|
||||||
.code-highlighter {
|
|
||||||
margin: 0 !important;
|
|
||||||
border-radius: 0 0 12px 12px !important;
|
|
||||||
font-family: 'Commit_Mono', monospace !important;
|
|
||||||
font-size: 0.875rem !important;
|
|
||||||
line-height: 1.65 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-highlighter pre[class*="language-"] {
|
|
||||||
margin: 0 !important;
|
|
||||||
padding: 1.75rem 1.5rem 1.5rem !important;
|
|
||||||
background: transparent !important;
|
|
||||||
border-radius: 0 0 12px 12px !important;
|
|
||||||
overflow-x: auto !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Номера строк */
|
|
||||||
.code-highlighter .token-line {
|
|
||||||
padding-left: 1.25rem !important;
|
|
||||||
counter-increment: line;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Заголовки */
|
/* Заголовки */
|
||||||
.blog-content h1, .blog-content h2, .blog-content h3 {
|
.blog-content h1, .blog-content h2, .blog-content h3 {
|
||||||
color: hsl(270, 73%, 63%) !important;
|
color: hsl(270, 73%, 63%) !important;
|
||||||
|
margin-top: 2em;
|
||||||
|
margin-bottom: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-content a {
|
||||||
|
color: hsl(270, 73%, 63%) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive */
|
/* Responsive */
|
||||||
@@ -292,7 +268,6 @@ function BlogPost() {
|
|||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border-left: none;
|
border-left: none;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
box-shadow: none;
|
|
||||||
}
|
}
|
||||||
.copy-button {
|
.copy-button {
|
||||||
top: 10px;
|
top: 10px;
|
||||||
|
|||||||
Reference in New Issue
Block a user