Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions app/components/Icons/StringIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
export function StringIcon(props: React.SVGProps<SVGSVGElement>) {
import React from "react";

export function StringIcon({ className }: { className?: string }) {
return (
<svg
className={props.className}
viewBox="-2 -5 24 24"
className={className}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4.536 6.845a3.908 3.908 0 0 1 2.598.566l-.025-.032a4.114 4.114 0 0 1 1.766 2.478 4.228 4.228 0 0 1-.391 3.047 4.026 4.026 0 0 1-2.33 1.92 3.898 3.898 0 0 1-2.973-.273h-.03a1.296 1.296 0 0 0-.082-.045c-.033-.018-.066-.035-.096-.056a4.236 4.236 0 0 1-.99-.88A7.746 7.746 0 0 1 .095 9.743a8.717 8.717 0 0 1 .191-3.498c.3-1.14.827-2.203 1.55-3.12A8.282 8.282 0 0 1 4.477.918 8.047 8.047 0 0 1 7.763 0c.287 0 .562.117.765.326.203.209.317.492.317.787 0 .296-.114.579-.317.788a1.066 1.066 0 0 1-.765.326c-.96.04-1.895.332-2.716.847A5.758 5.758 0 0 0 3.07 5.17a6.53 6.53 0 0 0-.905 2.906 3.962 3.962 0 0 1 2.37-1.232ZM15.53 6.83c.901-.12 1.815.079 2.591.565h-.006a4.105 4.105 0 0 1 1.761 2.473 4.22 4.22 0 0 1-.39 3.04 4.016 4.016 0 0 1-2.324 1.917 3.886 3.886 0 0 1-2.966-.273h-.036c-.03-.021-.063-.038-.097-.056a1.317 1.317 0 0 1-.08-.045 4.226 4.226 0 0 1-.987-.879 7.782 7.782 0 0 1-1.902-3.848 8.715 8.715 0 0 1 .194-3.49 8.564 8.564 0 0 1 1.546-3.111A8.276 8.276 0 0 1 15.469.92 8.037 8.037 0 0 1 18.742 0c.286 0 .56.117.763.325.202.209.316.491.316.786 0 .295-.114.577-.316.786a1.063 1.063 0 0 1-.763.325 5.526 5.526 0 0 0-2.707.846 5.738 5.738 0 0 0-1.968 2.092 6.519 6.519 0 0 0-.902 2.9 3.952 3.952 0 0 1 2.364-1.23Z"
fill="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z"
/>
</svg>
);
Expand Down
223 changes: 223 additions & 0 deletions app/components/JsonStringView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import { useJson } from "~/hooks/useJson";
import { useJsonDoc } from "~/hooks/useJsonDoc";
import { CopyTextButton } from "./CopyTextButton";
import { Body } from "./Primitives/Body";
import { Mono } from "./Primitives/Mono";
import { SmallTitle } from "./Primitives/SmallTitle";
import { useState } from "react";

export function JsonStringView() {
const [json] = useJson();
const { minimal } = useJsonDoc();
const [formatType, setFormatType] = useState<"java" | "csharp" | "javascript" | "python" | "go" | "c" | "cpp" | "rust">("java");

// 智能转义函数,避免重复转义
const smartEscape = (str: string): string => {
// 检查是否包含已经转义的JSON字符串(包含 \\" 模式)
const hasEscapedJson = /\\"/.test(str);

if (hasEscapedJson) {
// 如果包含已转义的JSON,我们需要特殊处理
// 将 \\" 替换为 \",然后进行正常转义
return str
.replace(/\\\\"/g, '\\"') // 将 \\" 替换为 \"
.replace(/\\/g, '\\\\') // 转义反斜杠
.replace(/"/g, '\\"'); // 转义双引号
}

// 否则进行正常转义
return str
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"');
};

// 将JSON转换为Java String格式(压缩成一行)
const convertToJavaString = (obj: any): string => {
const jsonString = JSON.stringify(obj); // 压缩成一行
console.log('Java conversion - original JSON string:', jsonString);
const result = `String jsonString = "${smartEscape(jsonString)}";`;
console.log('Java conversion - final result:', result);
return result;
};

// 将JSON转换为C# String格式(压缩成一行)
const convertToCSharpString = (obj: any): string => {
const jsonString = JSON.stringify(obj); // 压缩成一行
return `string jsonString = "${smartEscape(jsonString)}";`;
};

// 将JSON转换为JavaScript String格式(压缩成一行)
const convertToJavaScriptString = (obj: any): string => {
const jsonString = JSON.stringify(obj); // 压缩成一行
return `const jsonString = "${smartEscape(jsonString)}";`;
};

// 将JSON转换为Python String格式(压缩成一行)
const convertToPythonString = (obj: any): string => {
const jsonString = JSON.stringify(obj); // 压缩成一行
return `json_string = "${smartEscape(jsonString)}"`;
};

// 将JSON转换为Go String格式(压缩成一行)
const convertToGoString = (obj: any): string => {
const jsonString = JSON.stringify(obj); // 压缩成一行
return `jsonString := "${smartEscape(jsonString)}"`;
};

// 将JSON转换为C String格式(压缩成一行)
const convertToCString = (obj: any): string => {
const jsonString = JSON.stringify(obj); // 压缩成一行
return `char* jsonString = "${smartEscape(jsonString)}";`;
};

// 将JSON转换为C++ String格式(压缩成一行)
const convertToCppString = (obj: any): string => {
const jsonString = JSON.stringify(obj); // 压缩成一行
return `std::string jsonString = "${smartEscape(jsonString)}";`;
};

// 将JSON转换为Rust String格式(压缩成一行)
const convertToRustString = (obj: any): string => {
const jsonString = JSON.stringify(obj); // 压缩成一行
return `let json_string = "${smartEscape(jsonString)}";`;
};

// 根据选择的格式生成对应的字符串
const getFormattedString = () => {
switch (formatType) {
case "java":
return convertToJavaString(json);
case "csharp":
return convertToCSharpString(json);
case "javascript":
return convertToJavaScriptString(json);
case "python":
return convertToPythonString(json);
case "go":
return convertToGoString(json);
case "c":
return convertToCString(json);
case "cpp":
return convertToCppString(json);
case "rust":
return convertToRustString(json);
default:
return convertToJavaString(json);
}
};

const formattedString = getFormattedString();
const containerHeight = minimal ? "calc(100vh - 66px)" : "calc(100vh - 106px)";

return (
<div className="flex flex-col h-full">
{/* 控制栏 */}
<div className="flex items-center justify-between p-4 border-b border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800">
<div className="flex items-center space-x-4">
<SmallTitle>Code String Format</SmallTitle>
<div className="flex items-center space-x-2 flex-wrap">
<button
onClick={() => setFormatType("java")}
className={`px-3 py-1 rounded-sm text-sm font-medium transition ${
formatType === "java"
? "bg-lime-500 text-white"
: "bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-600"
}`}
>
Java
</button>
<button
onClick={() => setFormatType("csharp")}
className={`px-3 py-1 rounded-sm text-sm font-medium transition ${
formatType === "csharp"
? "bg-lime-500 text-white"
: "bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-600"
}`}
>
C#
</button>
<button
onClick={() => setFormatType("javascript")}
className={`px-3 py-1 rounded-sm text-sm font-medium transition ${
formatType === "javascript"
? "bg-lime-500 text-white"
: "bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-600"
}`}
>
JS
</button>
<button
onClick={() => setFormatType("python")}
className={`px-3 py-1 rounded-sm text-sm font-medium transition ${
formatType === "python"
? "bg-lime-500 text-white"
: "bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-600"
}`}
>
Python
</button>
<button
onClick={() => setFormatType("go")}
className={`px-3 py-1 rounded-sm text-sm font-medium transition ${
formatType === "go"
? "bg-lime-500 text-white"
: "bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-600"
}`}
>
Go
</button>
<button
onClick={() => setFormatType("c")}
className={`px-3 py-1 rounded-sm text-sm font-medium transition ${
formatType === "c"
? "bg-lime-500 text-white"
: "bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-600"
}`}
>
C
</button>
<button
onClick={() => setFormatType("cpp")}
className={`px-3 py-1 rounded-sm text-sm font-medium transition ${
formatType === "cpp"
? "bg-lime-500 text-white"
: "bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-600"
}`}
>
C++
</button>
<button
onClick={() => setFormatType("rust")}
className={`px-3 py-1 rounded-sm text-sm font-medium transition ${
formatType === "rust"
? "bg-lime-500 text-white"
: "bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-600"
}`}
>
Rust
</button>
</div>
</div>

<div className="flex items-center space-x-2">
<Body className="text-slate-600 dark:text-slate-400">
{formattedString.length} characters
</Body>
<CopyTextButton value={formattedString} />
</div>
</div>

{/* 代码字符串显示区域 */}
<div
className="flex-1 overflow-auto p-4"
style={{ height: containerHeight }}
>
<div className="bg-slate-900 rounded-sm p-4 h-full">
<Mono className="text-slate-200 text-sm whitespace-pre-wrap break-all">
{formattedString}
</Mono>
</div>
</div>
</div>
);
}
13 changes: 13 additions & 0 deletions app/components/SideBar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { TemplateIcon, CodeIcon, DownloadIcon } from "@heroicons/react/outline";
import { TreeIcon } from "~/components/Icons/TreeIcon";
import { StringIcon } from "~/components/Icons/StringIcon";
import { useHotkeys } from "react-hotkeys-hook";
import { Link, useLocation, useNavigate } from "remix";
import { useJsonDoc } from "~/hooks/useJsonDoc";
Expand Down Expand Up @@ -50,6 +51,18 @@ export function SideBar() {
</ToolTip>
<TreeIcon className="p-2 w-full h-full" />
</SidebarLink>
<SidebarLink to={`/j/${doc.id}/string`} hotKey="option+4,alt+4">
<ToolTip arrow="left">
<Body>Code String</Body>
<ShortcutIcon className="w-[26px] h-[26px] ml-1 text-slate-700 bg-slate-200 dark:text-slate-300 dark:bg-slate-800">
</ShortcutIcon>
<ShortcutIcon className="w-[26px] h-[26px] ml-1 text-slate-700 bg-slate-200 dark:text-slate-300 dark:bg-slate-800">
4
</ShortcutIcon>
</ToolTip>
<StringIcon className="p-2 w-full h-full" />
</SidebarLink>
</ol>
<ol>
<SidebarLink>
Expand Down
30 changes: 16 additions & 14 deletions app/components/UrlForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,28 @@ export function UrlForm({ className }: UrlFormProps) {
action="/actions/createFromUrl"
className={`${className}`}
>
<div className="flex">
<input
type="text"
<div className="flex flex-col space-y-3">
<textarea
name="jsonUrl"
id="jsonUrl"
className="block flex-grow text-base text-slate-200 placeholder:text-slate-300 bg-slate-900/40 border border-slate-600 rounded-l-sm py-2 px-3 transition duration-300 focus:ring-indigo-500 focus:border-indigo-500"
rows={8}
className="block w-full text-base text-slate-200 placeholder:text-slate-300 bg-slate-900/40 border border-slate-600 rounded-sm py-3 px-4 transition duration-300 focus:ring-indigo-500 focus:border-indigo-500 resize-y min-h-[200px] font-mono text-sm"
placeholder="Enter a JSON URL or paste in JSON here..."
value={inputValue}
onChange={(event) => setInputValue(event.target.value)}
/>
<button
type="submit"
value="Go"
className={`inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-r-sm text-white bg-lime-500 transition hover:bg-lime-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-lime-500 ${
isButtonDisabled && "disabled:opacity-50 disabled:hover:bg-lime-500"
}`}
disabled={isButtonDisabled}
>
{isNotIdle ? "..." : "Go"}
</button>
<div className="flex justify-end">
<button
type="submit"
value="Go"
className={`inline-flex items-center justify-center px-6 py-2 border border-transparent font-medium rounded-sm text-white bg-lime-500 transition hover:bg-lime-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-lime-500 ${
isButtonDisabled && "disabled:opacity-50 disabled:hover:bg-lime-500"
}`}
disabled={isButtonDisabled}
>
{isNotIdle ? "..." : "Go"}
</button>
</div>
</div>
</Form>
);
Expand Down
5 changes: 5 additions & 0 deletions app/routes/j/$id/string.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { JsonStringView } from "~/components/JsonStringView";

export default function StringViewPage() {
return <JsonStringView />;
}
62 changes: 54 additions & 8 deletions app/utilities/safeFetch.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,58 @@
export default function safeFetch(
/*
* @Author: wangwendong1024 [email protected]
* @Date: 2025-08-26 20:12:00
* @LastEditors: wangwendong1024 [email protected]
* @LastEditTime: 2025-08-27 20:33:54
* @FilePath: \jsonhero-web\app\utilities\safeFetch.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
export default async function safeFetch(
url: string,
options: RequestInit = {}
options: RequestInit = {},
retries = 3,
timeout = 5000
): Promise<Response> {
return fetch(url, {
...options,
headers: {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
...options.headers,
},
// Log request details
console.log(`[${new Date().toISOString()}] Request:`, {
method: options.method || 'GET',
url,
headers: options.headers,
body: options.body ? JSON.parse(options.body.toString()) : undefined
});

for (let i = 0; i < retries; i++) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);

const response = await fetch(url, {
...options,
signal: controller.signal,
headers: {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
...options.headers,
},
});

// Log response details
console.log(`[${new Date().toISOString()}] Response:`, {
status: response.status,
statusText: response.statusText,
url: response.url
});

clearTimeout(timeoutId);
return response;
} catch (error) {
console.error(`[${new Date().toISOString()}] Attempt ${i + 1} failed:`, error);

if (i === retries - 1) {
console.error(`[${new Date().toISOString()}] Max retries reached for ${url}`);
throw error;
}

await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
throw new Error("Max retries reached");
}