Improve ObjectEditor and Add TableEditor

This commit is contained in:
dwindown
2025-09-23 14:17:13 +07:00
parent cf750114f7
commit 977e784df2
15 changed files with 5329 additions and 345 deletions

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Search, Code, Link2, FileText, Hash, RefreshCw, GitCompare, Type, Edit3 } from 'lucide-react';
import { Search, Code, Link2, FileText, Hash, RefreshCw, GitCompare, Type, Edit3, Table } from 'lucide-react';
import ToolCard from '../components/ToolCard';
const Home = () => {
@@ -14,6 +14,13 @@ const Home = () => {
path: '/object-editor',
tags: ['Visual', 'JSON', 'PHP', 'Objects', 'Editor']
},
{
icon: Table,
title: 'Table Editor',
description: 'Import, edit, and export tabular data from URLs, files, or paste CSV/JSON',
path: '/table-editor',
tags: ['Table', 'CSV', 'JSON', 'Data', 'Editor']
},
{
icon: Link2,
title: 'URL Encoder/Decoder',
@@ -28,13 +35,6 @@ const Home = () => {
path: '/base64',
tags: ['Base64', 'Encode', 'Binary']
},
{
icon: RefreshCw,
title: 'CSV ↔ JSON Converter',
description: 'Convert between CSV and JSON formats with custom delimiters',
path: '/csv-json',
tags: ['CSV', 'JSON', 'Convert']
},
{
icon: FileText,
title: 'Code Beautifier/Minifier',

File diff suppressed because it is too large Load Diff

3637
src/pages/TableEditor.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,16 @@
import React, { useState, useEffect } from 'react';
import { Type, Copy, RotateCcw } from 'lucide-react';
import { Type, Copy, RotateCcw, Globe, Download, AlertCircle, CheckCircle, Clock, X } from 'lucide-react';
import ToolLayout from '../components/ToolLayout';
import CopyButton from '../components/CopyButton';
import { extractContentFromUrl, CONTENT_TYPE_INFO } from '../utils/contentExtractor';
const TextLengthTool = () => {
const [text, setText] = useState('');
const [url, setUrl] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [urlResult, setUrlResult] = useState(null);
const [error, setError] = useState('');
const [useArticleOnly, setUseArticleOnly] = useState(true);
const [stats, setStats] = useState({
characters: 0,
charactersNoSpaces: 0,
@@ -65,8 +71,52 @@ const TextLengthTool = () => {
calculateStats();
}, [text]);
// Handle URL fetching
const fetchUrlContent = async () => {
if (!url.trim()) {
setError('Please enter a valid URL');
return;
}
setIsLoading(true);
setError('');
setUrlResult(null);
try {
const result = await extractContentFromUrl(url.trim());
if (result.success) {
setUrlResult(result);
// Set text based on user preference
const textToAnalyze = useArticleOnly ? result.articleText : result.allText;
setText(textToAnalyze);
setError('');
} else {
setError(result.error);
setUrlResult(null);
}
} catch (err) {
let errorMessage = err.message;
if (errorMessage.includes('Failed to fetch')) {
errorMessage = 'Unable to fetch content due to CORS restrictions or network issues';
}
setError(errorMessage);
setUrlResult(null);
} finally {
setIsLoading(false);
}
};
const clearText = () => {
setText('');
setUrlResult(null);
setError('');
};
const clearUrl = () => {
setUrl('');
setUrlResult(null);
setError('');
};
const loadSample = () => {
@@ -101,6 +151,121 @@ Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deseru
description="Analyze text length, word count, and other text statistics"
icon={Type}
>
{/* URL Input Section */}
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4 mb-6">
<div className="flex items-center gap-2 mb-3">
<Globe className="h-5 w-5 text-blue-600 dark:text-blue-400" />
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Analyze Content from URL</h3>
</div>
<div className="space-y-3">
<div className="flex gap-2">
<div className="relative flex-1">
<input
type="url"
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="https://example.com/article"
className="tool-input w-full pr-10"
disabled={isLoading}
/>
{url && !isLoading && (
<button
onClick={clearUrl}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 transition-colors"
>
<X className="h-4 w-4" />
</button>
)}
</div>
<button
onClick={fetchUrlContent}
disabled={isLoading || !url.trim()}
className="bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 text-white font-medium px-4 py-2 rounded-md transition-colors flex items-center whitespace-nowrap"
>
{isLoading ? (
<>
<Clock className="h-4 w-4 mr-2 animate-spin" />
Fetching...
</>
) : (
<>
<Download className="h-4 w-4 mr-2" />
Fetch Content
</>
)}
</button>
</div>
{/* URL Result Status */}
{urlResult && (
<div className="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-3">
<div className="flex items-start gap-3">
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<span className="text-lg">{CONTENT_TYPE_INFO[urlResult.contentType].emoji}</span>
<span className={`font-medium ${CONTENT_TYPE_INFO[urlResult.contentType].color}`}>
{CONTENT_TYPE_INFO[urlResult.contentType].label}
</span>
</div>
<div className="text-sm text-gray-600 dark:text-gray-400 mb-2">
{CONTENT_TYPE_INFO[urlResult.contentType].description}
</div>
{urlResult.title && (
<div className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-1">
{urlResult.title}
</div>
)}
<div className="text-xs text-gray-500 dark:text-gray-400">
Article: {urlResult.metrics.articleWordCount} words
Total: {urlResult.metrics.totalWordCount} words
Ratio: {Math.round(urlResult.metrics.contentRatio * 100)}%
</div>
</div>
<div className="flex items-center gap-2">
<label className="flex items-center text-sm">
<input
type="checkbox"
checked={useArticleOnly}
onChange={(e) => {
setUseArticleOnly(e.target.checked);
const textToAnalyze = e.target.checked ? urlResult.articleText : urlResult.allText;
setText(textToAnalyze);
}}
className="mr-2"
/>
Article Only
</label>
</div>
</div>
</div>
)}
{/* Error Display */}
{error && (
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-3">
<div className="flex items-start gap-2">
<AlertCircle className="h-4 w-4 text-red-600 dark:text-red-400 mt-0.5 flex-shrink-0" />
<div className="flex-1">
<div className="text-sm text-red-700 dark:text-red-300 mb-2">{error}</div>
{error.includes('fetch') && (
<div className="text-xs text-red-600 dark:text-red-400">
<p className="mb-1"><strong>Common solutions:</strong></p>
<ul className="list-disc list-inside space-y-1">
<li>Some websites block cross-origin requests for security</li>
<li>Try copying the article text manually and pasting it below</li>
<li>The site might require JavaScript to load content</li>
<li>Check if the URL is accessible and returns HTML content</li>
</ul>
</div>
)}
</div>
</div>
</div>
)}
</div>
</div>
{/* Controls */}
<div className="flex flex-wrap gap-3 mb-6">
<button onClick={loadSample} className="tool-button-secondary">
@@ -138,9 +303,32 @@ Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deseru
{/* Statistics */}
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
Text Statistics
</h3>
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
Text Statistics
</h3>
{(stats.characters > 0 || stats.words > 0) && (
<button
onClick={() => {
const statsText = `Text Statistics:
Characters: ${formatNumber(stats.characters)}
Characters (no spaces): ${formatNumber(stats.charactersNoSpaces)}
Words: ${formatNumber(stats.words)}
Lines: ${formatNumber(stats.lines)}
Sentences: ${formatNumber(stats.sentences)}
Paragraphs: ${formatNumber(stats.paragraphs)}
Bytes: ${formatNumber(stats.bytes)}
${stats.words > 0 ? `Reading time: ${getReadingTime()}
Typing time: ${getTypingTime()}` : ''}`;
navigator.clipboard.writeText(statsText);
}}
className="flex items-center space-x-2 px-3 py-1.5 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
>
<Copy className="h-4 w-4" />
<span>Copy Statistics</span>
</button>
)}
</div>
{/* Main Stats Grid */}
<div className="grid grid-cols-2 gap-4">
@@ -220,30 +408,6 @@ Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deseru
</div>
)}
{/* Copy Statistics */}
{(stats.characters > 0 || stats.words > 0) && (
<div className="mt-4">
<button
onClick={() => {
const statsText = `Text Statistics:
Characters: ${formatNumber(stats.characters)}
Characters (no spaces): ${formatNumber(stats.charactersNoSpaces)}
Words: ${formatNumber(stats.words)}
Lines: ${formatNumber(stats.lines)}
Sentences: ${formatNumber(stats.sentences)}
Paragraphs: ${formatNumber(stats.paragraphs)}
Bytes: ${formatNumber(stats.bytes)}
${stats.words > 0 ? `Reading time: ${getReadingTime()}
Typing time: ${getTypingTime()}` : ''}`;
navigator.clipboard.writeText(statsText);
}}
className="flex items-center space-x-2 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
>
<Copy className="h-4 w-4" />
<span>Copy Statistics</span>
</button>
</div>
)}
</div>
</div>
@@ -251,11 +415,13 @@ Typing time: ${getTypingTime()}` : ''}`;
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md p-4 mt-6">
<h4 className="text-blue-800 dark:text-blue-200 font-medium mb-2">Usage Tips</h4>
<ul className="text-blue-700 dark:text-blue-300 text-sm space-y-1">
<li> Perfect for checking character limits for social media posts, essays, or articles</li>
<li> Real-time counting updates as you type or paste text</li>
<li> Includes reading and typing time estimates based on average speeds</li>
<li> Byte count shows the actual storage size of your text in UTF-8 encoding</li>
<li> Use "Show Details" to see additional statistics like sentences and paragraphs</li>
<li> <strong>URL Analysis:</strong> Fetch and analyze content from any web page or article</li>
<li> <strong>Smart Content Detection:</strong> Automatically detects articles vs general web content</li>
<li> <strong>Article vs Full Page:</strong> Choose to analyze just the main article or entire page content</li>
<li> <strong>Real-time Counting:</strong> Updates as you type or paste text</li>
<li> <strong>Reading Time:</strong> Estimates based on average reading speed (225 WPM)</li>
<li> <strong>Content Quality:</strong> Shows content-to-noise ratio for web pages</li>
<li> Perfect for checking character limits for social media, essays, or articles</li>
</ul>
</div>
</ToolLayout>