поиск и еще мелочи
This commit is contained in:
629
index.c
629
index.c
@@ -1,4 +1,5 @@
|
|||||||
// /home/romkazvo/www/cgi-bin/index.c
|
// /home/romkazvo/www/cgi-bin/index.c
|
||||||
|
#define _GNU_SOURCE
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -10,6 +11,13 @@
|
|||||||
#include <locale.h>
|
#include <locale.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
|
// === ПРОТОТИПЫ ФУНКЦИЙ ===
|
||||||
|
void url_encode(const char *src, char *dst, size_t dst_size);
|
||||||
|
void url_decode_enhanced(const char *src, char *dst, size_t dst_size);
|
||||||
|
void format_size(long long size, char* buffer);
|
||||||
|
void format_date(time_t mtime, char* buffer, size_t size);
|
||||||
|
void html_escape(const char *src, char *dst, size_t dst_size);
|
||||||
|
|
||||||
// === КОНФИГУРАЦИЯ ===
|
// === КОНФИГУРАЦИЯ ===
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -30,6 +38,51 @@ typedef struct {
|
|||||||
|
|
||||||
config_t g_config;
|
config_t g_config;
|
||||||
|
|
||||||
|
// === ПАРАМЕТРЫ ЗАПРОСА (сортировка, поиск) ===
|
||||||
|
typedef struct {
|
||||||
|
char sort_by[16]; // "name", "size", "date"
|
||||||
|
int sort_order; // 1 = asc, -1 = desc
|
||||||
|
char search[256];
|
||||||
|
int recursive; // 1 = рекурсивный поиск, 0 = только текущая папка
|
||||||
|
} query_params_t;
|
||||||
|
|
||||||
|
query_params_t g_params;
|
||||||
|
|
||||||
|
// === ПОИСК БЕЗ УЧЁТА РЕГИСТРА (поддерживает UTF-8) ===
|
||||||
|
char* my_strcasestr(const char *haystack, const char *needle) {
|
||||||
|
if (!haystack || !needle || *needle == '\0') return (char*)haystack;
|
||||||
|
|
||||||
|
const char *h = haystack;
|
||||||
|
const char *n = needle;
|
||||||
|
size_t needle_len = strlen(needle);
|
||||||
|
|
||||||
|
while (*h) {
|
||||||
|
if (strncasecmp(h, n, needle_len) == 0) {
|
||||||
|
return (char*)h;
|
||||||
|
}
|
||||||
|
h++;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === HTML ЭКРАНИРОВАНИЕ (защита от XSS) ===
|
||||||
|
void html_escape(const char *src, char *dst, size_t dst_size) {
|
||||||
|
if (!src || !dst || dst_size == 0) return;
|
||||||
|
char *p = dst;
|
||||||
|
size_t i = 0;
|
||||||
|
while (*src && i < dst_size - 6) {
|
||||||
|
switch (*src) {
|
||||||
|
case '<': strcpy(p, "<"); p += 4; i += 4; break;
|
||||||
|
case '>': strcpy(p, ">"); p += 4; i += 4; break;
|
||||||
|
case '"': strcpy(p, """); p += 6; i += 6; break;
|
||||||
|
case '&': strcpy(p, "&"); p += 5; i += 5; break;
|
||||||
|
default: *p++ = *src; i++; break;
|
||||||
|
}
|
||||||
|
src++;
|
||||||
|
}
|
||||||
|
*p = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
// Простой парсер INI (без зависимостей)
|
// Простой парсер INI (без зависимостей)
|
||||||
int load_config(const char *path, config_t *cfg) {
|
int load_config(const char *path, config_t *cfg) {
|
||||||
FILE *f = fopen(path, "r");
|
FILE *f = fopen(path, "r");
|
||||||
@@ -147,7 +200,7 @@ int is_string_in_list(const char *str, const char *list) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// === ОСТАЛЬНОЙ КОД (без изменений, только теперь используем g_config вместо #define) ===
|
// === ОСТАЛЬНОЙ КОД ===
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char name[256];
|
char name[256];
|
||||||
@@ -219,7 +272,10 @@ int check_folder_password(const char *folder_name, const char *password) {
|
|||||||
*colon = '\0';
|
*colon = '\0';
|
||||||
char *folder = line;
|
char *folder = line;
|
||||||
char *pass = colon + 1;
|
char *pass = colon + 1;
|
||||||
if (strcmp(folder, folder_name) == 0) {
|
// Проверяем как полный путь, так и имя папки
|
||||||
|
const char *last_slash = strrchr(folder_name, '/');
|
||||||
|
const char *base_name = last_slash ? last_slash + 1 : folder_name;
|
||||||
|
if (strcmp(folder, folder_name) == 0 || strcmp(folder, base_name) == 0) {
|
||||||
fclose(f);
|
fclose(f);
|
||||||
return strcmp(pass, password) == 0;
|
return strcmp(pass, password) == 0;
|
||||||
}
|
}
|
||||||
@@ -228,8 +284,8 @@ int check_folder_password(const char *folder_name, const char *password) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int is_folder_protected(const char *folder_name) {
|
int is_folder_protected(const char *folder_path) {
|
||||||
if (!folder_name || folder_name[0] == '\0') return 0;
|
if (!folder_path || folder_path[0] == '\0') return 0;
|
||||||
|
|
||||||
FILE *f = fopen(g_config.passwd_file, "r");
|
FILE *f = fopen(g_config.passwd_file, "r");
|
||||||
if (!f) return 0;
|
if (!f) return 0;
|
||||||
@@ -240,7 +296,11 @@ int is_folder_protected(const char *folder_name) {
|
|||||||
char *colon = strchr(line, ':');
|
char *colon = strchr(line, ':');
|
||||||
if (!colon) continue;
|
if (!colon) continue;
|
||||||
*colon = '\0';
|
*colon = '\0';
|
||||||
if (strcmp(line, folder_name) == 0) {
|
// Получаем имя папки (последний сегмент пути)
|
||||||
|
const char *last_slash = strrchr(folder_path, '/');
|
||||||
|
const char *folder_name = last_slash ? last_slash + 1 : folder_path;
|
||||||
|
// Проверяем как полный путь, так и имя папки
|
||||||
|
if (strcmp(line, folder_path) == 0 || strcmp(line, folder_name) == 0) {
|
||||||
fclose(f);
|
fclose(f);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -316,12 +376,105 @@ int is_previewable(const char* filename) {
|
|||||||
is_string_in_list(ext, g_config.preview_pdf));
|
is_string_in_list(ext, g_config.preview_pdf));
|
||||||
}
|
}
|
||||||
|
|
||||||
int compare_entries(const void *a, const void *b) {
|
// === РЕКУРСИВНЫЙ ПОИСК (игнорирует защищённые папки) ===
|
||||||
|
void search_recursive(const char *base_path, const char *display_path, const char *search_term, entry_t *results, int *count, int max_results) {
|
||||||
|
DIR *dir = opendir(base_path);
|
||||||
|
if (!dir) return;
|
||||||
|
|
||||||
|
struct dirent *entry;
|
||||||
|
struct stat file_stat;
|
||||||
|
char full_path[1024];
|
||||||
|
char display_subpath[1024];
|
||||||
|
|
||||||
|
while ((entry = readdir(dir)) != NULL && *count < max_results) {
|
||||||
|
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
|
||||||
|
if (entry->d_name[0] == '.') continue;
|
||||||
|
|
||||||
|
// Проверяем скрытые папки/файлы из конфига
|
||||||
|
int hidden = 0;
|
||||||
|
for (int i = 0; i < g_config.hidden_dirs_count; i++) {
|
||||||
|
if (strcmp(entry->d_name, g_config.hidden_dirs[i]) == 0) { hidden = 1; break; }
|
||||||
|
}
|
||||||
|
for (int i = 0; i < g_config.hidden_files_count; i++) {
|
||||||
|
if (strcmp(entry->d_name, g_config.hidden_files[i]) == 0) { hidden = 1; break; }
|
||||||
|
}
|
||||||
|
if (hidden) continue;
|
||||||
|
|
||||||
|
snprintf(full_path, sizeof(full_path), "%s/%s", base_path, entry->d_name);
|
||||||
|
if (display_path[0] == '\0') {
|
||||||
|
snprintf(display_subpath, sizeof(display_subpath), "%s", entry->d_name);
|
||||||
|
} else {
|
||||||
|
snprintf(display_subpath, sizeof(display_subpath), "%s/%s", display_path, entry->d_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если папка защищена — не заходим и не показываем
|
||||||
|
if (is_folder_protected(display_subpath) || is_folder_protected(entry->d_name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stat(full_path, &file_stat) == 0) {
|
||||||
|
if (my_strcasestr(entry->d_name, search_term) != NULL) {
|
||||||
|
entry_t *result = &results[*count];
|
||||||
|
strncpy(result->name, entry->d_name, sizeof(result->name) - 1);
|
||||||
|
result->name[sizeof(result->name) - 1] = '\0';
|
||||||
|
|
||||||
|
if (S_ISDIR(file_stat.st_mode)) {
|
||||||
|
result->is_dir = 1;
|
||||||
|
result->size = 0;
|
||||||
|
result->dir_size = get_dir_size(full_path);
|
||||||
|
result->file_count = count_files_in_dir(full_path);
|
||||||
|
result->mtime = file_stat.st_mtime;
|
||||||
|
strcpy(result->icon, "📁");
|
||||||
|
result->is_protected = 0;
|
||||||
|
|
||||||
|
url_encode(display_subpath, result->encoded_path, sizeof(result->encoded_path));
|
||||||
|
strncpy(result->file_url, display_subpath, sizeof(result->file_url) - 1);
|
||||||
|
(*count)++;
|
||||||
|
} else if (S_ISREG(file_stat.st_mode)) {
|
||||||
|
result->is_dir = 0;
|
||||||
|
result->size = file_stat.st_size;
|
||||||
|
result->dir_size = 0;
|
||||||
|
result->file_count = 0;
|
||||||
|
result->mtime = file_stat.st_mtime;
|
||||||
|
strcpy(result->icon, get_file_icon(entry->d_name));
|
||||||
|
result->is_protected = 0;
|
||||||
|
|
||||||
|
url_encode(display_subpath, result->encoded_path, sizeof(result->encoded_path));
|
||||||
|
strncpy(result->file_url, display_subpath, sizeof(result->file_url) - 1);
|
||||||
|
(*count)++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (S_ISDIR(file_stat.st_mode)) {
|
||||||
|
search_recursive(full_path, display_subpath, search_term, results, count, max_results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// === СОРТИРОВКА ===
|
||||||
|
|
||||||
|
int compare_entries_sorted(const void *a, const void *b) {
|
||||||
const entry_t *entryA = (const entry_t *)a;
|
const entry_t *entryA = (const entry_t *)a;
|
||||||
const entry_t *entryB = (const entry_t *)b;
|
const entry_t *entryB = (const entry_t *)b;
|
||||||
|
|
||||||
|
// Папки всегда сверху
|
||||||
if (entryA->is_dir && !entryB->is_dir) return -1;
|
if (entryA->is_dir && !entryB->is_dir) return -1;
|
||||||
if (!entryA->is_dir && entryB->is_dir) return 1;
|
if (!entryA->is_dir && entryB->is_dir) return 1;
|
||||||
return strcasecmp(entryA->name, entryB->name);
|
|
||||||
|
int result = 0;
|
||||||
|
if (strcmp(g_params.sort_by, "size") == 0) {
|
||||||
|
long long sizeA = entryA->is_dir ? entryA->dir_size : entryA->size;
|
||||||
|
long long sizeB = entryB->is_dir ? entryB->dir_size : entryB->size;
|
||||||
|
result = (sizeA > sizeB) - (sizeA < sizeB);
|
||||||
|
} else if (strcmp(g_params.sort_by, "date") == 0) {
|
||||||
|
result = (entryA->mtime > entryB->mtime) - (entryA->mtime < entryB->mtime);
|
||||||
|
} else {
|
||||||
|
result = strcasecmp(entryA->name, entryB->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result * g_params.sort_order;
|
||||||
}
|
}
|
||||||
|
|
||||||
void format_size(long long size, char* buffer) {
|
void format_size(long long size, char* buffer) {
|
||||||
@@ -360,18 +513,29 @@ void url_decode_enhanced(const char *src, char *dst, size_t dst_size) {
|
|||||||
size_t decoded_len = 0;
|
size_t decoded_len = 0;
|
||||||
while (*src && decoded_len < dst_size - 1) {
|
while (*src && decoded_len < dst_size - 1) {
|
||||||
if (*src == '%') {
|
if (*src == '%') {
|
||||||
|
// Проверяем, что есть ещё минимум 2 символа
|
||||||
if (src[1] && src[2] && isxdigit(src[1]) && isxdigit(src[2])) {
|
if (src[1] && src[2] && isxdigit(src[1]) && isxdigit(src[2])) {
|
||||||
char hex[3] = {src[1], src[2], '\0'};
|
char hex[3] = {src[1], src[2], '\0'};
|
||||||
unsigned char c = (unsigned char)strtol(hex, NULL, 16);
|
unsigned char c = (unsigned char)strtol(hex, NULL, 16);
|
||||||
if (c >= 0x80) { *p++ = c; decoded_len++; }
|
if (c >= 0x80 || c >= 0x20 || c == 0x0A || c == 0x0D) {
|
||||||
else if (c >= 0x20 || c == 0x0A || c == 0x0D) { *p++ = c; decoded_len++; }
|
*p++ = c;
|
||||||
else { *p++ = '_'; decoded_len++; }
|
decoded_len++;
|
||||||
|
} else {
|
||||||
|
*p++ = '_';
|
||||||
|
decoded_len++;
|
||||||
|
}
|
||||||
src += 3;
|
src += 3;
|
||||||
} else { *p++ = *src++; decoded_len++; }
|
} else {
|
||||||
|
// Невалидный % — просто пропускаем
|
||||||
|
src++;
|
||||||
|
}
|
||||||
} else if (*src == '+') {
|
} else if (*src == '+') {
|
||||||
*p++ = ' '; decoded_len++; src++;
|
*p++ = ' ';
|
||||||
|
decoded_len++;
|
||||||
|
src++;
|
||||||
} else {
|
} else {
|
||||||
*p++ = *src++; decoded_len++;
|
*p++ = *src++;
|
||||||
|
decoded_len++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*p = '\0';
|
*p = '\0';
|
||||||
@@ -391,7 +555,9 @@ void print_breadcrumb(buffer_t *buf, const char *display_path) {
|
|||||||
strcat(temp_path, token);
|
strcat(temp_path, token);
|
||||||
url_encode(temp_path, encoded, sizeof(encoded));
|
url_encode(temp_path, encoded, sizeof(encoded));
|
||||||
char link[4096];
|
char link[4096];
|
||||||
snprintf(link, sizeof(link), "<a href=\"/cgi-bin/index.cgi?path=%s\">%s</a>", encoded, token);
|
char safe_token[512];
|
||||||
|
html_escape(token, safe_token, sizeof(safe_token));
|
||||||
|
snprintf(link, sizeof(link), "<a href=\"/cgi-bin/index.cgi?path=%s\">%s</a>", encoded, safe_token);
|
||||||
buffer_append(buf, link);
|
buffer_append(buf, link);
|
||||||
token = strtok(NULL, "/");
|
token = strtok(NULL, "/");
|
||||||
}
|
}
|
||||||
@@ -406,7 +572,9 @@ void print_password_form(buffer_t *buf, const char *folder_name) {
|
|||||||
buffer_append(buf, " <div class=\"lock-icon\">🔒</div>\n");
|
buffer_append(buf, " <div class=\"lock-icon\">🔒</div>\n");
|
||||||
buffer_append(buf, " <h2>Папка защищена паролем</h2>\n");
|
buffer_append(buf, " <h2>Папка защищена паролем</h2>\n");
|
||||||
buffer_append(buf, " <p>Введите пароль для доступа к папке <strong>");
|
buffer_append(buf, " <p>Введите пароль для доступа к папке <strong>");
|
||||||
buffer_append(buf, folder_name);
|
char safe_name[512];
|
||||||
|
html_escape(folder_name, safe_name, sizeof(safe_name));
|
||||||
|
buffer_append(buf, safe_name);
|
||||||
buffer_append(buf, "</strong></p>\n");
|
buffer_append(buf, "</strong></p>\n");
|
||||||
buffer_append(buf, " <form method=\"GET\" action=\"/cgi-bin/index.cgi\">\n");
|
buffer_append(buf, " <form method=\"GET\" action=\"/cgi-bin/index.cgi\">\n");
|
||||||
buffer_append(buf, " <input type=\"hidden\" name=\"path\" value=\"");
|
buffer_append(buf, " <input type=\"hidden\" name=\"path\" value=\"");
|
||||||
@@ -443,6 +611,7 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
int entry_count = 0;
|
int entry_count = 0;
|
||||||
int dir_count = 0, file_count = 0;
|
int dir_count = 0, file_count = 0;
|
||||||
long long total_size = 0;
|
long long total_size = 0;
|
||||||
|
int is_recursive_search = 0;
|
||||||
|
|
||||||
char folder_name[256] = "";
|
char folder_name[256] = "";
|
||||||
if (display_path && display_path[0]) {
|
if (display_path && display_path[0]) {
|
||||||
@@ -478,105 +647,202 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dir = opendir(base_path);
|
// === ЕСЛИ ЕСТЬ ПОИСК — ИСПОЛЬЗУЕМ РЕКУРСИВНЫЙ ОБХОД ===
|
||||||
if (!dir) {
|
if (g_params.search[0] != '\0') {
|
||||||
char alt_path[1024];
|
int result_count = 0;
|
||||||
snprintf(alt_path, sizeof(alt_path), "%s", g_config.base_path);
|
search_recursive(g_config.base_path, "", g_params.search, entries, &result_count, g_config.max_entries);
|
||||||
if (display_path[0]) {
|
|
||||||
char *encoded = strdup(display_path);
|
if (result_count == 0) {
|
||||||
url_decode_enhanced(encoded, alt_path + strlen(alt_path), sizeof(alt_path) - strlen(alt_path));
|
|
||||||
free(encoded);
|
|
||||||
}
|
|
||||||
dir = opendir(alt_path);
|
|
||||||
if (!dir) {
|
|
||||||
buffer_append(buf, "<div class=\"empty-state\">\n");
|
buffer_append(buf, "<div class=\"empty-state\">\n");
|
||||||
buffer_append(buf, " <div class=\"icon\">📁</div>\n");
|
buffer_append(buf, " <div class=\"icon\">🔍</div>\n");
|
||||||
buffer_append(buf, " <h3>Ошибка открытия директории</h3>\n");
|
buffer_append(buf, " <h3>Ничего не найдено</h3>\n");
|
||||||
char error_msg[512];
|
buffer_append(buf, " <p>По запросу \"");
|
||||||
snprintf(error_msg, sizeof(error_msg), " <p>Путь: %s</p>\n", base_path);
|
char safe_search[512];
|
||||||
buffer_append(buf, error_msg);
|
html_escape(g_params.search, safe_search, sizeof(safe_search));
|
||||||
snprintf(error_msg, sizeof(error_msg), " <p>Ошибка: %s</p>\n", strerror(errno));
|
buffer_append(buf, safe_search);
|
||||||
buffer_append(buf, error_msg);
|
buffer_append(buf, "\" ничего не найдено</p>\n");
|
||||||
buffer_append(buf, " <p><a href=\"/cgi-bin/index.cgi\">← Вернуться на главную</a></p>\n");
|
buffer_append(buf, "</div>\n");
|
||||||
|
free(entries);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry_count = result_count;
|
||||||
|
is_recursive_search = 1;
|
||||||
|
|
||||||
|
for (int i = 0; i < entry_count; i++) {
|
||||||
|
if (entries[i].is_dir) {
|
||||||
|
dir_count++;
|
||||||
|
total_size += entries[i].dir_size;
|
||||||
|
} else {
|
||||||
|
file_count++;
|
||||||
|
total_size += entries[i].size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// === ОБЫЧНЫЙ ОБХОД ТЕКУЩЕЙ ПАПКИ ===
|
||||||
|
dir = opendir(base_path);
|
||||||
|
if (!dir) {
|
||||||
|
char alt_path[1024];
|
||||||
|
snprintf(alt_path, sizeof(alt_path), "%s", g_config.base_path);
|
||||||
|
if (display_path[0]) {
|
||||||
|
char *encoded = strdup(display_path);
|
||||||
|
url_decode_enhanced(encoded, alt_path + strlen(alt_path), sizeof(alt_path) - strlen(alt_path));
|
||||||
|
free(encoded);
|
||||||
|
}
|
||||||
|
dir = opendir(alt_path);
|
||||||
|
if (!dir) {
|
||||||
|
buffer_append(buf, "<div class=\"empty-state\">\n");
|
||||||
|
buffer_append(buf, " <div class=\"icon\">📁</div>\n");
|
||||||
|
buffer_append(buf, " <h3>Ошибка открытия директории</h3>\n");
|
||||||
|
char error_msg[512];
|
||||||
|
snprintf(error_msg, sizeof(error_msg), " <p>Путь: %s</p>\n", base_path);
|
||||||
|
buffer_append(buf, error_msg);
|
||||||
|
snprintf(error_msg, sizeof(error_msg), " <p>Ошибка: %s</p>\n", strerror(errno));
|
||||||
|
buffer_append(buf, error_msg);
|
||||||
|
buffer_append(buf, " <p><a href=\"/cgi-bin/index.cgi\">← Вернуться на главную</a></p>\n");
|
||||||
|
buffer_append(buf, "</div>\n");
|
||||||
|
free(entries);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while ((entry = readdir(dir)) != NULL && entry_count < g_config.max_entries) {
|
||||||
|
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
|
||||||
|
|
||||||
|
int hidden = 0;
|
||||||
|
for (int i = 0; i < g_config.hidden_dirs_count; i++) {
|
||||||
|
if (strcmp(entry->d_name, g_config.hidden_dirs[i]) == 0) { hidden = 1; break; }
|
||||||
|
}
|
||||||
|
for (int i = 0; i < g_config.hidden_files_count; i++) {
|
||||||
|
if (strcmp(entry->d_name, g_config.hidden_files[i]) == 0) { hidden = 1; break; }
|
||||||
|
}
|
||||||
|
if (entry->d_name[0] == '.') hidden = 1;
|
||||||
|
if (hidden) continue;
|
||||||
|
|
||||||
|
snprintf(full_path, sizeof(full_path), "%s/%s", base_path, entry->d_name);
|
||||||
|
if (stat(full_path, &file_stat) == 0) {
|
||||||
|
strncpy(entries[entry_count].name, entry->d_name, sizeof(entries[0].name) - 1);
|
||||||
|
entries[entry_count].name[sizeof(entries[0].name) - 1] = '\0';
|
||||||
|
|
||||||
|
if (S_ISDIR(file_stat.st_mode)) {
|
||||||
|
entries[entry_count].is_dir = 1;
|
||||||
|
entries[entry_count].size = 0;
|
||||||
|
entries[entry_count].mtime = file_stat.st_mtime;
|
||||||
|
entries[entry_count].file_count = count_files_in_dir(full_path);
|
||||||
|
entries[entry_count].dir_size = get_dir_size(full_path);
|
||||||
|
total_size += entries[entry_count].dir_size;
|
||||||
|
|
||||||
|
if (is_folder_protected(entry->d_name)) {
|
||||||
|
strcpy(entries[entry_count].icon, "🔒");
|
||||||
|
entries[entry_count].is_protected = 1;
|
||||||
|
} else {
|
||||||
|
strcpy(entries[entry_count].icon, "📁");
|
||||||
|
entries[entry_count].is_protected = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char new_path[2048];
|
||||||
|
snprintf(new_path, sizeof(new_path), "%s%s%s", display_path, display_path[0] ? "/" : "", entries[entry_count].name);
|
||||||
|
url_encode(new_path, entries[entry_count].encoded_path, sizeof(entries[0].encoded_path));
|
||||||
|
dir_count++;
|
||||||
|
} else if (S_ISREG(file_stat.st_mode)) {
|
||||||
|
entries[entry_count].is_dir = 0;
|
||||||
|
entries[entry_count].size = file_stat.st_size;
|
||||||
|
entries[entry_count].mtime = file_stat.st_mtime;
|
||||||
|
entries[entry_count].file_count = 0;
|
||||||
|
entries[entry_count].dir_size = 0;
|
||||||
|
strcpy(entries[entry_count].icon, get_file_icon(entry->d_name));
|
||||||
|
entries[entry_count].is_protected = 0;
|
||||||
|
|
||||||
|
char encoded_filename[1024];
|
||||||
|
url_encode(entry->d_name, encoded_filename, sizeof(encoded_filename));
|
||||||
|
snprintf(entries[entry_count].file_url, sizeof(entries[0].file_url), "%s%s%s", display_path, display_path[0] ? "/" : "", encoded_filename);
|
||||||
|
|
||||||
|
total_size += file_stat.st_size;
|
||||||
|
file_count++;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
entry_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
|
||||||
|
if (entry_count == 0) {
|
||||||
|
buffer_append(buf, "<div class=\"empty-state\">\n");
|
||||||
|
buffer_append(buf, " <div class=\"icon\">📄</div>\n");
|
||||||
|
buffer_append(buf, " <h3>Здесь пусто</h3>\n");
|
||||||
|
buffer_append(buf, " <p>В этой директории нет файлов или папок</p>\n");
|
||||||
buffer_append(buf, "</div>\n");
|
buffer_append(buf, "</div>\n");
|
||||||
free(entries);
|
free(entries);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while ((entry = readdir(dir)) != NULL && entry_count < g_config.max_entries) {
|
qsort(entries, entry_count, sizeof(entry_t), compare_entries_sorted);
|
||||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
|
|
||||||
|
|
||||||
int hidden = 0;
|
|
||||||
for (int i = 0; i < g_config.hidden_dirs_count; i++) {
|
|
||||||
if (strcmp(entry->d_name, g_config.hidden_dirs[i]) == 0) { hidden = 1; break; }
|
|
||||||
}
|
|
||||||
for (int i = 0; i < g_config.hidden_files_count; i++) {
|
|
||||||
if (strcmp(entry->d_name, g_config.hidden_files[i]) == 0) { hidden = 1; break; }
|
|
||||||
}
|
|
||||||
if (entry->d_name[0] == '.') hidden = 1;
|
|
||||||
if (hidden) continue;
|
|
||||||
|
|
||||||
snprintf(full_path, sizeof(full_path), "%s/%s", base_path, entry->d_name);
|
|
||||||
if (stat(full_path, &file_stat) == 0) {
|
|
||||||
strncpy(entries[entry_count].name, entry->d_name, sizeof(entries[0].name) - 1);
|
|
||||||
entries[entry_count].name[sizeof(entries[0].name) - 1] = '\0';
|
|
||||||
|
|
||||||
if (S_ISDIR(file_stat.st_mode)) {
|
|
||||||
entries[entry_count].is_dir = 1;
|
|
||||||
entries[entry_count].size = 0;
|
|
||||||
entries[entry_count].mtime = file_stat.st_mtime;
|
|
||||||
entries[entry_count].file_count = count_files_in_dir(full_path);
|
|
||||||
entries[entry_count].dir_size = get_dir_size(full_path);
|
|
||||||
total_size += entries[entry_count].dir_size;
|
|
||||||
|
|
||||||
if (is_folder_protected(entry->d_name)) {
|
|
||||||
strcpy(entries[entry_count].icon, "🔒");
|
|
||||||
entries[entry_count].is_protected = 1;
|
|
||||||
} else {
|
|
||||||
strcpy(entries[entry_count].icon, "📁");
|
|
||||||
entries[entry_count].is_protected = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
char new_path[2048];
|
|
||||||
snprintf(new_path, sizeof(new_path), "%s%s%s", display_path, display_path[0] ? "/" : "", entries[entry_count].name);
|
|
||||||
url_encode(new_path, entries[entry_count].encoded_path, sizeof(entries[0].encoded_path));
|
|
||||||
dir_count++;
|
|
||||||
} else if (S_ISREG(file_stat.st_mode)) {
|
|
||||||
entries[entry_count].is_dir = 0;
|
|
||||||
entries[entry_count].size = file_stat.st_size;
|
|
||||||
entries[entry_count].mtime = file_stat.st_mtime;
|
|
||||||
entries[entry_count].file_count = 0;
|
|
||||||
entries[entry_count].dir_size = 0;
|
|
||||||
strcpy(entries[entry_count].icon, get_file_icon(entry->d_name));
|
|
||||||
entries[entry_count].is_protected = 0;
|
|
||||||
snprintf(entries[entry_count].file_url, sizeof(entries[0].file_url), "%s%s%s", display_path, display_path[0] ? "/" : "", entries[entry_count].name);
|
|
||||||
total_size += file_stat.st_size;
|
|
||||||
file_count++;
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
entry_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closedir(dir);
|
|
||||||
|
|
||||||
if (entry_count == 0) {
|
// === ТУЛБАР (поиск + сортировка) ===
|
||||||
buffer_append(buf, "<div class=\"empty-state\">\n");
|
char encoded_path[1024];
|
||||||
buffer_append(buf, " <div class=\"icon\">📄</div>\n");
|
url_encode(display_path, encoded_path, sizeof(encoded_path));
|
||||||
buffer_append(buf, " <h3>Здесь пусто</h3>\n");
|
char safe_search[512];
|
||||||
buffer_append(buf, " <p>В этой директории нет файлов или папок</p>\n");
|
html_escape(g_params.search, safe_search, sizeof(safe_search));
|
||||||
buffer_append(buf, "</div>\n");
|
|
||||||
free(entries);
|
buffer_append(buf, "<div class=\"toolbar\">\n");
|
||||||
return;
|
buffer_append(buf, " <div class=\"search-box\">\n");
|
||||||
|
buffer_append(buf, " <form method=\"GET\" action=\"/cgi-bin/index.cgi\">\n");
|
||||||
|
buffer_append(buf, " <input type=\"hidden\" name=\"path\" value=\"");
|
||||||
|
buffer_append(buf, encoded_path);
|
||||||
|
buffer_append(buf, "\">\n");
|
||||||
|
buffer_append(buf, " <input type=\"text\" name=\"search\" placeholder=\"🔍 Поиск по всему сайту...\" value=\"");
|
||||||
|
buffer_append(buf, safe_search);
|
||||||
|
buffer_append(buf, "\">\n");
|
||||||
|
buffer_append(buf, " <button type=\"submit\">Найти</button>\n");
|
||||||
|
buffer_append(buf, " </form>\n");
|
||||||
|
buffer_append(buf, " </div>\n");
|
||||||
|
buffer_append(buf, " <div class=\"sort-buttons\">\n");
|
||||||
|
buffer_append(buf, " <span class=\"sort-label\">Сортировка:</span>\n");
|
||||||
|
buffer_append(buf, " <a href=\"/cgi-bin/index.cgi?path=");
|
||||||
|
buffer_append(buf, encoded_path);
|
||||||
|
buffer_append(buf, "&sort=name\" class=\"sort-btn");
|
||||||
|
if (strcmp(g_params.sort_by, "name") == 0) buffer_append(buf, " active");
|
||||||
|
buffer_append(buf, "\">📝 Имя</a>\n");
|
||||||
|
buffer_append(buf, " <a href=\"/cgi-bin/index.cgi?path=");
|
||||||
|
buffer_append(buf, encoded_path);
|
||||||
|
buffer_append(buf, "&sort=size\" class=\"sort-btn");
|
||||||
|
if (strcmp(g_params.sort_by, "size") == 0) buffer_append(buf, " active");
|
||||||
|
buffer_append(buf, "\">📊 Размер</a>\n");
|
||||||
|
buffer_append(buf, " <a href=\"/cgi-bin/index.cgi?path=");
|
||||||
|
buffer_append(buf, encoded_path);
|
||||||
|
buffer_append(buf, "&sort=date\" class=\"sort-btn");
|
||||||
|
if (strcmp(g_params.sort_by, "date") == 0) buffer_append(buf, " active");
|
||||||
|
buffer_append(buf, "\">📅 Дата</a>\n");
|
||||||
|
buffer_append(buf, " </div>\n");
|
||||||
|
buffer_append(buf, "</div>\n");
|
||||||
|
|
||||||
|
// === ВЫВОД РЕЗУЛЬТАТОВ ===
|
||||||
|
if (is_recursive_search) {
|
||||||
|
buffer_append(buf, "<div style=\"font-size:0.85rem;color:var(--text-muted);margin-bottom:0.5rem;padding:0.25rem 0.5rem;\">");
|
||||||
|
buffer_append(buf, "🔍 Результаты поиска по всему сайту: ");
|
||||||
|
char tmp[32];
|
||||||
|
snprintf(tmp, sizeof(tmp), "%d", entry_count);
|
||||||
|
buffer_append(buf, tmp);
|
||||||
|
buffer_append(buf, " файлов/папок найдено</div>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
qsort(entries, entry_count, sizeof(entry_t), compare_entries);
|
|
||||||
buffer_append(buf, "<ul>\n");
|
buffer_append(buf, "<ul>\n");
|
||||||
|
|
||||||
if (dir_count > 0) {
|
// Вывод папок
|
||||||
|
int has_dirs = 0;
|
||||||
|
for (int i = 0; i < entry_count; i++) {
|
||||||
|
if (entries[i].is_dir) { has_dirs = 1; break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_dirs) {
|
||||||
|
int display_dir_count = 0;
|
||||||
|
for (int i = 0; i < entry_count; i++) {
|
||||||
|
if (entries[i].is_dir) display_dir_count++;
|
||||||
|
}
|
||||||
char section_title[128];
|
char section_title[128];
|
||||||
snprintf(section_title, sizeof(section_title), " <li class=\"section-title\">📁 Папки (%d)</li>\n", dir_count);
|
snprintf(section_title, sizeof(section_title), " <li class=\"section-title\">📁 Папки (%d)</li>\n", display_dir_count);
|
||||||
buffer_append(buf, section_title);
|
buffer_append(buf, section_title);
|
||||||
|
|
||||||
for (int i = 0; i < entry_count; i++) {
|
for (int i = 0; i < entry_count; i++) {
|
||||||
@@ -587,18 +853,30 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
buffer_append(buf, " <a class=\"dir-link protected-folder\" href=\"javascript:void(0)\" onclick=\"showPasswordForm('");
|
buffer_append(buf, " <a class=\"dir-link protected-folder\" href=\"javascript:void(0)\" onclick=\"showPasswordForm('");
|
||||||
buffer_append(buf, entries[i].encoded_path);
|
buffer_append(buf, entries[i].encoded_path);
|
||||||
buffer_append(buf, "', '");
|
buffer_append(buf, "', '");
|
||||||
buffer_append(buf, entries[i].name);
|
char safe_name[512];
|
||||||
|
html_escape(entries[i].name, safe_name, sizeof(safe_name));
|
||||||
|
buffer_append(buf, safe_name);
|
||||||
buffer_append(buf, "')\">\n");
|
buffer_append(buf, "')\">\n");
|
||||||
buffer_append(buf, " <span class=\"file-icon\">🔒</span>\n");
|
buffer_append(buf, " <span class=\"file-icon\">🔒</span>\n");
|
||||||
buffer_append(buf, " <span class=\"file-name\">");
|
buffer_append(buf, " <span class=\"file-name\">");
|
||||||
buffer_append(buf, entries[i].name);
|
if (is_recursive_search) {
|
||||||
|
char safe_url[512];
|
||||||
|
html_escape(entries[i].file_url, safe_url, sizeof(safe_url));
|
||||||
|
buffer_append(buf, safe_url);
|
||||||
|
} else {
|
||||||
|
char safe_name2[512];
|
||||||
|
html_escape(entries[i].name, safe_name2, sizeof(safe_name2));
|
||||||
|
buffer_append(buf, safe_name2);
|
||||||
|
}
|
||||||
buffer_append(buf, " <span style=\"font-size:0.7rem;color:var(--text-muted);\">(защищена)</span></span>\n");
|
buffer_append(buf, " <span style=\"font-size:0.7rem;color:var(--text-muted);\">(защищена)</span></span>\n");
|
||||||
buffer_append(buf, " </a>\n");
|
buffer_append(buf, " </a>\n");
|
||||||
buffer_append(buf, " <div class=\"file-controls\">\n");
|
buffer_append(buf, " <div class=\"file-controls\">\n");
|
||||||
buffer_append(buf, " <button class=\"preview-btn\" onclick=\"showPasswordForm('");
|
buffer_append(buf, " <button class=\"preview-btn\" onclick=\"showPasswordForm('");
|
||||||
buffer_append(buf, entries[i].encoded_path);
|
buffer_append(buf, entries[i].encoded_path);
|
||||||
buffer_append(buf, "', '");
|
buffer_append(buf, "', '");
|
||||||
buffer_append(buf, entries[i].name);
|
char safe_name3[512];
|
||||||
|
html_escape(entries[i].name, safe_name3, sizeof(safe_name3));
|
||||||
|
buffer_append(buf, safe_name3);
|
||||||
buffer_append(buf, "')\" title=\"Ввести пароль\">🔑</button>\n");
|
buffer_append(buf, "')\" title=\"Ввести пароль\">🔑</button>\n");
|
||||||
buffer_append(buf, " </div>\n");
|
buffer_append(buf, " </div>\n");
|
||||||
buffer_append(buf, " </div>\n");
|
buffer_append(buf, " </div>\n");
|
||||||
@@ -613,8 +891,16 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
buffer_append(buf, entries[i].icon);
|
buffer_append(buf, entries[i].icon);
|
||||||
buffer_append(buf, "</span>\n");
|
buffer_append(buf, "</span>\n");
|
||||||
buffer_append(buf, " <span class=\"file-name\">");
|
buffer_append(buf, " <span class=\"file-name\">");
|
||||||
buffer_append(buf, entries[i].name);
|
if (is_recursive_search) {
|
||||||
if (entries[i].file_count > 0) {
|
char safe_url[512];
|
||||||
|
html_escape(entries[i].file_url, safe_url, sizeof(safe_url));
|
||||||
|
buffer_append(buf, safe_url);
|
||||||
|
} else {
|
||||||
|
char safe_name4[512];
|
||||||
|
html_escape(entries[i].name, safe_name4, sizeof(safe_name4));
|
||||||
|
buffer_append(buf, safe_name4);
|
||||||
|
}
|
||||||
|
if (entries[i].file_count > 0 && !is_recursive_search) {
|
||||||
char count_str[32];
|
char count_str[32];
|
||||||
snprintf(count_str, sizeof(count_str), " (%d)", entries[i].file_count);
|
snprintf(count_str, sizeof(count_str), " (%d)", entries[i].file_count);
|
||||||
buffer_append(buf, count_str);
|
buffer_append(buf, count_str);
|
||||||
@@ -633,9 +919,19 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file_count > 0) {
|
// Вывод файлов
|
||||||
|
int has_files = 0;
|
||||||
|
for (int i = 0; i < entry_count; i++) {
|
||||||
|
if (!entries[i].is_dir) { has_files = 1; break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_files) {
|
||||||
|
int display_file_count = 0;
|
||||||
|
for (int i = 0; i < entry_count; i++) {
|
||||||
|
if (!entries[i].is_dir) display_file_count++;
|
||||||
|
}
|
||||||
char section_title[128];
|
char section_title[128];
|
||||||
snprintf(section_title, sizeof(section_title), " <li class=\"section-title\">📄 Файлы (%d)</li>\n", file_count);
|
snprintf(section_title, sizeof(section_title), " <li class=\"section-title\">📄 Файлы (%d)</li>\n", display_file_count);
|
||||||
buffer_append(buf, section_title);
|
buffer_append(buf, section_title);
|
||||||
|
|
||||||
for (int i = 0; i < entry_count; i++) {
|
for (int i = 0; i < entry_count; i++) {
|
||||||
@@ -647,25 +943,46 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
|
|
||||||
buffer_append(buf, " <li>\n");
|
buffer_append(buf, " <li>\n");
|
||||||
buffer_append(buf, " <div class=\"file-row\">\n");
|
buffer_append(buf, " <div class=\"file-row\">\n");
|
||||||
buffer_append(buf, " <a class=\"file-link\" href=\"/");
|
|
||||||
buffer_append(buf, entries[i].file_url);
|
char file_link[2048];
|
||||||
|
if (is_recursive_search) {
|
||||||
|
snprintf(file_link, sizeof(file_link), "/%s", entries[i].file_url);
|
||||||
|
} else {
|
||||||
|
snprintf(file_link, sizeof(file_link), "/%s", entries[i].file_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer_append(buf, " <a class=\"file-link\" href=\"");
|
||||||
|
buffer_append(buf, file_link);
|
||||||
buffer_append(buf, "\" download>\n");
|
buffer_append(buf, "\" download>\n");
|
||||||
buffer_append(buf, " <span class=\"file-icon\">");
|
buffer_append(buf, " <span class=\"file-icon\">");
|
||||||
buffer_append(buf, entries[i].icon);
|
buffer_append(buf, entries[i].icon);
|
||||||
buffer_append(buf, "</span>\n");
|
buffer_append(buf, "</span>\n");
|
||||||
buffer_append(buf, " <span class=\"file-name\">");
|
buffer_append(buf, " <span class=\"file-name\">");
|
||||||
buffer_append(buf, entries[i].name);
|
if (is_recursive_search) {
|
||||||
|
char safe_url[512];
|
||||||
|
html_escape(entries[i].file_url, safe_url, sizeof(safe_url));
|
||||||
|
buffer_append(buf, safe_url);
|
||||||
|
} else {
|
||||||
|
char safe_name5[512];
|
||||||
|
html_escape(entries[i].name, safe_name5, sizeof(safe_name5));
|
||||||
|
buffer_append(buf, safe_name5);
|
||||||
|
}
|
||||||
buffer_append(buf, "</span>\n");
|
buffer_append(buf, "</span>\n");
|
||||||
buffer_append(buf, " </a>\n");
|
buffer_append(buf, " </a>\n");
|
||||||
|
|
||||||
buffer_append(buf, " <div class=\"file-controls\">\n");
|
buffer_append(buf, " <div class=\"file-controls\">\n");
|
||||||
if (is_previewable(entries[i].name)) {
|
if (is_previewable(entries[i].name)) {
|
||||||
buffer_append(buf, " <button class=\"preview-btn\" onclick=\"openPreview('/");
|
buffer_append(buf, " <button class=\"preview-btn\" onclick=\"openPreview('");
|
||||||
buffer_append(buf, entries[i].file_url);
|
buffer_append(buf, file_link);
|
||||||
buffer_append(buf, "')\" title=\"Открыть в браузере\">👁</button>\n");
|
buffer_append(buf, "')\" title=\"Открыть в браузере\">👁</button>\n");
|
||||||
}
|
}
|
||||||
buffer_append(buf, " <button class=\"copy-btn\" onclick=\"copyLink('/");
|
buffer_append(buf, " <button class=\"download-btn\" onclick=\"downloadFile('");
|
||||||
buffer_append(buf, entries[i].file_url);
|
buffer_append(buf, file_link);
|
||||||
|
buffer_append(buf, "')\" title=\"Скачать файл\">⬇️</button>\n");
|
||||||
|
buffer_append(buf, " <button class=\"copy-btn\" onclick=\"copyLink('");
|
||||||
|
buffer_append(buf, file_link);
|
||||||
buffer_append(buf, "')\" title=\"Копировать ссылку на файл\">🔗</button>\n");
|
buffer_append(buf, "')\" title=\"Копировать ссылку на файл\">🔗</button>\n");
|
||||||
|
|
||||||
buffer_append(buf, " <span class=\"file-meta date\">");
|
buffer_append(buf, " <span class=\"file-meta date\">");
|
||||||
buffer_append(buf, date_str);
|
buffer_append(buf, date_str);
|
||||||
buffer_append(buf, "</span>\n");
|
buffer_append(buf, "</span>\n");
|
||||||
@@ -681,13 +998,22 @@ void print_content(buffer_t *buf, const char *base_path, const char *display_pat
|
|||||||
|
|
||||||
buffer_append(buf, "</ul>\n");
|
buffer_append(buf, "</ul>\n");
|
||||||
|
|
||||||
char total_size_str[32];
|
// Статистика
|
||||||
format_size(total_size, total_size_str);
|
if (is_recursive_search) {
|
||||||
char stats[256];
|
char stats[256];
|
||||||
snprintf(stats, sizeof(stats),
|
snprintf(stats, sizeof(stats),
|
||||||
"<div class=\"stats\">\n Папки: %d | Файлы: %d | Общий размер: %s\n</div>\n",
|
"<div class=\"stats\">\n Найдено: папок %d | файлов %d\n</div>\n",
|
||||||
dir_count, file_count, total_size_str);
|
dir_count, file_count);
|
||||||
buffer_append(buf, stats);
|
buffer_append(buf, stats);
|
||||||
|
} else {
|
||||||
|
char total_size_str[32];
|
||||||
|
format_size(total_size, total_size_str);
|
||||||
|
char stats[256];
|
||||||
|
snprintf(stats, sizeof(stats),
|
||||||
|
"<div class=\"stats\">\n Папки: %d | Файлы: %d | Общий размер: %s\n</div>\n",
|
||||||
|
dir_count, file_count, total_size_str);
|
||||||
|
buffer_append(buf, stats);
|
||||||
|
}
|
||||||
|
|
||||||
free(entries);
|
free(entries);
|
||||||
}
|
}
|
||||||
@@ -724,19 +1050,23 @@ int main() {
|
|||||||
|
|
||||||
// Загружаем конфиг
|
// Загружаем конфиг
|
||||||
if (load_config("/home/romkazvo/www/cgi-bin/config.ini", &g_config) != 0) {
|
if (load_config("/home/romkazvo/www/cgi-bin/config.ini", &g_config) != 0) {
|
||||||
// Если конфиг не найден — используем дефолты
|
|
||||||
set_default_config(&g_config);
|
set_default_config(&g_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("Content-type: text/html; charset=utf-8\n\n");
|
// === ПАРСИМ ПАРАМЕТРЫ ЗАПРОСА ===
|
||||||
|
strcpy(g_params.sort_by, "name");
|
||||||
char base_path[1024];
|
g_params.sort_order = 1;
|
||||||
strcpy(base_path, g_config.base_path);
|
g_params.search[0] = '\0';
|
||||||
char display_path[1024] = "";
|
g_params.recursive = 0;
|
||||||
char safe_display_path[1024] = "";
|
|
||||||
|
|
||||||
char *query_string = getenv("QUERY_STRING");
|
char *query_string = getenv("QUERY_STRING");
|
||||||
|
char display_path[1024] = "";
|
||||||
|
char safe_display_path[1024] = "";
|
||||||
|
char base_path[1024];
|
||||||
|
strcpy(base_path, g_config.base_path);
|
||||||
|
|
||||||
if (query_string) {
|
if (query_string) {
|
||||||
|
// === ПАРСИМ PATH ===
|
||||||
char *path_start = strstr(query_string, "path=");
|
char *path_start = strstr(query_string, "path=");
|
||||||
if (path_start) {
|
if (path_start) {
|
||||||
path_start += 5;
|
path_start += 5;
|
||||||
@@ -758,6 +1088,36 @@ int main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === ПАРСИМ SORT ===
|
||||||
|
char *sort_start = strstr(query_string, "sort=");
|
||||||
|
if (sort_start) {
|
||||||
|
sort_start += 5;
|
||||||
|
char *sort_end = strchr(sort_start, '&');
|
||||||
|
int sort_len = sort_end ? sort_end - sort_start : strlen(sort_start);
|
||||||
|
if (sort_len > 0 && sort_len < sizeof(g_params.sort_by)) {
|
||||||
|
strncpy(g_params.sort_by, sort_start, sort_len);
|
||||||
|
g_params.sort_by[sort_len] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === ПАРСИМ SEARCH (отдельно от path!) ===
|
||||||
|
char *search_start = strstr(query_string, "search=");
|
||||||
|
if (search_start) {
|
||||||
|
search_start += 7;
|
||||||
|
char *search_end = strchr(search_start, '&');
|
||||||
|
int search_len = search_end ? search_end - search_start : strlen(search_start);
|
||||||
|
if (search_len > 0 && search_len < sizeof(g_params.search) - 1) {
|
||||||
|
char encoded_search[256];
|
||||||
|
strncpy(encoded_search, search_start, search_len);
|
||||||
|
encoded_search[search_len] = '\0';
|
||||||
|
url_decode_enhanced(encoded_search, g_params.search, sizeof(g_params.search));
|
||||||
|
if (g_params.search[0] != '\0') {
|
||||||
|
g_params.recursive = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === ОБРАБОТКА ПАРОЛЯ ===
|
||||||
if (strstr(query_string, "password=")) {
|
if (strstr(query_string, "password=")) {
|
||||||
char *error = strstr(query_string, "error=1");
|
char *error = strstr(query_string, "error=1");
|
||||||
if (!error) {
|
if (!error) {
|
||||||
@@ -781,19 +1141,22 @@ int main() {
|
|||||||
if (decoded_pass[0] == '\0') {
|
if (decoded_pass[0] == '\0') {
|
||||||
char encoded_path[1024];
|
char encoded_path[1024];
|
||||||
url_encode(display_path, encoded_path, sizeof(encoded_path));
|
url_encode(display_path, encoded_path, sizeof(encoded_path));
|
||||||
printf("Location: /cgi-bin/index.cgi?path=%s&error=1\n\n", encoded_path);
|
printf("Status: 302 Found\r\n");
|
||||||
|
printf("Location: /cgi-bin/index.cgi?path=%s&error=1\r\n\r\n", encoded_path);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (!check_folder_password(folder_name, decoded_pass)) {
|
if (!check_folder_password(folder_name, decoded_pass)) {
|
||||||
char encoded_path[1024];
|
char encoded_path[1024];
|
||||||
url_encode(display_path, encoded_path, sizeof(encoded_path));
|
url_encode(display_path, encoded_path, sizeof(encoded_path));
|
||||||
printf("Location: /cgi-bin/index.cgi?path=%s&error=1\n\n", encoded_path);
|
printf("Status: 302 Found\r\n");
|
||||||
|
printf("Location: /cgi-bin/index.cgi?path=%s&error=1\r\n\r\n", encoded_path);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
char encoded_path[1024];
|
char encoded_path[1024];
|
||||||
url_encode(display_path, encoded_path, sizeof(encoded_path));
|
url_encode(display_path, encoded_path, sizeof(encoded_path));
|
||||||
printf("Location: /cgi-bin/index.cgi?path=%s&error=1\n\n", encoded_path);
|
printf("Status: 302 Found\r\n");
|
||||||
|
printf("Location: /cgi-bin/index.cgi?path=%s&error=1\r\n\r\n", encoded_path);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -803,6 +1166,8 @@ int main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printf("Content-type: text/html; charset=utf-8\n\n");
|
||||||
|
|
||||||
print_template(base_path, display_path);
|
print_template(base_path, display_path);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
295
style.css
295
style.css
@@ -174,11 +174,11 @@ ul {
|
|||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
padding: 0.75rem 0.5rem;
|
padding: 1rem 0.5rem;
|
||||||
border-bottom: 1px solid var(--border-light);
|
border-bottom: 1px solid var(--border-light);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: nowrap; /* Исправлено: теперь не переносятся */
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
li:last-child { border-bottom: none; }
|
li:last-child { border-bottom: none; }
|
||||||
@@ -194,8 +194,8 @@ li:hover {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
gap: 0.75rem;
|
gap: 1rem;
|
||||||
flex-wrap: nowrap; /* Запрещаем перенос */
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-link {
|
.file-link {
|
||||||
@@ -222,11 +222,12 @@ li:hover {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-meta {
|
.file-meta {
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
font-size: 0.85rem;
|
font-size: 0.75rem;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
@@ -240,6 +241,28 @@ li:hover {
|
|||||||
min-width: 70px;
|
min-width: 70px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.file-meta {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
.file-meta.date {
|
||||||
|
min-width: 75px;
|
||||||
|
}
|
||||||
|
.file-meta.size {
|
||||||
|
min-width: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.file-meta.date {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.file-meta.size {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
min-width: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.file-icon {
|
.file-icon {
|
||||||
margin-right: 0.75rem;
|
margin-right: 0.75rem;
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
@@ -324,8 +347,10 @@ li:hover {
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Стили для кнопок */
|
/* ================================================
|
||||||
.preview-btn, .copy-btn {
|
ЕДИНЫЙ СТИЛЬ ДЛЯ ВСЕХ КНОПОК
|
||||||
|
================================================ */
|
||||||
|
.preview-btn, .copy-btn, .download-btn {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
@@ -342,7 +367,7 @@ li:hover {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-btn:hover, .copy-btn:hover {
|
.preview-btn:hover, .copy-btn:hover, .download-btn:hover {
|
||||||
background: var(--accent-blue);
|
background: var(--accent-blue);
|
||||||
color: white;
|
color: white;
|
||||||
border-color: var(--accent-blue);
|
border-color: var(--accent-blue);
|
||||||
@@ -350,7 +375,103 @@ li:hover {
|
|||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Стили для формы ввода пароля */
|
/* ================================================
|
||||||
|
ТУЛБАР (поиск + сортировка)
|
||||||
|
================================================ */
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 0.75rem 0.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
flex: 1 1 200px;
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box form {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box input[type="text"] {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box input[type="text"]:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box button {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
background: var(--accent-blue);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box button:hover {
|
||||||
|
background: var(--accent-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.3rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-label {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-weight: 500;
|
||||||
|
margin-right: 0.2rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-btn {
|
||||||
|
padding: 0.4rem 0.75rem;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-btn:hover {
|
||||||
|
background: var(--hover-bg);
|
||||||
|
border-color: var(--accent-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-btn.active {
|
||||||
|
background: var(--accent-blue);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--accent-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================
|
||||||
|
Стили для формы ввода пароля
|
||||||
|
================================================ */
|
||||||
.password-form-container {
|
.password-form-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -457,7 +578,9 @@ li:hover {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Модальное окно предпросмотра */
|
/* ================================================
|
||||||
|
Модальное окно предпросмотра
|
||||||
|
================================================ */
|
||||||
.preview-modal {
|
.preview-modal {
|
||||||
display: none;
|
display: none;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -575,7 +698,6 @@ li:hover {
|
|||||||
background: rgba(255,255,255,0.5);
|
background: rgba(255,255,255,0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Уведомление */
|
|
||||||
#copyNotification {
|
#copyNotification {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 80px;
|
bottom: 80px;
|
||||||
@@ -592,18 +714,10 @@ li:hover {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* === АДАПТИВНОСТЬ === */
|
/* ================================================
|
||||||
|
Адаптивность
|
||||||
|
================================================ */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.file-meta {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
.file-meta.date {
|
|
||||||
min-width: 75px;
|
|
||||||
}
|
|
||||||
.file-meta.size {
|
|
||||||
min-width: 60px;
|
|
||||||
}
|
|
||||||
.preview-content {
|
.preview-content {
|
||||||
margin: 2.5% auto;
|
margin: 2.5% auto;
|
||||||
}
|
}
|
||||||
@@ -616,39 +730,117 @@ li:hover {
|
|||||||
.preview-image-container {
|
.preview-image-container {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
padding: 0.5rem 0.25rem !important;
|
||||||
|
flex-wrap: nowrap !important;
|
||||||
|
}
|
||||||
|
.file-row {
|
||||||
|
flex-wrap: nowrap !important;
|
||||||
|
gap: 0.3rem !important;
|
||||||
|
}
|
||||||
|
.file-controls {
|
||||||
|
flex-wrap: nowrap !important;
|
||||||
|
gap: 0.15rem !important;
|
||||||
|
}
|
||||||
|
.file-meta.date {
|
||||||
|
min-width: 60px !important;
|
||||||
|
}
|
||||||
|
.file-meta.size {
|
||||||
|
min-width: 50px !important;
|
||||||
|
font-size: 0.65rem !important;
|
||||||
|
}
|
||||||
|
.toolbar {
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
gap: 0.5rem !important;
|
||||||
|
padding: 0.5rem !important;
|
||||||
|
}
|
||||||
|
.search-box {
|
||||||
|
flex: 1 1 100% !important;
|
||||||
|
min-width: 0 !important;
|
||||||
|
}
|
||||||
|
.sort-buttons {
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
/* Скрываем дату на телефонах */
|
.toolbar {
|
||||||
|
flex-direction: column !important;
|
||||||
|
align-items: stretch !important;
|
||||||
|
gap: 0.5rem !important;
|
||||||
|
padding: 0.5rem !important;
|
||||||
|
}
|
||||||
|
.search-box {
|
||||||
|
width: 100% !important;
|
||||||
|
min-width: 0 !important;
|
||||||
|
flex: 1 1 auto !important;
|
||||||
|
}
|
||||||
|
.search-box form {
|
||||||
|
flex-wrap: nowrap !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
.search-box input[type="text"] {
|
||||||
|
flex: 1 1 auto !important;
|
||||||
|
min-width: 60px !important;
|
||||||
|
font-size: 0.85rem !important;
|
||||||
|
padding: 0.5rem 0.6rem !important;
|
||||||
|
}
|
||||||
|
.search-box button {
|
||||||
|
flex: 0 0 auto !important;
|
||||||
|
font-size: 0.8rem !important;
|
||||||
|
padding: 0.4rem 0.7rem !important;
|
||||||
|
white-space: nowrap !important;
|
||||||
|
}
|
||||||
|
.sort-buttons {
|
||||||
|
display: flex !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
gap: 0.3rem !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
.sort-label {
|
||||||
|
font-size: 0.65rem !important;
|
||||||
|
width: 100% !important;
|
||||||
|
text-align: center !important;
|
||||||
|
margin-right: 0 !important;
|
||||||
|
}
|
||||||
|
.sort-btn {
|
||||||
|
flex: 0 1 auto !important;
|
||||||
|
font-size: 0.65rem !important;
|
||||||
|
padding: 0.25rem 0.4rem !important;
|
||||||
|
white-space: nowrap !important;
|
||||||
|
}
|
||||||
|
.preview-btn, .copy-btn, .download-btn {
|
||||||
|
width: 26px !important;
|
||||||
|
height: 26px !important;
|
||||||
|
font-size: 0.65rem !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
.file-meta.date {
|
.file-meta.date {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
/* Уменьшаем размер кнопок */
|
|
||||||
.preview-btn, .copy-btn {
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
.file-meta.size {
|
.file-meta.size {
|
||||||
font-size: 0.7rem;
|
font-size: 0.55rem !important;
|
||||||
min-width: 50px;
|
min-width: 35px !important;
|
||||||
}
|
}
|
||||||
.file-name {
|
.file-name {
|
||||||
font-size: 0.85rem;
|
font-size: 0.75rem !important;
|
||||||
}
|
}
|
||||||
.file-icon {
|
.file-icon {
|
||||||
font-size: 1.1rem;
|
font-size: 1rem !important;
|
||||||
width: 20px;
|
width: 18px !important;
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.3rem !important;
|
||||||
}
|
}
|
||||||
.file-controls {
|
.file-controls {
|
||||||
gap: 0.3rem;
|
gap: 0.1rem !important;
|
||||||
}
|
}
|
||||||
li {
|
li {
|
||||||
padding: 0.5rem 0.25rem;
|
padding: 0.3rem 0.15rem !important;
|
||||||
}
|
}
|
||||||
.file-row {
|
.file-row {
|
||||||
gap: 0.4rem;
|
gap: 0.15rem !important;
|
||||||
}
|
}
|
||||||
.theme-toggle {
|
.theme-toggle {
|
||||||
top: 5px;
|
top: 5px;
|
||||||
@@ -711,15 +903,28 @@ li:hover {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.file-name {
|
.file-name {
|
||||||
font-size: 0.8rem;
|
font-size: 0.7rem !important;
|
||||||
}
|
}
|
||||||
.file-meta.size {
|
.file-meta.size {
|
||||||
font-size: 0.65rem;
|
font-size: 0.5rem !important;
|
||||||
min-width: 40px;
|
min-width: 28px !important;
|
||||||
}
|
}
|
||||||
.preview-btn, .copy-btn {
|
.preview-btn, .copy-btn, .download-btn {
|
||||||
width: 24px;
|
width: 20px !important;
|
||||||
height: 24px;
|
height: 20px !important;
|
||||||
font-size: 0.7rem;
|
font-size: 0.5rem !important;
|
||||||
|
}
|
||||||
|
.search-box input[type="text"] {
|
||||||
|
font-size: 0.75rem !important;
|
||||||
|
padding: 0.3rem 0.4rem !important;
|
||||||
|
min-width: 40px !important;
|
||||||
|
}
|
||||||
|
.search-box button {
|
||||||
|
font-size: 0.7rem !important;
|
||||||
|
padding: 0.3rem 0.5rem !important;
|
||||||
|
}
|
||||||
|
.sort-btn {
|
||||||
|
font-size: 0.6rem !important;
|
||||||
|
padding: 0.2rem 0.3rem !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,29 +37,36 @@
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
/* Стили для кнопки копирования */
|
/* Все кнопки в едином стиле */
|
||||||
.copy-btn {
|
.preview-btn,
|
||||||
background: transparent;
|
.copy-btn,
|
||||||
color: var(--text-secondary);
|
.download-btn {
|
||||||
border: 1px solid var(--border-color);
|
background: transparent !important;
|
||||||
border-radius: 6px;
|
color: var(--text-secondary) !important;
|
||||||
width: 32px;
|
border: 1px solid var(--border-color) !important;
|
||||||
height: 32px;
|
border-radius: 6px !important;
|
||||||
cursor: pointer;
|
width: 32px !important;
|
||||||
font-size: 0.85rem;
|
height: 32px !important;
|
||||||
display: inline-flex;
|
cursor: pointer !important;
|
||||||
align-items: center;
|
font-size: 0.85rem !important;
|
||||||
justify-content: center;
|
display: inline-flex !important;
|
||||||
transition: all 0.2s ease;
|
align-items: center !important;
|
||||||
opacity: 0.7;
|
justify-content: center !important;
|
||||||
|
transition: all 0.2s ease !important;
|
||||||
|
opacity: 0.7 !important;
|
||||||
|
flex-shrink: 0 !important;
|
||||||
}
|
}
|
||||||
.copy-btn:hover {
|
|
||||||
background: var(--accent-blue);
|
.preview-btn:hover,
|
||||||
color: white;
|
.copy-btn:hover,
|
||||||
border-color: var(--accent-blue);
|
.download-btn:hover {
|
||||||
opacity: 1;
|
background: var(--accent-blue) !important;
|
||||||
transform: scale(1.05);
|
color: white !important;
|
||||||
|
border-color: var(--accent-blue) !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
transform: scale(1.05) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Уведомление */
|
/* Уведомление */
|
||||||
#copyNotification {
|
#copyNotification {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -76,11 +83,14 @@
|
|||||||
transition: opacity 0.3s;
|
transition: opacity 0.3s;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.copy-btn {
|
.preview-btn,
|
||||||
width: 28px;
|
.copy-btn,
|
||||||
height: 28px;
|
.download-btn {
|
||||||
font-size: 0.8rem;
|
width: 28px !important;
|
||||||
|
height: 28px !important;
|
||||||
|
font-size: 0.75rem !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -213,7 +223,17 @@
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== НОВАЯ ФУНКЦИЯ КОПИРОВАНИЯ ==========
|
// === СКАЧИВАНИЕ ФАЙЛА ===
|
||||||
|
window.downloadFile = function(path) {
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = path;
|
||||||
|
link.download = path.split('/').pop();
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
};
|
||||||
|
|
||||||
|
// === КОПИРОВАНИЕ ССЫЛКИ ===
|
||||||
window.copyLink = function(path) {
|
window.copyLink = function(path) {
|
||||||
const url = window.location.origin + path;
|
const url = window.location.origin + path;
|
||||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
@@ -256,7 +276,6 @@
|
|||||||
setTimeout(() => div.remove(), 400);
|
setTimeout(() => div.remove(), 400);
|
||||||
}, 2500);
|
}, 2500);
|
||||||
}
|
}
|
||||||
// ==============================================
|
|
||||||
|
|
||||||
window.scrollToTop = function() {
|
window.scrollToTop = function() {
|
||||||
window.scrollTo({
|
window.scrollTo({
|
||||||
|
|||||||
Reference in New Issue
Block a user