sync from local backup
This commit is contained in:
@@ -0,0 +1,582 @@
|
||||
.tool-detail-page {
|
||||
min-height: 100vh;
|
||||
background: #f0f2f5;
|
||||
}
|
||||
|
||||
.tool-detail-hero {
|
||||
min-height: 200px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.tool-detail-hero::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: radial-gradient(circle at 30% 50%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 70% 80%, rgba(255, 255, 255, 0.08) 0%, transparent 40%);
|
||||
animation: tdHeroLight 8s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes tdHeroLight {
|
||||
0% { transform: translate(0, 0); }
|
||||
100% { transform: translate(-20px, -10px); }
|
||||
}
|
||||
|
||||
.tool-detail-hero-particles {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.td-particle {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
animation: tdFloat 6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.td-particle-1 { width: 50px; height: 50px; top: 15%; left: 8%; animation-delay: 0s; }
|
||||
.td-particle-2 { width: 35px; height: 35px; top: 65%; left: 18%; animation-delay: 1s; }
|
||||
.td-particle-3 { width: 65px; height: 65px; top: 25%; right: 12%; animation-delay: 2s; }
|
||||
.td-particle-4 { width: 25px; height: 25px; top: 70%; right: 25%; animation-delay: 0.5s; }
|
||||
.td-particle-5 { width: 40px; height: 40px; top: 45%; left: 45%; animation-delay: 1.5s; }
|
||||
.td-particle-6 { width: 30px; height: 30px; bottom: 20%; left: 30%; animation-delay: 3s; }
|
||||
|
||||
@keyframes tdFloat {
|
||||
0%, 100% { transform: translateY(0) scale(1); opacity: 0.3; }
|
||||
50% { transform: translateY(-18px) scale(1.1); opacity: 0.6; }
|
||||
}
|
||||
|
||||
.tool-detail-hero-content {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.tool-detail-hero-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 20px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(10px);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28px;
|
||||
color: #fff;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.tool-detail-hero-title {
|
||||
font-size: 32px;
|
||||
font-weight: 800;
|
||||
color: #fff;
|
||||
margin: 0 0 8px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.tool-detail-hero-desc {
|
||||
font-size: 15px;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
margin: 0 0 20px;
|
||||
}
|
||||
|
||||
.tool-detail-hero-stats {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
border-radius: 30px;
|
||||
padding: 10px 28px;
|
||||
}
|
||||
|
||||
.td-hero-stat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.td-hero-stat-divider {
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.tool-detail-container {
|
||||
max-width: 1100px;
|
||||
margin: -30px auto 0;
|
||||
padding: 0 20px 60px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.tool-detail-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.td-back-btn {
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
color: #64748b;
|
||||
border-color: #e2e8f0;
|
||||
}
|
||||
|
||||
.td-back-btn:hover {
|
||||
color: #667eea;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.tool-detail-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.td-action-btn {
|
||||
height: 38px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.td-action-btn:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.td-action-primary {
|
||||
background: linear-gradient(135deg, #667eea, #764ba2) !important;
|
||||
border: none !important;
|
||||
color: #fff !important;
|
||||
box-shadow: 0 4px 14px rgba(102, 126, 234, 0.35);
|
||||
}
|
||||
|
||||
.td-action-primary:hover {
|
||||
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.45) !important;
|
||||
}
|
||||
|
||||
.td-action-danger {
|
||||
color: #ef4444 !important;
|
||||
border-color: #fca5a5 !important;
|
||||
}
|
||||
|
||||
.td-action-danger:hover {
|
||||
background: #fef2f2 !important;
|
||||
}
|
||||
|
||||
.tool-detail-panels {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.tool-detail-panel {
|
||||
background: #fff;
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||
border: 1px solid #e8ecf1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.tool-detail-panel:hover {
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.tool-detail-panel-header {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.tool-detail-panel-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #334155;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.td-panel-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #cbd5e1;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.td-panel-dot.input-dot {
|
||||
background: #667eea;
|
||||
}
|
||||
|
||||
.td-panel-dot.success-dot {
|
||||
background: #10b981;
|
||||
}
|
||||
|
||||
.td-panel-dot.error-dot {
|
||||
background: #ef4444;
|
||||
}
|
||||
|
||||
.tool-detail-panel-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tool-detail-panel-body textarea {
|
||||
width: 100%;
|
||||
min-height: 380px;
|
||||
border: none;
|
||||
outline: none;
|
||||
resize: vertical;
|
||||
padding: 16px;
|
||||
font-family: "JetBrains Mono", "Fira Code", "Consolas", monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
background: #1e1e2e;
|
||||
color: #a6e3a1;
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tool-detail-panel-body textarea::placeholder {
|
||||
color: #6c7086;
|
||||
}
|
||||
|
||||
.tool-detail-panel-footer {
|
||||
padding: 8px 16px;
|
||||
border-top: 1px solid #f1f5f9;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #fafbfc;
|
||||
}
|
||||
|
||||
.td-char-count {
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.td-copy-inline {
|
||||
font-size: 12px;
|
||||
color: #667eea !important;
|
||||
padding: 0 !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.json-output-panel-body {
|
||||
flex: 1;
|
||||
background: #1e1e2e;
|
||||
}
|
||||
|
||||
.json-view-tabs {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.json-view-tabs .ant-tabs-nav {
|
||||
margin: 0;
|
||||
padding: 0 16px;
|
||||
background: #181825;
|
||||
}
|
||||
|
||||
.json-view-tabs .ant-tabs-tab {
|
||||
font-size: 12px;
|
||||
padding: 8px 0;
|
||||
color: #6c7086 !important;
|
||||
}
|
||||
|
||||
.json-view-tabs .ant-tabs-tab-active .ant-tabs-tab-btn {
|
||||
color: #89dceb !important;
|
||||
}
|
||||
|
||||
.json-view-tabs .ant-tabs-ink-bar {
|
||||
background: #89dceb;
|
||||
}
|
||||
|
||||
.json-view-tabs .ant-tabs-content-holder {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.json-view-tabs .ant-tabs-content {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.json-view-tabs .ant-tabs-tabpane {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.json-code-output {
|
||||
width: 100%;
|
||||
min-height: 380px;
|
||||
padding: 16px;
|
||||
font-family: "JetBrains Mono", "Fira Code", "Consolas", monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
background: #1e1e2e;
|
||||
color: #a6e3a1;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.json-code-output.output-error {
|
||||
color: #f38ba8;
|
||||
}
|
||||
|
||||
.json-code-output.output-empty {
|
||||
color: #6c7086;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.json-key {
|
||||
color: #89dceb;
|
||||
}
|
||||
|
||||
.json-string {
|
||||
color: #a6e3a1;
|
||||
}
|
||||
|
||||
.json-number {
|
||||
color: #fab387;
|
||||
}
|
||||
|
||||
.json-boolean {
|
||||
color: #89b4fa;
|
||||
}
|
||||
|
||||
.json-null {
|
||||
color: #f38ba8;
|
||||
}
|
||||
|
||||
.json-tree-output {
|
||||
width: 100%;
|
||||
min-height: 380px;
|
||||
padding: 16px;
|
||||
background: #1e1e2e;
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
font-family: "JetBrains Mono", "Fira Code", "Consolas", monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.json-tree-output ul {
|
||||
padding-left: 1em !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.json-tree-output li {
|
||||
list-style: none !important;
|
||||
}
|
||||
|
||||
.tree-placeholder {
|
||||
color: #6c7086;
|
||||
font-style: italic;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.tool-detail-tips {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.td-tip-card {
|
||||
background: white;
|
||||
border-radius: 14px;
|
||||
padding: 20px;
|
||||
border: 1px solid #e8ecf1;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.td-tip-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.td-tip-icon {
|
||||
font-size: 28px;
|
||||
flex-shrink: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.td-tip-content h4 {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
margin: 0 0 6px;
|
||||
}
|
||||
|
||||
.td-tip-content p {
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.tool-detail-hero {
|
||||
min-height: 160px;
|
||||
padding: 30px 16px;
|
||||
}
|
||||
|
||||
.tool-detail-hero-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.tool-detail-container {
|
||||
padding: 0 12px 40px;
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
||||
.tool-detail-toolbar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.tool-detail-actions {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tool-detail-panels {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.tool-detail-panel-body textarea,
|
||||
.json-code-output,
|
||||
.json-tree-output {
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
.td-action-btn {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tool-detail-tips {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 暗色模式 ===== */
|
||||
[data-theme="dark"] .tool-detail-page {
|
||||
background: #0f172a;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tool-detail-panel {
|
||||
background: #1e293b;
|
||||
border-color: #334155;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tool-detail-panel-header,
|
||||
[data-theme="dark"] .tool-detail-panel-footer {
|
||||
background: rgba(102, 126, 234, 0.08);
|
||||
border-color: #334155;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tool-detail-panel-title {
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .td-panel-dot {
|
||||
background: #475569;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .td-back-btn {
|
||||
color: #94a3b8 !important;
|
||||
border-color: #334155 !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .td-back-btn:hover {
|
||||
color: #818cf8 !important;
|
||||
border-color: #818cf8 !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .td-tip-card {
|
||||
background: #1e293b;
|
||||
border-color: #334155;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .td-tip-content h4 {
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .td-tip-content p {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .td-char-count {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .td-action-danger {
|
||||
color: #f87171 !important;
|
||||
border-color: #f87171 !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .td-action-danger:hover {
|
||||
background: rgba(239, 68, 68, 0.1) !important;
|
||||
}
|
||||
|
||||
/* JsonFormatter 特有样式 */
|
||||
[data-theme="dark"] .json-view-tabs .ant-tabs-nav {
|
||||
background: #181825;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .json-view-tabs .ant-tabs-tab {
|
||||
color: #6c7086 !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .json-view-tabs .ant-tabs-tab-active .ant-tabs-tab-btn {
|
||||
color: #89dceb !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .json-view-tabs .ant-tabs-ink-bar {
|
||||
background: #89dceb;
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
import "./JsonFormatter.css";
|
||||
import React, { useState } from "react";
|
||||
import { Button, Tag, Tabs } from "antd";
|
||||
import { message } from "@/utils/message";
|
||||
import {
|
||||
CopyOutlined,
|
||||
ClearOutlined,
|
||||
CodeOutlined,
|
||||
CheckCircleOutlined,
|
||||
ThunderboltOutlined,
|
||||
BookOutlined,
|
||||
ArrowLeftOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { JSONTree } from "react-json-tree";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useRecordHistory } from "@/hooks/useRecordHistory";
|
||||
import ToolFavoriteButton from "@/components/ToolFavoriteButton/ToolFavoriteButton";
|
||||
|
||||
const treeTheme = {
|
||||
scheme: "custom",
|
||||
author: "custom",
|
||||
base00: "#1e1e2e",
|
||||
base01: "#313244",
|
||||
base02: "#45475a",
|
||||
base03: "#585b70",
|
||||
base04: "#6c7086",
|
||||
base05: "#cdd6f4",
|
||||
base06: "#cdd6f4",
|
||||
base07: "#cdd6f4",
|
||||
base08: "#f38ba8",
|
||||
base09: "#fab387",
|
||||
base0A: "#f9e2af",
|
||||
base0B: "#a6e3a1",
|
||||
base0C: "#89dceb",
|
||||
base0D: "#89b4fa",
|
||||
base0E: "#cba6f7",
|
||||
base0F: "#fab387",
|
||||
};
|
||||
|
||||
function syntaxHighlight(json: string): string {
|
||||
const escaped = json
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
return escaped.replace(
|
||||
/("(\\u[\da-fA-F]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
|
||||
(match) => {
|
||||
let cls = "json-number";
|
||||
if (/^"/.test(match)) {
|
||||
if (/:$/.test(match)) {
|
||||
cls = "json-key";
|
||||
const key = match.replace(/:$/, "");
|
||||
return `<span class="${cls}">${key}</span>:`;
|
||||
} else {
|
||||
cls = "json-string";
|
||||
}
|
||||
} else if (/true|false/.test(match)) {
|
||||
cls = "json-boolean";
|
||||
} else if (/null/.test(match)) {
|
||||
cls = "json-null";
|
||||
}
|
||||
return `<span class="${cls}">${match}</span>`;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const JsonFormatter: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [input, setInput] = useState("");
|
||||
|
||||
useRecordHistory({
|
||||
type: "tool",
|
||||
title: t("jsonFormatter.heroTitle"),
|
||||
description: t("jsonFormatter.heroSubtitle"),
|
||||
category: t("jsonFormatter.category"),
|
||||
link: "/json-formatter",
|
||||
});
|
||||
const [output, setOutput] = useState("");
|
||||
const [parsedData, setParsedData] = useState<any>(null);
|
||||
const [isError, setIsError] = useState(false);
|
||||
const [viewMode, setViewMode] = useState<"code" | "tree">("code");
|
||||
|
||||
const handleFormat = () => {
|
||||
if (!input.trim()) {
|
||||
message.warning(t("jsonFormatter.pleaseEnterJson"));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(input);
|
||||
setOutput(JSON.stringify(parsed, null, 2));
|
||||
setParsedData(parsed);
|
||||
setIsError(false);
|
||||
message.success(t("jsonFormatter.formatSuccess"));
|
||||
} catch (e: any) {
|
||||
setOutput(`${t("jsonFormatter.parseError")}: ${e.message}`);
|
||||
setParsedData(null);
|
||||
setIsError(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCompress = () => {
|
||||
if (!input.trim()) {
|
||||
message.warning(t("jsonFormatter.pleaseEnterJson"));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(input);
|
||||
setOutput(JSON.stringify(parsed));
|
||||
setParsedData(parsed);
|
||||
setIsError(false);
|
||||
message.success(t("jsonFormatter.compressSuccess"));
|
||||
} catch (e: any) {
|
||||
setOutput(`${t("jsonFormatter.parseError")}: ${e.message}`);
|
||||
setParsedData(null);
|
||||
setIsError(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopy = () => {
|
||||
if (!output) {
|
||||
message.warning(t("jsonFormatter.noContentToCopy"));
|
||||
return;
|
||||
}
|
||||
navigator.clipboard.writeText(output).then(() => {
|
||||
message.success(t("jsonFormatter.copiedToClipboard"));
|
||||
});
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
setInput("");
|
||||
setOutput("");
|
||||
setParsedData(null);
|
||||
setIsError(false);
|
||||
message.info(t("jsonFormatter.cleared"));
|
||||
};
|
||||
|
||||
const handleSample = () => {
|
||||
const sample = JSON.stringify(
|
||||
{
|
||||
name: t("jsonFormatter.sampleName"),
|
||||
version: "2.0",
|
||||
features: [
|
||||
t("jsonFormatter.sampleFeature1"),
|
||||
t("jsonFormatter.sampleFeature2"),
|
||||
t("jsonFormatter.sampleFeature3"),
|
||||
],
|
||||
stats: { users: 128000, articles: 2680 },
|
||||
isActive: true,
|
||||
metadata: null,
|
||||
},
|
||||
null,
|
||||
2
|
||||
);
|
||||
setInput(sample);
|
||||
message.info(t("jsonFormatter.sampleLoaded"));
|
||||
};
|
||||
|
||||
const tabItems = [
|
||||
{
|
||||
key: "code",
|
||||
label: t("jsonFormatter.codeView"),
|
||||
children: (
|
||||
<div
|
||||
className={`json-code-output ${isError ? "output-error" : ""} ${
|
||||
!output ? "output-empty" : ""
|
||||
}`}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: output
|
||||
? isError
|
||||
? output
|
||||
: syntaxHighlight(output)
|
||||
: t("jsonFormatter.resultPlaceholder"),
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "tree",
|
||||
label: t("jsonFormatter.treeView"),
|
||||
children: (
|
||||
<div className="json-tree-output">
|
||||
{parsedData ? (
|
||||
<JSONTree data={parsedData} theme={treeTheme} invertTheme={false} />
|
||||
) : (
|
||||
<span className="tree-placeholder">
|
||||
{t("jsonFormatter.treePlaceholder")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="tool-detail-page">
|
||||
<div className="tool-detail-hero">
|
||||
<div className="tool-detail-hero-particles">
|
||||
{Array.from({ length: 6 }).map((_, i) => (
|
||||
<div key={i} className={`td-particle td-particle-${i + 1}`} />
|
||||
))}
|
||||
</div>
|
||||
<div className="tool-detail-hero-content">
|
||||
<div className="tool-detail-hero-icon">
|
||||
<CodeOutlined />
|
||||
</div>
|
||||
<div className="tool-detail-hero-title-wrapper">
|
||||
<h1 className="tool-detail-hero-title">
|
||||
{t("jsonFormatter.heroTitle")}
|
||||
</h1>
|
||||
<ToolFavoriteButton urlPath="/utility/json-formatter" variant="hero" />
|
||||
</div>
|
||||
<p className="tool-detail-hero-desc">
|
||||
{t("jsonFormatter.heroSubtitle")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="tool-detail-container">
|
||||
<div className="tool-detail-toolbar">
|
||||
<div className="tool-detail-toolbar-left">
|
||||
<Button
|
||||
icon={<ArrowLeftOutlined />}
|
||||
className="td-back-btn"
|
||||
onClick={() => navigate("/utility")}
|
||||
>
|
||||
{t("jsonFormatter.backToTools")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="tool-detail-actions">
|
||||
<Button
|
||||
className="td-action-btn td-action-primary"
|
||||
type="primary"
|
||||
icon={<CheckCircleOutlined />}
|
||||
onClick={handleFormat}
|
||||
>
|
||||
{t("jsonFormatter.format")}
|
||||
</Button>
|
||||
<Button
|
||||
className="td-action-btn"
|
||||
icon={<ThunderboltOutlined />}
|
||||
onClick={handleCompress}
|
||||
>
|
||||
{t("jsonFormatter.compress")}
|
||||
</Button>
|
||||
<Button
|
||||
className="td-action-btn"
|
||||
icon={<CopyOutlined />}
|
||||
onClick={handleCopy}
|
||||
>
|
||||
{t("jsonFormatter.copyResult")}
|
||||
</Button>
|
||||
<Button
|
||||
className="td-action-btn"
|
||||
icon={<BookOutlined />}
|
||||
onClick={handleSample}
|
||||
>
|
||||
{t("jsonFormatter.sample")}
|
||||
</Button>
|
||||
<Button
|
||||
className="td-action-btn td-action-danger"
|
||||
icon={<ClearOutlined />}
|
||||
onClick={handleClear}
|
||||
>
|
||||
{t("jsonFormatter.clear")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="tool-detail-panels">
|
||||
<div className="tool-detail-panel">
|
||||
<div className="tool-detail-panel-header">
|
||||
<h3 className="tool-detail-panel-title">
|
||||
<span className="td-panel-dot input-dot" />
|
||||
{t("jsonFormatter.input")}
|
||||
</h3>
|
||||
<Tag color="blue">JSON</Tag>
|
||||
</div>
|
||||
<div className="tool-detail-panel-body">
|
||||
<textarea
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
placeholder={t("jsonFormatter.inputPlaceholder")}
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="tool-detail-panel-footer">
|
||||
<span className="td-char-count">
|
||||
{input.length} {t("jsonFormatter.characters")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="tool-detail-panel">
|
||||
<div className="tool-detail-panel-header">
|
||||
<h3 className="tool-detail-panel-title">
|
||||
<span
|
||||
className={`td-panel-dot ${
|
||||
isError
|
||||
? "error-dot"
|
||||
: output
|
||||
? "success-dot"
|
||||
: ""
|
||||
}`}
|
||||
/>
|
||||
{t("jsonFormatter.output")}
|
||||
</h3>
|
||||
{output && !isError && (
|
||||
<Tag color="green">{t("jsonFormatter.valid")}</Tag>
|
||||
)}
|
||||
{output && isError && (
|
||||
<Tag color="red">{t("jsonFormatter.error")}</Tag>
|
||||
)}
|
||||
</div>
|
||||
<div className="tool-detail-panel-body json-output-panel-body">
|
||||
<Tabs
|
||||
activeKey={viewMode}
|
||||
onChange={(key) => setViewMode(key as "code" | "tree")}
|
||||
className="json-view-tabs"
|
||||
size="small"
|
||||
items={tabItems}
|
||||
/>
|
||||
</div>
|
||||
<div className="tool-detail-panel-footer">
|
||||
<span className="td-char-count">
|
||||
{output.length} {t("jsonFormatter.characters")}
|
||||
</span>
|
||||
{output && !isError && (
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<CopyOutlined />}
|
||||
onClick={handleCopy}
|
||||
className="td-copy-inline"
|
||||
>
|
||||
{t("jsonFormatter.copy")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="tool-detail-tips">
|
||||
<div className="td-tip-card">
|
||||
<div className="td-tip-icon">💡</div>
|
||||
<div className="td-tip-content">
|
||||
<h4>{t("jsonFormatter.tipUsageTitle")}</h4>
|
||||
<p>{t("jsonFormatter.tipUsageText")}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="td-tip-card">
|
||||
<div className="td-tip-icon">⚡</div>
|
||||
<div className="td-tip-content">
|
||||
<h4>{t("jsonFormatter.tipQuickTitle")}</h4>
|
||||
<p>{t("jsonFormatter.tipQuickText")}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="td-tip-card">
|
||||
<div className="td-tip-icon">🔒</div>
|
||||
<div className="td-tip-content">
|
||||
<h4>{t("jsonFormatter.tipSecurityTitle")}</h4>
|
||||
<p>{t("jsonFormatter.tipSecurityText")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default JsonFormatter;
|
||||
@@ -0,0 +1,233 @@
|
||||
.jf-mobile-page {
|
||||
background: #f0f2f5;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.jf-mobile-hero {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 12px 16px 24px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.jf-mobile-hero-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.jf-mobile-back-btn {
|
||||
color: white !important;
|
||||
font-size: 18px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50% !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.jf-mobile-back-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.15) !important;
|
||||
}
|
||||
|
||||
.jf-mobile-hero-title {
|
||||
margin: 0 0 4px;
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.jf-mobile-hero-subtitle {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
opacity: 0.9;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.jf-mobile-content {
|
||||
padding: 12px 12px 0;
|
||||
}
|
||||
|
||||
.jf-mobile-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
padding-bottom: 4px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.jf-mobile-actions::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jf-mobile-action-btn {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 10px 16px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid #e2e8f0;
|
||||
background: white;
|
||||
color: #6366f1;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.jf-mobile-action-btn:active {
|
||||
background: #f1f5f9;
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
.jf-mobile-action-btn--danger {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.jf-mobile-action-btn--danger:active {
|
||||
background: #fef2f2;
|
||||
}
|
||||
|
||||
.jf-mobile-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.jf-mobile-char-count {
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.jf-mobile-section {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.jf-mobile-label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #475569;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.jf-mobile-textarea {
|
||||
width: 100%;
|
||||
min-height: 140px;
|
||||
padding: 12px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
background: #1e1e2e;
|
||||
color: #cdd6f4;
|
||||
font-family: "Fira Code", "Cascadia Code", "JetBrains Mono", monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
resize: vertical;
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.jf-mobile-textarea::placeholder {
|
||||
color: #6c7086;
|
||||
}
|
||||
|
||||
.jf-mobile-textarea:focus {
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.15);
|
||||
}
|
||||
|
||||
.jf-mobile-output {
|
||||
background: #1e1e2e;
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
min-height: 120px;
|
||||
max-height: 360px;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.jf-mobile-output-pre {
|
||||
margin: 0;
|
||||
font-family: "Fira Code", "Cascadia Code", "JetBrains Mono", monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: #cdd6f4;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.jf-mobile-output-placeholder {
|
||||
color: #6c7086;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.json-key {
|
||||
color: #89dceb;
|
||||
}
|
||||
|
||||
.json-string {
|
||||
color: #a6e3a1;
|
||||
}
|
||||
|
||||
.json-number {
|
||||
color: #fab387;
|
||||
}
|
||||
|
||||
.json-boolean {
|
||||
color: #89b4fa;
|
||||
}
|
||||
|
||||
.json-null {
|
||||
color: #f38ba8;
|
||||
}
|
||||
|
||||
@media (max-width: 320px) {
|
||||
.jf-mobile-action-btn {
|
||||
padding: 8px 12px;
|
||||
font-size: 12px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.jf-mobile-textarea {
|
||||
font-size: 12px;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.jf-mobile-output {
|
||||
min-height: 100px;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.jf-mobile-output-pre {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme="dark"] .jf-mobile-page {
|
||||
background: #0f172a;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .jf-mobile-action-btn {
|
||||
border-color: #334155;
|
||||
background: #1e293b;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .jf-mobile-action-btn:active {
|
||||
background: #334155;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .jf-mobile-action-btn--danger:active {
|
||||
background: #451a1a;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .jf-mobile-label {
|
||||
color: #94a3b8;
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
import React, { useState } from "react";
|
||||
import { Button, Tag } from "antd";
|
||||
import { message } from "@/utils/message";
|
||||
import {
|
||||
ArrowLeftOutlined, FormatPainterOutlined, CompressOutlined,
|
||||
CopyOutlined, FileTextOutlined, DeleteOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import "./JsonFormatterMobile.css";
|
||||
import ToolFavoriteButton from "@/components/ToolFavoriteButton/ToolFavoriteButton";
|
||||
|
||||
function highlightJson(json: string): string {
|
||||
const escaped = json
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
|
||||
return escaped.replace(
|
||||
/("(\\u[\da-fA-F]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)/g,
|
||||
(match) => {
|
||||
let cls = "json-number";
|
||||
if (/^"/.test(match)) {
|
||||
if (/:$/.test(match)) {
|
||||
cls = "json-key";
|
||||
} else {
|
||||
cls = "json-string";
|
||||
}
|
||||
} else if (/true|false/.test(match)) {
|
||||
cls = "json-boolean";
|
||||
} else if (/null/.test(match)) {
|
||||
cls = "json-null";
|
||||
}
|
||||
return `<span class="${cls}">${match}</span>`;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const JsonFormatterMobile: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [input, setInput] = useState<string>("");
|
||||
const [output, setOutput] = useState<string>("");
|
||||
const [isError, setIsError] = useState<boolean>(false);
|
||||
|
||||
const getSampleData = () => ({
|
||||
name: t("jsonFormatter.sampleName"),
|
||||
age: 28,
|
||||
isActive: true,
|
||||
address: {
|
||||
city: t("jsonFormatter.sampleCity"),
|
||||
district: t("jsonFormatter.sampleDistrict"),
|
||||
zipCode: null,
|
||||
},
|
||||
hobbies: [
|
||||
t("jsonFormatter.sampleHobby1"),
|
||||
t("jsonFormatter.sampleHobby2"),
|
||||
t("jsonFormatter.sampleHobby3"),
|
||||
],
|
||||
education: {
|
||||
degree: t("jsonFormatter.sampleDegree"),
|
||||
school: t("jsonFormatter.sampleSchool"),
|
||||
graduated: false,
|
||||
},
|
||||
});
|
||||
|
||||
const handleFormat = () => {
|
||||
if (!input.trim()) {
|
||||
message.warning(t("jsonFormatter.pleaseEnterJson"));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(input);
|
||||
const formatted = JSON.stringify(parsed, null, 2);
|
||||
setOutput(formatted);
|
||||
setIsError(false);
|
||||
message.success(t("jsonFormatter.formatSuccess"));
|
||||
} catch (e) {
|
||||
setOutput((e as Error).message);
|
||||
setIsError(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCompress = () => {
|
||||
if (!input.trim()) {
|
||||
message.warning(t("jsonFormatter.pleaseEnterJson"));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(input);
|
||||
const compressed = JSON.stringify(parsed);
|
||||
setOutput(compressed);
|
||||
setIsError(false);
|
||||
message.success(t("jsonFormatter.compressSuccess"));
|
||||
} catch (e) {
|
||||
setOutput((e as Error).message);
|
||||
setIsError(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopy = () => {
|
||||
if (!output) {
|
||||
message.warning(t("jsonFormatter.noContentToCopy"));
|
||||
return;
|
||||
}
|
||||
navigator.clipboard.writeText(output).then(() => {
|
||||
message.success(t("jsonFormatter.copiedToClipboard"));
|
||||
});
|
||||
};
|
||||
|
||||
const handleSample = () => {
|
||||
const sample = JSON.stringify(getSampleData(), null, 2);
|
||||
setInput(sample);
|
||||
message.success(t("jsonFormatter.sampleLoaded"));
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
setInput("");
|
||||
setOutput("");
|
||||
setIsError(false);
|
||||
message.info(t("jsonFormatter.cleared"));
|
||||
};
|
||||
|
||||
const hasOutput = output && !isError;
|
||||
|
||||
return (
|
||||
<div className="jf-mobile-page">
|
||||
<div className="jf-mobile-hero">
|
||||
<div className="jf-mobile-hero-nav">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={() => navigate("/utility")}
|
||||
className="jf-mobile-back-btn"
|
||||
/>
|
||||
</div>
|
||||
<div className="jf-mobile-hero-title-row">
|
||||
<h1 className="jf-mobile-hero-title">
|
||||
{t("jsonFormatter.heroTitle")}
|
||||
</h1>
|
||||
<ToolFavoriteButton urlPath="/utility/json-formatter" variant="hero" />
|
||||
</div>
|
||||
<p className="jf-mobile-hero-subtitle">
|
||||
{t("jsonFormatter.heroSubtitle")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="jf-mobile-content">
|
||||
<div className="jf-mobile-actions">
|
||||
<button className="jf-mobile-action-btn" onClick={handleFormat}>
|
||||
<FormatPainterOutlined />
|
||||
<span>{t("jsonFormatter.format")}</span>
|
||||
</button>
|
||||
<button className="jf-mobile-action-btn" onClick={handleCompress}>
|
||||
<CompressOutlined />
|
||||
<span>{t("jsonFormatter.compress")}</span>
|
||||
</button>
|
||||
<button className="jf-mobile-action-btn" onClick={handleCopy}>
|
||||
<CopyOutlined />
|
||||
<span>{t("jsonFormatter.copy")}</span>
|
||||
</button>
|
||||
<button className="jf-mobile-action-btn" onClick={handleSample}>
|
||||
<FileTextOutlined />
|
||||
<span>{t("jsonFormatter.sample")}</span>
|
||||
</button>
|
||||
<button className="jf-mobile-action-btn jf-mobile-action-btn--danger" onClick={handleClear}>
|
||||
<DeleteOutlined />
|
||||
<span>{t("jsonFormatter.clear")}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{input && (
|
||||
<div className="jf-mobile-status">
|
||||
<Tag color={isError ? "error" : "success"}>
|
||||
{isError
|
||||
? t("jsonFormatter.formatError")
|
||||
: t("jsonFormatter.formatValid")}
|
||||
</Tag>
|
||||
<span className="jf-mobile-char-count">
|
||||
{input.length} {t("jsonFormatter.characters")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="jf-mobile-section">
|
||||
<label className="jf-mobile-label">
|
||||
{t("jsonFormatter.inputJson")}
|
||||
</label>
|
||||
<textarea
|
||||
className="jf-mobile-textarea"
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
placeholder={t("jsonFormatter.mobileInputPlaceholder")}
|
||||
rows={6}
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="jf-mobile-section">
|
||||
<label className="jf-mobile-label">
|
||||
{t("jsonFormatter.outputResult")}
|
||||
</label>
|
||||
<div className="jf-mobile-output">
|
||||
{hasOutput ? (
|
||||
<pre
|
||||
className="jf-mobile-output-pre"
|
||||
dangerouslySetInnerHTML={{ __html: highlightJson(output) }}
|
||||
/>
|
||||
) : (
|
||||
<span className="jf-mobile-output-placeholder">
|
||||
{isError ? output : t("jsonFormatter.mobileResultPlaceholder")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default JsonFormatterMobile;
|
||||
Reference in New Issue
Block a user