Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c160afe
feat: import snippets
louiswol94 Nov 27, 2025
41c9ba3
Update src/js/components/Import/FromFileUpload/components/ImportResul…
louiswol94 Dec 5, 2025
5f67617
Update src/js/components/Import/FromFileUpload/FileUploadForm.tsx
louiswol94 Dec 5, 2025
90f4f55
Update src/js/components/Import/FromFileUpload/FileUploadForm.tsx
louiswol94 Dec 5, 2025
840d553
Update src/js/components/Import/FromOtherPlugins/components/SimpleSni…
louiswol94 Dec 5, 2025
9b3c02e
Update src/js/components/Import/FromOtherPlugins/components/SimpleSni…
louiswol94 Dec 5, 2025
30d897b
Update src/js/components/Import/FromFileUpload/components/SnippetSele…
louiswol94 Dec 5, 2025
6a057c4
Update src/js/components/Import/FromFileUpload/components/DuplicateAc…
louiswol94 Dec 5, 2025
d7b01a2
Update src/js/components/Import/FromFileUpload/components/DuplicateAc…
louiswol94 Dec 5, 2025
c7e0e7c
Update src/js/components/Import/FromFileUpload/components/DuplicateAc…
louiswol94 Dec 5, 2025
5fe0328
Update src/js/components/Import/FromFileUpload/components/ImportResul…
louiswol94 Dec 5, 2025
56abcb5
Update src/js/components/Import/FromFileUpload/components/ImportResul…
louiswol94 Dec 5, 2025
900a0d8
Update src/js/components/Import/FromFileUpload/FileUploadForm.tsx
louiswol94 Dec 5, 2025
5bff754
Update src/js/components/Import/FromOtherPlugins/components/ImporterS…
louiswol94 Dec 5, 2025
bd6633a
Update src/js/components/Import/FromOtherPlugins/components/ImportOpt…
louiswol94 Dec 5, 2025
c6c200c
Update src/js/components/Import/FromOtherPlugins/components/StatusDis…
louiswol94 Dec 5, 2025
d46d38e
Update src/js/components/Import/shared/components/ImportSection.tsx
louiswol94 Dec 5, 2025
9c10716
Update src/js/components/Import/FromOtherPlugins/components/ImportOpt…
louiswol94 Dec 5, 2025
038cd9c
Update src/js/components/Import/FromOtherPlugins/components/SimpleSni…
louiswol94 Dec 5, 2025
8957440
Update src/js/components/Import/FromFileUpload/components/DragDropUpl…
louiswol94 Dec 5, 2025
048a978
Update src/js/components/Import/FromFileUpload/components/DragDropUpl…
louiswol94 Dec 5, 2025
943b28f
Update src/js/components/Import/FromFileUpload/components/DuplicateAc…
louiswol94 Dec 5, 2025
1eba64f
Update src/js/components/Import/FromFileUpload/components/SelectedFil…
louiswol94 Dec 5, 2025
9378b56
Update src/js/components/Import/FromFileUpload/FileUploadForm.tsx
louiswol94 Dec 5, 2025
094039c
Update src/js/components/Import/FromFileUpload/FileUploadForm.tsx
louiswol94 Dec 18, 2025
cb3b1c5
Update src/js/components/Import/FromFileUpload/FileUploadForm.tsx
louiswol94 Dec 18, 2025
9a82f55
Update src/js/components/Import/FromOtherPlugins/components/SimpleSni…
louiswol94 Dec 18, 2025
1b5d37f
Update src/js/components/Import/FromOtherPlugins/components/StatusDis…
louiswol94 Dec 18, 2025
ce9fe10
Update src/js/components/Import/FromOtherPlugins/ImportForm.tsx
louiswol94 Dec 18, 2025
6496b3d
Update src/js/components/Import/shared/components/ImportCard.tsx
louiswol94 Dec 18, 2025
f97ce32
Update src/js/components/Import/ImportApp.tsx
louiswol94 Dec 18, 2025
f13294f
Merge branch 'core-beta' into import-snippets
louiswol94 Dec 18, 2025
a65496c
Merge branch 'core-beta' into import-snippets
louiswol94 Dec 18, 2025
617cd8a
Merge branch 'import-snippets' of https://github.com/codesnippetspro/…
louiswol94 Dec 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/webpack-js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const jsWebpackConfig: Configuration = {
entry: {
edit: { import: `${SOURCE_DIR}/edit.tsx`, dependOn: 'editor' },
editor: `${SOURCE_DIR}/editor.ts`,
import: `${SOURCE_DIR}/import.tsx`,
manage: `${SOURCE_DIR}/manage.ts`,
mce: `${SOURCE_DIR}/mce.ts`,
prism: `${SOURCE_DIR}/prism.ts`,
Expand Down
201 changes: 201 additions & 0 deletions src/js/components/Import/FromFileUpload/FileUploadForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import React, { useState, useRef, useEffect } from 'react'
import { __ } from '@wordpress/i18n'
import { Button } from '../../common/Button'
import {
DuplicateActionSelector,
DragDropUploadArea,
SelectedFilesList,
SnippetSelectionTable,
ImportResultDisplay
} from './components'
import { ImportCard } from '../shared'
import {
useFileSelection,
useSnippetSelection,
useImportWorkflow
} from './hooks'

type DuplicateAction = 'ignore' | 'replace' | 'skip'
type Step = 'upload' | 'select'

export const FileUploadForm: React.FC = () => {
const [duplicateAction, setDuplicateAction] = useState<DuplicateAction>('ignore')
const [currentStep, setCurrentStep] = useState<Step>('upload')
const selectSectionRef = useRef<HTMLDivElement>(null)

const fileSelection = useFileSelection()
const importWorkflow = useImportWorkflow()
const snippetSelection = useSnippetSelection(importWorkflow.availableSnippets)

useEffect(() => {
if (currentStep === 'select' && selectSectionRef.current) {
selectSectionRef.current.scrollIntoView({
behavior: 'smooth',
block: 'start'
})
}
}, [currentStep])

const handleFileSelect = (files: FileList | null) => {
fileSelection.handleFileSelect(files)
importWorkflow.clearUploadResult()
}

const handleParseFiles = async () => {
if (!fileSelection.selectedFiles) return

const success = await importWorkflow.parseFiles(fileSelection.selectedFiles)
if (success) {
snippetSelection.clearSelection()
setCurrentStep('select')
}
}

const handleImportSelected = async () => {
const snippetsToImport = snippetSelection.getSelectedSnippets()
await importWorkflow.importSnippets(snippetsToImport, duplicateAction)
}

const handleBackToUpload = () => {
setCurrentStep('upload')
fileSelection.clearFiles()
snippetSelection.clearSelection()
importWorkflow.resetWorkflow()
}

const isUploadDisabled = !fileSelection.selectedFiles ||
fileSelection.selectedFiles.length === 0 ||
importWorkflow.isUploading

const isImportDisabled = snippetSelection.selectedSnippets.size === 0 ||
importWorkflow.isImporting

return (
<div className="wrap">
<div className="import-form-container" style={{ maxWidth: '800px' }}>
<p>{__('Upload one or more Code Snippets export files and the snippets will be imported.', 'code-snippets')}</p>

<p>
{__('Afterward, you will need to visit the ', 'code-snippets')}
<a href="admin.php?page=snippets">
{__('All Snippets', 'code-snippets')}
</a>
{__(' page to activate the imported snippets.', 'code-snippets')}
</p>

{currentStep === 'upload' && (
<>

{(!importWorkflow.uploadResult || !importWorkflow.uploadResult.success) && (
<>
<DuplicateActionSelector
value={duplicateAction}
onChange={setDuplicateAction}
/>

<ImportCard>
<h2 style={{ margin: '0 0 1em 0' }}>{__('Choose Files', 'code-snippets')}</h2>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<h2 style={{ margin: '0 0 1em 0' }}>{__('Choose Files', 'code-snippets')}</h2>
<h2 style={{ margin: '0 0 1em' }}>{__('Choose Files', 'code-snippets')}</h2>

<p className="description" style={{ marginBlockEnd: '1em' }}>
{__('Choose one or more Code Snippets (.xml or .json) files to parse and preview.', 'code-snippets')}
</p>

<DragDropUploadArea
fileInputRef={fileSelection.fileInputRef}
onFileSelect={handleFileSelect}
disabled={importWorkflow.isUploading}
/>

{fileSelection.selectedFiles && fileSelection.selectedFiles.length > 0 && (
<SelectedFilesList
files={fileSelection.selectedFiles}
onRemoveFile={fileSelection.removeFile}
/>
)}

<div style={{ textAlign: 'center' }}>
<Button
primary
onClick={handleParseFiles}
disabled={isUploadDisabled}
style={{ minWidth: '200px' }}
>
{importWorkflow.isUploading
? __('Uploading files...', 'code-snippets')
: __('Upload files', 'code-snippets')
}
</Button>
</div>
</ImportCard>
</>
)}
</>
)}

{currentStep === 'select' && importWorkflow.availableSnippets.length > 0 && !importWorkflow.uploadResult?.success && (
<ImportCard ref={selectSectionRef}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBlockEnd: '20px' }}>
<Button onClick={handleBackToUpload} className="button-link">
{__('← Upload Different Files', 'code-snippets')}
</Button>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBlockEnd: '10px' }}>
<div>
<h3 style={{ margin: '0' }}>{__('Available Snippets', 'code-snippets')} ({importWorkflow.availableSnippets.length})</h3>
<p style={{ margin: '0.5em 0 1em 0', color: '#666' }}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<p style={{ margin: '0.5em 0 1em 0', color: '#666' }}>
<p style={{ margin: '0.5em 0 1em', color: '#666' }}>

{__('Select the snippets you want to import:', 'code-snippets')}
</p>
</div>
<div>
<Button onClick={snippetSelection.handleSelectAll} style={{ marginInlineEnd: '10px' }}>
{snippetSelection.isAllSelected
? __('Deselect All', 'code-snippets')
: __('Select All', 'code-snippets')
}
</Button>
<Button
primary
onClick={handleImportSelected}
disabled={isImportDisabled}
>
{importWorkflow.isImporting
? __('Importing...', 'code-snippets')
: __('Import Selected', 'code-snippets')} ({snippetSelection.selectedSnippets.size})
</Button>
</div>
</div>

<SnippetSelectionTable
snippets={importWorkflow.availableSnippets}
selectedSnippets={snippetSelection.selectedSnippets}
isAllSelected={snippetSelection.isAllSelected}
onSnippetToggle={snippetSelection.handleSnippetToggle}
onSelectAll={snippetSelection.handleSelectAll}
/>

<div style={{ textAlign: 'end', marginBlockStart: '1em' }}>
<Button onClick={snippetSelection.handleSelectAll} style={{ marginInlineEnd: '10px' }}>
{snippetSelection.isAllSelected
? __('Deselect All', 'code-snippets')
: __('Select All', 'code-snippets')
}
</Button>
<Button
primary
onClick={handleImportSelected}
disabled={isImportDisabled}
>
{importWorkflow.isImporting
? __('Importing...', 'code-snippets')
: __('Import Selected', 'code-snippets')} ({snippetSelection.selectedSnippets.size})
</Button>
</div>
</ImportCard>
)}

