fix: enhance code content display with syntax highlighting and improve logging structure
This commit is contained in:
@@ -363,6 +363,18 @@ h1 .home-link:hover {
|
|||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.zoom-text-content.code-content {
|
||||||
|
background-color: var(--bg-primary);
|
||||||
|
border-color: var(--border-color);
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-text-content.code-content code {
|
||||||
|
display: block;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
.zoom-text-content::-webkit-scrollbar {
|
.zoom-text-content::-webkit-scrollbar {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
}
|
}
|
||||||
@@ -393,7 +405,7 @@ h1 .home-link:hover {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
cursor: zoom-out;
|
cursor: default;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
@@ -600,6 +612,18 @@ body.view-page {
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-content-view.code-content {
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-content-view.code-content code {
|
||||||
|
display: block;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
.text-content-view::-webkit-scrollbar {
|
.text-content-view::-webkit-scrollbar {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Black Hole Share - View</title>
|
<title>Black Hole Share - View</title>
|
||||||
<link rel="stylesheet" href="/style.css">
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="view-page">
|
<body class="view-page">
|
||||||
@@ -32,6 +34,7 @@
|
|||||||
<!-- Zoom overlay -->
|
<!-- Zoom overlay -->
|
||||||
<div id="zoomOverlay" class="zoom-overlay" style="display: none;"></div>
|
<div id="zoomOverlay" class="zoom-overlay" style="display: none;"></div>
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const contentArea = document.getElementById('contentArea');
|
const contentArea = document.getElementById('contentArea');
|
||||||
const zoomOverlay = document.getElementById('zoomOverlay');
|
const zoomOverlay = document.getElementById('zoomOverlay');
|
||||||
@@ -40,6 +43,28 @@
|
|||||||
const pathParts = window.location.pathname.split('/');
|
const pathParts = window.location.pathname.split('/');
|
||||||
const assetId = pathParts[pathParts.length - 1];
|
const assetId = pathParts[pathParts.length - 1];
|
||||||
|
|
||||||
|
function escapeHtml(text) {
|
||||||
|
return text.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCodeLike(text) {
|
||||||
|
const lines = text.split('\n');
|
||||||
|
if (lines.length < 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const indicators = [
|
||||||
|
/;\s*$/,
|
||||||
|
/^\s*(fn|function|class|def|public|private|struct|enum|pub\s+struct)\b/,
|
||||||
|
/^\s*#\[/,
|
||||||
|
/=>|::|#include|import\s+\w+/,
|
||||||
|
/\{|\}|\(|\)|\[|\]/,
|
||||||
|
];
|
||||||
|
const indicatorHits = indicators.reduce((count, re) => count + (re.test(text) ? 1 : 0), 0);
|
||||||
|
return indicatorHits >= 2;
|
||||||
|
}
|
||||||
|
|
||||||
async function loadContent() {
|
async function loadContent() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/content/${assetId}`);
|
const response = await fetch(`/api/content/${assetId}`);
|
||||||
@@ -82,12 +107,23 @@
|
|||||||
} else if (contentType.startsWith('text/')) {
|
} else if (contentType.startsWith('text/')) {
|
||||||
// Display text
|
// Display text
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
contentArea.innerHTML = `<div class="text-content-view" style="cursor: zoom-in;">${text.replace(/</g, '<').replace(/>/g, '>')}</div>`;
|
const safeText = escapeHtml(text);
|
||||||
|
const isCode = isCodeLike(text);
|
||||||
|
const textHtml = isCode
|
||||||
|
? `<pre class="text-content-view code-content"><code>${safeText}</code></pre>`
|
||||||
|
: `<div class="text-content-view">${safeText}</div>`;
|
||||||
|
|
||||||
|
contentArea.innerHTML = textHtml;
|
||||||
|
if (isCode && window.hljs) {
|
||||||
|
contentArea.querySelectorAll('pre code').forEach((block) => {
|
||||||
|
window.hljs.highlightElement(block);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const textContent = contentArea.querySelector('.text-content-view');
|
const textContent = contentArea.querySelector('.text-content-view');
|
||||||
textContent.addEventListener('click', function (e) {
|
textContent.addEventListener('click', function (e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
showZoom(text, true);
|
showZoom(text, true, isCode);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
contentArea.innerHTML = '<p class="error">Unsupported content type</p>';
|
contentArea.innerHTML = '<p class="error">Unsupported content type</p>';
|
||||||
@@ -99,11 +135,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showZoom(content, isText = false) {
|
function showZoom(content, isText = false, isCode = false) {
|
||||||
if (isText) {
|
if (isText) {
|
||||||
zoomOverlay.innerHTML = `
|
const safeText = escapeHtml(content);
|
||||||
<div class="zoom-text-content">${content.replace(/</g, '<').replace(/>/g, '>')}</div>
|
const zoomClass = isCode ? 'zoom-text-content code-content' : 'zoom-text-content';
|
||||||
`;
|
const zoomHtml = isCode
|
||||||
|
? `<pre class="${zoomClass}"><code>${safeText}</code></pre>`
|
||||||
|
: `<div class="${zoomClass}">${safeText}</div>`;
|
||||||
|
zoomOverlay.innerHTML = zoomHtml;
|
||||||
|
if (isCode && window.hljs) {
|
||||||
|
zoomOverlay.querySelectorAll('pre code').forEach((block) => {
|
||||||
|
window.hljs.highlightElement(block);
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
zoomOverlay.innerHTML = `<img id="zoomImage" src="${content}" alt="Zoomed Content"
|
zoomOverlay.innerHTML = `<img id="zoomImage" src="${content}" alt="Zoomed Content"
|
||||||
style="max-width: 95vw; max-height: 95vh; object-fit: contain; box-shadow: 0 0 50px rgba(51, 204, 255, 0.5);">`;
|
style="max-width: 95vw; max-height: 95vh; object-fit: contain; box-shadow: 0 0 50px rgba(51, 204, 255, 0.5);">`;
|
||||||
@@ -115,8 +159,6 @@
|
|||||||
zoomOverlay.style.display = 'none';
|
zoomOverlay.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomOverlay.addEventListener('click', hideZoom);
|
|
||||||
|
|
||||||
document.addEventListener('keydown', function (e) {
|
document.addEventListener('keydown', function (e) {
|
||||||
if (e.key === 'Escape' || e.key === 'Esc') {
|
if (e.key === 'Escape' || e.key === 'Esc') {
|
||||||
hideZoom();
|
hideZoom();
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use futures::lock::Mutex;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::DATA_STORAGE;
|
|
||||||
use crate::logs::{LogEventType, log_event};
|
use crate::logs::{LogEventType, log_event};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
@@ -87,13 +86,13 @@ impl Asset {
|
|||||||
serde_json::to_value(self).unwrap_or(Value::Null)
|
serde_json::to_value(self).unwrap_or(Value::Null)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save(&self) -> Result<String> {
|
// pub fn save(&self) -> Result<String> {
|
||||||
let id = self.id.clone();
|
// let id = self.id.clone();
|
||||||
let path = format!("{}{}", DATA_STORAGE, self.id);
|
// let path = format!("{}{}", DATA_STORAGE, self.id);
|
||||||
std::fs::create_dir_all(DATA_STORAGE)?;
|
// std::fs::create_dir_all(DATA_STORAGE)?;
|
||||||
std::fs::write(&path, self.to_bytes()?)?;
|
// std::fs::write(&path, self.to_bytes()?)?;
|
||||||
Ok(id)
|
// Ok(id)
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use actix_web::HttpRequest;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::LOG_DIR;
|
use crate::{LOG_DIR, LOG_FILE_NAME};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct LogHttpRequest {
|
pub struct LogHttpRequest {
|
||||||
@@ -68,7 +68,7 @@ impl From<LogEventType> for LogEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn log_event(event: LogEventType) {
|
pub fn log_event(event: LogEventType) {
|
||||||
let log_path = LOG_DIR.to_string() + "access.log";
|
let log_path = LOG_DIR.to_string() + LOG_FILE_NAME;
|
||||||
|
|
||||||
let Ok(mut file) = OpenOptions::new().create(true).append(true).open(log_path) else {
|
let Ok(mut file) = OpenOptions::new().create(true).append(true).open(log_path) else {
|
||||||
eprintln!("failed to open log file for asset event");
|
eprintln!("failed to open log file for asset event");
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use std::{env, fs, path::PathBuf, sync::LazyLock};
|
|||||||
|
|
||||||
pub static HTML_DIR: &str = "data/html/";
|
pub static HTML_DIR: &str = "data/html/";
|
||||||
pub static LOG_DIR: &str = "data/logs/";
|
pub static LOG_DIR: &str = "data/logs/";
|
||||||
pub static DATA_STORAGE: &str = "data/storage/";
|
pub static LOG_FILE_NAME: &str = "log.txt";
|
||||||
|
|
||||||
pub static BIND_ADDR: LazyLock<String> = LazyLock::new(|| match env::var("BIND_ADDR") {
|
pub static BIND_ADDR: LazyLock<String> = LazyLock::new(|| match env::var("BIND_ADDR") {
|
||||||
Ok(addr) => {
|
Ok(addr) => {
|
||||||
@@ -88,8 +88,8 @@ async fn catch_all(req: HttpRequest, _payload: Option<web::Json<Value>>) -> acti
|
|||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
let _ = fs::create_dir_all(DATA_STORAGE);
|
|
||||||
let _ = fs::create_dir_all(LOG_DIR);
|
let _ = fs::create_dir_all(LOG_DIR);
|
||||||
|
let _ = fs::remove_file(format!("{}{}", LOG_DIR, LOG_FILE_NAME));
|
||||||
|
|
||||||
let assets = data_mgt::AssetTracker::new();
|
let assets = data_mgt::AssetTracker::new();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user