diff --git a/src/App.js b/src/App.js
index efca6312..b9cb1133 100644
--- a/src/App.js
+++ b/src/App.js
@@ -13,6 +13,7 @@ import DiffTool from './pages/DiffTool';
import TextLengthTool from './pages/TextLengthTool';
import ObjectEditor from './pages/ObjectEditor';
import TableEditor from './pages/TableEditor';
+import ReleaseNotes from './pages/ReleaseNotes';
import TermsOfService from './pages/TermsOfService';
import PrivacyPolicy from './pages/PrivacyPolicy';
import { initGA } from './utils/analytics';
@@ -41,6 +42,7 @@ function App() {
} />
} />
} />
+ } />
} />
} />
diff --git a/src/components/Layout.js b/src/components/Layout.js
index 13101421..7f1b0823 100644
--- a/src/components/Layout.js
+++ b/src/components/Layout.js
@@ -5,7 +5,7 @@ import ThemeToggle from './ThemeToggle';
import ToolSidebar from './ToolSidebar';
import SEOHead from './SEOHead';
import ConsentBanner from './ConsentBanner';
-import { TOOLS, SITE_CONFIG, getCategoryConfig } from '../config/tools';
+import { NON_TOOLS, TOOLS, SITE_CONFIG, getCategoryConfig } from '../config/tools';
import { useAnalytics } from '../hooks/useAnalytics';
const Layout = ({ children }) => {
@@ -161,18 +161,29 @@ const Layout = ({ children }) => {
-
setIsMobileMenuOpen(false)}
- className={`flex items-center space-x-3 px-4 py-3 rounded-xl text-sm font-medium transition-all duration-300 ${
- isActive('/')
- ? 'bg-gradient-to-r from-blue-500 to-purple-500 text-white shadow-lg'
- : 'text-slate-600 hover:text-slate-900 dark:text-slate-300 dark:hover:text-white hover:bg-white/50 dark:hover:bg-slate-700/50'
- }`}
- >
-
-
Home
-
+ {/* Non-Tools Section */}
+ {NON_TOOLS.map((tool) => {
+ const IconComponent = tool.icon;
+ const categoryConfig = getCategoryConfig(tool.category);
+
+ return (
+
setIsMobileMenuOpen(false)}
+ className={`flex items-center space-x-3 px-4 py-3 rounded-xl text-sm font-medium transition-all duration-300 ${
+ isActive(tool.path)
+ ? 'bg-gradient-to-r from-indigo-500 to-purple-500 text-white shadow-lg'
+ : 'text-slate-600 hover:text-slate-900 dark:text-slate-300 dark:hover:text-white hover:bg-white/50 dark:hover:bg-slate-700/50'
+ }`}
+ >
+
+
+
+
{tool.name}
+
+ );
+ })}
@@ -216,117 +227,93 @@ const Layout = ({ children }) => {
{/* Tool Sidebar - only show on tool pages */}
{isToolPage && (
-
)}
{/* Main Content Area */}
-
+
{isToolPage ? (
-
-
+
+
+
{children}
- {/* Footer for tool pages - inside scrollable content */}
-
) : (
{children}
- {/* Footer for homepage */}
-
)}
+ {/* Global Footer */}
+
+
{/* GDPR Consent Banner */}
diff --git a/src/components/ToolSidebar.js b/src/components/ToolSidebar.js
index b949aaca..0d72eb94 100644
--- a/src/components/ToolSidebar.js
+++ b/src/components/ToolSidebar.js
@@ -1,14 +1,20 @@
import React, { useState } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { Search, ChevronLeft, ChevronRight, Sparkles } from 'lucide-react';
-import { NAVIGATION_TOOLS, SITE_CONFIG } from '../config/tools';
+import { NON_TOOLS, TOOLS, SITE_CONFIG } from '../config/tools';
const ToolSidebar = () => {
const location = useLocation();
const [isCollapsed, setIsCollapsed] = useState(true);
const [searchTerm, setSearchTerm] = useState('');
- const filteredTools = NAVIGATION_TOOLS.filter(tool =>
+ // Filter non-tools and tools separately
+ const filteredNonTools = NON_TOOLS.filter(tool =>
+ tool.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ tool.description.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+
+ const filteredTools = TOOLS.filter(tool =>
tool.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
tool.description.toLowerCase().includes(searchTerm.toLowerCase())
);
@@ -64,6 +70,76 @@ const ToolSidebar = () => {
{/* Tools List */}
{/* Stats */}
-
+
{SITE_CONFIG.totalTools} Tools Available
@@ -85,6 +86,37 @@ const Home = () => {
Zero Data Collection
+
+ {/* What's New Button */}
+
+
+ {/* Animated background effect */}
+
+
+ {/* Sparkle effect */}
+
+
+
+
+
+
+
+
What's New
+
+ Latest updates & features
+
+
+
+
+
+
{/* Tools Grid */}
diff --git a/src/pages/ReleaseNotes.js b/src/pages/ReleaseNotes.js
new file mode 100644
index 00000000..16a6253e
--- /dev/null
+++ b/src/pages/ReleaseNotes.js
@@ -0,0 +1,402 @@
+import React, { useState, useEffect } from 'react';
+import { Calendar, Sparkles, Bug, Zap, Shield, ChevronDown, ChevronUp } from 'lucide-react';
+import ToolLayout from '../components/ToolLayout';
+
+const ReleaseNotes = () => {
+ const [releases, setReleases] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [expandedReleases, setExpandedReleases] = useState(new Set());
+
+ // Parse commit messages into user-friendly release notes
+ const parseCommitMessage = (message) => {
+ // Skip non-user-informative commits
+ const skipPatterns = [
+ /^fix eslint/i,
+ /^remove.*eslint/i,
+ /^update.*package/i,
+ /^add debug/i,
+ /^fix.*dependency/i,
+ /deployment/i,
+ /^fix.*mismatch/i
+ ];
+
+ if (skipPatterns.some(pattern => pattern.test(message))) {
+ return null;
+ }
+
+ // Transform commit messages to user-friendly descriptions
+ const transformations = [
+ {
+ pattern: /feat.*enhanced.*what.*new.*feature.*non_tools.*category.*global.*footer/i,
+ type: 'feature',
+ title: 'What\'s New Feature & Navigation Improvements',
+ description: 'Added attractive "What\'s New" button to homepage, created NON_TOOLS category for better navigation organization, separated navigation items in sidebar and mobile menu, and implemented unified global footer across all pages'
+ },
+ {
+ pattern: /improve.*objecteditor.*postmantable.*ui\/ux/i,
+ type: 'enhancement',
+ title: 'Enhanced Object Editor & Table View',
+ description: 'Improved user interface and experience with better JSON parsing, HTML rendering, and copy functionality'
+ },
+ {
+ pattern: /feat.*analytics.*mobile.*ui/i,
+ type: 'feature',
+ title: 'Mobile UI Improvements',
+ description: 'Optimized interface for mobile devices with better analytics integration'
+ },
+ {
+ pattern: /feat.*seo.*gdpr/i,
+ type: 'feature',
+ title: 'SEO & Privacy Compliance',
+ description: 'Comprehensive SEO optimization and GDPR compliance features for better discoverability and privacy protection'
+ },
+ {
+ pattern: /improve.*objecteditor.*tableeditor/i,
+ type: 'enhancement',
+ title: 'Enhanced Data Editors',
+ description: 'Major improvements to Object Editor and new Table Editor with advanced data manipulation features'
+ },
+ {
+ pattern: /enhanced.*object.*editor.*fetch.*mobile/i,
+ type: 'feature',
+ title: 'Object Editor with Data Fetching',
+ description: 'Added ability to fetch data from URLs directly in Object Editor with mobile-optimized interface'
+ },
+ {
+ pattern: /complete.*postman.*table.*view/i,
+ type: 'feature',
+ title: 'Postman-Style Table View',
+ description: 'New professional table visualization with consistent design and advanced data exploration features'
+ },
+ {
+ pattern: /enhanced.*mindmap.*visualization/i,
+ type: 'feature',
+ title: 'Professional Mindmap Visualization',
+ description: 'Beautiful mindmap interface for visualizing complex data structures with interactive navigation'
+ },
+ {
+ pattern: /add.*text.*length.*checker/i,
+ type: 'feature',
+ title: 'Text Analysis Tool',
+ description: 'New comprehensive text analysis tool with length checking and detailed text statistics'
+ },
+ {
+ pattern: /fix.*php.*serialization.*long.*text/i,
+ type: 'fix',
+ title: 'PHP Serialization Improvements',
+ description: 'Fixed PHP serialization handling and added support for long text fields in Visual Editor'
+ },
+ {
+ pattern: /enhanced.*developer.*tools.*ux/i,
+ type: 'enhancement',
+ title: 'Developer Tools UX Enhancement',
+ description: 'Improved overall user experience with visual enhancements and better tool organization'
+ }
+ ];
+
+ for (const transform of transformations) {
+ if (transform.pattern.test(message)) {
+ return {
+ type: transform.type,
+ title: transform.title,
+ description: transform.description
+ };
+ }
+ }
+
+ // Fallback for unmatched patterns
+ if (message.includes('🐛') || message.toLowerCase().includes('fix')) {
+ return {
+ type: 'fix',
+ title: 'Bug Fixes',
+ description: message.replace(/🐛|fix/gi, '').trim()
+ };
+ }
+
+ if (message.includes('✨') || message.toLowerCase().includes('feat')) {
+ return {
+ type: 'feature',
+ title: 'New Feature',
+ description: message.replace(/✨|feat:/gi, '').trim()
+ };
+ }
+
+ return null;
+ };
+
+ // Get type icon and color
+ const getTypeConfig = (type) => {
+ const configs = {
+ feature: {
+ icon: ,
+ color: 'text-blue-600 dark:text-blue-400',
+ bgColor: 'bg-blue-100 dark:bg-blue-900/20',
+ label: 'New Feature'
+ },
+ enhancement: {
+ icon: ,
+ color: 'text-purple-600 dark:text-purple-400',
+ bgColor: 'bg-purple-100 dark:bg-purple-900/20',
+ label: 'Enhancement'
+ },
+ fix: {
+ icon: ,
+ color: 'text-green-600 dark:text-green-400',
+ bgColor: 'bg-green-100 dark:bg-green-900/20',
+ label: 'Bug Fix'
+ },
+ security: {
+ icon: ,
+ color: 'text-red-600 dark:text-red-400',
+ bgColor: 'bg-red-100 dark:bg-red-900/20',
+ label: 'Security'
+ }
+ };
+ return configs[type] || configs.enhancement;
+ };
+
+ // Group releases by date
+ const groupReleasesByDate = (releases) => {
+ const grouped = {};
+ releases.forEach(release => {
+ const date = new Date(release.date).toDateString();
+ if (!grouped[date]) {
+ grouped[date] = [];
+ }
+ grouped[date].push(release);
+ });
+ return grouped;
+ };
+
+ const toggleRelease = (date) => {
+ const newExpanded = new Set(expandedReleases);
+ if (newExpanded.has(date)) {
+ newExpanded.delete(date);
+ } else {
+ newExpanded.add(date);
+ }
+ setExpandedReleases(newExpanded);
+ };
+
+ useEffect(() => {
+ // Simulate fetching commit data (in real app, this would be an API call)
+ const commitData = [
+ {
+ hash: 'new2024',
+ date: '2025-09-24T18:57:18+07:00',
+ message: 'feat: Enhanced What\'s New feature with NON_TOOLS category and global footer'
+ },
+ {
+ hash: '21d0406e',
+ date: '2025-09-24T14:05:10+07:00',
+ message: 'Improve ObjectEditor and PostmanTable UI/UX'
+ },
+ {
+ hash: '57655410',
+ date: '2025-09-24T01:15:20+07:00',
+ message: 'feat: optimize analytics and mobile UI improvements'
+ },
+ {
+ hash: '2e67a2bc',
+ date: '2025-09-24T00:12:28+07:00',
+ message: 'feat: comprehensive SEO optimization and GDPR compliance'
+ },
+ {
+ hash: '977e784d',
+ date: '2025-09-23T14:17:13+07:00',
+ message: 'Improve ObjectEditor and Add TableEditor'
+ },
+ {
+ hash: 'e1c74e4a',
+ date: '2025-09-21T16:33:28+07:00',
+ message: '✨ Enhanced Object Editor with fetch data & mobile improvements'
+ },
+ {
+ hash: '12d45590',
+ date: '2025-09-21T15:09:17+07:00',
+ message: '🎯 Complete Postman-Style Table View with Consistent Design'
+ },
+ {
+ hash: '82d14622',
+ date: '2025-09-21T07:09:33+07:00',
+ message: '✨ Enhanced mindmap visualization with professional UI'
+ },
+ {
+ hash: '6f5bdf5f',
+ date: '2025-08-21T23:45:46+07:00',
+ message: 'Add Text Length Checker tool with comprehensive text analysis features'
+ },
+ {
+ hash: '65cc3bc5',
+ date: '2025-08-21T23:19:22+07:00',
+ message: 'Fix PHP serialization and add Long Text type to Visual Editor'
+ },
+ {
+ hash: '97459ea3',
+ date: '2025-08-07T20:05:11+07:00',
+ message: 'feat: Enhanced developer tools UX with visual improvements'
+ }
+ ];
+
+ const parsedReleases = commitData
+ .map(commit => {
+ const parsed = parseCommitMessage(commit.message);
+ if (!parsed) return null;
+
+ return {
+ ...parsed,
+ date: commit.date,
+ hash: commit.hash
+ };
+ })
+ .filter(Boolean);
+
+ setReleases(parsedReleases);
+ setLoading(false);
+
+ // Auto-expand only the first (latest) release
+ const groupedByDate = groupReleasesByDate(parsedReleases);
+ const sortedDates = Object.keys(groupedByDate).sort((a, b) => new Date(b) - new Date(a));
+
+ const autoExpand = new Set();
+ if (sortedDates.length > 0) {
+ autoExpand.add(sortedDates[0]); // Only expand the latest date
+ }
+ setExpandedReleases(autoExpand);
+ }, []);
+
+ const groupedReleases = groupReleasesByDate(releases);
+
+ return (
+
+
+ {/* Header */}
+
+
+
+
+
+ What's New
+
+
+ Discover the latest features, improvements, and bug fixes that make your development workflow even better.
+
+
+
+ {loading ? (
+
+ ) : (
+
+ {/* Timeline line */}
+
+
+
+ {Object.entries(groupedReleases)
+ .sort(([a], [b]) => new Date(b) - new Date(a))
+ .map(([date, dayReleases], index) => {
+ const isExpanded = expandedReleases.has(date);
+ const releaseDate = new Date(date);
+ const isRecent = (new Date() - releaseDate) < 7 * 24 * 60 * 60 * 1000;
+
+ return (
+
+ {/* Timeline dot */}
+
+
+
+ {/* Date Header */}
+
+
+ {/* Release Items */}
+ {isExpanded && (
+
+ {dayReleases.map((release, index) => {
+ const typeConfig = getTypeConfig(release.type);
+
+ return (
+
+
+
+
+ {typeConfig.icon}
+
+
+
+
+
+ {release.title}
+
+
+ {typeConfig.label}
+
+
+
+ {release.description}
+
+
+
+ {new Date(release.date).toLocaleTimeString('en-US', {
+ hour: '2-digit',
+ minute: '2-digit'
+ })}
+
+ #{release.hash}
+
+
+
+
+ );
+ })}
+
+ )}
+
+
+ );
+ })}
+
+
+ )}
+
+ {/* Footer */}
+
+
+ Stay tuned for more exciting updates and improvements!
+
+
+
+
+ );
+};
+
+export default ReleaseNotes;
diff --git a/src/pages/TableEditor.js b/src/pages/TableEditor.js
index fd57b83f..12b667ca 100644
--- a/src/pages/TableEditor.js
+++ b/src/pages/TableEditor.js
@@ -51,6 +51,8 @@ const TableEditor = () => {
const [originalFileName, setOriginalFileName] = useState(""); // For export naming
const [isTableFullscreen, setIsTableFullscreen] = useState(false); // For fullscreen table view
const [frozenColumns, setFrozenColumns] = useState(0); // Number of columns to freeze on horizontal scroll
+ const [columnWidths, setColumnWidths] = useState({}); // Store custom column widths
+ const [resizing, setResizing] = useState(null); // Track which column is being resized
const [showClearConfirmModal, setShowClearConfirmModal] = useState(false); // For clear confirmation modal
const [showInputChangeModal, setShowInputChangeModal] = useState(false); // For input method change confirmation
const [pendingTabChange, setPendingTabChange] = useState(null); // Store pending tab change
@@ -1162,6 +1164,40 @@ const TableEditor = () => {
setData([...data, newRow]);
};
+ // Column resize functions
+ const getColumnWidth = (columnId) => {
+ return columnWidths[columnId] || 150; // Default width
+ };
+
+ const handleResizeStart = (e, columnId) => {
+ e.preventDefault();
+ const startX = e.clientX;
+ const startWidth = getColumnWidth(columnId);
+
+ setResizing({ columnId, startX, startWidth });
+
+ const handleMouseMove = (e) => {
+ if (!resizing && resizing?.columnId === columnId) return;
+
+ const deltaX = e.clientX - startX;
+ const newWidth = Math.max(50, startWidth + deltaX); // Minimum width of 50px
+
+ setColumnWidths(prev => ({
+ ...prev,
+ [columnId]: newWidth
+ }));
+ };
+
+ const handleMouseUp = () => {
+ setResizing(null);
+ document.removeEventListener('mousemove', handleMouseMove);
+ document.removeEventListener('mouseup', handleMouseUp);
+ };
+
+ document.addEventListener('mousemove', handleMouseMove);
+ document.addEventListener('mouseup', handleMouseUp);
+ };
+
// Add new column
const addColumn = () => {
const newColumnId = `col_${Date.now()}`;
@@ -2185,11 +2221,12 @@ const TableEditor = () => {
| 0
- ? "sticky left-0 z-20 bg-blue-50 dark:!bg-blue-900 w-12"
- : "w-12"
+ ? "sticky left-0 z-20 bg-blue-50 dark:!bg-blue-900"
+ : ""
}`}
+ style={{ width: '40px', maxWidth: '40px', minWidth: '40px' }}
>
{
{columns.map((column, index) => {
const isFrozen = index < frozenColumns;
const leftOffset = isFrozen
- ? 45 + index * 150 // 45px for checkbox column + 150px per frozen column (no gap)
+ ? 40 + columns.slice(0, index).reduce((acc, col) => acc + getColumnWidth(col.id), 0)
: 0;
return (
|
{
+
+ {/* Resize handle */}
+ handleResizeStart(e, column.id)}
+ title="Drag to resize column"
+ />
|
);
})}
@@ -2310,11 +2353,12 @@ const TableEditor = () => {
className="hover:bg-gray-50 dark:hover:bg-gray-700"
>
0
- ? "sticky left-0 z-10 bg-blue-50 dark:!bg-blue-900 w-12"
- : "w-12"
+ ? "sticky left-0 z-10 bg-blue-50 dark:!bg-blue-900"
+ : ""
}`}
+ style={{ width: '40px', maxWidth: '40px', minWidth: '40px' }}
>
{
{columns.map((column, index) => {
const isFrozen = index < frozenColumns;
const leftOffset = isFrozen
- ? 45 + index * 150 // 45px for checkbox column + 150px per frozen column (no gap)
+ ? 40 + columns.slice(0, index).reduce((acc, col) => acc + getColumnWidth(col.id), 0)
: 0;
return (
|
{editingCell?.rowId === row.id &&
editingCell?.columnId === column.id ? (
@@ -2478,24 +2521,19 @@ const TableEditor = () => {
{/* System Row - Add Row */}
|
- {/* Sticky Add Row button on the left */}
- |
+ |
|
- {/* Empty cells to fill the rest of the row */}
-
- {/* Empty space for visual consistency */}
- |
diff --git a/src/pages/TermsOfService.js b/src/pages/TermsOfService.js
index 6d4589b9..8ec523f3 100644
--- a/src/pages/TermsOfService.js
+++ b/src/pages/TermsOfService.js
@@ -155,13 +155,6 @@ const TermsOfService = () => {
-
- {/* Footer */}
-
-
- © {SITE_CONFIG.year} {SITE_CONFIG.title} • Built with ❤️ for developers worldwide
-
-
);