initial commit
This commit is contained in:
parent
e468202f95
commit
423b9a25fb
24 changed files with 6670 additions and 0 deletions
827
native/wordpress/maple-icons-wp/CLAUDE.MD
Normal file
827
native/wordpress/maple-icons-wp/CLAUDE.MD
Normal file
|
|
@ -0,0 +1,827 @@
|
|||
# CLAUDE.md — Maple Icons WordPress Plugin
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Document | When to Reference |
|
||||
|----------|-------------------|
|
||||
| **CLAUDE.md** (this file) | Architecture, file structure, build order |
|
||||
| **SECURITY.md** | Writing ANY PHP code (same patterns as Maple Local Fonts) |
|
||||
|
||||
---
|
||||
|
||||
## Project Overview
|
||||
|
||||
Build a WordPress plugin called **Maple Icons** that:
|
||||
1. Fetches open-source icon sets from CDN and stores them locally
|
||||
2. Provides preset icon sets: Heroicons, Lucide, Feather, Phosphor, Material
|
||||
3. Only ONE icon set can be active at a time (though multiple can be downloaded)
|
||||
4. Offers a Gutenberg block "Maple Icons" for inserting icons
|
||||
5. Icons use `currentColor` to inherit text color from Global Styles
|
||||
|
||||
**Key principle:** Download once, serve locally. Zero runtime external requests after initial fetch.
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Minimum PHP:** 7.4
|
||||
- **Minimum WordPress:** 6.5 (for block.json apiVersion 3 and Global Styles)
|
||||
- **License:** GPL-2.0-or-later
|
||||
|
||||
---
|
||||
|
||||
## Preset Icon Sets
|
||||
|
||||
All icons fetched from jsdelivr CDN with pinned versions:
|
||||
|
||||
| Set | Package | Version | Styles | ~Icons | viewBox |
|
||||
|-----|---------|---------|--------|--------|---------|
|
||||
| Heroicons | `heroicons` | 2.1.1 | outline, solid, mini | 290 | 24×24 |
|
||||
| Lucide | `lucide-static` | 0.303.0 | icons | 1,400 | 24×24 |
|
||||
| Feather | `feather-icons` | 4.29.1 | icons | 280 | 24×24 |
|
||||
| Phosphor | `@phosphor-icons/core` | 2.1.1 | regular, bold, light, thin, fill, duotone | 1,200× styles | 256×256 (normalize) |
|
||||
| Material | `@material-design-icons/svg` | 0.14.13 | filled, outlined, round, sharp, two-tone | 2,500 | 24×24 |
|
||||
|
||||
### CDN URL Patterns
|
||||
|
||||
```
|
||||
Heroicons: https://cdn.jsdelivr.net/npm/heroicons@2.1.1/24/outline/{name}.svg
|
||||
Lucide: https://cdn.jsdelivr.net/npm/lucide-static@0.303.0/icons/{name}.svg
|
||||
Feather: https://cdn.jsdelivr.net/npm/feather-icons@4.29.1/dist/icons/{name}.svg
|
||||
Phosphor: https://cdn.jsdelivr.net/npm/@phosphor-icons/core@2.1.1/assets/{style}/{name}.svg
|
||||
Material: https://cdn.jsdelivr.net/npm/@material-design-icons/svg@0.14.13/{style}/{name}.svg
|
||||
```
|
||||
|
||||
### SVG Normalization Required
|
||||
|
||||
| Set | Issue | Fix |
|
||||
|-----|-------|-----|
|
||||
| Phosphor | viewBox="0 0 256 256" | Normalize to 24×24 |
|
||||
| Material | `fill="#000000"` hardcoded | Replace with `currentColor` |
|
||||
| All | May have XML declarations, comments | Strip during save |
|
||||
|
||||
---
|
||||
|
||||
## User Flow
|
||||
|
||||
```
|
||||
Admin visits Settings → Maple Icons (or plugin action link)
|
||||
↓
|
||||
Sees list of preset icon sets with Download/Delete buttons
|
||||
↓
|
||||
Clicks "Download" on Heroicons → progress bar → icons saved locally
|
||||
↓
|
||||
Clicks "Set Active" to make Heroicons the active set
|
||||
↓
|
||||
User inserts "Maple Icons" block in Gutenberg editor
|
||||
↓
|
||||
Block shows + button → click → icon picker modal with search
|
||||
↓
|
||||
User searches/filters → selects icon → SVG inserted inline
|
||||
↓
|
||||
SVG uses currentColor → inherits from Global Styles
|
||||
↓
|
||||
All icons served from local storage (wp-content/maple-icons/)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
maple-icons-wp/
|
||||
├── maple-icons.php # Main plugin file
|
||||
├── index.php # Silence is golden
|
||||
├── uninstall.php # Clean removal
|
||||
├── readme.txt # WordPress.org readme
|
||||
├── package.json # Block build config
|
||||
├── includes/
|
||||
│ ├── index.php
|
||||
│ ├── class-mi-icon-sets.php # Preset icon set definitions
|
||||
│ ├── class-mi-icon-registry.php # Icon management & search
|
||||
│ ├── class-mi-downloader.php # CDN fetch & local storage
|
||||
│ ├── class-mi-admin-page.php # Settings page
|
||||
│ └── class-mi-ajax-handler.php # AJAX handlers
|
||||
├── presets/ # Bundled manifests (icon names, tags)
|
||||
│ ├── index.php
|
||||
│ ├── heroicons.json
|
||||
│ ├── lucide.json
|
||||
│ ├── feather.json
|
||||
│ ├── phosphor.json
|
||||
│ └── material.json
|
||||
├── build/ # Compiled block assets (generated)
|
||||
│ ├── index.php
|
||||
│ ├── index.js
|
||||
│ ├── index.asset.php
|
||||
│ └── style-index.css
|
||||
├── src/ # Block source (for development)
|
||||
│ ├── index.js # Block registration
|
||||
│ ├── edit.js # Editor component
|
||||
│ ├── save.js # Save component (inline SVG)
|
||||
│ ├── icon-picker.js # Icon selection modal
|
||||
│ ├── editor.scss # Editor-only styles
|
||||
│ └── block.json # Block metadata
|
||||
├── assets/
|
||||
│ ├── index.php
|
||||
│ ├── admin.css # Settings page styles
|
||||
│ └── admin.js # Settings page JS (download progress, etc.)
|
||||
└── languages/
|
||||
├── index.php
|
||||
└── maple-icons.pot
|
||||
```
|
||||
|
||||
### Local Icon Storage
|
||||
|
||||
Icons are stored in:
|
||||
```
|
||||
wp-content/maple-icons/{set-slug}/{style}/{icon-name}.svg
|
||||
```
|
||||
|
||||
Example:
|
||||
```
|
||||
wp-content/maple-icons/heroicons/outline/academic-cap.svg
|
||||
wp-content/maple-icons/heroicons/solid/academic-cap.svg
|
||||
wp-content/maple-icons/lucide/icons/activity.svg
|
||||
wp-content/maple-icons/phosphor/regular/airplane.svg
|
||||
```
|
||||
|
||||
Using `wp-content/maple-icons/` (not uploads) to avoid cleanup plugin interference.
|
||||
|
||||
---
|
||||
|
||||
## Class Responsibilities
|
||||
|
||||
### MI_Icon_Sets
|
||||
Static definitions of all preset icon sets.
|
||||
|
||||
```php
|
||||
class MI_Icon_Sets {
|
||||
public static function get_all(): array;
|
||||
public static function get(string $slug): ?array;
|
||||
public static function get_cdn_url(string $slug, string $style, string $name): string;
|
||||
public static function get_manifest_path(string $slug): string;
|
||||
}
|
||||
```
|
||||
|
||||
Returns structure:
|
||||
```php
|
||||
[
|
||||
'slug' => 'heroicons',
|
||||
'name' => 'Heroicons',
|
||||
'version' => '2.1.1',
|
||||
'license' => 'MIT',
|
||||
'url' => 'https://heroicons.com',
|
||||
'cdn_base' => 'https://cdn.jsdelivr.net/npm/heroicons@2.1.1/',
|
||||
'styles' => [
|
||||
'outline' => ['path' => '24/outline', 'label' => 'Outline'],
|
||||
'solid' => ['path' => '24/solid', 'label' => 'Solid'],
|
||||
'mini' => ['path' => '20/solid', 'label' => 'Mini'],
|
||||
],
|
||||
'default_style' => 'outline',
|
||||
'viewbox' => '0 0 24 24',
|
||||
'normalize' => false, // true for Phosphor (256→24)
|
||||
'color_fix' => false, // true for Material (replace hardcoded colors)
|
||||
]
|
||||
```
|
||||
|
||||
### MI_Icon_Registry
|
||||
Manages downloaded sets and provides icon search/retrieval.
|
||||
|
||||
```php
|
||||
class MI_Icon_Registry {
|
||||
public function get_downloaded_sets(): array;
|
||||
public function get_active_set(): ?string;
|
||||
public function set_active(string $slug): bool;
|
||||
public function is_downloaded(string $slug): bool;
|
||||
public function get_icons_for_set(string $slug, string $style): array;
|
||||
public function search_icons(string $query, int $limit = 50): array;
|
||||
public function get_icon_svg(string $slug, string $style, string $name): string|WP_Error;
|
||||
public function delete_set(string $slug): bool;
|
||||
}
|
||||
```
|
||||
|
||||
### MI_Downloader
|
||||
Handles fetching icons from CDN and storing locally.
|
||||
|
||||
```php
|
||||
class MI_Downloader {
|
||||
public function download_set(string $slug, callable $progress_callback = null): array|WP_Error;
|
||||
public function download_icon(string $slug, string $style, string $name): string|WP_Error;
|
||||
private function normalize_svg(string $svg, array $set_config): string;
|
||||
private function get_local_path(string $slug, string $style, string $name): string;
|
||||
}
|
||||
```
|
||||
|
||||
### MI_Admin_Page
|
||||
Settings page under Settings → Maple Icons.
|
||||
|
||||
- List all preset icon sets
|
||||
- Show download status (downloaded/not downloaded)
|
||||
- Download button with progress indicator
|
||||
- Delete button for downloaded sets
|
||||
- Radio buttons to select active set
|
||||
- Preview sample icons from each downloaded set
|
||||
|
||||
### MI_Ajax_Handler
|
||||
AJAX endpoints:
|
||||
|
||||
- `mi_download_set` — Download an icon set from CDN
|
||||
- `mi_delete_set` — Delete a downloaded icon set
|
||||
- `mi_set_active` — Set the active icon set
|
||||
- `mi_search_icons` — Search icons in active set (for block picker)
|
||||
- `mi_get_icon_svg` — Get specific icon SVG (for block)
|
||||
|
||||
---
|
||||
|
||||
## Settings Storage
|
||||
|
||||
```php
|
||||
// Option name: maple_icons_settings
|
||||
[
|
||||
'active_set' => 'heroicons', // Slug of active set, or empty string
|
||||
'downloaded_sets' => [
|
||||
'heroicons' => [
|
||||
'version' => '2.1.1',
|
||||
'downloaded_at' => '2024-01-15 10:30:00',
|
||||
'icon_count' => 876, // Total across all styles
|
||||
],
|
||||
'lucide' => [
|
||||
'version' => '0.303.0',
|
||||
'downloaded_at' => '2024-01-15 11:00:00',
|
||||
'icon_count' => 1400,
|
||||
],
|
||||
],
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Manifest Format (presets/*.json)
|
||||
|
||||
Each preset ships with a manifest listing all icons:
|
||||
|
||||
```json
|
||||
{
|
||||
"slug": "heroicons",
|
||||
"name": "Heroicons",
|
||||
"version": "2.1.1",
|
||||
"icons": [
|
||||
{
|
||||
"name": "academic-cap",
|
||||
"tags": ["education", "graduation", "school", "hat"],
|
||||
"category": "objects",
|
||||
"styles": ["outline", "solid", "mini"]
|
||||
},
|
||||
{
|
||||
"name": "adjustments-horizontal",
|
||||
"tags": ["settings", "controls", "sliders"],
|
||||
"category": "ui",
|
||||
"styles": ["outline", "solid", "mini"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Manifests are bundled with the plugin to avoid runtime dependency on jsdelivr API.
|
||||
|
||||
---
|
||||
|
||||
## Gutenberg Block Architecture
|
||||
|
||||
### Block Registration (block.json)
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"apiVersion": 3,
|
||||
"name": "jetrails/maple-icons",
|
||||
"version": "1.0.0",
|
||||
"title": "Maple Icons",
|
||||
"category": "design",
|
||||
"icon": "star-filled",
|
||||
"description": "Insert an icon from your downloaded icon sets.",
|
||||
"keywords": ["icon", "svg", "symbol", "maple"],
|
||||
"textdomain": "maple-icons",
|
||||
"attributes": {
|
||||
"iconSet": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"iconStyle": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"iconName": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"iconSVG": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"size": {
|
||||
"type": "number",
|
||||
"default": 24
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"strokeWidth": {
|
||||
"type": "number",
|
||||
"default": 0
|
||||
},
|
||||
"strokeColor": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
}
|
||||
},
|
||||
"supports": {
|
||||
"html": false,
|
||||
"align": ["left", "center", "right"],
|
||||
"color": {
|
||||
"text": true,
|
||||
"background": true,
|
||||
"gradients": true
|
||||
},
|
||||
"spacing": {
|
||||
"margin": true,
|
||||
"padding": true
|
||||
},
|
||||
"shadow": true
|
||||
},
|
||||
"editorScript": "file:./index.js",
|
||||
"editorStyle": "file:./style-index.css"
|
||||
}
|
||||
```
|
||||
|
||||
### Block Style Controls (Inspector Panel)
|
||||
|
||||
The block sidebar will include these controls:
|
||||
|
||||
**Icon Settings:**
|
||||
- Icon picker button (change icon)
|
||||
- Size slider (8-256px, default 24)
|
||||
- Accessibility label text input
|
||||
|
||||
**Color Settings (via WordPress supports):**
|
||||
- Icon color (text color) — inherited by SVG via `currentColor`
|
||||
- Background color
|
||||
- Gradient support
|
||||
|
||||
**Stroke Settings (custom panel):**
|
||||
- Stroke width slider (0-10px)
|
||||
- Stroke color picker
|
||||
|
||||
**Spacing (via WordPress supports):**
|
||||
- Padding controls
|
||||
- Margin controls
|
||||
|
||||
**Effects:**
|
||||
- Drop shadow presets (via WordPress `shadow` support)
|
||||
|
||||
### Style Application
|
||||
|
||||
```jsx
|
||||
// In edit.js and save.js, compute styles:
|
||||
const iconStyles = {
|
||||
width: `${size}px`,
|
||||
height: `${size}px`,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
};
|
||||
|
||||
// Stroke is applied via CSS filter or SVG manipulation
|
||||
const svgStyles = strokeWidth > 0 ? {
|
||||
filter: `drop-shadow(0 0 0 ${strokeColor})`,
|
||||
WebkitTextStroke: `${strokeWidth}px ${strokeColor}`,
|
||||
} : {};
|
||||
|
||||
// Note: For true SVG stroke, we modify the SVG's stroke attribute
|
||||
// This is handled in the SVG wrapper, not CSS
|
||||
```
|
||||
|
||||
### SVG Stroke Handling
|
||||
|
||||
For stroke on SVG icons, we wrap the SVG output:
|
||||
|
||||
```jsx
|
||||
// The SVG itself uses currentColor for fill/stroke
|
||||
// Additional stroke effect applied via CSS or inline style
|
||||
<span
|
||||
className="wp-block-maple-icon__svg"
|
||||
style={{
|
||||
// Apply stroke via paint-order and stroke properties
|
||||
'--mi-stroke-width': strokeWidth ? `${strokeWidth}px` : undefined,
|
||||
'--mi-stroke-color': strokeColor || undefined,
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: iconSVG }}
|
||||
/>
|
||||
```
|
||||
|
||||
With CSS:
|
||||
```css
|
||||
.wp-block-maple-icon__svg svg {
|
||||
stroke: var(--mi-stroke-color, currentColor);
|
||||
stroke-width: var(--mi-stroke-width, 0);
|
||||
paint-order: stroke fill;
|
||||
}
|
||||
```
|
||||
|
||||
### Block Behavior
|
||||
|
||||
**Initial State (no icon selected):**
|
||||
- Shows + button placeholder
|
||||
- Click opens icon picker modal
|
||||
|
||||
**With icon selected:**
|
||||
- Displays inline SVG
|
||||
- Sidebar shows: size control, accessibility label, change icon button
|
||||
|
||||
**Icon Picker Modal:**
|
||||
- Search input (filters as you type)
|
||||
- Style selector dropdown (if set has multiple styles)
|
||||
- Grid of icon thumbnails
|
||||
- Click to select → SVG fetched and stored in attributes
|
||||
|
||||
### Save Output (save.js)
|
||||
|
||||
```jsx
|
||||
export default function save({ attributes }) {
|
||||
const { iconSVG, size, label } = attributes;
|
||||
|
||||
if (!iconSVG) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const blockProps = useBlockProps.save({
|
||||
className: 'wp-block-maple-icon',
|
||||
style: {
|
||||
width: `${size}px`,
|
||||
height: `${size}px`,
|
||||
display: 'inline-flex',
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<span {...blockProps}>
|
||||
<span
|
||||
dangerouslySetInnerHTML={{ __html: iconSVG }}
|
||||
role={label ? 'img' : 'presentation'}
|
||||
aria-label={label || undefined}
|
||||
aria-hidden={!label ? 'true' : undefined}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Download Process
|
||||
|
||||
### Batch Download Flow
|
||||
|
||||
1. User clicks "Download" for an icon set
|
||||
2. Frontend disables button, shows progress bar
|
||||
3. AJAX request to `mi_download_set`
|
||||
4. Backend:
|
||||
a. Load manifest from `presets/{slug}.json`
|
||||
b. Create local directory structure
|
||||
c. For each icon in manifest:
|
||||
- Fetch SVG from CDN
|
||||
- Normalize (viewBox, currentColor)
|
||||
- Save to local filesystem
|
||||
d. Update settings with download info
|
||||
5. Return success with icon count
|
||||
6. Frontend updates UI to show "Downloaded" state
|
||||
|
||||
### Batching Strategy
|
||||
|
||||
To avoid timeouts and memory issues:
|
||||
- Process icons in batches of 50
|
||||
- Use streaming/chunked approach
|
||||
- Allow resume on failure (track progress in transient)
|
||||
|
||||
```php
|
||||
// Download in batches
|
||||
$batch_size = 50;
|
||||
$icons = $manifest['icons'];
|
||||
$total = count($icons);
|
||||
|
||||
for ($i = 0; $i < $total; $i += $batch_size) {
|
||||
$batch = array_slice($icons, $i, $batch_size);
|
||||
foreach ($batch as $icon) {
|
||||
// Download each icon in batch
|
||||
}
|
||||
// Update progress transient
|
||||
set_transient('mi_download_progress_' . $slug, [
|
||||
'completed' => min($i + $batch_size, $total),
|
||||
'total' => $total,
|
||||
], HOUR_IN_SECONDS);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SVG Sanitization
|
||||
|
||||
Same approach as original CLAUDE.md — strict allowlist of SVG elements and attributes.
|
||||
|
||||
```php
|
||||
function mi_sanitize_svg($svg) {
|
||||
$allowed_tags = [
|
||||
'svg' => ['xmlns', 'viewbox', 'width', 'height', 'fill', 'stroke', ...],
|
||||
'path' => ['d', 'fill', 'stroke', 'stroke-width', ...],
|
||||
'circle' => ['cx', 'cy', 'r', 'fill', 'stroke', ...],
|
||||
'rect' => ['x', 'y', 'width', 'height', 'rx', 'ry', ...],
|
||||
'line' => ['x1', 'y1', 'x2', 'y2', ...],
|
||||
'polyline' => ['points', ...],
|
||||
'polygon' => ['points', ...],
|
||||
'ellipse' => ['cx', 'cy', 'rx', 'ry', ...],
|
||||
'g' => ['fill', 'stroke', 'transform', ...],
|
||||
];
|
||||
|
||||
// Remove dangerous content
|
||||
$svg = preg_replace('/<script\b[^>]*>.*?<\/script>/is', '', $svg);
|
||||
$svg = preg_replace('/\son\w+\s*=/i', ' data-removed=', $svg);
|
||||
|
||||
return wp_kses($svg, $allowed_tags);
|
||||
}
|
||||
```
|
||||
|
||||
### SVG Normalization
|
||||
|
||||
```php
|
||||
function mi_normalize_svg($svg, $set_config) {
|
||||
// 1. Strip XML declaration
|
||||
$svg = preg_replace('/<\?xml[^>]*\?>/i', '', $svg);
|
||||
|
||||
// 2. Strip comments
|
||||
$svg = preg_replace('/<!--.*?-->/s', '', $svg);
|
||||
|
||||
// 3. Normalize viewBox for Phosphor (256 → 24)
|
||||
if ($set_config['normalize']) {
|
||||
$svg = preg_replace('/viewBox=["\']0 0 256 256["\']/', 'viewBox="0 0 24 24"', $svg);
|
||||
}
|
||||
|
||||
// 4. Fix hardcoded colors for Material
|
||||
if ($set_config['color_fix']) {
|
||||
$svg = preg_replace('/fill=["\']#[0-9a-fA-F]{3,6}["\']/', 'fill="currentColor"', $svg);
|
||||
$svg = preg_replace('/fill=["\']black["\']/', 'fill="currentColor"', $svg);
|
||||
}
|
||||
|
||||
// 5. Ensure currentColor is used
|
||||
// Most sets already use currentColor, but double-check
|
||||
|
||||
// 6. Remove width/height attributes (let CSS control)
|
||||
$svg = preg_replace('/\s(width|height)=["\'][^"\']*["\']/', '', $svg);
|
||||
|
||||
return trim($svg);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Admin Settings Page UI
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Maple Icons │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ICON SETS │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ ◉ Heroicons ✓ Downloaded │ │
|
||||
│ │ 290 icons · Outline, Solid, Mini · MIT │ │
|
||||
│ │ [Preview: ⚙ 🏠 👤 ✉ 🔔 ⭐] │ │
|
||||
│ │ [Delete] │ │
|
||||
│ ├─────────────────────────────────────────────────────────────┤ │
|
||||
│ │ ○ Lucide ✓ Downloaded │ │
|
||||
│ │ 1,400 icons · Icons · ISC │ │
|
||||
│ │ [Preview: ⚙ 📁 📄 💾 🖨 ⬇] │ │
|
||||
│ │ [Delete] │ │
|
||||
│ ├─────────────────────────────────────────────────────────────┤ │
|
||||
│ │ ○ Feather Not downloaded │ │
|
||||
│ │ 280 icons · Icons · MIT │ │
|
||||
│ │ [Download] │ │
|
||||
│ ├─────────────────────────────────────────────────────────────┤ │
|
||||
│ │ ○ Phosphor Not downloaded │ │
|
||||
│ │ 7,200 icons · 6 styles · MIT │ │
|
||||
│ │ [Download] │ │
|
||||
│ ├─────────────────────────────────────────────────────────────┤ │
|
||||
│ │ ○ Material Not downloaded │ │
|
||||
│ │ 2,500 icons · 5 styles · Apache 2.0 │ │
|
||||
│ │ [Download] │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ◉ = Active icon set (used in Maple Icons block) │
|
||||
│ │
|
||||
│ ─────────────────────────────────────────────────────────────── │
|
||||
│ │
|
||||
│ USAGE │
|
||||
│ 1. Download one or more icon sets above │
|
||||
│ 2. Select which set should be active (radio button) │
|
||||
│ 3. Insert icons using the "Maple Icons" block in the editor │
|
||||
│ 4. Icons inherit your theme's text color automatically │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Icon Picker Modal (Block Editor)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Select Icon [X] │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 🔍 Search icons... │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Style: [Outline ▼] │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │ │
|
||||
│ │ │ ⚙ │ │ 🏠 │ │ 👤 │ │ ✉ │ │ 🔔 │ │ ⭐ │ │ ❤ │ │ 🔍 │ │ │
|
||||
│ │ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ │ │
|
||||
│ │ cog home user mail bell star heart search │ │
|
||||
│ │ │ │
|
||||
│ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │ │
|
||||
│ │ │ + │ │ ✓ │ │ ✕ │ │ ⬆ │ │ ⬇ │ │ ◀ │ │ ▶ │ │ ↻ │ │ │
|
||||
│ │ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ │ │
|
||||
│ │ plus check x up down left right refresh │ │
|
||||
│ │ │ │
|
||||
│ │ [Load More...] │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Selected: arrow-right │
|
||||
│ │
|
||||
│ [Cancel] [Insert Icon] │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Build Order
|
||||
|
||||
### Phase 1: Foundation
|
||||
1. `maple-icons.php` — Plugin header, constants, autoloader, activation hook
|
||||
2. `index.php` files in ALL directories
|
||||
3. `includes/class-mi-icon-sets.php` — Static preset definitions
|
||||
|
||||
### Phase 2: Download Infrastructure
|
||||
4. `includes/class-mi-downloader.php` — CDN fetch, normalize, store
|
||||
5. `includes/class-mi-icon-registry.php` — Downloaded set management
|
||||
6. `includes/class-mi-ajax-handler.php` — Download/delete/activate endpoints
|
||||
7. Create manifest files: `presets/*.json`
|
||||
|
||||
### Phase 3: Admin Interface
|
||||
8. `includes/class-mi-admin-page.php` — Settings page
|
||||
9. `assets/admin.css` — Settings page styles
|
||||
10. `assets/admin.js` — Download progress, AJAX handlers
|
||||
|
||||
### Phase 4: Gutenberg Block
|
||||
11. `src/block.json` — Block metadata
|
||||
12. `src/index.js` — Block registration
|
||||
13. `src/edit.js` — Editor component
|
||||
14. `src/save.js` — Save component
|
||||
15. `src/icon-picker.js` — Modal component
|
||||
16. `src/editor.scss` — Editor styles
|
||||
17. `package.json` + build
|
||||
|
||||
### Phase 5: Cleanup & Polish
|
||||
18. `uninstall.php` — Clean removal
|
||||
19. `readme.txt` — WordPress.org readme
|
||||
20. Testing
|
||||
|
||||
---
|
||||
|
||||
## Constants
|
||||
|
||||
```php
|
||||
define('MI_VERSION', '1.0.0');
|
||||
define('MI_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
||||
define('MI_PLUGIN_URL', plugin_dir_url(__FILE__));
|
||||
define('MI_PLUGIN_BASENAME', plugin_basename(__FILE__));
|
||||
define('MI_ICONS_DIR', WP_CONTENT_DIR . '/maple-icons/');
|
||||
define('MI_ICONS_URL', content_url('/maple-icons/'));
|
||||
define('MI_PRESETS_DIR', MI_PLUGIN_DIR . 'presets/');
|
||||
|
||||
// Limits
|
||||
define('MI_DOWNLOAD_BATCH_SIZE', 50);
|
||||
define('MI_SEARCH_LIMIT', 50);
|
||||
define('MI_DOWNLOAD_TIMEOUT', 10); // Per-icon timeout in seconds
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Checklist
|
||||
|
||||
Reference SECURITY.md for full details. Key points:
|
||||
|
||||
- [ ] ABSPATH check on every PHP file
|
||||
- [ ] index.php in every directory
|
||||
- [ ] Nonce verification FIRST in every AJAX handler
|
||||
- [ ] Capability check SECOND (`manage_options` for settings, `edit_posts` for block)
|
||||
- [ ] Validate icon set slugs against preset allowlist
|
||||
- [ ] Validate style slugs against set's defined styles
|
||||
- [ ] Sanitize all SVG content before storage
|
||||
- [ ] Validate file paths (prevent path traversal)
|
||||
- [ ] Use `wp_remote_get()` for CDN requests
|
||||
- [ ] Escape all output
|
||||
|
||||
---
|
||||
|
||||
## WordPress Hooks Used
|
||||
|
||||
```php
|
||||
// Activation/Deactivation
|
||||
register_activation_hook(__FILE__, 'mi_activate');
|
||||
register_deactivation_hook(__FILE__, 'mi_deactivate');
|
||||
|
||||
// Block registration
|
||||
add_action('init', 'mi_register_block');
|
||||
|
||||
// Admin menu
|
||||
add_action('admin_menu', 'mi_register_settings_page');
|
||||
|
||||
// Admin assets (settings page only)
|
||||
add_action('admin_enqueue_scripts', 'mi_enqueue_admin_assets');
|
||||
|
||||
// Plugin action links (Settings link in plugin list)
|
||||
add_filter('plugin_action_links_' . MI_PLUGIN_BASENAME, 'mi_add_action_links');
|
||||
|
||||
// AJAX handlers (admin only)
|
||||
add_action('wp_ajax_mi_download_set', 'mi_ajax_download_set');
|
||||
add_action('wp_ajax_mi_delete_set', 'mi_ajax_delete_set');
|
||||
add_action('wp_ajax_mi_set_active', 'mi_ajax_set_active');
|
||||
add_action('wp_ajax_mi_search_icons', 'mi_ajax_search_icons');
|
||||
add_action('wp_ajax_mi_get_icon_svg', 'mi_ajax_get_icon_svg');
|
||||
|
||||
// WooCommerce HPOS compatibility
|
||||
add_action('before_woocommerce_init', 'mi_declare_hpos_compatibility');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Compatibility Notes
|
||||
|
||||
### WooCommerce HPOS
|
||||
Declare compatibility (we don't touch orders):
|
||||
```php
|
||||
add_action('before_woocommerce_init', function() {
|
||||
if (class_exists(\Automattic\WooCommerce\Utilities\FeaturesUtil::class)) {
|
||||
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
|
||||
'custom_order_tables',
|
||||
__FILE__,
|
||||
true
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Caching Plugins
|
||||
Icons stored locally means no cache issues. SVG is inline in post_content.
|
||||
|
||||
### Wordfence
|
||||
CDN requests only happen during admin download action, using standard `wp_remote_get()`.
|
||||
|
||||
### LearnDash / WPForms
|
||||
No interference — we only add a block, no frontend hooks.
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
### Download Flow
|
||||
- [ ] Download Heroicons → all icons saved locally
|
||||
- [ ] Download progress shows correctly
|
||||
- [ ] Download Lucide (large set) → doesn't timeout
|
||||
- [ ] Delete downloaded set → files removed
|
||||
- [ ] Switch active set → setting persists
|
||||
|
||||
### Block Editor
|
||||
- [ ] Insert Maple Icons block → shows placeholder
|
||||
- [ ] Click + → opens icon picker
|
||||
- [ ] Search filters icons correctly
|
||||
- [ ] Style dropdown works (for sets with multiple styles)
|
||||
- [ ] Select icon → SVG appears in editor
|
||||
- [ ] Save post → icon persists
|
||||
- [ ] Frontend → icon displays correctly
|
||||
- [ ] Change text color in Global Styles → icon color changes
|
||||
|
||||
### Security
|
||||
- [ ] Download without nonce → 403
|
||||
- [ ] Download as non-admin → 403
|
||||
- [ ] Invalid set slug → rejected
|
||||
- [ ] Path traversal attempt → rejected
|
||||
|
||||
### Edge Cases
|
||||
- [ ] No sets downloaded → block shows helpful message
|
||||
- [ ] Active set deleted → block handles gracefully
|
||||
- [ ] CDN unreachable during download → appropriate error
|
||||
- [ ] Partial download failure → can retry/resume
|
||||
Loading…
Add table
Add a link
Reference in a new issue