Initial commit: WordPress wp-content (themes, plugins, languages)
- Theme: momentry (custom theme with REST API routes) - Plugins: code-snippets (contains all API proxies) - Languages: zh_TW translations - Excludes: cache, backups, uploads, logs
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import React from 'react'
|
||||
import { __ } from '@wordpress/i18n'
|
||||
import { useDragAndDrop } from '../hooks/useDragAndDrop'
|
||||
|
||||
interface DragDropUploadAreaProps {
|
||||
fileInputRef: React.RefObject<HTMLInputElement>
|
||||
onFileSelect: (files: FileList | null) => void
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export const DragDropUploadArea: React.FC<DragDropUploadAreaProps> = ({
|
||||
fileInputRef,
|
||||
onFileSelect,
|
||||
disabled = false
|
||||
}) => {
|
||||
const { dragOver, handleDragOver, handleDragLeave, handleDrop } = useDragAndDrop({
|
||||
onFilesDrop: onFileSelect
|
||||
})
|
||||
|
||||
const handleClick = () => {
|
||||
if (!disabled) {
|
||||
fileInputRef.current?.click()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`upload-drop-zone ${dragOver ? 'drag-over' : ''}`}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
onClick={handleClick}
|
||||
style={{
|
||||
border: `2px dashed ${dragOver ? '#0073aa' : '#ccd0d4'}`,
|
||||
borderRadius: '4px',
|
||||
padding: '40px 20px',
|
||||
textAlign: 'center',
|
||||
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||
backgroundColor: dragOver ? '#f0f6fc' : disabled ? '#f6f7f7' : '#fafafa',
|
||||
marginBottom: '20px',
|
||||
transition: 'all 0.3s ease',
|
||||
opacity: disabled ? 0.6 : 1
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '48px', marginBottom: '10px', color: '#666' }}>📁</div>
|
||||
<p style={{ margin: '0 0 8px 0', fontSize: '16px', fontWeight: '500' }}>
|
||||
{__('Drag and drop files here, or click to browse', 'code-snippets')}
|
||||
</p>
|
||||
<p style={{ margin: '0', color: '#666', fontSize: '14px' }}>
|
||||
{__('Supports JSON and XML files', 'code-snippets')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept="application/json,.json,text/xml"
|
||||
multiple
|
||||
onChange={(e) => onFileSelect(e.target.files)}
|
||||
style={{ display: 'none' }}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import React from 'react'
|
||||
import { __ } from '@wordpress/i18n'
|
||||
import { ImportCard } from '../../shared'
|
||||
|
||||
type DuplicateAction = 'ignore' | 'replace' | 'skip'
|
||||
|
||||
interface DuplicateActionSelectorProps {
|
||||
value: DuplicateAction
|
||||
onChange: (action: DuplicateAction) => void
|
||||
}
|
||||
|
||||
export const DuplicateActionSelector: React.FC<DuplicateActionSelectorProps> = ({
|
||||
value,
|
||||
onChange
|
||||
}) => {
|
||||
return (
|
||||
<ImportCard>
|
||||
<h2 style={{ margin: '0 0 1em 0' }}>{__('Duplicate Snippets', 'code-snippets')}</h2>
|
||||
<p className="description" style={{ marginBottom: '1em' }}>
|
||||
{__('What should happen if an existing snippet is found with an identical name to an imported snippet?', 'code-snippets')}
|
||||
</p>
|
||||
|
||||
<fieldset>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||
<label style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', cursor: 'pointer' }}>
|
||||
<input
|
||||
type="radio"
|
||||
name="duplicate_action"
|
||||
value="ignore"
|
||||
checked={value === 'ignore'}
|
||||
onChange={(e) => onChange(e.target.value as DuplicateAction)}
|
||||
style={{ marginTop: '2px' }}
|
||||
/>
|
||||
<span>
|
||||
{__('Ignore any duplicate snippets: import all snippets from the file regardless and leave all existing snippets unchanged.', 'code-snippets')}
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', cursor: 'pointer' }}>
|
||||
<input
|
||||
type="radio"
|
||||
name="duplicate_action"
|
||||
value="replace"
|
||||
checked={value === 'replace'}
|
||||
onChange={(e) => onChange(e.target.value as DuplicateAction)}
|
||||
style={{ marginTop: '2px' }}
|
||||
/>
|
||||
<span>
|
||||
{__('Replace any existing snippets with a newly imported snippet of the same name.', 'code-snippets')}
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', cursor: 'pointer' }}>
|
||||
<input
|
||||
type="radio"
|
||||
name="duplicate_action"
|
||||
value="skip"
|
||||
checked={value === 'skip'}
|
||||
onChange={(e) => onChange(e.target.value as DuplicateAction)}
|
||||
style={{ marginTop: '2px' }}
|
||||
/>
|
||||
<span>
|
||||
{__('Do not import any duplicate snippets; leave all existing snippets unchanged.', 'code-snippets')}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</ImportCard>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import React from 'react'
|
||||
import { __ } from '@wordpress/i18n'
|
||||
import { ImportCard } from '../../shared'
|
||||
|
||||
interface ImportResult {
|
||||
success: boolean
|
||||
message: string
|
||||
imported?: number
|
||||
warnings?: string[]
|
||||
}
|
||||
|
||||
interface ImportResultDisplayProps {
|
||||
result: ImportResult
|
||||
}
|
||||
|
||||
export const ImportResultDisplay: React.FC<ImportResultDisplayProps> = ({ result }) => {
|
||||
return (
|
||||
<ImportCard>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '12px' }}>
|
||||
<div style={{
|
||||
backgroundColor: result.success ? '#00a32a' : '#d63638',
|
||||
borderRadius: '50%',
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
marginTop: '2px'
|
||||
}}>
|
||||
<span style={{ color: 'white', fontSize: '14px', fontWeight: 'bold' }}>
|
||||
{result.success ? '✓' : '✕'}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<h3 style={{ margin: '0 0 8px 0', fontSize: '16px', fontWeight: '600' }}>
|
||||
{result.success
|
||||
? __('Import Successful!', 'code-snippets')
|
||||
: __('Import Failed', 'code-snippets')
|
||||
}
|
||||
</h3>
|
||||
<p style={{ margin: '0 0 8px 0', color: '#666' }}>
|
||||
{result.message}
|
||||
</p>
|
||||
|
||||
{result.success && (
|
||||
<p style={{ margin: '0', color: '#666' }}>
|
||||
{__('Go to ', 'code-snippets')}
|
||||
<a href="admin.php?page=snippets" style={{ color: '#2271b1', textDecoration: 'none' }}>
|
||||
{__('All Snippets', 'code-snippets')}
|
||||
</a>
|
||||
{__(' to activate your imported snippets.', 'code-snippets')}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{result.warnings && result.warnings.length > 0 && (
|
||||
<div style={{ marginTop: '12px' }}>
|
||||
<h4 style={{ margin: '0 0 8px 0', fontSize: '14px', color: '#d63638' }}>
|
||||
{__('Warnings:', 'code-snippets')}
|
||||
</h4>
|
||||
<ul style={{ margin: '0', paddingLeft: '20px' }}>
|
||||
{result.warnings.map((warning, index) => (
|
||||
<li key={index} style={{ color: '#666', fontSize: '14px' }}>
|
||||
{warning}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ImportCard>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import React from 'react'
|
||||
import { __ } from '@wordpress/i18n'
|
||||
import { formatFileSize } from '../utils/fileUtils'
|
||||
|
||||
interface SelectedFilesListProps {
|
||||
files: FileList
|
||||
onRemoveFile: (index: number) => void
|
||||
}
|
||||
|
||||
export const SelectedFilesList: React.FC<SelectedFilesListProps> = ({
|
||||
files,
|
||||
onRemoveFile
|
||||
}) => {
|
||||
return (
|
||||
<div className="selected-files" style={{ marginBottom: '20px' }}>
|
||||
<h3 style={{ margin: '0 0 12px 0', fontSize: '14px', fontWeight: '600' }}>
|
||||
{__('Selected Files:', 'code-snippets')} ({files.length})
|
||||
</h3>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||
{Array.from(files).map((file, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '8px 12px',
|
||||
backgroundColor: '#f9f9f9',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid #ddd'
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<span style={{ fontSize: '16px' }}>📄</span>
|
||||
<div>
|
||||
<div style={{ fontWeight: '500' }}>{file.name}</div>
|
||||
<div style={{ fontSize: '12px', color: '#666' }}>
|
||||
{formatFileSize(file.size)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onRemoveFile(index)
|
||||
}}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
color: '#d63638',
|
||||
cursor: 'pointer',
|
||||
fontSize: '16px',
|
||||
padding: '4px'
|
||||
}}
|
||||
title={__('Remove file', 'code-snippets')}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import React from 'react'
|
||||
import { __ } from '@wordpress/i18n'
|
||||
import type { ImportableSnippet } from '../../../../hooks/useFileUploadAPI'
|
||||
|
||||
interface SnippetSelectionTableProps {
|
||||
snippets: ImportableSnippet[]
|
||||
selectedSnippets: Set<number | string>
|
||||
isAllSelected: boolean
|
||||
onSnippetToggle: (snippetId: number | string) => void
|
||||
onSelectAll: () => void
|
||||
}
|
||||
|
||||
export const SnippetSelectionTable: React.FC<SnippetSelectionTableProps> = ({
|
||||
snippets,
|
||||
selectedSnippets,
|
||||
isAllSelected,
|
||||
onSnippetToggle,
|
||||
onSelectAll
|
||||
}) => {
|
||||
const getTypeColor = (type: string): string => {
|
||||
switch (type) {
|
||||
case 'css': return '#9B59B6'
|
||||
case 'js': return '#FFEB3B'
|
||||
case 'html': return '#EF6A36'
|
||||
default: return '#1D97C6'
|
||||
}
|
||||
}
|
||||
|
||||
const truncateDescription = (description: string | undefined): string => {
|
||||
const desc = description || __('No description', 'code-snippets')
|
||||
return desc.length > 50 ? desc.substring(0, 50) + '...' : desc
|
||||
}
|
||||
|
||||
return (
|
||||
<table className="wp-list-table widefat fixed striped" style={{ borderRadius: '5px', tableLayout: 'fixed' }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" className="check-column" style={{ padding: '8px 0', width: '40px' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isAllSelected}
|
||||
onChange={onSelectAll}
|
||||
/>
|
||||
</th>
|
||||
<th scope="col" style={{ width: '200px' }}>{__('Name', 'code-snippets')}</th>
|
||||
<th scope="col" style={{ width: '90px', textAlign: 'center' }}>{__('Type', 'code-snippets')}</th>
|
||||
<th scope="col" style={{ width: 'auto' }}>{__('Description', 'code-snippets')}</th>
|
||||
<th scope="col" style={{ width: '120px' }}>{__('Tags', 'code-snippets')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{snippets.map(snippet => (
|
||||
<tr key={snippet.table_data.id}>
|
||||
<th scope="row" className="check-column">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedSnippets.has(snippet.table_data.id)}
|
||||
onChange={() => onSnippetToggle(snippet.table_data.id)}
|
||||
/>
|
||||
</th>
|
||||
<td>
|
||||
<strong>{snippet.table_data.title}</strong>
|
||||
{snippet.source_file && (
|
||||
<div style={{ fontSize: '12px', color: '#666', marginTop: '2px' }}>
|
||||
from {snippet.source_file}
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td style={{ width: '90px', textAlign: 'center' }}>
|
||||
<span style={{
|
||||
backgroundColor: getTypeColor(snippet.table_data.type),
|
||||
color: 'white',
|
||||
padding: '3px 6px',
|
||||
fontSize: '10px',
|
||||
textTransform: 'uppercase',
|
||||
borderRadius: '3px'
|
||||
}}>
|
||||
{snippet.table_data.type}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{truncateDescription(snippet.table_data.description)}
|
||||
</td>
|
||||
<td>{snippet.table_data.tags || '—'}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export { DuplicateActionSelector } from './DuplicateActionSelector'
|
||||
export { DragDropUploadArea } from './DragDropUploadArea'
|
||||
export { SelectedFilesList } from './SelectedFilesList'
|
||||
export { SnippetSelectionTable } from './SnippetSelectionTable'
|
||||
export { ImportResultDisplay } from './ImportResultDisplay'
|
||||
Reference in New Issue
Block a user