diff --git a/README.md b/README.md deleted file mode 100644 index 54a0825..0000000 --- a/README.md +++ /dev/null @@ -1,117 +0,0 @@ -# JSONP - Multi tab JSON toolkit - -

- A versatile web-based toolkit for working with JSON data. Open-source and works offline! -

- -

- JSONP Logo -

- -## ๐ŸŒŸ Overview - -JSONP is a powerful, browser-based JSON toolkit that helps developers work with JSON data more efficiently. It works completely offline and includes multiple tools for formatting, comparing, converting, and generating JSON data. - -## โœจ Features - -### ๐Ÿงน JSON Formatter & Validator -- Multi-tab support with color coding -- Tree view with expandable nodes -- Search functionality -- Error highlighting -- Real-time validation - -### ๐Ÿ” JSON Comparison (Diff Checker) -- Side-by-side diff view -- Multi-tab support -- Visual difference highlighting - -### ๐Ÿ’ป Code Generation -- TypeScript interfaces -- Python dataclasses -- Go structs - -### ๐Ÿ”„ Format Conversion -- Python dictionary โ†” JSON converter -- Preserves data types -- Syntax highlighting - -### ๐Ÿงช Mock Data Generator -- Faker.js integration -- JSON/CSV export -- Table/JSON view modes -- Customizable schemas -- Built-in presets - -### ๐Ÿ“ Markdown Editor -- Real-time preview -- Syntax highlighting -- Multi-tab support - -## ๐Ÿš€ Getting Started - -1. **Quick Start** - ```bash - git clone https://github.com/shravan20/jsonp.git - cd jsonp - # Open index.html in your browser - ``` - -2. **Usage** - - Open `index.html` in any modern browser - - Drag & drop JSON files directly - - Use tabs to organize multiple documents - - Toggle between different tools using the sidebar - -## ๐Ÿ’ก Key Features - -- **Offline Support**: Works completely offline - no server needed -- **Multi-tab Interface**: Work with multiple JSON documents simultaneously -- **Dark/Light Mode**: Comfortable viewing in any environment -- **Keyboard Shortcuts**: Efficient workflow with keyboard navigation -- **Local Storage**: Auto-saves your work -- **Mobile Responsive**: Works on all devices - -## ๐Ÿ› ๏ธ Development - -```bash -# Install dependencies -npm install - -# Format code -npm run format - -# Lint code -npm run lint - -# Run checks -npm run check -``` - -## ๐Ÿค Contributing - -We welcome contributions! Please see: - -1. [Code of Conduct](./CODE_OF_CONDUCT.md) -2. [Contributing Guidelines](./CONTRIBUTING.md) -3. [Issue Templates](./.github/ISSUE_TEMPLATE) - -## ๐Ÿ“ Changelog - -See [CHANGELOG.md](./CHANGELOG.md) for a detailed history of changes. - -## ๐Ÿ“„ License - -MIT Licensed - Free for personal and commercial use. See [LICENSE](./LICENSE) for details. - -## ๐ŸŒ Links - -- [GitHub Repository](https://github.com/shravan20/jsonp) -- [Report Issues](https://github.com/shravan20/jsonp/issues) -- [Feature Requests](https://github.com/shravan20/jsonp/issues/new?template=feature_request.md) - ---- - -

- Made with โค๏ธ in India -