{importWorkflow.uploadResult && (
<ImportResultDisplay result={importWorkflow.uploadResult} />
)}
</div>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -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',
marginBlockEnd: '20px',
transition: 'all 0.3s ease',
opacity: disabled ? 0.6 : 1
}}
>
<div style={{ fontSize: '48px', marginBlockEnd: '10px', color: '#666' }}>📁</div>
<p style={{ margin: '0 0 8px 0', fontSize: '16px', fontWeight: '500' }}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<p style={{ margin: '0 0 8px 0', fontSize: '16px', fontWeight: '500' }}>
<p style={{ margin: '0 0 8px', 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}
/>
</>
)
}
Original file line number Diff line number Diff line change
@@ -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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<h2 style={{ margin: '0 0 1em 0' }}>{__('Duplicate Snippets', 'code-snippets')}</h2>
<h2 style={{ margin: '0 0 1em' }}>{__('Duplicate Snippets', 'code-snippets')}</h2>

<p className="description" style={{ marginBlockEnd: '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={{ marginBlockStart: '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={{ marginBlockStart: '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={{ marginBlockStart: '2px' }}
/>
<span>
{__('Do not import any duplicate snippets; leave all existing snippets unchanged.', 'code-snippets')}
</span>
</label>
</div>
</fieldset>
</ImportCard>
)
}
Loading
Loading