diff --git a/index.css b/index.css deleted file mode 100644 index f252904..0000000 --- a/index.css +++ /dev/null @@ -1,1221 +0,0 @@ -/* Complete index.css with improved alignment, spacing, theme handling, and UI components */ - -/* Base Styles */ -body { - font-family: Arial, sans-serif; - margin: 0; - padding: 20px; - background-color: #f0f0f0; - color: #333; - transition: background-color 0.3s, color 0.3s; -} - -.container { - max-width: 1200px; - margin: 0 auto; - padding: 0 20px; -} - -h1 { - margin: 0 20px 0 0; - font-size: 1.8rem; -} - -.dark-mode-toggle, -.shortcut-preview-button { - padding: 8px 16px; - background-color: #007bff; - color: white; - border: none; - cursor: pointer; - border-radius: 4px; - margin-right: 10px; - font-size: 0.9rem; -} - -/* Mode Selector */ -.mode-selector { - margin: 20px 0; - display: flex; - flex-wrap: wrap; - gap: 10px; -} - -.mode-selector button { - padding: 10px 16px; - background-color: #007bff; - color: white; - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 0.9rem; -} - -.mode-selector button.active { - background-color: #0056b3; - font-weight: bold; -} - -/* Tabs */ -.tabs { - display: flex; - flex-wrap: wrap; - gap: 5px; - margin-bottom: 15px; -} - -.tab-button { - padding: 8px 12px; - background-color: #e0e0e0; - border: none; - cursor: pointer; - border-radius: 4px; - display: flex; - align-items: center; - gap: 5px; - transition: background-color 0.3s; - font-size: 0.9rem; -} - -.tab-button.active { - background-color: #ffffff; - font-weight: bold; -} - -.tab-button .close-tab { - color: red; - font-weight: bold; - cursor: pointer; -} - -.tab-button .tab-color-picker { - width: 16px; - height: 16px; - padding: 0; - border: none; - cursor: pointer; -} - -.add-tab-button { - background-color: #007bff; - color: white; - padding: 8px 16px; - border-radius: 4px; - cursor: pointer; - border: none; - font-size: 0.9rem; -} - -/* Content Panels */ -.json-tab-content { - display: none; -} - -.json-tab-content.active { - display: block; -} - -textarea { - width: 100%; - height: 200px; - padding: 10px; - font-family: monospace; - font-size: 0.95rem; - border: 1px solid #ccc; - border-radius: 4px; - margin-bottom: 15px; - box-sizing: border-box; -} - -/* Utility Containers */ -.search-container, -.upload-download-container { - display: flex; - align-items: center; - gap: 10px; - margin-bottom: 15px; - flex-wrap: wrap; -} -/*Change made to make markdown preview text visible in dark mode*/ -body.dark-mode .toastui-editor-contents { - color: #ffffff; -} -body.dark-mode .toastui-editor-contents * { - color: #ffffff; -} - -.preview-section { - background-color: white; - padding: 20px; - border-radius: 5px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - overflow: auto; - word-wrap: normal; - display: none; - max-height: 600px; /* Add max height */ -} - -.preview-section.active { - display: block; -} - -.copy-button, -button.copy-button { - padding: 6px 12px; - background-color: #28a745; - color: white; - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 0.85rem; -} - -.copy-button:hover { - background-color: #218838; -} - -/* Tree View */ -.tree-node { - margin: 4px 0; - padding-left: 20px; - position: relative; -} - -.tree-key { - cursor: pointer; - padding: 2px 5px; - border-radius: 3px; - display: inline-block; -} - -.tree-key:hover { - background-color: rgba(0, 123, 255, 0.1); -} - -.type-string { - color: #4caf50; -} - -.type-number { - color: #2196f3; -} - -.type-boolean { - color: #9c27b0; -} - -.type-null { - color: #666; -} - -.type-object { - color: #ff9800; -} - -.type-array { - color: #e91e63; -} - -/* Dark Mode */ -body.dark-mode { - background-color: #121212; - color: #e0e0e0; -} - -body.dark-mode textarea, -body.dark-mode .preview-section, -body.dark-mode .modal-content { - background-color: #2e2e2e; - color: #e0e0e0; - border-color: #555; -} - -body.dark-mode .tab-button { - background-color: #333; - color: #e0e0e0; -} - -body.dark-mode .tab-button.active { - background-color: #555; -} - -/* Reordering Drag Styles */ -.tab-button.dragging { - opacity: 0.6; - border: 2px dashed #007bff; -} - -.tab-button.drag-over { - transform: translateY(2px) scale(1.02); - box-shadow: 0 2px 6px rgba(0, 123, 255, 0.3); -} - -/* Markdown & Modal */ -.markdown-preview { - background: #fff; - padding: 1rem; - border-radius: 5px; - border: 1px solid #ddd; - max-height: 500px; - overflow-y: auto; - line-height: 1.6; -} - -.markdown-preview code { - background: #f0f0f0; - padding: 2px 4px; - border-radius: 3px; -} - -.modal { - display: none; - position: fixed; - z-index: 1000; - left: 0; - top: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); -} - -.modal-content { - background: #fff; - margin: 10% auto; - padding: 20px; - width: 400px; - border-radius: 5px; - position: relative; -} - -.close-modal { - position: absolute; - top: 10px; - right: 15px; - font-size: 1.4rem; - cursor: pointer; -} - -/* Tab Rename Tooltip */ -.tab-rename-tooltip { - position: absolute; - z-index: 1000; - background-color: #fff; - border: 1px solid #ccc; - border-radius: 3px; - padding: 5px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); -} - -body.dark-mode .tab-rename-tooltip { - background-color: #2e2e2e; - color: #e0e0e0; - border-color: #555; -} - -/* GitHub buttons */ -.github-links { - text-align: center; - margin: 20px 0; -} - -.github-button, -.contribute-button { - display: inline-flex; - align-items: center; - padding: 10px 15px; - margin: 0 10px; - border-radius: 5px; - text-decoration: none; - color: white; - font-size: 0.9rem; -} - -.github-button { - background-color: #24292e; -} - -.github-button:hover { - background-color: #444; -} - -.contribute-button { - background-color: #2ea44f; -} - -.contribute-button:hover { - background-color: #22863a; -} - -.github-button i, -.contribute-button i { - margin-right: 8px; -} - -.tree-children { - margin-left: 20px; - overflow: hidden; - transition: max-height 0.3s ease, opacity 0.3s ease; - max-height: 1000px; - opacity: 1; -} - -.tree-children.collapsed { - max-height: 0; - opacity: 0; - pointer-events: none; -} - -.tree-key { - position: relative; - cursor: pointer; - padding: 4px 6px; - display: inline-block; - border-radius: 3px; - transition: background-color 0.2s ease, box-shadow 0.2s ease; - outline: none; -} - -.tree-key:hover, -.tree-key:focus { - background-color: rgba(0, 123, 255, 0.12); - box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); -} - -.tree-key:active { - background-color: rgba(0, 123, 255, 0.25); -} - -.tree-key::before { - content: "\25B6"; - display: inline-block; - transition: transform 0.2s ease; - margin-right: 6px; - font-size: 0.8em; - color: #555; -} - -.tree-key.expanded::before { - transform: rotate(90deg); - color: #007bff; -} - -.tree-key .node-info { - font-size: 0.8em; - color: #999; - margin-left: 6px; -} - -.tree-key[title] { - position: relative; -} - -.tree-key[title]:hover::after { - content: attr(title); - position: absolute; - top: 100%; - left: 0; - background: rgba(0, 0, 0, 0.75); - color: #fff; - font-size: 12px; - padding: 4px 6px; - border-radius: 4px; - white-space: nowrap; - margin-top: 4px; - z-index: 10; - pointer-events: none; -} - -body.dark-mode .tree-key[title]:hover::after { - background: rgba(255, 255, 255, 0.1); - color: #e0e0e0; - border: 1px solid #666; -} - -/* Accessibility: keyboard focus outline */ -.tree-key:focus-visible { - outline: 2px solid #007bff; - outline-offset: 2px; -} - -/* Shift-click recursive expand/collapse (JS logic must add 'recursive' class to target for styling if needed) */ -.tree-key.shift-toggle { - background-color: rgba(0, 123, 255, 0.08); -} - -/* Enhanced index.css with animation for tree expand/collapse, dropdown arrow, tooltip support, hover/click effects, keyboard accessibility, child count, and shift-click support */ - -/* JSON to Code preview themes */ -.code-output { - background-color: #1e1e1e; - color: #d4d4d4; - font-family: "Fira Code", monospace; - font-size: 14px; - line-height: 1.6; - padding: 16px; - border-radius: 6px; - overflow-x: auto; - white-space: pre; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); - margin-top: 10px; -} - -.code-output.python { - background-color: #0b2239; - color: #dcdcdc; -} - -.code-output.go { - background-color: #263238; - color: #c3e88d; -} - -.code-output.typescript { - background-color: #1e1e1e; - color: #d4d4d4; -} - -.code-output .keyword { - color: #569cd6; - font-weight: bold; -} - -.code-output .string { - color: #ce9178; -} - -.code-output .number { - color: #b5cea8; -} - -.code-output .type { - color: #4ec9b0; - font-style: italic; -} - -.code-output .comment { - color: #6a9955; - font-style: italic; -} - -.tree-children { - margin-left: 20px; - overflow: hidden; - transition: max-height 0.3s ease, opacity 0.3s ease; - max-height: 1000px; - opacity: 1; -} - -.tree-children.collapsed { - max-height: 0; - opacity: 0; - pointer-events: none; -} - -.tree-key { - position: relative; - cursor: pointer; - padding: 4px 6px; - display: inline-block; - border-radius: 3px; - transition: background-color 0.2s ease, box-shadow 0.2s ease; - outline: none; -} - -.tree-key:hover, -.tree-key:focus { - background-color: rgba(0, 123, 255, 0.12); - box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); -} - -.tree-key:active { - background-color: rgba(0, 123, 255, 0.25); -} - -.tree-key::before { - content: "\25B6"; - display: inline-block; - transition: transform 0.2s ease; - margin-right: 6px; - font-size: 0.8em; - color: #555; -} - -.tree-key.expanded::before { - transform: rotate(90deg); - color: #007bff; -} - -.tree-key .node-info { - font-size: 0.8em; - color: #999; - margin-left: 6px; -} - -.tree-key[title] { - position: relative; -} - -.tree-key[title]:hover::after { - content: attr(title); - position: absolute; - top: 100%; - left: 0; - background: rgba(0, 0, 0, 0.75); - color: #fff; - font-size: 12px; - padding: 4px 6px; - border-radius: 4px; - white-space: nowrap; - margin-top: 4px; - z-index: 10; - pointer-events: none; -} - -body.dark-mode .tree-key[title]:hover::after { - background: rgba(255, 255, 255, 0.1); - color: #e0e0e0; - border: 1px solid #666; -} - -/* Accessibility: keyboard focus outline */ -.tree-key:focus-visible { - outline: 2px solid #007bff; - outline-offset: 2px; -} - -/* Shift-click recursive expand/collapse (JS logic must add 'recursive' class to target for styling if needed) */ -.tree-key.shift-toggle { - background-color: rgba(0, 123, 255, 0.08); -} - -/* Editor Specific */ -#editor-section .tab-button { - padding: 8px 12px; - background-color: #f5f5f5; - border: none; - border-radius: 4px; - cursor: pointer; - margin-right: 5px; - font-size: 0.9rem; -} - -#editor-section .tab-button.active { - background-color: #ffffff; - font-weight: bold; -} - -#editor-section .add-tab-button { - padding: 8px 16px; - background-color: #007bff; - color: white; - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 0.9rem; -} - -#editor-tab-contents .json-tab-content { - display: none; - margin-top: 10px; -} - -#editor-tab-contents .json-tab-content.active { - display: block; -} - -.toastui-editor-defaultUI { - border-radius: 6px; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); -} - -/* Dark Mode */ -/* Dark mode enhancements for toastui Editor */ -body.dark-mode .toastui-editor-defaultUI { - background-color: #1e1e1e !important; - color: #eee !important; - border-color: #333 !important; -} - -body.dark-mode .toastui-editor-defaultUI .ProseMirror { - background-color: #1e1e1e !important; - color: #eee !important; -} - -body.dark-mode .toastui-editor-defaultUI .toastui-editor-toolbar { - background-color: #2a2a2a !important; - border-bottom: 1px solid #444 !important; -} - -body.dark-mode .toastui-editor-defaultUI .toastui-editor-contents pre, -body.dark-mode .toastui-editor-defaultUI .toastui-editor-contents code { - background-color: #2b2b2b !important; - color: #c5c5c5 !important; -} - -body.dark-mode .toastui-editor-defaultUI .toastui-editor-md-container, -body.dark-mode .toastui-editor-defaultUI .toastui-editor-ww-container { - background-color: #1e1e1e !important; - color: #eee !important; -} - -/* Tooltip/input styling */ -body.dark-mode .tab-rename-tooltip { - background-color: #2c2c2c; - border: 1px solid #555; - color: #fff; - padding: 4px; - z-index: 1000; - position: absolute; - border-radius: 4px; -} - -body.dark-mode .tab-rename-tooltip input { - background-color: #1f1f1f; - color: #eee; - border: 1px solid #555; -} - -/* Active tab indicator */ -body.dark-mode .tab-button.active { - background-color: #333; - color: #fff; -} - -/* Tree View Scrolling Fix */ -.preview-section { - background-color: white; - padding: 20px; - border-radius: 5px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - overflow: auto; - word-wrap: normal; - display: none; - max-height: 600px; /* Add max height */ -} - -.preview-section.active { - display: block; -} - -.tree-view { - padding: 10px; - font-family: monospace; - line-height: 1.5; - overflow-y: auto; /* Ensure vertical scrolling */ - overflow-x: auto; /* Handle horizontal overflow */ -} - -/* Enhance tree node spacing and structure */ -.tree-node { - margin: 4px 0; - padding-left: 20px; - position: relative; -} - -/* Dark mode adjustments */ -body.dark-mode .preview-section { - background-color: #1e1e1e; - border: 1px solid #333; -} - -body.dark-mode .tree-view { - color: #e0e0e0; -} - -/* Scrollbar styling for better visibility */ -.tree-view::-webkit-scrollbar { - width: 12px; - height: 12px; -} - -.tree-view::-webkit-scrollbar-track { - background: #f1f1f1; - border-radius: 6px; -} - -.tree-view::-webkit-scrollbar-thumb { - background: #888; - border-radius: 6px; - border: 3px solid #f1f1f1; -} - -.tree-view::-webkit-scrollbar-thumb:hover { - background: #555; -} - -/* Dark mode scrollbar */ -body.dark-mode .tree-view::-webkit-scrollbar-track { - background: #1e1e1e; -} - -body.dark-mode .tree-view::-webkit-scrollbar-thumb { - background: #666; - border: 3px solid #1e1e1e; -} - -body.dark-mode .tree-view::-webkit-scrollbar-thumb:hover { - background: #888; -} - -/* New App Layout */ -.app-container { - display: flex; - min-height: 100vh; - background-color: #f0f0f0; -} - -/* Sidebar Styles */ -.sidebar { - width: 250px; - background-color: #252526; - color: #ffffff; - display: flex; - flex-direction: column; - flex-shrink: 0; -} - -.sidebar-header { - padding: 15px; - border-bottom: 1px solid #3c3c3c; -} - -.sidebar-header h3 { - margin: 0; - font-size: 1.1rem; - color: #cccccc; -} - -.sidebar-content { - flex: 1; - overflow-y: auto; -} - -.feature-group { - padding: 10px 0; -} - -.feature-item { - padding: 8px 15px; - display: flex; - align-items: center; - gap: 10px; - cursor: pointer; - transition: background-color 0.2s; - color: #cccccc; -} - -.feature-item:hover { - background-color: #2a2d2e; -} - -.feature-item.active { - background-color: #37373d; - color: #ffffff; -} - -.feature-item i { - width: 16px; - text-align: center; -} - -/* Main Content Area */ -.main-content { - flex: 1; - padding: 20px; - background-color: #f0f0f0; - overflow-y: auto; -} - -.header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 20px; -} - -.header-actions { - display: flex; - gap: 10px; -} - -/* Dark Mode Adjustments */ -body.dark-mode .app-container { - background-color: #1e1e1e; -} - -body.dark-mode .main-content { - background-color: #1e1e1e; -} - -body.dark-mode .sidebar { - background-color: #252526; - border-right: 1px solid #3c3c3c; -} - -/* Mobile Sidebar Toggle */ -.mobile-sidebar-toggle { - display: none; - position: fixed; - top: 10px; - left: 10px; - z-index: 1000; - background: #252526; - color: white; - border: none; - border-radius: 4px; - padding: 10px; - cursor: pointer; -} - -.mobile-sidebar-close { - display: none; - background: transparent; - border: none; - color: #cccccc; - cursor: pointer; - padding: 5px; -} - -/* Responsive Styles */ -@media (max-width: 768px) { - body { - padding: 10px; - } - - .app-container { - flex-direction: column; - } - - .mobile-sidebar-toggle { - display: block; - } - - .mobile-sidebar-close { - display: block; - position: absolute; - right: 10px; - top: 50%; - transform: translateY(-50%); - } - - .sidebar { - position: fixed; - left: -250px; - top: 0; - bottom: 0; - z-index: 1000; - transition: left 0.3s ease; - } - - .sidebar.active { - left: 0; - } - - .main-content { - margin-left: 0; - padding: 15px; - padding-top: 50px; - } - - .header { - flex-direction: column; - align-items: flex-start; - gap: 15px; - } - - .header h1 { - font-size: 1.5rem; - margin-right: 0; - } - - /* Adjust tabs layout */ - .tabs { - flex-wrap: nowrap; - overflow-x: auto; - padding-bottom: 5px; - -webkit-overflow-scrolling: touch; - } - - .tab-button { - flex-shrink: 0; - } - - /* Adjust form controls */ - .search-container, - .upload-download-container { - flex-direction: column; - align-items: stretch; - } - - .search-container input, - .search-container button, - .upload-download-container button { - width: 100%; - margin-bottom: 10px; - } - - /* Adjust JSON compare layout */ - #compare-section [style*="display:flex"] { - flex-direction: column !important; - } - - #compare-section .json-input-left, - #compare-section .json-input-right { - width: 100% !important; - margin-bottom: 10px; - } - - /* Adjust mock data generator layout */ - #mockgen-section .mock-preview-table { - display: block; - overflow-x: auto; - white-space: nowrap; - } - - /* Adjust markdown editor */ - .toastui-editor-defaultUI { - flex-direction: column !important; - } - - .toastui-editor-defaultUI .toastui-editor-md-container, - .toastui-editor-defaultUI .toastui-editor-ww-container { - width: 100% !important; - } -} - -/* Small mobile devices */ -@media (max-width: 480px) { - .header-actions { - flex-direction: column; - width: 100%; - } - - .header-actions button { - width: 100%; - } - - .github-links { - flex-direction: column; - gap: 10px; - } - - .github-links a { - width: 100%; - justify-content: center; - } -} - -/* Tablet adjustments */ -@media (min-width: 769px) and (max-width: 1024px) { - .sidebar { - width: 200px; - } - - .main-content { - margin-left: 200px; - } -} - -/* Touch device optimizations */ -@media (hover: none) { - .feature-item:hover { - background-color: transparent; - } - - .feature-item:active { - background-color: #2a2d2e; - } - - .tab-button:hover { - background-color: transparent; - } - - .tab-button:active { - background-color: #e0e0e0; - } -} - -/* Dark mode adjustments for mobile */ -body.dark-mode .mobile-sidebar-toggle { - background: #333; - color: #fff; -} - -body.dark-mode .mobile-sidebar-close { - color: #fff; -} - -/* Improve scrollbar appearance on mobile */ -@media (max-width: 768px) { - ::-webkit-scrollbar { - width: 6px; - height: 6px; - } - - ::-webkit-scrollbar-thumb { - background: rgba(0, 0, 0, 0.2); - border-radius: 3px; - } - - body.dark-mode ::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.2); - } -} - -/* Mock Data Table Styling */ -.mock-preview-table { - width: 100%; - border-collapse: collapse; - margin: 20px 0; - background-color: #fff; - border-radius: 8px; - overflow: hidden; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); -} - -.mock-preview-table thead { - background-color: #f8f9fa; - border-bottom: 2px solid #dee2e6; -} - -.mock-preview-table th { - padding: 12px 15px; - text-align: left; - font-weight: 600; - font-size: 0.9rem; - color: #495057; - text-transform: uppercase; - letter-spacing: 0.5px; - border-bottom: 2px solid #dee2e6; - position: sticky; - top: 0; - background-color: #f8f9fa; - z-index: 1; -} - -.mock-preview-table td { - padding: 12px 15px; - border-bottom: 1px solid #dee2e6; - font-size: 0.9rem; - color: #212529; - max-width: 300px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.mock-preview-table tbody tr:hover { - background-color: #f8f9fa; -} - -.mock-preview-table tbody tr:last-child td { - border-bottom: none; -} - -/* Error row styling */ -.mock-preview-table tr.error td { - background-color: #fff3f3; - color: #dc3545; -} - -/* Dark mode support */ -body.dark-mode .mock-preview-table { - background-color: #2d2d2d; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); -} - -body.dark-mode .mock-preview-table thead { - background-color: #333; - border-bottom-color: #444; -} - -body.dark-mode .mock-preview-table th { - color: #e0e0e0; - background-color: #333; - border-bottom-color: #444; -} - -body.dark-mode .mock-preview-table td { - color: #e0e0e0; - border-bottom-color: #444; -} - -body.dark-mode .mock-preview-table tbody tr:hover { - background-color: #383838; -} - -body.dark-mode .mock-preview-table tr.error td { - background-color: #442326; - color: #ff6b6b; -} - -/* Responsive table */ -@media (max-width: 768px) { - #mockgen-section .mock-preview-table { - display: block; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - } - - .mock-preview-table thead { - display: block; - } - - .mock-preview-table tbody { - display: block; - } - - .mock-preview-table th, - .mock-preview-table td { - min-width: 120px; /* Ensure minimum column width */ - } - - /* Add horizontal scroll indicator */ - .mock-preview-table::after { - content: "โŸท"; - position: absolute; - bottom: 10px; - right: 10px; - background: rgba(0, 0, 0, 0.5); - color: white; - padding: 5px 10px; - border-radius: 20px; - font-size: 14px; - opacity: 0.7; - pointer-events: none; - } - - body.dark-mode .mock-preview-table::after { - background: rgba(255, 255, 255, 0.2); - } -} - -/* Container for the table */ -#mock-output-container { - position: relative; - margin-top: 20px; - border-radius: 8px; - overflow: hidden; -} - -.preset-select { - margin-left: 10px; - padding: 5px 10px; - border: 1px solid var(--border-color); - border-radius: 4px; - background-color: var(--bg-color); - color: var(--text-color); - font-size: 14px; - cursor: pointer; - transition: border-color 0.2s; -} - -.preset-select:hover { - border-color: var(--primary-color); -} - -.preset-select:focus { - outline: none; - border-color: var(--primary-color); - box-shadow: 0 0 0 2px var(--primary-color-light); -} diff --git a/index.js b/index.js deleted file mode 100644 index d70ca1c..0000000 --- a/index.js +++ /dev/null @@ -1,1890 +0,0 @@ -/* ========== Global Persistence Functions ========== */ -function getActiveMode() { - // Find the active feature item in the sidebar - const activeFeature = document.querySelector(".feature-item.active"); - if (activeFeature) { - // Extract mode from the onclick attribute - const onclickAttr = activeFeature.getAttribute("onclick"); - const match = onclickAttr.match(/switchMode\('(.+?)'\)/); - if (match) return match[1]; - } - - // Fallback to checking sections - if (document.getElementById("formatter-section").style.display !== "none") return "formatter"; - if (document.getElementById("compare-section").style.display !== "none") return "compare"; - if (document.getElementById("codegen-section").style.display !== "none") return "codegen"; - if (document.getElementById("convert-section").style.display !== "none") return "convert"; - if (document.getElementById("mockgen-section").style.display !== "none") return "mockgen"; - if (document.getElementById("editor-section").style.display !== "none") return "editor"; - return "formatter"; // Default fallback -} - -function saveGlobalState() { - const state = { - darkMode: document.body.classList.contains("dark-mode"), - activeMode: getActiveMode(), - formatter: { - activeTab: - document.querySelector("#formatter-tab-contents .json-tab-content.active")?.id || "", - tabs: [], - activeFeatureTab: {}, // Store active feature tab (Raw/Tree/Error) for each formatter tab - }, - compare: { - activeTab: document.querySelector("#compare-tab-contents .json-tab-content.active")?.id || "", - tabs: [], - }, - codegen: { - activeTab: document.querySelector("#codegen-tab-contents .json-tab-content.active")?.id || "", - tabs: [], - }, - }; - // Formatter tabs - document.querySelectorAll("#formatter-tabs-container .tab-button[data-tab]").forEach((btn) => { - const tabId = btn.getAttribute("data-tab"); - const name = btn.querySelector(".tab-name").textContent; - const color = btn.querySelector(".tab-color-picker")?.value || "#e0e0e0"; - const content = document.querySelector("#" + tabId + " .json-input")?.value || ""; - state.formatter.tabs.push({ - id: tabId, - name, - color, - content, - }); - }); - // Compare tabs - document.querySelectorAll("#compare-tabs-container .tab-button[data-tab]").forEach((btn) => { - const tabId = btn.getAttribute("data-tab"); - const name = btn.querySelector(".tab-name").textContent; - const leftContent = document.querySelector("#" + tabId + " .json-input-left")?.value || ""; - const rightContent = document.querySelector("#" + tabId + " .json-input-right")?.value || ""; - state.compare.tabs.push({ - id: tabId, - name, - leftContent, - rightContent, - }); - }); - // Codegen tabs - document.querySelectorAll("#codegen-tabs-container .tab-button[data-tab]").forEach((btn) => { - const tabId = btn.getAttribute("data-tab"); - const name = btn.querySelector(".tab-name").textContent; - const input = document.querySelector("#" + tabId + " .json-input")?.value || ""; - const lang = document.getElementById("lang-select-" + tabId)?.value || "typescript"; - state.codegen.tabs.push({ - id: tabId, - name, - input, - lang, - }); - }); - localStorage.setItem("jsonToolState", JSON.stringify(state)); -} - -function loadGlobalState() { - const stateStr = localStorage.getItem("jsonToolState"); - if (!stateStr) return; - const state = JSON.parse(stateStr); - // Dark Mode - if (state.darkMode) document.body.classList.add("dark-mode"); - else document.body.classList.remove("dark-mode"); - // Active Feature Mode (Formatter/Compare/Codegen/etc.) - if (state.activeMode) { - switchMode(state.activeMode); - } else { - switchMode("formatter"); // Default fallback - } - - // Load Formatter tabs - const ftc = document.getElementById("formatter-tabs-container"); - ftc.querySelectorAll(".tab-button[data-tab]").forEach((btn) => btn.remove()); - document.getElementById("formatter-tab-contents").innerHTML = ""; - formatterTabCount = 0; - state.formatter.tabs.forEach((tabData) => { - createFormatterTab(tabData); - }); - if (state.formatter.activeTab) switchFormatterTab(state.formatter.activeTab); - - // Load Compare tabs - const ctc = document.getElementById("compare-tabs-container"); - ctc.querySelectorAll(".tab-button[data-tab]").forEach((btn) => btn.remove()); - document.getElementById("compare-tab-contents").innerHTML = ""; - compareTabCount = 0; - state.compare.tabs.forEach((tabData) => { - createCompareTabWithData(tabData); - }); - if (state.compare.activeTab) switchCompareTab(state.compare.activeTab); - - // Load Codegen tabs - const cgtc = document.getElementById("codegen-tabs-container"); - cgtc.querySelectorAll(".tab-button[data-tab]").forEach((btn) => btn.remove()); - document.getElementById("codegen-tab-contents").innerHTML = ""; - codegenTabCount = 0; - state.codegen.tabs.forEach((tabData) => { - createCodegenTabWithData(tabData); - }); - if (state.codegen.activeTab) switchCodegenTab(state.codegen.activeTab); - - enableTabReordering("formatter-tabs-container"); - enableTabReordering("compare-tabs-container"); - enableTabReordering("codegen-tabs-container"); -} - -/* ========== COPY FUNCTIONS ========== */ -function copyRawJSON(tabId) { - const rawPre = document.querySelector(`#${tabId}-raw-preview .raw-json`); - copyToClipboard(rawPre.textContent, "JSON copied to clipboard"); -} - -function copyCompareLeft(tabId) { - const leftTA = document.querySelector(`#${tabId} .json-input-left`); - copyToClipboard(leftTA.value, "Left JSON copied"); -} - -function copyCompareRight(tabId) { - const rightTA = document.querySelector(`#${tabId} .json-input-right`); - copyToClipboard(rightTA.value, "Right JSON copied"); -} - -function copyCodeOutput(tabId) { - const codePre = document.querySelector(`#${tabId} .code-output`); - copyToClipboard(codePre.textContent, "Code copied"); -} - -function copyToClipboard(text, successMessage) { - navigator.clipboard - .writeText(text) - .then(() => { - Swal.fire({ - icon: "success", - title: successMessage, - toast: true, - timer: 2000, - position: "top-end", - showConfirmButton: false, - }); - }) - .catch((err) => { - console.error("Failed to copy:", err); - Swal.fire({ - icon: "error", - title: "Copy failed", - toast: true, - timer: 2000, - position: "top-end", - showConfirmButton: false, - }); - }); -} - -/* ========== Mode Selector ========== */ -function switchMode(mode) { - const sections = ["formatter", "compare", "codegen", "convert", "mockgen", "editor"]; - sections.forEach((s) => { - const section = document.getElementById(`${s}-section`); - if (section) { - section.style.display = "none"; - } - }); - - const targetSection = document.getElementById(`${mode}-section`); - if (targetSection) { - targetSection.style.display = "block"; - } - - // Update sidebar active state - document.querySelectorAll(".feature-item").forEach((item) => { - item.classList.remove("active"); - }); - document.querySelector(`.feature-item[onclick*="${mode}"]`)?.classList.add("active"); - - if (mode === "mockgen") { - renderMockgenDocs(); - } else if (mode === "editor") { - loadEditorGlobalState(); - } - - applyEditorTabDarkMode(); - saveGlobalState(); // Save state when mode changes -} - -/* ========== Formatter Functions ========== */ -let formatterTabCount = 0; - -function addFormatterTab() { - createFormatterTab(); - switchFormatterTab("formatterTab" + formatterTabCount); - saveGlobalState(); -} - -function createFormatterTab(tabData = null) { - formatterTabCount++; - const tabId = "formatterTab" + formatterTabCount; - - // Create tab button - const tabButton = document.createElement("button"); - tabButton.className = "tab-button"; - tabButton.setAttribute("data-tab", tabId); - tabButton.onclick = () => switchFormatterTab(tabId); - - const tabName = tabData?.name || "Tab " + formatterTabCount; - const tabColor = tabData?.color || "#e0e0e0"; - - tabButton.innerHTML = `${tabName} - - ร—`; - - tabButton.addEventListener("dblclick", () => openTabRenameTooltip(tabId, "formatter")); - - const tabsContainer = document.getElementById("formatter-tabs-container"); - const addButton = tabsContainer.querySelector(".add-tab-button"); - if (tabsContainer && addButton) { - tabsContainer.insertBefore(tabButton, addButton); - } - // Create tab content - const tabContent = document.createElement("div"); - tabContent.id = tabId; - tabContent.className = "json-tab-content"; - tabContent.innerHTML = ` - -
- - -
-
- - - -
-
- - - -
-
-

-               
-
-
-
-
-
-
- -
- -

-                  
-
- -
-
-

-             `;
-  document.getElementById("formatter-tab-contents").appendChild(tabContent);
-  // Set content if provided
-  if (tabData?.content) {
-    tabContent.querySelector(".json-input").value = tabData.content;
-  }
-  const textarea = tabContent.querySelector(".json-input");
-  textarea.addEventListener("paste", () => setTimeout(() => autoFormatTextarea(textarea), 100));
-  textarea.addEventListener("blur", () => autoFormatTextarea(textarea));
-  textarea.addEventListener("input", () => updateFormatterPreview(tabId));
-  updateFormatterPreview(tabId);
-  enableTabReordering("formatter-tabs-container");
-  applyTabButtonTheme();
-}
-
-function switchFormatterTab(tabId) {
-  document
-    .querySelectorAll("#formatter-tab-contents .json-tab-content")
-    .forEach((tab) => tab.classList.remove("active"));
-  const selectedTab = document.getElementById(tabId);
-  if (selectedTab) selectedTab.classList.add("active");
-  document.querySelectorAll("#formatter-tabs-container .tab-button[data-tab]").forEach((btn) => {
-    btn.classList.toggle("active", btn.getAttribute("data-tab") === tabId);
-  });
-  saveGlobalState();
-}
-
-function updateFormatterPreview(tabId) {
-  const tabContent = document.getElementById(tabId);
-  const textarea = tabContent.querySelector(".json-input");
-  const rawPreview = tabContent.querySelector(".raw-json");
-  const errorMessage = tabContent.querySelector(".error-message");
-  try {
-    const parsed = JSON.parse(textarea.value);
-    const formatted = JSON.stringify(parsed, null, 2);
-    rawPreview.textContent = formatted;
-    createTreeView(parsed, tabContent.querySelector(".tree-view"));
-    errorMessage.textContent = "";
-    showFormatterPreviewTab(tabId, "raw");
-    textarea.value = formatted;
-  } catch (e) {
-    errorMessage.textContent = "Error: " + e.message;
-    showFormatterPreviewTab(tabId, "error");
-  }
-  document.querySelectorAll(".tree-key").forEach((key) => {
-    key.addEventListener("focus", () => {
-      key.scrollIntoView({ block: "nearest", behavior: "smooth" });
-    });
-  });
-  saveGlobalState();
-}
-
-function showFormatterPreviewTab(tabId, previewType) {
-  const tabContent = document.getElementById(tabId);
-  const previews = tabContent.querySelectorAll(".preview-section");
-  previews.forEach((section) => {
-    section.classList.toggle("active", section.id === `${tabId}-${previewType}-preview`);
-  });
-  const buttons = tabContent.querySelectorAll(".tabs .tab-button");
-  buttons.forEach((btn) => {
-    btn.classList.toggle("active", btn.textContent.toLowerCase().includes(previewType));
-  });
-}
-
-function searchFormatterJSON(tabId) {
-  const tabContent = document.getElementById(tabId);
-  const searchInput = tabContent.querySelector(".search-input").value.trim().toLowerCase();
-  const rawPreview = tabContent.querySelector(".raw-json");
-  const treeView = tabContent.querySelector(".tree-view");
-  tabContent.querySelectorAll(".highlight").forEach((el) => {
-    const parent = el.parentNode;
-    parent.replaceChild(document.createTextNode(el.textContent), el);
-  });
-  if (!searchInput) return;
-  const regex = new RegExp(`(${searchInput.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, "gi");
-  if (rawPreview.classList.contains("active")) {
-    const content = rawPreview.textContent;
-    rawPreview.innerHTML = content.replace(regex, '$1');
-  }
-  if (treeView.classList.contains("active")) {
-    function highlightNode(node) {
-      if (node.nodeType === Node.TEXT_NODE) {
-        const matches = node.nodeValue.match(regex);
-        if (matches) {
-          const span = document.createElement("span");
-          span.innerHTML = node.nodeValue.replace(regex, '$1');
-          node.parentNode.replaceChild(span, node);
-        }
-      } else if (node.nodeType === Node.ELEMENT_NODE && node.childNodes) {
-        node.childNodes.forEach((child) => highlightNode(child));
-      }
-    }
-    treeView.childNodes.forEach((child) => highlightNode(child));
-  }
-  saveGlobalState();
-}
-
-function updateFormatterTabColor(tabId, colorValue) {
-  // If needed, update visual indicators here.
-  saveGlobalState();
-}
-
-function uploadFormatterJSON(tabId, inputElement) {
-  if (inputElement.files?.[0]) {
-    const file = inputElement.files[0];
-    const reader = new FileReader();
-    reader.onload = (e) => {
-      const content = e.target.result;
-      const tabContent = document.getElementById(tabId);
-      const textarea = tabContent.querySelector(".json-input");
-      textarea.value = content;
-      updateFormatterPreview(tabId);
-    };
-    reader.readAsText(file);
-    inputElement.value = "";
-  }
-}
-
-function downloadFormatterJSON(tabId) {
-  const tabContent = document.getElementById(tabId);
-  const content = tabContent.querySelector(".json-input").value;
-  const blob = new Blob([content], {
-    type: "application/json",
-  });
-  const url = URL.createObjectURL(blob);
-  const a = document.createElement("a");
-  a.href = url;
-  a.download = tabId + ".json";
-  document.body.appendChild(a);
-  a.click();
-  document.body.removeChild(a);
-  URL.revokeObjectURL(url);
-}
-
-async function closeFormatterTab(tabId, event) {
-  if (event) {
-    event.stopPropagation();
-    event.preventDefault();
-  }
-
-  const result = await Swal.fire({
-    title: "Close tab?",
-    text: "Are you sure you want to close this tab?",
-    icon: "warning",
-    showCancelButton: true,
-    confirmButtonText: "Yes",
-    cancelButtonText: "No",
-  });
-
-  if (!result.isConfirmed) return;
-
-  const tabButton = document.querySelector(
-    `#formatter-tabs-container .tab-button[data-tab="${tabId}"]`
-  );
-  const tabContent = document.getElementById(tabId);
-  if (tabButton) tabButton.remove();
-  if (tabContent) tabContent.remove();
-  const remaining = document.querySelectorAll("#formatter-tab-contents .json-tab-content");
-  if (remaining.length > 0) switchFormatterTab(remaining[remaining.length - 1].id);
-  saveGlobalState();
-}
-
-/* ========== Compare Functions ========== */
-let compareTabCount = 0;
-
-function addCompareTab() {
-  createCompareTab();
-  switchCompareTab("compareTab" + compareTabCount);
-  saveGlobalState();
-}
-
-function createCompareTab() {
-  compareTabCount++;
-  const tabId = "compareTab" + compareTabCount;
-  // Create tab button
-  const tabButton = document.createElement("button");
-  tabButton.className = "tab-button";
-  tabButton.setAttribute("data-tab", tabId);
-  tabButton.onclick = () => switchCompareTab(tabId);
-  tabButton.innerHTML = `Tab ${compareTabCount}
-               ร—`;
-  tabButton.addEventListener("dblclick", () => openTabRenameTooltip(tabId, "compare"));
-  const tabsContainer = document.getElementById("compare-tabs-container");
-  const addButton = tabsContainer.querySelector(".add-tab-button");
-  tabsContainer.insertBefore(tabButton, addButton);
-  // Create tab content
-  const tabContent = document.createElement("div");
-  tabContent.id = tabId;
-  tabContent.className = "json-tab-content";
-  tabContent.innerHTML = `
-               
- - -
- -
- `; - document.getElementById("compare-tab-contents").appendChild(tabContent); - const leftTA = tabContent.querySelector(".json-input-left"); - const rightTA = tabContent.querySelector(".json-input-right"); - leftTA.addEventListener("paste", () => setTimeout(() => autoFormatTextarea(leftTA), 100)); - leftTA.addEventListener("blur", () => autoFormatTextarea(leftTA)); - rightTA.addEventListener("paste", () => setTimeout(() => autoFormatTextarea(rightTA), 100)); - rightTA.addEventListener("blur", () => autoFormatTextarea(rightTA)); - saveGlobalState(); - enableTabReordering("compare-tabs-container"); -} -// Create Compare tab using saved data -function createCompareTabWithData(tabData) { - compareTabCount++; - const tabId = tabData.id; - const tabButton = document.createElement("button"); - tabButton.className = "tab-button"; - tabButton.setAttribute("data-tab", tabId); - tabButton.onclick = () => switchCompareTab(tabId); - tabButton.innerHTML = `${tabData.name} - ร—`; - tabButton.addEventListener("dblclick", () => openTabRenameTooltip(tabId, "compare")); - const tabsContainer = document.getElementById("compare-tabs-container"); - const addButton = tabsContainer.querySelector(".add-tab-button"); - tabsContainer.insertBefore(tabButton, addButton); - const tabContent = document.createElement("div"); - tabContent.id = tabId; - tabContent.className = "json-tab-content"; - tabContent.innerHTML = ` -
- - -
- -
- `; - document.getElementById("compare-tab-contents").appendChild(tabContent); - const leftTA = tabContent.querySelector(".json-input-left"); - const rightTA = tabContent.querySelector(".json-input-right"); - leftTA.value = tabData.leftContent; - rightTA.value = tabData.rightContent; - leftTA.addEventListener("paste", () => setTimeout(() => autoFormatTextarea(leftTA), 100)); - leftTA.addEventListener("blur", () => autoFormatTextarea(leftTA)); - rightTA.addEventListener("paste", () => setTimeout(() => autoFormatTextarea(rightTA), 100)); - rightTA.addEventListener("blur", () => autoFormatTextarea(rightTA)); - saveGlobalState(); - enableTabReordering("compare-tabs-container"); -} - -function switchCompareTab(tabId) { - document - .querySelectorAll("#compare-tab-contents .json-tab-content") - .forEach((tab) => tab.classList.remove("active")); - const selectedTab = document.getElementById(tabId); - if (selectedTab) selectedTab.classList.add("active"); - document.querySelectorAll("#compare-tabs-container .tab-button[data-tab]").forEach((btn) => { - btn.classList.toggle("active", btn.getAttribute("data-tab") === tabId); - }); - saveGlobalState(); -} - -function compareJSONs(tabId) { - const tabContent = document.getElementById(tabId); - const leftTA = tabContent.querySelector(".json-input-left"); - const rightTA = tabContent.querySelector(".json-input-right"); - const resultDiv = tabContent.querySelector(".compare-result"); - const leftText = leftTA.value; - const rightText = rightTA.value; - let leftObj; - let rightObj; - try { - leftObj = JSON.parse(leftText); - } catch (e) { - resultDiv.textContent = "Left JSON Error: " + e.message; - return; - } - try { - rightObj = JSON.parse(rightText); - } catch (e) { - resultDiv.textContent = "Right JSON Error: " + e.message; - return; - } - const leftFormatted = JSON.stringify(leftObj, null, 2); - const rightFormatted = JSON.stringify(rightObj, null, 2); - resultDiv.innerHTML = ` -
- - -
- ${diffJSONsPreview(leftFormatted, rightFormatted)} - `; - leftTA.value = leftFormatted; - rightTA.value = rightFormatted; - saveGlobalState(); -} - -function diffJSONsPreview(leftText, rightText) { - const isDarkMode = document.body.classList.contains("dark-mode"); - // Choose a diff color based on the theme: - // For light mode, use a light red; for dark mode, use a darker or more muted red. - const diffStyle = isDarkMode ? "background-color:#662222;" : "background-color:#ddd;"; - - const leftLines = leftText.split("\n"); - const rightLines = rightText.split("\n"); - const maxLines = Math.max(leftLines.length, rightLines.length); - let html = ""; - for (let i = 0; i < maxLines; i++) { - const lLine = leftLines[i] || ""; - const rLine = rightLines[i] || ""; - // Apply diffStyle if the lines differ - const style = lLine === rLine ? "" : diffStyle; - html += ` - - - `; - } - html += "
-
${lLine}
-
-
${rLine}
-
"; - return html; -} - -async function closeCompareTab(tabId, event) { - if (event) { - event.stopPropagation(); - event.preventDefault(); - } - - const result = await Swal.fire({ - title: "Close tab?", - text: "Are you sure you want to close this tab?", - icon: "warning", - showCancelButton: true, - confirmButtonText: "Yes", - cancelButtonText: "No", - }); - - if (!result.isConfirmed) return; - - const tabButton = document.querySelector( - `#compare-tabs-container .tab-button[data-tab="${tabId}"]` - ); - const tabContent = document.getElementById(tabId); - if (tabButton) tabButton.remove(); - if (tabContent) tabContent.remove(); - const remaining = document.querySelectorAll("#compare-tab-contents .json-tab-content"); - if (remaining.length > 0) switchCompareTab(remaining[remaining.length - 1].id); - saveGlobalState(); -} - -/* ========== CodeGen Functions ========== */ -let codegenTabCount = 0; - -function addCodegenTab() { - createCodegenTab(); - switchCodegenTab("codegenTab" + codegenTabCount); - saveGlobalState(); -} - -function createCodegenTab() { - codegenTabCount++; - const tabId = "codegenTab" + codegenTabCount; - // Create tab button - const tabButton = document.createElement("button"); - tabButton.className = "tab-button"; - tabButton.setAttribute("data-tab", tabId); - tabButton.onclick = () => switchCodegenTab(tabId); - tabButton.innerHTML = `Tab ${codegenTabCount} - ร—`; - tabButton.addEventListener("dblclick", () => openTabRenameTooltip(tabId, "codegen")); - const tabsContainer = document.getElementById("codegen-tabs-container"); - const addButton = tabsContainer.querySelector(".add-tab-button"); - tabsContainer.insertBefore(tabButton, addButton); - // Create tab content - const tabContent = document.createElement("div"); - tabContent.id = tabId; - tabContent.className = "json-tab-content"; - tabContent.innerHTML = ` - -
- - - - -
-

-             `;
-  document.getElementById("codegen-tab-contents").appendChild(tabContent);
-  const textarea = tabContent.querySelector(".json-input");
-  textarea.addEventListener("paste", () => setTimeout(() => autoFormatTextarea(textarea), 100));
-  textarea.addEventListener("blur", () => autoFormatTextarea(textarea));
-  saveGlobalState();
-}
-
-function createCodegenTabWithData(tabData) {
-  codegenTabCount++;
-  const tabId = tabData.id;
-  const tabButton = document.createElement("button");
-  tabButton.className = "tab-button";
-  tabButton.setAttribute("data-tab", tabId);
-  tabButton.onclick = () => switchCodegenTab(tabId);
-  tabButton.innerHTML = `${tabData.name}
-               ร—`;
-  tabButton.addEventListener("dblclick", () => openTabRenameTooltip(tabId, "codegen"));
-  const tabsContainer = document.getElementById("codegen-tabs-container");
-  const addButton = tabsContainer.querySelector(".add-tab-button");
-  tabsContainer.insertBefore(tabButton, addButton);
-  const tabContent = document.createElement("div");
-  tabContent.id = tabId;
-  tabContent.className = "json-tab-content";
-  tabContent.innerHTML = `
-               
-               
- - - -
-

-             `;
-  document.getElementById("codegen-tab-contents").appendChild(tabContent);
-  const textarea = tabContent.querySelector(".json-input");
-  textarea.value = tabData.input;
-  const selectElem = document.getElementById("lang-select-" + tabId);
-  selectElem.value = tabData.lang;
-  textarea.addEventListener("paste", () => setTimeout(() => autoFormatTextarea(textarea), 100));
-  textarea.addEventListener("blur", () => autoFormatTextarea(textarea));
-  saveGlobalState();
-  enableTabReordering("codegen-tabs-container");
-}
-
-function switchCodegenTab(tabId) {
-  document
-    .querySelectorAll("#codegen-tab-contents .json-tab-content")
-    .forEach((tab) => tab.classList.remove("active"));
-  const selectedTab = document.getElementById(tabId);
-  if (selectedTab) selectedTab.classList.add("active");
-  document.querySelectorAll("#codegen-tabs-container .tab-button[data-tab]").forEach((btn) => {
-    btn.classList.toggle("active", btn.getAttribute("data-tab") === tabId);
-  });
-  saveGlobalState();
-}
-
-function generateCode(tabId) {
-  const tabContent = document.getElementById(tabId);
-  const textarea = tabContent.querySelector(".json-input");
-  const langSelect = document.getElementById("lang-select-" + tabId);
-  const outputPre = tabContent.querySelector(".code-output");
-  const inputText = textarea.value;
-  const lang = langSelect.value;
-  let obj;
-  try {
-    obj = JSON.parse(inputText);
-  } catch (e) {
-    outputPre.textContent = "Invalid JSON: " + e.message;
-    return;
-  }
-  let code = "";
-  if (lang === "typescript") code = generateTypeScript(obj, "Root");
-  else if (lang === "python") code = generatePython(obj, "Root");
-  else if (lang === "go") code = generateGo(obj, "Root");
-  outputPre.innerHTML = code;
-  // .replace(/(interface|class|type)\b/g, '$1')
-  // .replace(/"([^"]+)"/g, '"$1"')
-  // .replace(/\b\d+\b/g, '$&')
-  // .replace(/\bstring|number|boolean|any|void\b/g, '$&');
-
-  saveGlobalState();
-}
-
-async function closeCodegenTab(tabId, event) {
-  if (event) {
-    event.stopPropagation();
-    event.preventDefault();
-  }
-
-  const result = await Swal.fire({
-    title: "Close tab?",
-    text: "Are you sure you want to close this tab?",
-    icon: "warning",
-    showCancelButton: true,
-    confirmButtonText: "Yes",
-    cancelButtonText: "No",
-  });
-
-  if (!result.isConfirmed) return;
-
-  const tabButton = document.querySelector(
-    `#codegen-tabs-container .tab-button[data-tab="${tabId}"]`
-  );
-  const tabContent = document.getElementById(tabId);
-  if (tabButton) tabButton.remove();
-  if (tabContent) tabContent.remove();
-  const remaining = document.querySelectorAll("#codegen-tab-contents .json-tab-content");
-  if (remaining.length > 0) switchCodegenTab(remaining[remaining.length - 1].id);
-  saveGlobalState();
-}
-
-/* ========== Utility Functions ========== */
-function autoFormatTextarea(textarea) {
-  try {
-    const parsed = JSON.parse(textarea.value);
-    textarea.value = JSON.stringify(parsed, null, 2);
-  } catch (e) {
-    // Do nothing if invalid
-  }
-}
-
-function createTreeView(data, parentElement) {
-  parentElement.innerHTML = "";
-  const focusedNode = null;
-
-  function processNode(value, parent, key, path = []) {
-    const node = document.createElement("div");
-    node.className = "tree-node";
-    const currentPath = [...path, key];
-    const displayKey = key !== undefined ? key : "";
-
-    if (typeof value === "object" && value !== null) {
-      const isArray = Array.isArray(value);
-      const keySpan = document.createElement("span");
-      keySpan.className = `tree-key ${isArray ? "type-array" : "type-object"}`;
-      keySpan.tabIndex = 0;
-      keySpan.innerHTML = `
-                  ${displayKey}
-                  ${
-                    isArray ? `[${value.length}]` : `{${Object.keys(value).length}}`
-                  }
-              `;
-
-      // Keyboard navigation
-      keySpan.addEventListener("keydown", (e) => {
-        switch (e.key) {
-          case "ArrowRight":
-            if (keySpan.classList.contains("collapsed")) toggleNode();
-            break;
-          case "ArrowLeft":
-            if (!keySpan.classList.contains("collapsed")) toggleNode();
-            break;
-          case "ArrowDown":
-            focusNextNode(keySpan);
-            break;
-          case "ArrowUp":
-            focusPreviousNode(keySpan);
-            break;
-        }
-      });
-
-      // Context menu
-      keySpan.addEventListener("contextmenu", (e) => {
-        e.preventDefault();
-        showContextMenu(e, currentPath, value);
-      });
-
-      const children = document.createElement("div");
-      children.className = "tree-children";
-      children.style.display = "none";
-
-      const toggleNode = () => {
-        keySpan.classList.toggle("expanded");
-        keySpan.classList.toggle("collapsed");
-        children.style.display = children.style.display === "none" ? "block" : "none";
-      };
-
-      keySpan.addEventListener("click", toggleNode);
-
-      if (isArray) {
-        value.forEach((item, index) => processNode(item, children, index, currentPath));
-      } else {
-        Object.entries(value).forEach(([k, v]) => processNode(v, children, k, currentPath));
-      }
-
-      node.appendChild(keySpan);
-      node.appendChild(children);
-      parent.appendChild(node);
-    } else {
-      const valueSpan = document.createElement("span");
-      valueSpan.innerHTML = `
-                  ${key}: 
-                  ${formatValue(value)}
-              `;
-
-      // Value context menu
-      valueSpan.addEventListener("contextmenu", (e) => {
-        e.preventDefault();
-        showContextMenu(e, currentPath, value);
-      });
-
-      node.appendChild(valueSpan);
-      parent.appendChild(node);
-    }
-
-    return node;
-  }
-
-  function getValueTypeClass(value) {
-    if (value === null) return "type-null";
-    if (Array.isArray(value)) return "type-array";
-    if (typeof value === "object") return "type-object";
-    return `type-${typeof value}`;
-  }
-
-  function formatValue(value) {
-    if (value === null) return "null";
-    if (typeof value === "string") return `"${value}"`;
-    return value.toString();
-  }
-
-  function showContextMenu(e, path, value) {
-    const menu = document.createElement("div");
-    menu.className = "tree-context-menu";
-    menu.style.left = `${e.pageX}px`;
-    menu.style.top = `${e.pageY}px`;
-
-    const copyPath = document.createElement("div");
-    copyPath.className = "tree-context-menu-item";
-    copyPath.textContent = "Copy Path";
-    copyPath.onclick = () => navigator.clipboard.writeText(path.join("."));
-
-    const copyValue = document.createElement("div");
-    copyValue.className = "tree-context-menu-item";
-    copyValue.textContent = "Copy Value";
-    copyValue.onclick = () => navigator.clipboard.writeText(JSON.stringify(value));
-
-    menu.appendChild(copyPath);
-    menu.appendChild(copyValue);
-    document.body.appendChild(menu);
-
-    const closeMenu = () => {
-      document.body.removeChild(menu);
-      document.removeEventListener("click", closeMenu);
-    };
-
-    document.addEventListener("click", closeMenu);
-  }
-
-  function focusNextNode(currentNode) {
-    const allNodes = parentElement.querySelectorAll(".tree-key");
-    const currentIndex = Array.from(allNodes).indexOf(currentNode);
-    if (currentIndex < allNodes.length - 1) {
-      allNodes[currentIndex + 1].focus();
-    }
-  }
-
-  function focusPreviousNode(currentNode) {
-    const allNodes = parentElement.querySelectorAll(".tree-key");
-    const currentIndex = Array.from(allNodes).indexOf(currentNode);
-    if (currentIndex > 0) {
-      allNodes[currentIndex - 1].focus();
-    }
-  }
-
-  processNode(data, parentElement);
-}
-
-function openTabRenameTooltip(tabId, mode) {
-  let containerSelector;
-  if (mode === "formatter") containerSelector = "#formatter-tabs-container";
-  else if (mode === "compare") containerSelector = "#compare-tabs-container";
-  else if (mode === "codegen") containerSelector = "#codegen-tabs-container";
-  else containerSelector = "#editor-tabs-container";
-
-  const tabButton = document.querySelector(containerSelector + ` .tab-button[data-tab="${tabId}"]`);
-  const existingTooltip = document.querySelector(".tab-rename-tooltip");
-  if (existingTooltip) existingTooltip.remove();
-  const tooltip = document.createElement("div");
-  tooltip.className = "tab-rename-tooltip";
-  const rect = tabButton.getBoundingClientRect();
-  tooltip.style.left = rect.left + "px";
-  tooltip.style.top = rect.bottom + window.scrollY + 5 + "px";
-  const input = document.createElement("input");
-  input.type = "text";
-  input.value = tabButton.querySelector(".tab-name").textContent;
-  input.style.width = "150px";
-  input.addEventListener("keydown", (e) => {
-    if (e.key === "Enter") finalizeRename();
-    else if (e.key === "Escape") tooltip.remove();
-  });
-  input.addEventListener("blur", finalizeRename);
-  tooltip.appendChild(input);
-  document.body.appendChild(tooltip);
-  input.focus();
-
-  function finalizeRename() {
-    const newName = input.value.trim();
-    if (newName) {
-      tabButton.querySelector(".tab-name").textContent = newName;
-    }
-    tooltip.remove();
-    saveGlobalState();
-  }
-}
-
-function generateTypeScript(obj, interfaceName) {
-  let result = `interface ${interfaceName} {\n`;
-  for (const key in obj) {
-    if (!Object.hasOwn(obj, key)) continue;
-    const value = obj[key];
-    if (value === null) {
-      result += `  ${key}: any;\n`;
-    } else if (Array.isArray(value)) {
-      if (value.length > 0) {
-        const elem = value[0];
-        if (typeof elem === "object" && elem !== null) {
-          const subInterface = interfaceName + capitalize(key);
-          result += `  ${key}: ${subInterface}[];\n`;
-          result += generateTypeScript(elem, subInterface);
-        } else {
-          result += `  ${key}: ${typeof elem}[];\n`;
-        }
-      } else {
-        result += `  ${key}: any[];\n`;
-      }
-    } else if (typeof value === "object") {
-      const subInterface = interfaceName + capitalize(key);
-      result += `  ${key}: ${subInterface};\n`;
-      result += generateTypeScript(value, subInterface);
-    } else {
-      result += `  ${key}: ${typeof value};\n`;
-    }
-  }
-  result += "}\n";
-  return result;
-}
-
-function generatePython(obj, className) {
-  let result = "from dataclasses import dataclass\nfrom typing import Any, List\n\n";
-  result += `@dataclass\nclass ${className}:\n`;
-  for (const key in obj) {
-    if (!Object.hasOwn(obj, key)) continue;
-    const value = obj[key];
-    let pyType = "Any";
-    if (value === null) {
-      pyType = "Any";
-    } else if (Array.isArray(value)) {
-      if (value.length > 0) {
-        const elem = value[0];
-        if (typeof elem === "object" && elem !== null) {
-          const subClass = className + capitalize(key);
-          pyType = `List[${subClass}]`;
-          result += "\n" + generatePython(elem, subClass);
-        } else {
-          if (typeof elem === "number") pyType = "List[float]";
-          else if (typeof elem === "string") pyType = "List[str]";
-          else if (typeof elem === "boolean") pyType = "List[bool]";
-          else pyType = "List[Any]";
-        }
-      } else {
-        pyType = "List[Any]";
-      }
-    } else if (typeof value === "object") {
-      const subClass = className + capitalize(key);
-      pyType = subClass;
-      result += "\n" + generatePython(value, subClass);
-    } else {
-      if (typeof value === "number") pyType = "float";
-      else if (typeof value === "string") pyType = "str";
-      else if (typeof value === "boolean") pyType = "bool";
-    }
-    result += `    ${key}: ${pyType}\n`;
-  }
-  return result;
-}
-
-function generateGo(obj, structName) {
-  let result = `type ${structName} struct {\n`;
-  for (const key in obj) {
-    if (!Object.hasOwn(obj, key)) continue;
-    const value = obj[key];
-    let goType = "interface{}";
-    if (value === null) {
-      goType = "interface{}";
-    } else if (Array.isArray(value)) {
-      if (value.length > 0) {
-        const elem = value[0];
-        if (typeof elem === "object" && elem !== null) {
-          const subStruct = structName + capitalize(key);
-          goType = `[]${subStruct}`;
-          result += generateGo(elem, subStruct);
-        } else {
-          if (typeof elem === "number") goType = "[]float64";
-          else if (typeof elem === "string") goType = "[]string";
-          else if (typeof elem === "boolean") goType = "[]bool";
-          else goType = "[]interface{}";
-        }
-      } else {
-        goType = "[]interface{}";
-      }
-    } else if (typeof value === "object") {
-      const subStruct = structName + capitalize(key);
-      goType = subStruct;
-      result += generateGo(value, subStruct);
-    } else {
-      if (typeof value === "number") goType = "float64";
-      else if (typeof value === "string") goType = "string";
-      else if (typeof value === "boolean") goType = "bool";
-    }
-    const fieldName = key.charAt(0).toUpperCase() + key.slice(1);
-    result += `    ${fieldName} ${goType} \`json:"${key}"\`\n`;
-  }
-  result += "}\n";
-  return result;
-}
-
-function capitalize(str) {
-  return str.charAt(0).toUpperCase() + str.slice(1);
-}
-
-/* ========== Convert to Dict Functions ========== */
-let currentConvertMode = "dict-to-json";
-
-function switchConvertDirection(mode) {
-  currentConvertMode = mode;
-  document.querySelectorAll("#convert-section .tab-button").forEach((btn) => {
-    btn.classList.toggle("active", btn.textContent.includes("Dict") === (mode === "dict-to-json"));
-  });
-  document.getElementById("convert-input").value = "";
-  document.getElementById("convert-output").textContent = "";
-}
-
-function convert() {
-  const input = document.getElementById("convert-input").value;
-  const output = document.getElementById("convert-output");
-
-  try {
-    if (currentConvertMode === "dict-to-json") {
-      // 1) None/True/False โ†’ null/true/false
-      let json = input
-        .replace(/\bNone\b/g, "null")
-        .replace(/\bTrue\b/g, "true")
-        .replace(/\bFalse\b/g, "false");
-      // 2) remove trailing commas before } or ]
-      json = json.replace(/,\s*(?=[}\]])/g, "");
-      // 3) convert only quoted 'strings' โ†’ "strings"
-      json = json.replace(/'([^']*?)'/g, '"$1"');
-
-      const parsed = JSON.parse(json);
-      output.textContent = JSON.stringify(parsed, null, 2);
-    } else {
-      // JSON โ†’ Python dict
-      const obj = JSON.parse(input);
-      let dictStr = JSON.stringify(obj, null, 2);
-      // 1) only replace "quoted" strings โ†’ 'strings'
-      dictStr = dictStr.replace(/"([^"]*?)"/g, "'$1'");
-      // 2) true/false/null โ†’ True/False/None
-      dictStr = dictStr
-        .replace(/\btrue\b/g, "True")
-        .replace(/\bfalse\b/g, "False")
-        .replace(/\bnull\b/g, "None");
-
-      output.textContent = dictStr;
-    }
-  } catch (e) {
-    output.textContent = "Error: " + e.message;
-  }
-}
-
-function copyConvertOutput() {
-  const output = document.getElementById("convert-output").textContent;
-  copyToClipboard(output, "Copied!");
-}
-
-/* ========== Mock data generator ========== */
-
-let latestMockData = [];
-
-function loadMockPreset(name) {
-  if (!name) return;
-  
-  const preset = window.PRESETS.mockgen[name];
-  if (!preset) return;
-
-  const schemaInput = document.getElementById('mock-schema-input');
-  if (schemaInput) {
-    schemaInput.value = JSON.stringify(preset.schema, null, 2);
-  }
-}
-
-function generateMockData() {
-  const input = document.getElementById("mock-schema-input").value;
-  const count = Number.parseInt(document.getElementById("mockgen-count").value || "1", 10);
-  const outputContainer = document.getElementById("mock-output-container");
-
-  try {
-    const schema = JSON.parse(input);
-    latestMockData = Array.from({ length: count }, () => {
-      try {
-        return mockFromSchema(schema);
-      } catch (err) {
-        return { error: err.message };
-      }
-    });
-    document.getElementById("mock-stats").textContent = `Showing ${count} record(s)`;
-    updateMockView();
-  } catch (e) {
-    outputContainer.innerHTML = `
โŒ Error: ${e.message}
`; - } -} -/* global faker */ -function resolveFakerPath(path) { - if (!path) throw new Error("Empty faker path"); - const parts = path.split("."); - let current = faker; - for (const part of parts) { - if (!current[part]) throw new Error(`Invalid faker path: "${path}"`); - current = current[part]; - } - if (typeof current === "function") return current(); - throw new Error(`Faker path "${path}" does not resolve to a function`); -} - -function mockFromSchema(schema) { - if (Array.isArray(schema)) { - return schema.map((item) => mockFromSchema(item)); - } - if (typeof schema === "object") { - const result = {}; - for (const [key, value] of Object.entries(schema)) { - result[key] = mockFromSchema(value); - } - return result; - } - if (typeof schema === "string") { - if (schema === "boolean") return Math.random() < 0.5; - if (schema.startsWith("number|")) { - const [min, max] = schema.split("|")[1].split("-").map(Number); - return faker.number.int({ min, max }); - } - - return resolveFakerPath(schema); - } - return schema; -} - -function updateMockView() { - const mode = document.querySelector('input[name="mock-view-mode"]:checked').value; - const container = document.getElementById("mock-output-container"); - container.innerHTML = ""; - - if (mode === "json") { - const pre = document.createElement("pre"); - pre.className = "code-output"; - pre.textContent = JSON.stringify( - latestMockData.length === 1 ? latestMockData[0] : latestMockData, - null, - 2 - ); - container.appendChild(pre); - } else { - container.appendChild(renderTableFromJson(latestMockData)); - } -} - -function renderTableFromJson(data) { - const table = document.createElement("table"); - table.className = "mock-preview-table"; - - if (!Array.isArray(data) || data.length === 0 || typeof data[0] !== "object") { - table.innerHTML = "No tabular data available"; - return table; - } - - const headers = [...new Set(data.flatMap((obj) => Object.keys(obj)))]; - const thead = document.createElement("thead"); - const headerRow = document.createElement("tr"); - headers.forEach((h) => { - const th = document.createElement("th"); - th.textContent = h; - // Add title for full text on hover - th.title = h; - headerRow.appendChild(th); - }); - thead.appendChild(headerRow); - table.appendChild(thead); - - const tbody = document.createElement("tbody"); - data.forEach((row) => { - const tr = document.createElement("tr"); - if (row.error) { - tr.classList.add("error"); - } - - headers.forEach((key) => { - const td = document.createElement("td"); - const val = row[key]; - let displayVal; - - if (val && typeof val === "object") { - displayVal = JSON.stringify(val); - } else { - displayVal = val ?? ""; - } - - td.textContent = displayVal; - // Add title for full text on hover - td.title = displayVal; - - tr.appendChild(td); - }); - tbody.appendChild(tr); - }); - table.appendChild(tbody); - return table; -} - -function copyMockOutput(format = "json") { - let text = ""; - if (format === "json") { - text = JSON.stringify( - latestMockData.length === 1 ? latestMockData[0] : latestMockData, - null, - 2 - ); - } else if (format === "csv") { - if (!latestMockData.length || typeof latestMockData[0] !== "object") return; - const keys = Object.keys(latestMockData[0]); - const rows = latestMockData.map((obj) => keys.map((k) => JSON.stringify(obj[k] ?? ""))); - text = [keys.join(","), ...rows.map((r) => r.join(","))].join("\n"); - } - - copyToClipboard(text, `Copied ${format.toUpperCase()}!`); -} - -function downloadFile(content, filename, type) { - const blob = new Blob([content], { type }); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = filename; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); -} - -let userInputValue = ""; // This variable will store filename input from user using the following event listener: - -document.getElementById("userInput").addEventListener("input", (e) => { - userInputValue = e.target.value; - //console.log("Saved input:", userInputValue); // Optional: see it live -}); - -function exportMockOutput(format = "json") { - if (!Array.isArray(latestMockData) || latestMockData.length === 0) { - console.error("No data available to export."); - return; - } - - let content = ""; - let filename = userInputValue !== "" ? userInputValue : "mock_data"; // Use user input if available - - if (format === "json") { - content = JSON.stringify( - latestMockData.length === 1 ? latestMockData[0] : latestMockData, - null, - 2 - ); - filename += ".json"; - downloadFile(content, filename, "application/json"); - } else if (format === "csv") { - if (!latestMockData.length || typeof latestMockData[0] !== "object") return; - - const keys = Object.keys(latestMockData[0]); - const rows = latestMockData.map((obj) => keys.map((k) => JSON.stringify(obj[k] ?? ""))); - content = [keys.join(","), ...rows.map((r) => r.join(","))].join("\n"); - filename += ".csv"; - downloadFile(content, filename, "text/csv"); - } -} - -const mockgenDocs = ` - # ๐Ÿงช Mock Data Schema Format - - ## Supported Types - - - \`"name.fullName"\`: Full name - - \`"internet.email"\`: Email address - - \`"number|10-50"\`: Custom range - - \`"boolean"\`: true / false - - Arrays: \`["lorem.word"]\` - - Nested: - - \`\`\`json - { - "user": { - "name": "name.fullName", - "email": "internet.email" - } - } - \`\`\` - - ## Example - - \`\`\`json - { - "id": "number|1000-9999", - "name": "name.fullName", - "email": "internet.email", - "isActive": "boolean" - } - \`\`\` - `; - -function switchMockTab(tab) { - const schemaPanel = document.getElementById("mockgen-schema-panel"); - const docsPanel = document.getElementById("mockgen-docs-panel"); - - document.getElementById("mock-tab-schema").classList.remove("active"); - document.getElementById("mock-tab-docs").classList.remove("active"); - - if (tab === "schema") { - schemaPanel.style.display = "block"; - docsPanel.style.display = "none"; - document.getElementById("mock-tab-schema").classList.add("active"); - } else { - schemaPanel.style.display = "none"; - docsPanel.style.display = "block"; - document.getElementById("mock-tab-docs").classList.add("active"); - renderMockgenDocs(); - } -} - -function renderMockgenDocs() { - document.getElementById("mockgen-docs-preview").innerHTML = marked.parse(mockgenDocs); -} - -// Text Editor -let editorTabCount = 0; -const editorInstances = {}; - -function applyTabButtonTheme() { - const isDark = document.body.classList.contains("dark-mode"); - const buttons = document.querySelectorAll(".tab-button[data-tab]"); - - buttons.forEach((button) => { - button.style.backgroundColor = isDark ? "#2a2a2a" : "#ffffff"; - button.style.color = isDark ? "#ffffff" : "#000000"; - }); -} - -function addEditorTab(tabData = null) { - let tabId; - if (tabData?.id) { - tabId = tabData.id; - const match = tabId.match(/editor-tab-(\d+)/); - if (match && Number.parseInt(match[1], 10) > editorTabCount) { - editorTabCount = Number.parseInt(match[1], 10); - } - } else { - editorTabCount++; - tabId = `editor-tab-${editorTabCount}`; - } - - const tabButton = document.createElement("button"); - tabButton.className = "tab-button"; - tabButton.setAttribute("data-tab", tabId); - tabButton.innerHTML = `${ - tabData?.title || `Note ${editorTabCount}` - }ร—`; - tabButton.onclick = () => switchEditorTab(tabId); - tabButton.addEventListener("dblclick", () => openTabRenameTooltip(tabId, "editor")); - - document - .getElementById("editor-tabs-container") - .insertBefore(tabButton, document.querySelector("#editor-tabs-container .add-tab-button")); - - const tabContent = document.createElement("div"); - tabContent.id = tabId; - tabContent.className = "json-tab-content"; - tabContent.innerHTML = ` -
-
- -
- `; - - document.getElementById("editor-tab-contents").appendChild(tabContent); - - const editor = new toastui.Editor({ - el: document.getElementById(`${tabId}-editor`), - height: "400px", - initialEditType: "markdown", - previewStyle: "vertical", - }); - editorInstances[tabId] = editor; - - const saved = localStorage.getItem(tabId); - if (saved) editor.setMarkdown(saved); - - enableEditorTabReordering(); - applyEditorTabDarkMode(); -} - -function switchEditorTab(tabId) { - document - .querySelectorAll("#editor-tab-contents .json-tab-content") - .forEach((el) => el.classList.remove("active")); - document.getElementById(tabId)?.classList.add("active"); - - document.querySelectorAll("#editor-tabs-container .tab-button").forEach((btn) => { - btn.classList.toggle("active", btn.getAttribute("data-tab") === tabId); - }); - updateEditorGlobalState(); -} - -function saveEditorContent(tabId) { - const content = editorInstances[tabId].getMarkdown(); - localStorage.setItem(tabId, content); - updateEditorGlobalState(); - Swal.fire({ - toast: true, - position: "top-end", - icon: "success", - title: "Autosaved", - showConfirmButton: false, - timer: 1500, - }); -} - -async function deleteEditorTab(tabId, event) { - if (event) { - event.stopPropagation(); - event.preventDefault(); - } - - const result = await Swal.fire({ - title: "Close tab?", - text: "Are you sure you want to close this tab?", - icon: "warning", - showCancelButton: true, - confirmButtonText: "Yes", - cancelButtonText: "No", - }); - - if (!result.isConfirmed) return; - - localStorage.removeItem(tabId); - delete editorInstances[tabId]; - document.querySelector(`#editor-tabs-container .tab-button[data-tab="${tabId}"]`)?.remove(); - document.getElementById(tabId)?.remove(); - const remaining = document.querySelectorAll("#editor-tab-contents .json-tab-content"); - if (remaining.length > 0) switchEditorTab(remaining[0].id); - updateEditorGlobalState(); -} - -function updateEditorGlobalState() { - const state = { - activeTab: document.querySelector("#editor-tab-contents .json-tab-content.active")?.id || "", - tabs: [], - }; - document.querySelectorAll("#editor-tabs-container .tab-button[data-tab]").forEach((btn) => { - const tabId = btn.getAttribute("data-tab"); - const title = btn.querySelector(".tab-name").textContent; - state.tabs.push({ id: tabId, title }); - }); - localStorage.setItem("editorState", JSON.stringify(state)); -} - -function loadEditorGlobalState() { - const stateStr = localStorage.getItem("editorState"); - const container = document.getElementById("editor-tabs-container"); - container.querySelectorAll(".tab-button[data-tab]").forEach((btn) => btn.remove()); - document.getElementById("editor-tab-contents").innerHTML = ""; - editorTabCount = 0; - - if (!stateStr) { - addEditorTab(); - return; - } - - const state = JSON.parse(stateStr); - state.tabs.forEach((tabData) => addEditorTab(tabData)); - - if (state.activeTab && document.getElementById(state.activeTab)) { - switchEditorTab(state.activeTab); - } else if (state.tabs.length > 0) { - switchEditorTab(state.tabs[0].id); - } -} - -function enableEditorTabReordering() { - const container = document.getElementById("editor-tabs-container"); - const buttons = container.querySelectorAll(".tab-button[data-tab]"); - buttons.forEach((btn) => { - btn.draggable = true; - btn.addEventListener("dragstart", (e) => { - e.dataTransfer.setData("text/plain", btn.getAttribute("data-tab")); - btn.classList.add("dragging"); - }); - btn.addEventListener("dragend", () => { - btn.classList.remove("dragging"); - container.querySelectorAll(".tab-button").forEach((b) => b.classList.remove("drag-over")); - }); - btn.addEventListener("dragover", (e) => { - e.preventDefault(); - btn.classList.add("drag-over"); - }); - btn.addEventListener("dragleave", () => { - btn.classList.remove("drag-over"); - }); - btn.addEventListener("drop", (e) => { - e.preventDefault(); - const draggedId = e.dataTransfer.getData("text/plain"); - const draggedBtn = container.querySelector(`[data-tab="${draggedId}"]`); - - btn.classList.remove("drag-over"); - - if (draggedBtn && draggedBtn !== btn) { - container.insertBefore(draggedBtn, btn); - updateEditorGlobalState(); - } - }); - }); -} - -function applyEditorTabDarkMode() { - const container = document.getElementById("editor-tabs-container"); - container.querySelectorAll(".tab-button").forEach((btn) => { - if (document.body.classList.contains("dark-mode")) { - btn.style.backgroundColor = "#2c2c2c"; - btn.style.color = "#eee"; - btn.style.borderColor = "#444"; - } else { - btn.style.backgroundColor = ""; - btn.style.color = ""; - btn.style.borderColor = ""; - } - }); -} - -/* ========== Mobile Sidebar Functions ========== */ -function toggleSidebar() { - const sidebar = document.querySelector(".sidebar"); - sidebar.classList.toggle("active"); - - // Close sidebar when clicking outside - if (sidebar.classList.contains("active")) { - const closeOnClickOutside = (e) => { - if (!sidebar.contains(e.target) && !e.target.matches(".mobile-sidebar-toggle")) { - sidebar.classList.remove("active"); - document.removeEventListener("click", closeOnClickOutside); - } - }; - // Add event listener with a slight delay to prevent immediate closing - setTimeout(() => { - document.addEventListener("click", closeOnClickOutside); - }, 100); - } -} - -// Add touch event handling for better mobile experience -document.addEventListener("DOMContentLoaded", () => { - let touchStartX = 0; - let touchEndX = 0; - const sidebar = document.querySelector(".sidebar"); - - document.addEventListener( - "touchstart", - (e) => { - touchStartX = e.changedTouches[0].screenX; - }, - false - ); - - document.addEventListener( - "touchend", - (e) => { - touchEndX = e.changedTouches[0].screenX; - handleSwipe(); - }, - false - ); - - function handleSwipe() { - const swipeThreshold = 50; - const swipeLength = touchEndX - touchStartX; - - // Swipe right to open sidebar - if (swipeLength > swipeThreshold && touchStartX < 30) { - sidebar.classList.add("active"); - } - // Swipe left to close sidebar - else if (swipeLength < -swipeThreshold && sidebar.classList.contains("active")) { - sidebar.classList.remove("active"); - } - } -}); - -/* ========== Shortcut Modal & Dark Mode ========== */ -function toggleShortcutModal() { - const modal = document.getElementById("shortcut-modal"); - modal.style.display = modal.style.display === "block" ? "none" : "block"; -} - -function toggleDarkMode() { - document.body.classList.toggle("dark-mode"); - applyTabButtonTheme(); - saveGlobalState(); - // If the Compare section is visible, update all diff previews - if (document.getElementById("compare-section").style.display !== "none") { - document.querySelectorAll("#compare-tab-contents .json-tab-content").forEach((tab) => { - compareJSONs(tab.id); - }); - } -} -/* ========== Keyboard Shortcuts ========== */ -document.addEventListener("keydown", (e) => { - if ( - e.ctrlKey && - e.key.toLowerCase() === "t" && - document.getElementById("formatter-section").style.display !== "none" - ) { - e.preventDefault(); - addFormatterTab(); - } - if ( - e.ctrlKey && - e.key.toLowerCase() === "w" && - document.getElementById("formatter-section").style.display !== "none" - ) { - e.preventDefault(); - const activeTab = document.querySelector("#formatter-tab-contents .json-tab-content.active"); - if (activeTab) closeFormatterTab(activeTab.id); - } - if (e.ctrlKey && (e.key === "/" || e.key === "?")) { - e.preventDefault(); - toggleShortcutModal(); - } - if (e.key === "Escape") { - const modal = document.getElementById("shortcut-modal"); - if (modal.style.display === "block") toggleShortcutModal(); - } - if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "s") { - e.preventDefault(); - const activeTab = document.querySelector("#editor-tab-contents .json-tab-content.active"); - if (activeTab) saveEditorContent(activeTab.id); - } -}); - -/* ========== Tab reordering ========== */ -function enableTabReordering(containerId) { - const container = document.getElementById(containerId); - const tabButtons = container.querySelectorAll(".tab-button[data-tab]"); - - tabButtons.forEach((button) => { - button.draggable = true; - - button.addEventListener("dragstart", (e) => { - e.dataTransfer.setData("text/plain", button.getAttribute("data-tab")); - button.classList.add("dragging"); - }); - - button.addEventListener("dragend", () => { - button.classList.remove("dragging"); - container.querySelectorAll(".tab-button").forEach((b) => b.classList.remove("drag-over")); - }); - - button.addEventListener("dragover", (e) => { - e.preventDefault(); - button.classList.add("drag-over"); - }); - - button.addEventListener("dragleave", () => { - button.classList.remove("drag-over"); - }); - - button.addEventListener("drop", (e) => { - e.preventDefault(); - const draggedId = e.dataTransfer.getData("text/plain"); - const draggedBtn = container.querySelector(`[data-tab="${draggedId}"]`); - - btn.classList.remove("drag-over"); - - if (draggedBtn && draggedBtn !== btn) { - container.insertBefore(draggedBtn, btn); - saveGlobalState(); - } - }); - }); -} - -/* ========== Preset Functions ========== */ -function populatePresetSelects() { - // Populate formatter presets - const formatterSelect = document.querySelector("#formatter-tabs-container .preset-select"); - Object.keys(window.PRESETS.formatter).forEach((key) => { - const option = document.createElement("option"); - option.value = key; - option.textContent = window.PRESETS.formatter[key].name; - formatterSelect.appendChild(option); - }); - - // Populate compare presets - const compareSelect = document.querySelector("#compare-tabs-container .preset-select"); - Object.keys(window.PRESETS.compare).forEach((key) => { - const option = document.createElement("option"); - option.value = key; - option.textContent = window.PRESETS.compare[key].name; - compareSelect.appendChild(option); - }); - - // Populate codegen presets - const codegenSelect = document.querySelector("#codegen-tabs-container .preset-select"); - Object.keys(window.PRESETS.codegen).forEach((key) => { - const option = document.createElement("option"); - option.value = key; - option.textContent = window.PRESETS.codegen[key].name; - codegenSelect.appendChild(option); - }); -} - -function loadFormatterPreset(presetName) { - if (!presetName) return; - - const preset = window.PRESETS.formatter[presetName]; - if (!preset) return; - - const activeTab = document.querySelector("#formatter-tab-contents .json-tab-content.active"); - if (!activeTab) return; - - const textarea = activeTab.querySelector(".json-input"); - if (textarea) { - textarea.value = preset.content; - updateFormatterPreview(activeTab.id); - } -} - -function loadComparePreset(presetName) { - if (!presetName) return; - - const preset = window.PRESETS.compare[presetName]; - if (!preset) return; - - const activeTab = document.querySelector("#compare-tab-contents .json-tab-content.active"); - if (!activeTab) return; - - const leftTextarea = activeTab.querySelector(".json-input-left"); - const rightTextarea = activeTab.querySelector(".json-input-right"); - - if (leftTextarea && rightTextarea) { - leftTextarea.value = preset.leftContent; - rightTextarea.value = preset.rightContent; - compareJSONs(activeTab.id); - } -} - -function loadCodegenPreset(presetName) { - if (!presetName) return; - - const preset = window.PRESETS.codegen[presetName]; - if (!preset) return; - - const activeTab = document.querySelector("#codegen-tab-contents .json-tab-content.active"); - if (!activeTab) return; - - const textarea = activeTab.querySelector(".json-input"); - const langSelect = document.getElementById("lang-select-" + activeTab.id); - - if (textarea && langSelect) { - textarea.value = preset.input; - langSelect.value = preset.lang; - generateCode(activeTab.id); - } -} - -/* ========== Initialization ========== */ -window.addEventListener("load", () => { - loadGlobalState(); - loadEditorGlobalState(); - // If no saved state, create a default tabs. - if (document.getElementById("formatter-tab-contents").children.length === 0) { - addFormatterTab(); - } - - if (document.getElementById("compare-tab-contents").children.length === 0) { - addCompareTab(); - } - - if (document.getElementById("codegen-tab-contents").children.length === 0) { - addCodegenTab(); - } - - if (document.getElementById("editor-tab-contents").children.length === 0) { - addEditorTab(); - switchEditorTab("editor-tab-1"); - } - - populatePresetSelects(); - applyTabButtonTheme(); - enableTabReordering("formatter-tabs-container"); - enableTabReordering("compare-tabs-container"); - enableTabReordering("codegen-tabs-container"); - enableEditorTabReordering(); - applyEditorTabDarkMode(); -}); - -setInterval(() => { - const editorVisible = document.getElementById("editor-section").style.display !== "none"; - const activeTab = document.querySelector("#editor-tab-contents .json-tab-content.active"); - - if (!Swal.isVisible() && editorVisible && activeTab) { - saveEditorContent(activeTab.id); - } -}, 5000); diff --git a/json-tool/README.md b/json-tool/README.md new file mode 100644 index 0000000..5726f61 --- /dev/null +++ b/json-tool/README.md @@ -0,0 +1,146 @@ +# JSONP - Multi tab JSON toolkit + +

+ A versatile web-based toolkit for working with JSON data. Open-source and works offline! +

+ +

+ Made with Love in India +

+ +## ๐Ÿ“ Project Structure + +``` +json-tool/ +โ”œโ”€โ”€ public/ +โ”‚ โ”œโ”€โ”€ index.html # Main entry point +โ”‚ โ””โ”€โ”€ assets/ +โ”‚ โ”œโ”€โ”€ css/ +โ”‚ โ”‚ โ”œโ”€โ”€ base.css # Reset/normalize +โ”‚ โ”‚ โ”œโ”€โ”€ main.css # Core styles +โ”‚ โ”‚ โ””โ”€โ”€ themes/ # Dark/light mode +โ”‚ โ””โ”€โ”€ js/ +โ”‚ โ”œโ”€โ”€ core/ # Reusable utilities +โ”‚ โ”‚ โ”œโ”€โ”€ storage.js +โ”‚ โ”‚ โ”œโ”€โ”€ dom.js +โ”‚ โ”‚ โ””โ”€โ”€ json-utils.js +โ”‚ โ”œโ”€โ”€ features/ # Feature modules +โ”‚ โ”‚ โ”œโ”€โ”€ formatter/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ formatter.js +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ formatter.html +โ”‚ โ”‚ โ”œโ”€โ”€ compare/ +โ”‚ โ”‚ โ”œโ”€โ”€ codegen/ +โ”‚ โ”‚ โ”œโ”€โ”€ mockgen/ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ mockgen.js +โ”‚ โ”‚ โ””โ”€โ”€ convert/ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ convert.js +โ”‚ โ”‚ โ””โ”€โ”€ editor/ +โ”‚ โ””โ”€โ”€ app.js # Main app initialization +โ””โ”€โ”€ README.md # Project documentation +``` + +## โœจ Features + +- **Core Tools** + + - ๐Ÿงน JSON Formatter & Validator + - Multi-tab support with color coding + - Tree view with expandable nodes + - Search functionality + - Error highlighting + - ๐Ÿ” JSON Comparison (diff checker) + - Side-by-side diff view + - Multi-tab support + - ๐Ÿ’ป Code Generation + - TypeScript interfaces + - Python dataclasses + - Go structs + - ๐Ÿ Python - json-to-dict and dict-to-json converter + - ๐Ÿงช Mock Data Generator + - Faker.js integration + - JSON/CSV export + - Table/JSON view modes + - Customizable schemas + - Built-in presets + - Real-time preview + - Custom field types + - Data validation + - ๐Ÿ”„ Data Conversion + - JSON to YAML + - JSON to XML + - JSON to CSV + - JSON to SQL + - Custom format support + - Batch conversion + - Template system + +- **Workflow** + + - ๐Ÿ“‘ Multi-tab interface with drag-and-drop reordering + - ๐ŸŒ“ Dark/Light mode toggle + - โŒจ๏ธ Keyboard shortcuts + - ๐Ÿ’พ Automatic local saving + - ๐Ÿ”„ Real-time previews + +- **Convenience** + - ๐Ÿ“ค๐Ÿ“ฅ Import/Export JSON files + - ๐ŸŒ Browser-based (no install needed) + - ๐Ÿ”„ Real-time validation + - ๐Ÿšฆ Error highlighting + - ๐Ÿ“‹ Copy/paste support + +## ๐Ÿ— Architecture + +- **Core Utilities** + + - `storage.js`: Local storage and state management + - `dom.js`: DOM manipulation helpers + - `json-utils.js`: JSON parsing and validation utilities + +- **Feature Modules** + + - Formatter: JSON formatting and validation + - Compare: JSON comparison and diffing + - Codegen: Code generation from JSON schemas + - Mockgen: Mock data generation with Faker.js + - Convert: Multi-format data conversion + +- **Styling** + - Modular CSS architecture + - Theme support (dark/light modes) + - Responsive design + +## ๐Ÿšง Planned Features + +- ๐Ÿ›  JSON Schema generator +- ๐Ÿ”„ JSON-to-XML conversion +- ๐Ÿ“Š Visual JSON chart view +- ๐Ÿงฉ Plugins/extensions system +- ๐ŸŒ Collaborative editing +- ๐Ÿ“‹ Smart copy-paste detection +- โญ Enhanced error handling and validation +- ๐Ÿ” Advanced search capabilities + +## ๐Ÿš€ Usage + +1. Open `index.html` in any modern browser +2. Drag & drop JSON files directly +3. Use tabs to organize multiple documents +4. Toggle modes using the top buttons + +> **Note**: All data stays local - nothing is uploaded to servers + +## ๐Ÿ“ Changelog + +For a detailed list of changes and version history, please see the [CHANGELOG](./CHANGELOG/README.md) directory. + +## ๐Ÿค Contributing + +We welcome contributions! Please see: + +- [Contribution Guidelines](https://github.com/shravan20/jsonp/blob/main/CONTRIBUTING.md) +- [GitHub Repository](https://github.com/shravan20/jsonp) + +## ๐Ÿ“„ License + +MIT Licensed - Free for personal and commercial use diff --git a/json.svg b/json-tool/json.svg similarity index 100% rename from json.svg rename to json-tool/json.svg diff --git a/json-tool/public/assets/css/base.css b/json-tool/public/assets/css/base.css new file mode 100644 index 0000000..172ffa0 --- /dev/null +++ b/json-tool/public/assets/css/base.css @@ -0,0 +1,226 @@ +/* Base Styles - Structure and layout */ +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 20px; + transition: background-color 0.3s, color 0.3s; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 20px; +} + +h1 { + margin: 0 20px 0 0; + font-size: 1.8rem; +} + +/* App Layout */ +.app-container { + display: flex; + min-height: 100vh; +} + +/* Sidebar Styles */ +.sidebar { + width: 250px; + display: flex; + flex-direction: column; + flex-shrink: 0; +} + +.sidebar-header { + padding: 15px; + border-bottom: 1px solid; +} + +.sidebar-header h3 { + margin: 0; + font-size: 1.1rem; +} + +.sidebar-content { + flex: 1; + overflow-y: auto; +} + +.feature-group { + padding: 10px 0; +} + +.feature-item { + padding: 8px 15px; + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; + transition: background-color 0.2s; +} + +.feature-item i { + width: 16px; + text-align: center; +} + +/* Main Content Area */ +.main-content { + flex: 1; + padding: 20px; + overflow-y: auto; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +.header-actions { + display: flex; + gap: 10px; +} + +/* Mobile Sidebar Toggle */ +.mobile-sidebar-toggle { + display: none; + position: fixed; + top: 10px; + left: 10px; + z-index: 1000; + border: none; + border-radius: 4px; + padding: 10px; + cursor: pointer; +} + +.mobile-sidebar-close { + display: none; + background: transparent; + border: none; + cursor: pointer; + padding: 5px; +} + +/* Responsive Styles */ +@media (max-width: 768px) { + body { + padding: 10px; + } + + .app-container { + flex-direction: column; + } + + .mobile-sidebar-toggle { + display: block; + } + + .mobile-sidebar-close { + display: block; + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); + } + + .sidebar { + position: fixed; + left: -250px; + top: 0; + bottom: 0; + z-index: 1000; + transition: left 0.3s ease; + } + + .sidebar.active { + left: 0; + } + + .main-content { + margin-left: 0; + padding: 15px; + padding-top: 50px; + } + + .header { + flex-direction: column; + align-items: flex-start; + gap: 15px; + } + + .header h1 { + font-size: 1.5rem; + margin-right: 0; + } + + /* Adjust tabs layout */ + .tabs { + flex-wrap: nowrap; + overflow-x: auto; + padding-bottom: 5px; + -webkit-overflow-scrolling: touch; + } + + .tab-button { + flex-shrink: 0; + } + + /* Adjust form controls */ + .search-container, + .upload-download-container { + flex-direction: column; + align-items: stretch; + } + + .search-container input, + .search-container button, + .upload-download-container button { + width: 100%; + margin-bottom: 10px; + } +} + +@media (max-width: 480px) { + .header-actions { + flex-direction: column; + width: 100%; + } + + .header-actions button { + width: 100%; + } +} + +@media (min-width: 769px) and (max-width: 1024px) { + .sidebar { + width: 200px; + } +} + +@media (hover: none) { + .feature-item:hover { + background-color: transparent; + } + + .feature-item:active { + background-color: #2a2d2e; + } + + .tab-button:hover { + background-color: transparent; + } + + .tab-button:active { + background-color: #e0e0e0; + } +} + +@media (max-width: 768px) { + ::-webkit-scrollbar { + width: 6px; + height: 6px; + } +} diff --git a/json-tool/public/assets/css/main.css b/json-tool/public/assets/css/main.css new file mode 100644 index 0000000..cc1908d --- /dev/null +++ b/json-tool/public/assets/css/main.css @@ -0,0 +1,401 @@ +/* Component Styles */ +/* Buttons */ +.dark-mode-toggle, +.shortcut-preview-button { + padding: 8px 16px; + border: none; + cursor: pointer; + border-radius: 4px; + margin-right: 10px; + font-size: 0.9rem; +} + +/* Mode Selector */ +.mode-selector { + margin: 20px 0; + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.mode-selector button { + padding: 10px 16px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 0.9rem; +} + +.mode-selector button.active { + font-weight: bold; +} + +/* Tabs */ +.tabs { + display: flex; + flex-wrap: wrap; + gap: 5px; + margin-bottom: 15px; +} + +.tab-button { + padding: 8px 12px; + border: none; + cursor: pointer; + border-radius: 4px; + display: flex; + align-items: center; + gap: 5px; + transition: background-color 0.3s; + font-size: 0.9rem; +} + +.tab-button.active { + background-color: #ffffff; + font-weight: bold; +} + +.tab-button .close-tab { + color: red; + font-weight: bold; + cursor: pointer; +} + +.tab-button .tab-color-picker { + width: 16px; + height: 16px; + padding: 0; + border: none; + cursor: pointer; +} + +.add-tab-button { + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + border: none; + font-size: 0.9rem; +} + +/* Content Panels */ +.json-tab-content { + display: none; +} + +.json-tab-content.active { + display: block; +} + +textarea { + width: 100%; + height: 200px; + padding: 10px; + font-family: monospace; + font-size: 0.95rem; + border: 1px solid; + border-radius: 4px; + margin-bottom: 15px; + box-sizing: border-box; +} + +/* Utility Containers */ +.search-container, +.upload-download-container { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 15px; + flex-wrap: wrap; +} + +.preview-section { + padding: 20px; + border-radius: 5px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + overflow: auto; + word-wrap: normal; + display: none; + max-height: 600px; +} + +.preview-section.active { + display: block; +} +.copy-button, +button.copy-button { + padding: 6px 12px; + background-color: #28a745; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 0.85rem; +} + +.copy-button:hover { + background-color: #218838; +} + +/* Tree View */ +.tree-node { + margin: 4px 0; + padding-left: 20px; + position: relative; +} + +.tree-key { + cursor: pointer; + padding: 2px 5px; + border-radius: 3px; + display: inline-block; +} + +.tree-key:hover { + background-color: rgba(0, 123, 255, 0.1); +} + +.type-string { + color: #4caf50; +} + +.type-number { + color: #2196f3; +} + +.type-boolean { + color: #9c27b0; +} + +.type-null { + color: #666; +} + +.type-object { + color: #ff9800; +} + +.type-array { + color: #e91e63; +} + +/* Reordering Drag Styles */ +.tab-button.dragging { + opacity: 0.6; + border: 2px dashed; +} + +.tab-button.drag-over { + transform: translateY(2px) scale(1.02); + box-shadow: 0 2px 6px rgba(0, 123, 255, 0.3); +} + +/* Markdown & Modal */ +.markdown-preview { + padding: 1rem; + border-radius: 5px; + border: 1px solid; + max-height: 500px; + overflow-y: auto; + line-height: 1.6; +} + +.markdown-preview code { + padding: 2px 4px; + border-radius: 3px; +} + +.modal { + display: none; + position: fixed; + z-index: 1000; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); +} + +.modal-content { + margin: 10% auto; + padding: 20px; + width: 400px; + border-radius: 5px; + position: relative; +} + +.close-modal { + position: absolute; + top: 10px; + right: 15px; + font-size: 1.4rem; + cursor: pointer; +} + +/* Tab Rename Tooltip */ +.tab-rename-tooltip { + position: absolute; + z-index: 1000; + border: 1px solid; + border-radius: 3px; + padding: 5px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} + +/* GitHub buttons */ +.github-links { + text-align: center; + margin: 20px 0; +} + +.github-button, +.contribute-button { + display: inline-flex; + align-items: center; + padding: 10px 15px; + margin: 0 10px; + border-radius: 5px; + text-decoration: none; + color: white; + font-size: 0.9rem; +} + +.github-button i, +.contribute-button i { + margin-right: 8px; +} + +.tree-children { + margin-left: 20px; + overflow: hidden; + transition: max-height 0.3s ease, opacity 0.3s ease; + max-height: 1000px; + opacity: 1; +} + +.tree-children.collapsed { + max-height: 0; + opacity: 0; + pointer-events: none; +} + +/* JSON to Code preview themes */ +.code-output { + font-family: "Fira Code", monospace; + font-size: 14px; + line-height: 1.6; + padding: 16px; + border-radius: 6px; + overflow-x: auto; + white-space: pre; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + margin-top: 10px; +} + +.code-output .keyword { + font-weight: bold; +} + +.code-output .comment { + font-style: italic; +} + +/* Editor Specific */ +#editor-section .tab-button { + padding: 8px 12px; + background-color: #f5f5f5; + border: none; + border-radius: 4px; + cursor: pointer; + margin-right: 5px; + font-size: 0.9rem; +} + +#editor-section .tab-button.active { + background-color: #ffffff; + font-weight: bold; +} + +#editor-section .add-tab-button { + padding: 8px 16px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 0.9rem; +} + +#editor-tab-contents .json-tab-content { + display: none; + margin-top: 10px; +} + +#editor-tab-contents .json-tab-content.active { + display: block; +} + +.toastui-editor-defaultUI { + border-radius: 6px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); +} + +/* Tree View Scrolling Fix */ +.tree-view { + padding: 10px; + font-family: monospace; + line-height: 1.5; + overflow-y: auto; + overflow-x: auto; +} + +/* Mock Data Table Styling */ +.mock-preview-table { + width: 100%; + border-collapse: collapse; + margin: 20px 0; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.mock-preview-table thead { + border-bottom: 2px solid; +} + +.mock-preview-table th { + padding: 12px 15px; + text-align: left; + font-weight: 600; + font-size: 0.9rem; + text-transform: uppercase; + letter-spacing: 0.5px; + border-bottom: 2px solid; + position: sticky; + top: 0; + z-index: 1; +} + +.mock-preview-table td { + padding: 12px 15px; + border-bottom: 1px solid; + font-size: 0.9rem; + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.mock-preview-table tbody tr:last-child td { + border-bottom: none; +} + +/* Error row styling */ +.mock-preview-table tr.error td { + color: #dc3545; +} + +/* Container for the table */ +#mock-output-container { + position: relative; + margin-top: 20px; + border-radius: 8px; + overflow: hidden; +} diff --git a/json-tool/public/assets/css/themes/dark.css b/json-tool/public/assets/css/themes/dark.css new file mode 100644 index 0000000..594b6bd --- /dev/null +++ b/json-tool/public/assets/css/themes/dark.css @@ -0,0 +1,352 @@ +/* Light Theme (Default) */ +body { + background-color: #f0f0f0; + color: #333; +} + +body.dark-mode { + background-color: #121212; + color: #e0e0e0; +} + +/* Sidebar */ +.sidebar { + background-color: #252526; + color: #ffffff; +} + +.sidebar-header { + border-bottom-color: #3c3c3c; +} + +.sidebar-header h3 { + color: #cccccc; +} + +.feature-item { + color: #cccccc; +} + +.feature-item:hover { + background-color: #2a2d2e; +} + +.feature-item.active { + background-color: #37373d; + color: #ffffff; +} + +/* Main Content */ +.main-content { + background-color: #f0f0f0; +} + +body.dark-mode .main-content { + background-color: #1e1e1e; +} + +body.dark-mode .sidebar { + background-color: #252526; + border-right: 1px solid #3c3c3c; +} + +/* Buttons */ +.dark-mode-toggle, +.shortcut-preview-button, +.mode-selector button, +.add-tab-button, +.copy-button { + background-color: #007bff; + color: white; +} + +.mode-selector button.active { + background-color: #0056b3; +} + +/* Tabs */ +.tab-button { + background-color: #e0e0e0; +} + +.tab-button.active { + background-color: #ffffff; +} + +body.dark-mode .tab-button { + background-color: #333; + color: #e0e0e0; +} + +body.dark-mode .tab-button.active { + background-color: #555; +} + +/* Textareas and preview sections */ +textarea, +.preview-section, +.modal-content { + background-color: white; + border-color: #ccc; +} + +body.dark-mode textarea, +body.dark-mode .preview-section, +body.dark-mode .modal-content { + background-color: #2e2e2e; + color: #e0e0e0; + border-color: #555; +} + +/* Markdown preview */ +.markdown-preview { + background: #fff; + border-color: #ddd; +} + +.markdown-preview code { + background: #f0f0f0; +} + +/* Modal */ +.modal-content { + background: #fff; +} + +/* Tree key colors */ +.type-string { + color: #4caf50; +} + +.type-number { + color: #2196f3; +} + +.type-boolean { + color: #9c27b0; +} + +.type-null { + color: #666; +} + +.type-object { + color: #ff9800; +} + +.type-array { + color: #e91e63; +} + +/* Drag styles */ +.tab-button.dragging { + border-color: #007bff; +} + +/* Tooltip */ +.tab-rename-tooltip { + background-color: #fff; + border-color: #ccc; +} + +body.dark-mode .tab-rename-tooltip { + background-color: #2c2c2c; + border-color: #555; + color: #fff; +} + +body.dark-mode .tab-rename-tooltip input { + background-color: #1f1f1f; + color: #eee; + border-color: #555; +} + +/* GitHub buttons */ +.github-button { + background-color: #24292e; +} + +.github-button:hover { + background-color: #444; +} + +.contribute-button { + background-color: #2ea44f; +} + +.contribute-button:hover { + background-color: #22863a; +} + +/* ToastUI Editor Dark Mode */ +body.dark-mode .toastui-editor-defaultUI { + background-color: #1e1e1e !important; + color: #eee !important; + border-color: #333 !important; +} + +body.dark-mode .toastui-editor-defaultUI .ProseMirror { + background-color: #1e1e1e !important; + color: #eee !important; +} + +body.dark-mode .toastui-editor-defaultUI .toastui-editor-toolbar { + background-color: #2a2a2a !important; + border-bottom-color: #444 !important; +} + +body.dark-mode .toastui-editor-defaultUI .toastui-editor-contents pre, +body.dark-mode .toastui-editor-defaultUI .toastui-editor-contents code { + background-color: #2b2b2b !important; + color: #c5c5c5 !important; +} + +body.dark-mode .toastui-editor-defaultUI .toastui-editor-md-container, +body.dark-mode .toastui-editor-defaultUI .toastui-editor-ww-container { + background-color: #1e1e1e !important; + color: #eee !important; +} + +/* Tree View */ +body.dark-mode .preview-section { + background-color: #1e1e1e; + border-color: #333; +} + +body.dark-mode .tree-view { + color: #e0e0e0; +} + +/* Scrollbars */ +.tree-view::-webkit-scrollbar-track { + background: #f1f1f1; +} + +.tree-view::-webkit-scrollbar-thumb { + background: #888; + border-color: #f1f1f1; +} + +.tree-view::-webkit-scrollbar-thumb:hover { + background: #555; +} + +body.dark-mode .tree-view::-webkit-scrollbar-track { + background: #1e1e1e; +} + +body.dark-mode .tree-view::-webkit-scrollbar-thumb { + background: #666; + border-color: #1e1e1e; +} + +body.dark-mode .tree-view::-webkit-scrollbar-thumb:hover { + background: #888; +} + +/* Mobile sidebar toggle */ +body.dark-mode .mobile-sidebar-toggle { + background: #333; + color: #fff; +} + +body.dark-mode .mobile-sidebar-close { + color: #fff; +} + +/* Mock Data Table */ +.mock-preview-table { + background-color: #fff; +} + +.mock-preview-table thead { + background-color: #f8f9fa; + border-bottom-color: #dee2e6; +} + +.mock-preview-table th { + color: #495057; + background-color: #f8f9fa; + border-bottom-color: #dee2e6; +} + +.mock-preview-table td { + color: #212529; + border-bottom-color: #dee2e6; +} + +.mock-preview-table tbody tr:hover { + background-color: #f8f9fa; +} + +/* Dark mode table */ +body.dark-mode .mock-preview-table { + background-color: #2d2d2d; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); +} + +body.dark-mode .mock-preview-table thead { + background-color: #333; + border-bottom-color: #444; +} + +body.dark-mode .mock-preview-table th { + color: #e0e0e0; + background-color: #333; + border-bottom-color: #444; +} + +body.dark-mode .mock-preview-table td { + color: #e0e0e0; + border-bottom-color: #444; +} + +body.dark-mode .mock-preview-table tbody tr:hover { + background-color: #383838; +} + +body.dark-mode .mock-preview-table tr.error td { + background-color: #442326; + color: #ff6b6b; +} + +/* JSON Code Output Themes */ +.code-output { + background-color: #1e1e1e; + color: #d4d4d4; +} + +.code-output.python { + background-color: #0b2239; + color: #dcdcdc; +} + +.code-output.go { + background-color: #263238; + color: #c3e88d; +} + +.code-output.typescript { + background-color: #1e1e1e; + color: #d4d4d4; +} + +.code-output .keyword { + color: #569cd6; +} + +.code-output .string { + color: #ce9178; +} + +.code-output .number { + color: #b5cea8; +} + +.code-output .type { + color: #4ec9b0; +} + +.code-output .comment { + color: #6a9955; +} diff --git a/json-tool/public/assets/js/app.js b/json-tool/public/assets/js/app.js new file mode 100644 index 0000000..7105902 --- /dev/null +++ b/json-tool/public/assets/js/app.js @@ -0,0 +1,112 @@ +/* ========== Mode Selector ========== */ +function switchMode(mode) { + const sections = [ + "formatter", + "compare", + "codegen", + "convert", + "mockgen", + "editor", + ]; + sections.forEach((s) => { + const section = document.getElementById(`${s}-section`); + if (section) { + section.style.display = "none"; + } + }); + + const targetSection = document.getElementById(`${mode}-section`); + if (targetSection) { + targetSection.style.display = "block"; + } + + // Update sidebar active state + document.querySelectorAll(".feature-item").forEach((item) => { + item.classList.remove("active"); + }); + document + .querySelector(`.feature-item[onclick*="${mode}"]`) + ?.classList.add("active"); + + if (mode === "mockgen") { + renderMockgenDocs(); + } else if (mode === "editor") { + loadEditorGlobalState(); + } + + applyEditorTabDarkMode(); + saveGlobalState(); // Save state when mode changes +} + +/* ========== Keyboard Shortcuts ========== */ +document.addEventListener("keydown", (e) => { + if ( + e.ctrlKey && + e.key.toLowerCase() === "t" && + document.getElementById("formatter-section").style.display !== "none" + ) { + e.preventDefault(); + addFormatterTab(); + } + if ( + e.ctrlKey && + e.key.toLowerCase() === "w" && + document.getElementById("formatter-section").style.display !== "none" + ) { + e.preventDefault(); + const activeTab = document.querySelector( + "#formatter-tab-contents .json-tab-content.active" + ); + if (activeTab) closeFormatterTab(activeTab.id); + } + if (e.ctrlKey && (e.key === "/" || e.key === "?")) { + e.preventDefault(); + toggleShortcutModal(); + } + if (e.key === "Escape") { + const modal = document.getElementById("shortcut-modal"); + if (modal.style.display === "block") toggleShortcutModal(); + } + if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "s") { + e.preventDefault(); + const activeTab = document.querySelector( + "#editor-tab-contents .json-tab-content.active" + ); + if (activeTab) saveEditorContent(activeTab.id); + } +}); + +/* ========== Initialization ========== */ +window.addEventListener("load", () => { + loadGlobalState(); + loadEditorGlobalState(); + // If no saved state, create a default tabs. + if (document.getElementById("formatter-tab-contents").children.length === 0) { + addFormatterTab(); + } + + if (document.getElementById("compare-tab-contents").children.length === 0) { + addCompareTab(); + } + + if (document.getElementById("codegen-tab-contents").children.length === 0) { + addCodegenTab(); + } + + if (document.getElementById("editor-tab-contents").children.length === 0) { + addEditorTab(); + switchEditorTab("editor-tab-1"); + } +}); + +setInterval(() => { + const editorVisible = + document.getElementById("editor-section").style.display !== "none"; + const activeTab = document.querySelector( + "#editor-tab-contents .json-tab-content.active" + ); + + if (!Swal.isVisible() && editorVisible && activeTab) { + saveEditorContent(activeTab.id); + } +}, 5000); diff --git a/json-tool/public/assets/js/core/dom.js b/json-tool/public/assets/js/core/dom.js new file mode 100644 index 0000000..194d5cb --- /dev/null +++ b/json-tool/public/assets/js/core/dom.js @@ -0,0 +1,217 @@ +/* ========== COPY FUNCTIONS ========== */ +function copyToClipboard(text, successMessage) { + (navigator.clipboard?.writeText + ? navigator.clipboard.writeText(text) + : new Promise((res, rej) => { + try { + const ta = Object.assign(document.createElement("textarea"), { + value: text, + style: "position:fixed;top:-1000px", + }); + document.body.appendChild(ta); + ta.select(); + document.execCommand("copy") ? res() : rej(); + ta.remove(); + } catch (e) { + rej(e); + } + }) + ) + .then(() => { + Swal.fire({ + icon: "success", + title: successMessage, + toast: true, + timer: 2000, + position: "top-end", + showConfirmButton: false, + }); + }) + .catch((err) => { + console.error("Failed to copy:", err); + Swal.fire({ + icon: "error", + title: "Copy failed", + toast: true, + timer: 2000, + position: "top-end", + showConfirmButton: false, + }); + }); +} + +function autoFormatTextarea(textarea) { + try { + const parsed = JSON.parse(textarea.value); + textarea.value = JSON.stringify(parsed, null, 2); + } catch (e) { + // Do nothing if invalid + } +} + +function openTabRenameTooltip(tabId, mode) { + let containerSelector; + if (mode === "formatter") containerSelector = "#formatter-tabs-container"; + else if (mode === "compare") containerSelector = "#compare-tabs-container"; + else if (mode === "codegen") containerSelector = "#codegen-tabs-container"; + else containerSelector = "#editor-tabs-container"; + + const tabButton = document.querySelector( + containerSelector + ` .tab-button[data-tab="${tabId}"]` + ); + const existingTooltip = document.querySelector(".tab-rename-tooltip"); + if (existingTooltip) existingTooltip.remove(); + const tooltip = document.createElement("div"); + tooltip.className = "tab-rename-tooltip"; + const rect = tabButton.getBoundingClientRect(); + tooltip.style.left = rect.left + "px"; + tooltip.style.top = rect.bottom + window.scrollY + 5 + "px"; + const input = document.createElement("input"); + input.type = "text"; + input.value = tabButton.querySelector(".tab-name").textContent; + input.style.width = "150px"; + input.addEventListener("keydown", (e) => { + if (e.key === "Enter") finalizeRename(); + else if (e.key === "Escape") tooltip.remove(); + }); + input.addEventListener("blur", finalizeRename); + tooltip.appendChild(input); + document.body.appendChild(tooltip); + input.focus(); + + function finalizeRename() { + const newName = input.value.trim(); + if (newName) { + tabButton.querySelector(".tab-name").textContent = newName; + } + tooltip.remove(); + saveGlobalState(); + } +} + +/* ========== Mobile Sidebar Functions ========== */ +function toggleSidebar() { + const sidebar = document.querySelector(".sidebar"); + sidebar.classList.toggle("active"); + + // Close sidebar when clicking outside + if (sidebar.classList.contains("active")) { + const closeOnClickOutside = (e) => { + if ( + !sidebar.contains(e.target) && + !e.target.matches(".mobile-sidebar-toggle") + ) { + sidebar.classList.remove("active"); + document.removeEventListener("click", closeOnClickOutside); + } + }; + // Add event listener with a slight delay to prevent immediate closing + setTimeout(() => { + document.addEventListener("click", closeOnClickOutside); + }, 100); + } +} + +// Add touch event handling for better mobile experience +document.addEventListener("DOMContentLoaded", () => { + let touchStartX = 0; + let touchEndX = 0; + const sidebar = document.querySelector(".sidebar"); + + document.addEventListener( + "touchstart", + (e) => { + touchStartX = e.changedTouches[0].screenX; + }, + false + ); + + document.addEventListener( + "touchend", + (e) => { + touchEndX = e.changedTouches[0].screenX; + handleSwipe(); + }, + false + ); + + function handleSwipe() { + const swipeThreshold = 50; + const swipeLength = touchEndX - touchStartX; + + // Swipe right to open sidebar + if (swipeLength > swipeThreshold && touchStartX < 30) { + sidebar.classList.add("active"); + } + // Swipe left to close sidebar + else if ( + swipeLength < -swipeThreshold && + sidebar.classList.contains("active") + ) { + sidebar.classList.remove("active"); + } + } +}); + +/* ========== Shortcut Modal & Dark Mode ========== */ +function toggleShortcutModal() { + const modal = document.getElementById("shortcut-modal"); + modal.style.display = modal.style.display === "block" ? "none" : "block"; +} + +function toggleDarkMode() { + document.body.classList.toggle("dark-mode"); + saveGlobalState(); + // If the Compare section is visible, update all diff previews + if (document.getElementById("compare-section").style.display !== "none") { + document + .querySelectorAll("#compare-tab-contents .json-tab-content") + .forEach((tab) => { + compareJSONs(tab.id); + }); + } +} + +/* ========== Tab reordering ========== */ +function enableTabReordering(containerId) { + const container = document.getElementById(containerId); + const tabButtons = container.querySelectorAll(".tab-button[data-tab]"); + + tabButtons.forEach((button) => { + button.draggable = true; + + button.addEventListener("dragstart", (e) => { + e.dataTransfer.setData("text/plain", button.getAttribute("data-tab")); + button.classList.add("dragging"); + }); + + button.addEventListener("dragend", () => { + button.classList.remove("dragging"); + container + .querySelectorAll(".tab-button") + .forEach((b) => b.classList.remove("drag-over")); + }); + + button.addEventListener("dragover", (e) => { + e.preventDefault(); + button.classList.add("drag-over"); + }); + + button.addEventListener("dragleave", () => { + button.classList.remove("drag-over"); + }); + + button.addEventListener("drop", (e) => { + e.preventDefault(); + const draggedId = e.dataTransfer.getData("text/plain"); + const draggedBtn = container.querySelector(`[data-tab="${draggedId}"]`); + + button.classList.remove("drag-over"); + + if (draggedBtn && draggedBtn !== button) { + container.insertBefore(draggedBtn, button); + saveGlobalState(); + } + }); + }); +} diff --git a/json-tool/public/assets/js/core/json-utils.js b/json-tool/public/assets/js/core/json-utils.js new file mode 100644 index 0000000..4d3dfd1 --- /dev/null +++ b/json-tool/public/assets/js/core/json-utils.js @@ -0,0 +1,47 @@ +function capitalize(str) { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +function formatValue(value) { + if (value === null) return "null"; + if (typeof value === "string") return `"${value}"`; + return value.toString(); +} + +function getValueTypeClass(value) { + if (value === null) return "type-null"; + if (Array.isArray(value)) return "type-array"; + if (typeof value === "object") return "type-object"; + return `type-${typeof value}`; +} + +function diffJSONsPreview(leftText, rightText) { + const isDarkMode = document.body.classList.contains("dark-mode"); + // Choose a diff color based on the theme: + // For light mode, use a light red; for dark mode, use a darker or more muted red. + const diffStyle = isDarkMode + ? "background-color:#662222;" + : "background-color:#ddd;"; + + const leftLines = leftText.split("\n"); + const rightLines = rightText.split("\n"); + const maxLines = Math.max(leftLines.length, rightLines.length); + let html = ""; + for (let i = 0; i < maxLines; i++) { + const lLine = leftLines[i] || ""; + const rLine = rightLines[i] || ""; + // Apply diffStyle if the lines differ + const style = lLine === rLine ? "" : diffStyle; + html += ` + + + `; + } + html += "
+
${lLine}
+
+
${rLine}
+
"; + return html; +} + diff --git a/json-tool/public/assets/js/core/storage.js b/json-tool/public/assets/js/core/storage.js new file mode 100644 index 0000000..dda2589 --- /dev/null +++ b/json-tool/public/assets/js/core/storage.js @@ -0,0 +1,160 @@ +/* ========== Global Persistence Functions ========== */ +function getActiveMode() { + // Find the active feature item in the sidebar + const activeFeature = document.querySelector(".feature-item.active"); + if (activeFeature) { + // Extract mode from the onclick attribute + const onclickAttr = activeFeature.getAttribute("onclick"); + const match = onclickAttr.match(/switchMode\('(.+?)'\)/); + if (match) return match[1]; + } + + // Fallback to checking sections + if (document.getElementById("formatter-section").style.display !== "none") + return "formatter"; + if (document.getElementById("compare-section").style.display !== "none") + return "compare"; + if (document.getElementById("codegen-section").style.display !== "none") + return "codegen"; + if (document.getElementById("convert-section").style.display !== "none") + return "convert"; + if (document.getElementById("mockgen-section").style.display !== "none") + return "mockgen"; + if (document.getElementById("editor-section").style.display !== "none") + return "editor"; + return "formatter"; // Default fallback +} + +function saveGlobalState() { + const state = { + darkMode: document.body.classList.contains("dark-mode"), + activeMode: getActiveMode(), + formatter: { + activeTab: + document.querySelector( + "#formatter-tab-contents .json-tab-content.active" + )?.id || "", + tabs: [], + activeFeatureTab: {}, // Store active feature tab (Raw/Tree/Error) for each formatter tab + }, + compare: { + activeTab: + document.querySelector("#compare-tab-contents .json-tab-content.active") + ?.id || "", + tabs: [], + }, + codegen: { + activeTab: + document.querySelector("#codegen-tab-contents .json-tab-content.active") + ?.id || "", + tabs: [], + }, + }; + + // Formatter tabs + document + .querySelectorAll("#formatter-tabs-container .tab-button[data-tab]") + .forEach((btn) => { + const tabId = btn.getAttribute("data-tab"); + const name = btn.querySelector(".tab-name").textContent; + const color = btn.querySelector(".tab-color-picker")?.value || "#e0e0e0"; + const content = + document.querySelector("#" + tabId + " .json-input")?.value || ""; + state.formatter.tabs.push({ + id: tabId, + name, + color, + content, + }); + }); + + // Compare tabs + document + .querySelectorAll("#compare-tabs-container .tab-button[data-tab]") + .forEach((btn) => { + const tabId = btn.getAttribute("data-tab"); + const name = btn.querySelector(".tab-name").textContent; + const leftContent = + document.querySelector("#" + tabId + " .json-input-left")?.value || ""; + const rightContent = + document.querySelector("#" + tabId + " .json-input-right")?.value || ""; + state.compare.tabs.push({ + id: tabId, + name, + leftContent, + rightContent, + }); + }); + + // Codegen tabs + document + .querySelectorAll("#codegen-tabs-container .tab-button[data-tab]") + .forEach((btn) => { + const tabId = btn.getAttribute("data-tab"); + const name = btn.querySelector(".tab-name").textContent; + const input = + document.querySelector("#" + tabId + " .json-input")?.value || ""; + const lang = + document.getElementById("lang-select-" + tabId)?.value || "typescript"; + state.codegen.tabs.push({ + id: tabId, + name, + input, + lang, + }); + }); + + localStorage.setItem("jsonToolState", JSON.stringify(state)); +} + +function loadGlobalState() { + const stateStr = localStorage.getItem("jsonToolState"); + if (!stateStr) return; + const state = JSON.parse(stateStr); + + // Dark Mode + if (state.darkMode) document.body.classList.add("dark-mode"); + else document.body.classList.remove("dark-mode"); + + // Active Feature Mode (Formatter/Compare/Codegen/etc.) + if (state.activeMode) { + switchMode(state.activeMode); + } else { + switchMode("formatter"); // Default fallback + } + + // Load Formatter tabs + const ftc = document.getElementById("formatter-tabs-container"); + ftc.querySelectorAll(".tab-button[data-tab]").forEach((btn) => btn.remove()); + document.getElementById("formatter-tab-contents").innerHTML = ""; + formatterTabCount = 0; + state.formatter.tabs.forEach((tabData) => { + createFormatterTab(tabData); + }); + if (state.formatter.activeTab) switchFormatterTab(state.formatter.activeTab); + + // Load Compare tabs + const ctc = document.getElementById("compare-tabs-container"); + ctc.querySelectorAll(".tab-button[data-tab]").forEach((btn) => btn.remove()); + document.getElementById("compare-tab-contents").innerHTML = ""; + compareTabCount = 0; + state.compare.tabs.forEach((tabData) => { + createCompareTabWithData(tabData); + }); + if (state.compare.activeTab) switchCompareTab(state.compare.activeTab); + + // Load Codegen tabs + const cgtc = document.getElementById("codegen-tabs-container"); + cgtc.querySelectorAll(".tab-button[data-tab]").forEach((btn) => btn.remove()); + document.getElementById("codegen-tab-contents").innerHTML = ""; + codegenTabCount = 0; + state.codegen.tabs.forEach((tabData) => { + createCodegenTabWithData(tabData); + }); + if (state.codegen.activeTab) switchCodegenTab(state.codegen.activeTab); + + enableTabReordering("formatter-tabs-container"); + enableTabReordering("compare-tabs-container"); + enableTabReordering("codegen-tabs-container"); +} + diff --git a/json-tool/public/assets/js/features/codegen/codegen.js b/json-tool/public/assets/js/features/codegen/codegen.js new file mode 100644 index 0000000..6a81919 --- /dev/null +++ b/json-tool/public/assets/js/features/codegen/codegen.js @@ -0,0 +1,328 @@ +let codegenTabCount = 0; + +function addCodegenTab() { + createCodegenTab(); + switchCodegenTab("codegenTab" + codegenTabCount); + saveGlobalState(); +} + +function createCodegenTab() { + codegenTabCount++; + const tabId = "codegenTab" + codegenTabCount; + // Create tab button + const tabButton = document.createElement("button"); + tabButton.className = "tab-button"; + tabButton.setAttribute("data-tab", tabId); + tabButton.onclick = () => switchCodegenTab(tabId); + + const nameSpan = document.createElement("span"); + nameSpan.className = "tab-name"; + nameSpan.textContent = `Tab ${codegenTabCount}`; + + const closeSpan = document.createElement("span"); + closeSpan.className = "close-tab"; + closeSpan.textContent = "ร—"; + closeSpan.onclick = (e) => closeCodegenTab(tabId, e); + + tabButton.append(nameSpan, closeSpan); + + tabButton.addEventListener("dblclick", () => + openTabRenameTooltip(tabId, "codegen") + ); + const tabsContainer = document.getElementById("codegen-tabs-container"); + const addButton = tabsContainer.querySelector(".add-tab-button"); + tabsContainer.insertBefore(tabButton, addButton); + enableTabReordering("codegen-tabs-container"); + + // Create tab content + const tabContent = document.createElement("div"); + tabContent.id = tabId; + tabContent.className = "json-tab-content"; + tabContent.innerHTML = ` + +
+ + + + +
+

+             `;
+  document.getElementById("codegen-tab-contents").appendChild(tabContent);
+  const textarea = tabContent.querySelector(".json-input");
+  textarea.addEventListener("paste", () =>
+    setTimeout(() => autoFormatTextarea(textarea), 100)
+  );
+  textarea.addEventListener("blur", () => autoFormatTextarea(textarea));
+  saveGlobalState();
+}
+
+function createCodegenTabWithData(tabData) {
+  codegenTabCount++;
+  const tabId = tabData.id;
+  const tabButton = document.createElement("button");
+  tabButton.className = "tab-button";
+  tabButton.setAttribute("data-tab", tabId);
+  tabButton.onclick = () => switchCodegenTab(tabId);
+
+  const nameSpan = document.createElement("span");
+  nameSpan.className = "tab-name";
+  nameSpan.textContent = tabData.name;
+
+  const closeSpan = document.createElement("span");
+  closeSpan.className = "close-tab";
+  closeSpan.textContent = "ร—";
+  closeSpan.onclick = (e) => closeCodegenTab(tabId, e);
+
+  tabButton.append(nameSpan, closeSpan);
+
+  tabButton.addEventListener("dblclick", () =>
+    openTabRenameTooltip(tabId, "codegen")
+  );
+  const tabsContainer = document.getElementById("codegen-tabs-container");
+  const addButton = tabsContainer.querySelector(".add-tab-button");
+  tabsContainer.insertBefore(tabButton, addButton);
+  const tabContent = document.createElement("div");
+  tabContent.id = tabId;
+  tabContent.className = "json-tab-content";
+  tabContent.innerHTML = `
+               
+               
+ + + +
+

+             `;
+  document.getElementById("codegen-tab-contents").appendChild(tabContent);
+  const textarea = tabContent.querySelector(".json-input");
+  textarea.value = tabData.input;
+  const selectElem = document.getElementById("lang-select-" + tabId);
+  selectElem.value = tabData.lang;
+  textarea.addEventListener("paste", () =>
+    setTimeout(() => autoFormatTextarea(textarea), 100)
+  );
+  textarea.addEventListener("blur", () => autoFormatTextarea(textarea));
+  saveGlobalState();
+  enableTabReordering("codegen-tabs-container");
+}
+
+function switchCodegenTab(tabId) {
+  document
+    .querySelectorAll("#codegen-tab-contents .json-tab-content")
+    .forEach((tab) => tab.classList.remove("active"));
+  const selectedTab = document.getElementById(tabId);
+  if (selectedTab) selectedTab.classList.add("active");
+  document
+    .querySelectorAll("#codegen-tabs-container .tab-button[data-tab]")
+    .forEach((btn) => {
+      btn.classList.toggle("active", btn.getAttribute("data-tab") === tabId);
+    });
+  saveGlobalState();
+}
+
+function generateCode(tabId) {
+  const tabContent = document.getElementById(tabId);
+  const textarea = tabContent.querySelector(".json-input");
+  const langSelect = document.getElementById("lang-select-" + tabId);
+  const outputPre = tabContent.querySelector(".code-output");
+  const inputText = textarea.value;
+  const lang = langSelect.value;
+  let obj;
+  try {
+    obj = JSON.parse(inputText);
+  } catch (e) {
+    outputPre.textContent = "Invalid JSON: " + e.message;
+    return;
+  }
+  let code = "";
+  if (lang === "typescript") code = generateTypeScript(obj, "Root");
+  else if (lang === "python") code = generatePython(obj, "Root");
+  else if (lang === "go") code = generateGo(obj, "Root");
+  outputPre.textContent = code;
+  saveGlobalState();
+}
+
+async function closeCodegenTab(tabId, event) {
+  if (event) {
+    event.stopPropagation();
+    event.preventDefault();
+  }
+
+  const result = await Swal.fire({
+    title: "Close tab?",
+    text: "Are you sure you want to close this tab?",
+    icon: "warning",
+    showCancelButton: true,
+    confirmButtonText: "Yes",
+    cancelButtonText: "No",
+  });
+
+  if (!result.isConfirmed) return;
+
+  const tabButton = document.querySelector(
+    `#codegen-tabs-container .tab-button[data-tab="${tabId}"]`
+  );
+  const tabContent = document.getElementById(tabId);
+  if (tabButton) tabButton.remove();
+  if (tabContent) tabContent.remove();
+  const remaining = document.querySelectorAll(
+    "#codegen-tab-contents .json-tab-content"
+  );
+  if (remaining.length > 0)
+    switchCodegenTab(remaining[remaining.length - 1].id);
+  saveGlobalState();
+}
+
+function copyCodeOutput(tabId) {
+  const codePre = document.querySelector(`#${tabId} .code-output`);
+  copyToClipboard(codePre.textContent, "Code copied");
+}
+
+function generateTypeScript(obj, interfaceName) {
+  let result = `interface ${interfaceName} {\n`;
+  for (const key in obj) {
+    if (!Object.hasOwn(obj, key)) continue;
+    const value = obj[key];
+    if (value === null) {
+      result += `  ${key}: any;\n`;
+    } else if (Array.isArray(value)) {
+      if (value.length > 0) {
+        const elem = value[0];
+        if (typeof elem === "object" && elem !== null) {
+          const subInterface = interfaceName + capitalize(key);
+          result += `  ${key}: ${subInterface}[];\n`;
+          result += generateTypeScript(elem, subInterface);
+        } else {
+          result += `  ${key}: ${typeof elem}[];\n`;
+        }
+      } else {
+        result += `  ${key}: any[];\n`;
+      }
+    } else if (typeof value === "object") {
+      const subInterface = interfaceName + capitalize(key);
+      result += `  ${key}: ${subInterface};\n`;
+      result += generateTypeScript(value, subInterface);
+    } else {
+      result += `  ${key}: ${typeof value};\n`;
+    }
+  }
+  result += "}\n";
+  return result;
+}
+
+function generatePython(obj, className, isRoot = true) {
+  let result = "";
+  if (isRoot) {
+    result +=
+      "from dataclasses import dataclass\nfrom typing import Any, List\n\n";
+  }
+  result += `@dataclass\\nclass ${className}:\\n`;
+  for (const key in obj) {
+    if (!Object.hasOwn(obj, key)) continue;
+    const value = obj[key];
+    let pyType = "Any";
+    if (value === null) {
+      pyType = "Any";
+    } else if (Array.isArray(value)) {
+      if (value.length > 0) {
+        const elem = value[0];
+        if (typeof elem === "object" && elem !== null) {
+          const subClass = className + capitalize(key);
+          pyType = `List[${subClass}]`;
+          result += "\\n" + generatePython(elem, subClass, false);
+        } else {
+          if (typeof elem === "number") pyType = "List[float]";
+          else if (typeof elem === "string") pyType = "List[str]";
+          else if (typeof elem === "boolean") pyType = "List[bool]";
+          else pyType = "List[Any]";
+        }
+      } else {
+        pyType = "List[Any]";
+      }
+    } else if (typeof value === "object") {
+      const subClass = className + capitalize(key);
+      pyType = subClass;
+      result += "\\n" + generatePython(value, subClass, false);
+    } else {
+      if (typeof value === "number") pyType = "float";
+      else if (typeof value === "string") pyType = "str";
+      else if (typeof value === "boolean") pyType = "bool";
+    }
+    result += `    ${key}: ${pyType}\n`;
+  }
+  return result;
+}
+
+function generateGo(obj, structName) {
+  let result = `type ${structName} struct {\n`;
+  for (const key in obj) {
+    if (!Object.hasOwn(obj, key)) continue;
+    const value = obj[key];
+    let goType = "interface{}";
+    if (value === null) {
+      goType = "interface{}";
+    } else if (Array.isArray(value)) {
+      if (value.length > 0) {
+        // Scan all elements for type consistency
+        let firstType = null;
+        let uniform = true;
+        let subStruct = structName + capitalize(key);
+        for (let i = 0; i < value.length; i++) {
+          const elem = value[i];
+          let elemType;
+          if (elem === null) {
+            elemType = "interface{}";
+          } else if (typeof elem === "object") {
+            elemType = subStruct;
+          } else if (typeof elem === "number") {
+            elemType = "float64";
+          } else if (typeof elem === "string") {
+            elemType = "string";
+          } else if (typeof elem === "boolean") {
+            elemType = "bool";
+          } else {
+            elemType = "interface{}";
+          }
+          if (firstType === null) {
+            firstType = elemType;
+          } else if (firstType !== elemType) {
+            uniform = false;
+            break;
+          }
+        }
+        if (uniform) {
+          if (firstType === subStruct) {
+            // If array of objects, generate sub-struct
+            result += generateGo(value[0], subStruct);
+          }
+          goType = `[]${firstType}`;
+        } else {
+          goType = "[]interface{}";
+        }
+      } else {
+        goType = "[]interface{}";
+      }
+    } else if (typeof value === "object") {
+      const subStruct = structName + capitalize(key);
+      goType = subStruct;
+      result += generateGo(value, subStruct);
+    } else {
+      if (typeof value === "number") goType = "float64";
+      else if (typeof value === "string") goType = "string";
+      else if (typeof value === "boolean") goType = "bool";
+    }
+    const fieldName = key.charAt(0).toUpperCase() + key.slice(1);
+    result += `    ${fieldName} ${goType} \`json:"${key}"\`\n`;
+  }
+  result += "}\n";
+  return result;
+}
diff --git a/json-tool/public/assets/js/features/compare/compare.js b/json-tool/public/assets/js/features/compare/compare.js
new file mode 100644
index 0000000..fb85c3e
--- /dev/null
+++ b/json-tool/public/assets/js/features/compare/compare.js
@@ -0,0 +1,187 @@
+let compareTabCount = 0;
+
+function addCompareTab() {
+  createCompareTab();
+  switchCompareTab("compareTab" + compareTabCount);
+  saveGlobalState();
+}
+
+function createCompareTab() {
+  compareTabCount++;
+  const tabId = "compareTab" + compareTabCount;
+  // Create tab button
+  const tabButton = document.createElement("button");
+  tabButton.className = "tab-button";
+  tabButton.setAttribute("data-tab", tabId);
+  tabButton.onclick = () => switchCompareTab(tabId);
+  tabButton.innerHTML = `Tab ${compareTabCount}
+               ร—`;
+  tabButton.addEventListener("dblclick", () =>
+    openTabRenameTooltip(tabId, "compare")
+  );
+  const tabsContainer = document.getElementById("compare-tabs-container");
+  const addButton = tabsContainer.querySelector(".add-tab-button");
+  tabsContainer.insertBefore(tabButton, addButton);
+  // Create tab content
+  const tabContent = document.createElement("div");
+  tabContent.id = tabId;
+  tabContent.className = "json-tab-content";
+  tabContent.innerHTML = `
+               
+ + +
+ +
+ `; + document.getElementById("compare-tab-contents").appendChild(tabContent); + const leftTA = tabContent.querySelector(".json-input-left"); + const rightTA = tabContent.querySelector(".json-input-right"); + leftTA.addEventListener("paste", () => + setTimeout(() => autoFormatTextarea(leftTA), 100) + ); + leftTA.addEventListener("blur", () => autoFormatTextarea(leftTA)); + rightTA.addEventListener("paste", () => + setTimeout(() => autoFormatTextarea(rightTA), 100) + ); + rightTA.addEventListener("blur", () => autoFormatTextarea(rightTA)); + saveGlobalState(); + enableTabReordering("compare-tabs-container"); +} + +// Create Compare tab using saved data +function createCompareTabWithData(tabData) { + compareTabCount++; + const tabId = tabData.id; + const tabButton = document.createElement("button"); + tabButton.className = "tab-button"; + tabButton.setAttribute("data-tab", tabId); + tabButton.onclick = () => switchCompareTab(tabId); + tabButton.innerHTML = `${tabData.name} + ร—`; + tabButton.addEventListener("dblclick", () => + openTabRenameTooltip(tabId, "compare") + ); + const tabsContainer = document.getElementById("compare-tabs-container"); + const addButton = tabsContainer.querySelector(".add-tab-button"); + tabsContainer.insertBefore(tabButton, addButton); + const tabContent = document.createElement("div"); + tabContent.id = tabId; + tabContent.className = "json-tab-content"; + tabContent.innerHTML = ` +
+ + +
+ +
+ `; + document.getElementById("compare-tab-contents").appendChild(tabContent); + const leftTA = tabContent.querySelector(".json-input-left"); + const rightTA = tabContent.querySelector(".json-input-right"); + leftTA.value = tabData.leftContent; + rightTA.value = tabData.rightContent; + leftTA.addEventListener("paste", () => + setTimeout(() => autoFormatTextarea(leftTA), 100) + ); + leftTA.addEventListener("blur", () => autoFormatTextarea(leftTA)); + rightTA.addEventListener("paste", () => + setTimeout(() => autoFormatTextarea(rightTA), 100) + ); + rightTA.addEventListener("blur", () => autoFormatTextarea(rightTA)); + saveGlobalState(); + enableTabReordering("compare-tabs-container"); +} + +function switchCompareTab(tabId) { + document + .querySelectorAll("#compare-tab-contents .json-tab-content") + .forEach((tab) => tab.classList.remove("active")); + const selectedTab = document.getElementById(tabId); + if (selectedTab) selectedTab.classList.add("active"); + document + .querySelectorAll("#compare-tabs-container .tab-button[data-tab]") + .forEach((btn) => { + btn.classList.toggle("active", btn.getAttribute("data-tab") === tabId); + }); + saveGlobalState(); +} + +function compareJSONs(tabId) { + const tabContent = document.getElementById(tabId); + const leftTA = tabContent.querySelector(".json-input-left"); + const rightTA = tabContent.querySelector(".json-input-right"); + const resultDiv = tabContent.querySelector(".compare-result"); + const leftText = leftTA.value; + const rightText = rightTA.value; + let leftObj; + let rightObj; + try { + leftObj = JSON.parse(leftText); + } catch (e) { + resultDiv.textContent = "Left JSON Error: " + e.message; + return; + } + try { + rightObj = JSON.parse(rightText); + } catch (e) { + resultDiv.textContent = "Right JSON Error: " + e.message; + return; + } + const leftFormatted = JSON.stringify(leftObj, null, 2); + const rightFormatted = JSON.stringify(rightObj, null, 2); + const diffPreview = diffJSONsPreview(leftFormatted, rightFormatted); + const sanitizedDiffPreview = DOMPurify.sanitize(diffPreview); + + resultDiv.innerHTML = ` +
+ + +
+ ${sanitizedDiffPreview} + `; + leftTA.value = leftFormatted; + rightTA.value = rightFormatted; + saveGlobalState(); +} + +async function closeCompareTab(tabId, event) { + if (event) { + event.stopPropagation(); + event.preventDefault(); + } + + const result = await Swal.fire({ + title: "Close tab?", + text: "Are you sure you want to close this tab?", + icon: "warning", + showCancelButton: true, + confirmButtonText: "Yes", + cancelButtonText: "No", + }); + + if (!result.isConfirmed) return; + + const tabButton = document.querySelector( + `#compare-tabs-container .tab-button[data-tab="${tabId}"]` + ); + const tabContent = document.getElementById(tabId); + if (tabButton) tabButton.remove(); + if (tabContent) tabContent.remove(); + const remaining = document.querySelectorAll( + "#compare-tab-contents .json-tab-content" + ); + if (remaining.length > 0) + switchCompareTab(remaining[remaining.length - 1].id); + saveGlobalState(); +} + +function copyCompareLeft(tabId) { + const leftTA = document.querySelector(`#${tabId} .json-input-left`); + copyToClipboard(leftTA.value, "Left JSON copied"); +} + +function copyCompareRight(tabId) { + const rightTA = document.querySelector(`#${tabId} .json-input-right`); + copyToClipboard(rightTA.value, "Right JSON copied"); +} diff --git a/json-tool/public/assets/js/features/convert/convert.js b/json-tool/public/assets/js/features/convert/convert.js new file mode 100644 index 0000000..eeb1ae8 --- /dev/null +++ b/json-tool/public/assets/js/features/convert/convert.js @@ -0,0 +1,55 @@ +let currentConvertMode = "dict-to-json"; + +function switchConvertDirection(mode) { + currentConvertMode = mode; + document.querySelectorAll("#convert-section .tab-button").forEach((btn) => { + btn.classList.toggle( + "active", + btn.textContent.includes("Dict") === (mode === "dict-to-json") + ); + }); + document.getElementById("convert-input").value = ""; + document.getElementById("convert-output").textContent = ""; +} + +function convert() { + const input = document.getElementById("convert-input").value; + const output = document.getElementById("convert-output"); + + try { + if (currentConvertMode === "dict-to-json") { + // 1) None/True/False โ†’ null/true/false + let json = input + .replace(/\bNone\b/g, "null") + .replace(/\bTrue\b/g, "true") + .replace(/\bFalse\b/g, "false"); + // 2) remove trailing commas before } or ] + json = json.replace(/,\s*(?=[}\]])/g, ""); + // 3) convert only quoted 'strings' โ†’ "strings" + json = json.replace(/'([^']*?)'/g, '"$1"'); + + const parsed = JSON.parse(json); + output.textContent = JSON.stringify(parsed, null, 2); + } else { + // JSON โ†’ Python dict + const obj = JSON.parse(input); + let dictStr = JSON.stringify(obj, null, 2); + // 1) only replace "quoted" strings โ†’ 'strings' + dictStr = dictStr.replace(/"([^"]*?)"/g, "'$1'"); + // 2) true/false/null โ†’ True/False/None + dictStr = dictStr + .replace(/\btrue\b/g, "True") + .replace(/\bfalse\b/g, "False") + .replace(/\bnull\b/g, "None"); + + output.textContent = dictStr; + } + } catch (e) { + output.textContent = "Error: " + e.message; + } +} + +function copyConvertOutput() { + const output = document.getElementById("convert-output").textContent; + copyToClipboard(output, "Copied!"); +} diff --git a/json-tool/public/assets/js/features/editor/editor.js b/json-tool/public/assets/js/features/editor/editor.js new file mode 100644 index 0000000..cf2063c --- /dev/null +++ b/json-tool/public/assets/js/features/editor/editor.js @@ -0,0 +1,177 @@ +let editorTabCount = 0; +const editorInstances = {}; + +function addEditorTab(tabData = null) { + let tabId; + if (tabData?.id) { + tabId = tabData.id; + const match = tabId.match(/editor-tab-(\d+)/); + if (match && Number.parseInt(match[1], 10) > editorTabCount) { + editorTabCount = Number.parseInt(match[1], 10); + } + } else { + editorTabCount++; + tabId = `editor-tab-${editorTabCount}`; + } + + const tabButton = document.createElement("button"); + tabButton.className = "tab-button"; + tabButton.setAttribute("data-tab", tabId); + tabButton.innerHTML = `${ + tabData?.title || `Note ${editorTabCount}` + }ร—`; + tabButton.onclick = () => switchEditorTab(tabId); + tabButton.addEventListener("dblclick", () => + openTabRenameTooltip(tabId, "editor") + ); + + document + .getElementById("editor-tabs-container") + .insertBefore( + tabButton, + document.querySelector("#editor-tabs-container .add-tab-button") + ); + + const tabContent = document.createElement("div"); + tabContent.id = tabId; + tabContent.className = "json-tab-content"; + tabContent.innerHTML = ` +
+
+ +
+ `; + + document.getElementById("editor-tab-contents").appendChild(tabContent); + + const editor = new toastui.Editor({ + el: document.getElementById(`${tabId}-editor`), + height: "400px", + initialEditType: "markdown", + previewStyle: "vertical", + }); + editorInstances[tabId] = editor; + + const saved = localStorage.getItem(tabId); + if (saved) editor.setMarkdown(saved); + + enableTabReordering("editor-tabs-container"); + applyEditorTabDarkMode(); +} + +function switchEditorTab(tabId) { + document + .querySelectorAll("#editor-tab-contents .json-tab-content") + .forEach((el) => el.classList.remove("active")); + document.getElementById(tabId)?.classList.add("active"); + + document + .querySelectorAll("#editor-tabs-container .tab-button") + .forEach((btn) => { + btn.classList.toggle("active", btn.getAttribute("data-tab") === tabId); + }); + updateEditorGlobalState(); +} + +function saveEditorContent(tabId, silent = false) { + const content = editorInstances[tabId].getMarkdown(); + localStorage.setItem(tabId, content); + updateEditorGlobalState(); + if (!silent) { + Swal.fire({ + toast: true, + position: "top-end", + icon: "success", + title: "Saved!", // Changed from Autosaved to Saved! + showConfirmButton: false, + timer: 1500, + }); + } +} + +async function deleteEditorTab(tabId, event) { + if (event) { + event.stopPropagation(); + event.preventDefault(); + } + + const result = await Swal.fire({ + title: "Close tab?", + text: "Are you sure you want to close this tab?", + icon: "warning", + showCancelButton: true, + confirmButtonText: "Yes", + cancelButtonText: "No", + }); + + if (!result.isConfirmed) return; + + localStorage.removeItem(tabId); + editorInstances[tabId]?.destroy(); + delete editorInstances[tabId]; + document + .querySelector(`#editor-tabs-container .tab-button[data-tab="${tabId}"]`) + ?.remove(); + document.getElementById(tabId)?.remove(); + const remaining = document.querySelectorAll( + "#editor-tab-contents .json-tab-content" + ); + if (remaining.length > 0) switchEditorTab(remaining[0].id); + updateEditorGlobalState(); +} + +function updateEditorGlobalState() { + const state = { + activeTab: + document.querySelector("#editor-tab-contents .json-tab-content.active") + ?.id || "", + tabs: [], + }; + document + .querySelectorAll("#editor-tabs-container .tab-button[data-tab]") + .forEach((btn) => { + const tabId = btn.getAttribute("data-tab"); + const title = btn.querySelector(".tab-name").textContent; + state.tabs.push({ id: tabId, title }); + }); + localStorage.setItem("editorState", JSON.stringify(state)); +} + +function loadEditorGlobalState() { + const stateStr = localStorage.getItem("editorState"); + const container = document.getElementById("editor-tabs-container"); + container + .querySelectorAll(".tab-button[data-tab]") + .forEach((btn) => btn.remove()); + document.getElementById("editor-tab-contents").innerHTML = ""; + editorTabCount = 0; + + if (!stateStr) { + addEditorTab(); + return; + } + + const state = JSON.parse(stateStr); + state.tabs.forEach((tabData) => addEditorTab(tabData)); + + if (state.activeTab && document.getElementById(state.activeTab)) { + switchEditorTab(state.activeTab); + } else if (state.tabs.length > 0) { + switchEditorTab(state.tabs[0].id); + } +} + +function applyEditorTabDarkMode() { + const container = document.getElementById("editor-tabs-container"); + container.querySelectorAll(".tab-button").forEach((btn) => { + if (document.body.classList.contains("dark-mode")) { + btn.style.backgroundColor = "#2c2c2c"; + btn.style.color = "#eee"; + btn.style.borderColor = "#444"; + } else { + btn.style.backgroundColor = ""; + btn.style.color = ""; + btn.style.borderColor = ""; + } + }); +} diff --git a/json-tool/public/assets/js/features/formatter/formatter.html b/json-tool/public/assets/js/features/formatter/formatter.html new file mode 100644 index 0000000..5a5af39 --- /dev/null +++ b/json-tool/public/assets/js/features/formatter/formatter.html @@ -0,0 +1,11 @@ + +
+
+ +
+
+ +
+
diff --git a/json-tool/public/assets/js/features/formatter/formatter.js b/json-tool/public/assets/js/features/formatter/formatter.js new file mode 100644 index 0000000..65c94b4 --- /dev/null +++ b/json-tool/public/assets/js/features/formatter/formatter.js @@ -0,0 +1,428 @@ +async function loadFormatterHTML() { + try { + const response = await fetch("assets/js/features/formatter/formatter.html"); + const html = await response.text(); + document.getElementById("formatter-section-container").innerHTML = html; + } catch (error) { + console.error("Error loading formatter HTML:", error); + } +} +document.addEventListener("DOMContentLoaded", loadFormatterHTML); + +let formatterTabCount = 0; + +function addFormatterTab() { + createFormatterTab(); + switchFormatterTab("formatterTab" + formatterTabCount); + saveGlobalState(); +} + +function createFormatterTab(tabData = null) { + formatterTabCount++; + const tabId = "formatterTab" + formatterTabCount; + + // Create tab button + const tabButton = document.createElement("button"); + tabButton.className = "tab-button"; + tabButton.setAttribute("data-tab", tabId); + tabButton.onclick = () => switchFormatterTab(tabId); + + const tabName = tabData?.name || "Tab " + formatterTabCount; + const tabColor = tabData?.color || "#e0e0e0"; + + tabButton.innerHTML = `${tabName} + + ร—`; + + tabButton.addEventListener("dblclick", () => + openTabRenameTooltip(tabId, "formatter") + ); + + const tabsContainer = document.getElementById("formatter-tabs-container"); + const addButton = tabsContainer.querySelector(".add-tab-button"); + if (tabsContainer && addButton) { + tabsContainer.insertBefore(tabButton, addButton); + } + + // Create tab content + const tabContent = document.createElement("div"); + tabContent.id = tabId; + tabContent.className = "json-tab-content"; + tabContent.innerHTML = ` + +
+ + +
+
+ + + +
+
+ + + +
+
+

+               
+
+
+
+
+
+
+ +
+ +

+                  
+
+ +
+
+

+             `;
+  document.getElementById("formatter-tab-contents").appendChild(tabContent);
+
+  // Set content if provided
+  if (tabData?.content) {
+    tabContent.querySelector(".json-input").value = tabData.content;
+  }
+
+  const textarea = tabContent.querySelector(".json-input");
+  textarea.addEventListener("paste", () =>
+    setTimeout(() => autoFormatTextarea(textarea), 100)
+  );
+  textarea.addEventListener("blur", () => autoFormatTextarea(textarea));
+  textarea.addEventListener("input", () => updateFormatterPreview(tabId));
+  updateFormatterPreview(tabId);
+  enableTabReordering("formatter-tabs-container");
+}
+
+function switchFormatterTab(tabId) {
+  document
+    .querySelectorAll("#formatter-tab-contents .json-tab-content")
+    .forEach((tab) => tab.classList.remove("active"));
+  const selectedTab = document.getElementById(tabId);
+  if (selectedTab) selectedTab.classList.add("active");
+  document
+    .querySelectorAll("#formatter-tabs-container .tab-button[data-tab]")
+    .forEach((btn) => {
+      btn.classList.toggle("active", btn.getAttribute("data-tab") === tabId);
+    });
+  saveGlobalState();
+}
+
+function updateFormatterPreview(tabId) {
+  const tabContent = document.getElementById(tabId);
+  const textarea = tabContent.querySelector(".json-input");
+  const rawPreview = tabContent.querySelector(".raw-json");
+  const errorMessage = tabContent.querySelector(".error-message");
+  try {
+    const parsed = JSON.parse(textarea.value);
+    const formatted = JSON.stringify(parsed, null, 2);
+    rawPreview.textContent = formatted;
+    createTreeView(parsed, tabContent.querySelector(".tree-view"));
+    errorMessage.textContent = "";
+    showFormatterPreviewTab(tabId, "raw");
+    // Avoid resetting user cursor while typing
+    if (document.activeElement !== textarea) {
+      textarea.value = formatted;
+    }
+  } catch (e) {
+    errorMessage.textContent = "Error: " + e.message;
+    showFormatterPreviewTab(tabId, "error");
+  }
+  document.querySelectorAll(".tree-key").forEach((key) => {
+    key.addEventListener("focus", () => {
+      key.scrollIntoView({ block: "nearest", behavior: "smooth" });
+    });
+  });
+  saveGlobalState();
+}
+
+function showFormatterPreviewTab(tabId, previewType) {
+  const tabContent = document.getElementById(tabId);
+  const previews = tabContent.querySelectorAll(".preview-section");
+  previews.forEach((section) => {
+    section.classList.toggle(
+      "active",
+      section.id === `${tabId}-${previewType}-preview`
+    );
+  });
+  const buttons = tabContent.querySelectorAll(".tabs .tab-button");
+  buttons.forEach((btn) => {
+    btn.classList.toggle(
+      "active",
+      btn.textContent.toLowerCase().includes(previewType)
+    );
+  });
+}
+
+function searchFormatterJSON(tabId) {
+  const tabContent = document.getElementById(tabId);
+  const searchInput = tabContent
+    .querySelector(".search-input")
+    .value.trim()
+    .toLowerCase();
+  const rawPreview = tabContent.querySelector(".raw-json");
+  const treeView = tabContent.querySelector(".tree-view");
+  tabContent.querySelectorAll(".highlight").forEach((el) => {
+    const parent = el.parentNode;
+    parent.replaceChild(document.createTextNode(el.textContent), el);
+  });
+  if (!searchInput) return;
+  const regex = new RegExp(
+    `(${searchInput.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`,
+    "gi"
+  );
+  if (rawPreview.parentElement.classList.contains("active")) {
+    const content = rawPreview.textContent;
+    rawPreview.innerHTML = content.replace(
+      regex,
+      '$1'
+    );
+  }
+  if (treeView.parentElement.classList.contains("active")) {
+    function highlightNode(node) {
+      if (node.nodeType === Node.TEXT_NODE) {
+        const matches = node.nodeValue.match(regex);
+        if (matches) {
+          const span = document.createElement("span");
+          span.innerHTML = node.nodeValue.replace(
+            regex,
+            '$1'
+          );
+          node.parentNode.replaceChild(span, node);
+        }
+      } else if (node.nodeType === Node.ELEMENT_NODE && node.childNodes) {
+        node.childNodes.forEach((child) => highlightNode(child));
+      }
+    }
+    treeView.childNodes.forEach((child) => highlightNode(child));
+  }
+  saveGlobalState();
+}
+
+function updateFormatterTabColor(tabId, colorValue) {
+  saveGlobalState();
+}
+
+function uploadFormatterJSON(tabId, inputElement) {
+  if (inputElement.files?.[0]) {
+    const file = inputElement.files[0];
+    const reader = new FileReader();
+    reader.onload = (e) => {
+      const content = e.target.result;
+      const tabContent = document.getElementById(tabId);
+      const textarea = tabContent.querySelector(".json-input");
+      textarea.value = content;
+      updateFormatterPreview(tabId);
+    };
+    reader.readAsText(file);
+    inputElement.value = "";
+  }
+}
+
+function downloadFormatterJSON(tabId) {
+  const tabContent = document.getElementById(tabId);
+  const content = tabContent.querySelector(".json-input").value;
+  const blob = new Blob([content], {
+    type: "application/json",
+  });
+  const url = URL.createObjectURL(blob);
+  const a = document.createElement("a");
+  a.href = url;
+  a.download = tabId + ".json";
+  document.body.appendChild(a);
+  a.click();
+  document.body.removeChild(a);
+  URL.revokeObjectURL(url);
+}
+
+async function closeFormatterTab(tabId, event) {
+  if (event) {
+    event.stopPropagation();
+    event.preventDefault();
+  }
+
+  const result = await Swal.fire({
+    title: "Close tab?",
+    text: "Are you sure you want to close this tab?",
+    icon: "warning",
+    showCancelButton: true,
+    confirmButtonText: "Yes",
+    cancelButtonText: "No",
+  });
+
+  if (!result.isConfirmed) return;
+
+  const tabButton = document.querySelector(
+    `#formatter-tabs-container .tab-button[data-tab="${tabId}"]`
+  );
+  const tabContent = document.getElementById(tabId);
+  if (tabButton) tabButton.remove();
+  if (tabContent) tabContent.remove();
+  const remaining = document.querySelectorAll(
+    "#formatter-tab-contents .json-tab-content"
+  );
+  if (remaining.length > 0)
+    switchFormatterTab(remaining[remaining.length - 1].id);
+  saveGlobalState();
+}
+
+function copyRawJSON(tabId) {
+  const rawPre = document.querySelector(`#${tabId}-raw-preview .raw-json`);
+  copyToClipboard(rawPre.textContent, "JSON copied to clipboard");
+}
+
+function createTreeView(data, parentElement) {
+  parentElement.innerHTML = "";
+  const focusedNode = null;
+
+  function processNode(value, parent, key, path = []) {
+    const node = document.createElement("div");
+    node.className = "tree-node";
+    const currentPath = [...path, key];
+    const displayKey = key !== undefined ? key : "";
+
+    if (typeof value === "object" && value !== null) {
+      const isArray = Array.isArray(value);
+      const keySpan = document.createElement("span");
+      keySpan.className = `tree-key ${isArray ? "type-array" : "type-object"}`;
+      keySpan.tabIndex = 0;
+
+      const nameEl = document.createElement("span");
+      nameEl.textContent = displayKey;
+      const infoEl = document.createElement("span");
+      infoEl.className = "node-info";
+      infoEl.textContent = isArray
+        ? `[${value.length}]`
+        : `{${Object.keys(value).length}}`;
+      keySpan.append(nameEl, infoEl);
+
+      // Keyboard navigation
+      keySpan.addEventListener("keydown", (e) => {
+        switch (e.key) {
+          case "ArrowRight":
+            if (keySpan.classList.contains("collapsed")) toggleNode();
+            break;
+          case "ArrowLeft":
+            if (!keySpan.classList.contains("collapsed")) toggleNode();
+            break;
+          case "ArrowDown":
+            focusNextNode(keySpan);
+            break;
+          case "ArrowUp":
+            focusPreviousNode(keySpan);
+            break;
+        }
+      });
+
+      // Context menu
+      keySpan.addEventListener("contextmenu", (e) => {
+        e.preventDefault();
+        showContextMenu(e, currentPath, value);
+      });
+
+      const children = document.createElement("div");
+      children.className = "tree-children";
+      children.style.display = "none";
+
+      const toggleNode = () => {
+        keySpan.classList.toggle("expanded");
+        keySpan.classList.toggle("collapsed");
+        children.style.display =
+          children.style.display === "none" ? "block" : "none";
+      };
+
+      keySpan.addEventListener("click", toggleNode);
+
+      if (isArray) {
+        value.forEach((item, index) =>
+          processNode(item, children, index, currentPath)
+        );
+      } else {
+        Object.entries(value).forEach(([k, v]) =>
+          processNode(v, children, k, currentPath)
+        );
+      }
+
+      node.appendChild(keySpan);
+      node.appendChild(children);
+      parent.appendChild(node);
+    } else {
+      const valueSpan = document.createElement("span");
+
+      const keyEl = document.createElement("span");
+      keyEl.className = "tree-key";
+      keyEl.textContent = `${key}: `;
+
+      const valEl = document.createElement("span");
+      valEl.className = getValueTypeClass(value);
+      valEl.textContent = formatValue(value);
+
+      valueSpan.append(keyEl, valEl);
+
+      // Value context menu
+      valueSpan.addEventListener("contextmenu", (e) => {
+        e.preventDefault();
+        showContextMenu(e, currentPath, value);
+      });
+
+      node.appendChild(valueSpan);
+      parent.appendChild(node);
+    }
+
+    return node;
+  }
+
+  function showContextMenu(e, path, value) {
+    const menu = document.createElement("div");
+    menu.className = "tree-context-menu";
+    menu.style.left = `${e.pageX}px`;
+    menu.style.top = `${e.pageY}px`;
+
+    const copyPath = document.createElement("div");
+    copyPath.className = "tree-context-menu-item";
+    copyPath.textContent = "Copy Path";
+    copyPath.onclick = () => navigator.clipboard.writeText(path.join("."));
+
+    const copyValue = document.createElement("div");
+    copyValue.className = "tree-context-menu-item";
+    copyValue.textContent = "Copy Value";
+    copyValue.onclick = () =>
+      navigator.clipboard.writeText(JSON.stringify(value));
+
+    menu.appendChild(copyPath);
+    menu.appendChild(copyValue);
+    document.body.appendChild(menu);
+
+    const closeMenu = () => {
+      document.body.removeChild(menu);
+      document.removeEventListener("click", closeMenu);
+    };
+
+    document.addEventListener("click", closeMenu);
+  }
+
+  function focusNextNode(currentNode) {
+    const allNodes = parentElement.querySelectorAll(".tree-key");
+    const currentIndex = Array.from(allNodes).indexOf(currentNode);
+    if (currentIndex < allNodes.length - 1) {
+      allNodes[currentIndex + 1].focus();
+    }
+  }
+
+  function focusPreviousNode(currentNode) {
+    const allNodes = parentElement.querySelectorAll(".tree-key");
+    const currentIndex = Array.from(allNodes).indexOf(currentNode);
+    if (currentIndex > 0) {
+      allNodes[currentIndex - 1].focus();
+    }
+  }
+
+  processNode(data, parentElement);
+}
diff --git a/json-tool/public/assets/js/features/mockgen/mockgen.js b/json-tool/public/assets/js/features/mockgen/mockgen.js
new file mode 100644
index 0000000..733eaa1
--- /dev/null
+++ b/json-tool/public/assets/js/features/mockgen/mockgen.js
@@ -0,0 +1,282 @@
+let latestMockData = [];
+const presets = {
+  User: {
+    id: "number|1000-9999",
+    name: "name.fullName",
+    email: "internet.email",
+    isActive: "boolean",
+  },
+  Product: {
+    id: "number|1-1000",
+    title: "commerce.productName",
+    price: "number|10-500",
+    available: "boolean",
+  },
+};
+
+function loadMockPreset(name) {
+  if (presets[name]) {
+    document.getElementById("mock-schema-input").value = JSON.stringify(
+      presets[name],
+      null,
+      2
+    );
+  }
+}
+
+function generateMockData() {
+  const input = document.getElementById("mock-schema-input").value;
+  const count = Number.parseInt(
+    document.getElementById("mockgen-count").value || "1",
+    10
+  );
+  const outputContainer = document.getElementById("mock-output-container");
+
+  try {
+    const schema = JSON.parse(input);
+    latestMockData = Array.from({ length: count }, () => {
+      try {
+        return mockFromSchema(schema);
+      } catch (err) {
+        return { error: err.message };
+      }
+    });
+    document.getElementById(
+      "mock-stats"
+    ).textContent = `Showing ${count} record(s)`;
+    updateMockView();
+  } catch (e) {
+    outputContainer.innerHTML = `
โŒ Error: ${e.message}
`; + } +} + +function mockFromSchema(schema) { + if (Array.isArray(schema)) { + return schema.map((item) => mockFromSchema(item)); + } + if (typeof schema === "object") { + const result = {}; + for (const [key, value] of Object.entries(schema)) { + result[key] = mockFromSchema(value); + } + return result; + } + if (typeof schema === "string") { + if (schema === "boolean") return Math.random() < 0.5; + if (schema.startsWith("number|")) { + const [min, max] = schema.split("|")[1].split("-").map(Number); + return faker.number.int({ min, max }); + } + + const fakerFn = schema.split("."); + let val = faker; + for (const part of fakerFn) { + val = val?.[part]; + } + if (typeof val === "function") return val(); + throw new Error(`Invalid faker path: "${schema}"`); + } + return schema; +} + +function updateMockView() { + const mode = document.querySelector( + 'input[name="mock-view-mode"]:checked' + ).value; + const container = document.getElementById("mock-output-container"); + container.innerHTML = ""; + + if (mode === "json") { + const pre = document.createElement("pre"); + pre.className = "code-output"; + pre.textContent = JSON.stringify( + latestMockData.length === 1 ? latestMockData[0] : latestMockData, + null, + 2 + ); + container.appendChild(pre); + } else { + container.appendChild(renderTableFromJson(latestMockData)); + } +} + +function renderTableFromJson(data) { + const table = document.createElement("table"); + table.className = "mock-preview-table"; + + if ( + !Array.isArray(data) || + data.length === 0 || + typeof data[0] !== "object" + ) { + table.innerHTML = "No tabular data available"; + return table; + } + + const headers = [...new Set(data.flatMap((obj) => Object.keys(obj)))]; + const thead = document.createElement("thead"); + const headerRow = document.createElement("tr"); + headers.forEach((h) => { + const th = document.createElement("th"); + th.textContent = h; + // Add title for full text on hover + th.title = h; + headerRow.appendChild(th); + }); + thead.appendChild(headerRow); + table.appendChild(thead); + + const tbody = document.createElement("tbody"); + data.forEach((row) => { + const tr = document.createElement("tr"); + if (row.error) { + tr.classList.add("error"); + } + + headers.forEach((key) => { + const td = document.createElement("td"); + const val = row[key]; + let displayVal; + + if (val && typeof val === "object") { + displayVal = JSON.stringify(val); + } else { + displayVal = val ?? ""; + } + + td.textContent = displayVal; + // Add title for full text on hover + td.title = displayVal; + + tr.appendChild(td); + }); + tbody.appendChild(tr); + }); + table.appendChild(tbody); + return table; +} + +function copyMockOutput(format = "json") { + let text = ""; + if (format === "json") { + text = JSON.stringify( + latestMockData.length === 1 ? latestMockData[0] : latestMockData, + null, + 2 + ); + } else if (format === "csv") { + if (!latestMockData.length || typeof latestMockData[0] !== "object") return; + const keys = Object.keys(latestMockData[0]); + const rows = latestMockData.map((obj) => + keys.map((k) => JSON.stringify(obj[k] ?? "")) + ); + text = [keys.join(","), ...rows.map((r) => r.join(","))].join("\n"); + } + + copyToClipboard(text, `Copied ${format.toUpperCase()}!`); +} + +function downloadFile(content, filename, type) { + const blob = new Blob([content], { type }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); +} + +let userInputValue = ""; + +document.getElementById("userInput").addEventListener("input", (e) => { + userInputValue = e.target.value; +}); + +function exportMockOutput(format = "json") { + if (!Array.isArray(latestMockData) || latestMockData.length === 0) { + console.error("No data available to export."); + return; + } + + let content = ""; + let filename = userInputValue !== "" ? userInputValue : "mock_data"; // Use user input if available + + if (format === "json") { + content = JSON.stringify( + latestMockData.length === 1 ? latestMockData[0] : latestMockData, + null, + 2 + ); + filename += ".json"; + downloadFile(content, filename, "application/json"); + } else if (format === "csv") { + if (!latestMockData.length || typeof latestMockData[0] !== "object") return; + + const keys = Object.keys(latestMockData[0]); + const rows = latestMockData.map((obj) => + keys.map((k) => JSON.stringify(obj[k] ?? "")) + ); + content = [keys.join(","), ...rows.map((r) => r.join(","))].join("\n"); + filename += ".csv"; + downloadFile(content, filename, "text/csv"); + } +} + +const mockgenDocs = ` + # ๐Ÿงช Mock Data Schema Format + + ## Supported Types + + - \`"name.fullName"\`: Full name + - \`"internet.email"\`: Email address + - \`"number|10-50"\`: Custom range + - \`"boolean"\`: true / false + - Arrays: \`["lorem.word"]\` + - Nested: + + \`\`\`json + { + "user": { + "name": "name.fullName", + "email": "internet.email" + } + } + \`\`\` + + ## Example + + \`\`\`json + { + "id": "number|1000-9999", + "name": "name.fullName", + "email": "internet.email", + "isActive": "boolean" + } + \`\`\` + `; + +function switchMockTab(tab) { + const schemaPanel = document.getElementById("mockgen-schema-panel"); + const docsPanel = document.getElementById("mockgen-docs-panel"); + + document.getElementById("mock-tab-schema").classList.remove("active"); + document.getElementById("mock-tab-docs").classList.remove("active"); + + if (tab === "schema") { + schemaPanel.style.display = "block"; + docsPanel.style.display = "none"; + document.getElementById("mock-tab-schema").classList.add("active"); + } else { + schemaPanel.style.display = "none"; + docsPanel.style.display = "block"; + document.getElementById("mock-tab-docs").classList.add("active"); + renderMockgenDocs(); + } +} + +function renderMockgenDocs() { + document.getElementById("mockgen-docs-preview").innerHTML = + marked.parse(mockgenDocs); +} diff --git a/index.html b/json-tool/public/index.html similarity index 88% rename from index.html rename to json-tool/public/index.html index 00ce211..c9aca31 100644 --- a/index.html +++ b/json-tool/public/index.html @@ -4,15 +4,20 @@ JSONP - Dev toolkit - - + + + + + @@ -26,7 +31,7 @@ rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" /> - + @@ -95,19 +100,7 @@

-
-
- - -
-
- -
-
+
@@ -130,9 +120,6 @@

-

@@ -315,6 +302,20 @@

Keyboard Shortcuts

alt="Made with love with Open Source" />

- + + + + + + + + + + + + + + +