fix(admin): map Winter icon-* names onto lucide for plugin navigation

Ported plugins send Winter icon-* class names on nav and settings; the SPA
never loads Font Awesome, so unknown names rendered as empty squares. Map
BM Studies, Quizzes, icon-pencil, and a closed Winter backend alias table
onto named @lucide/vue 1.17.0 exports, keep Square for unknown names, and
rebuild the embedded boardwalk dist.
This commit is contained in:
Jakub Zych
2026-10-05 19:28:06 +02:00
parent f453b80dc7
commit a00dafaf65
5 changed files with 261 additions and 15 deletions

View File

@@ -9,39 +9,98 @@ import {
ArrowRight,
ArrowUp,
ArrowUpRight,
Bell,
Book,
Bookmark,
Box,
Briefcase,
Building,
Calendar,
Camera,
CassetteTape,
ChartBar,
Check,
ChevronDown,
ChevronLeft,
ChevronRight,
Circle,
CircleAlert,
CircleCheck,
CircleQuestionMark,
ClipboardList,
Clock,
Cloud,
Code,
Copy,
CreditCard,
Database,
Disc,
Disc3,
Download,
ExternalLink,
File,
FileText,
Files,
Flag,
Folder,
Funnel,
Gauge,
Globe,
GraduationCap,
Heart,
House,
Image,
Inbox,
Info,
Key,
Languages,
LayoutGrid,
Leaf,
Library,
LifeBuoy,
Link,
List,
ListOrdered,
Lock,
LogOut,
Mail,
MapPin,
MessageCircle,
MicVocal,
Minus,
Network,
Palette,
PanelLeftClose,
PanelLeftOpen,
Paperclip,
Pause,
Pencil,
Phone,
Play,
Plus,
Puzzle,
RefreshCw,
Search,
SearchX,
Settings,
ShieldCheck,
ShoppingCart,
Square,
Star,
Sun,
Table,
Tags,
Trash2,
TriangleAlert,
Trophy,
Upload,
User,
UserMinus,
UserPlus,
Users,
UsersRound,
WandSparkles,
Wrench,
X,
} from '@lucide/vue'
@@ -52,51 +111,193 @@ export const icons: Readonly<Record<string, Component>> = {
'arrow-right': ArrowRight,
'arrow-up': ArrowUp,
'arrow-up-right': ArrowUpRight,
bell: Bell,
book: Book,
bookmark: Bookmark,
box: Box,
briefcase: Briefcase,
building: Building,
calendar: Calendar,
camera: Camera,
'cassette-tape': CassetteTape,
'chart-bar': ChartBar,
check: Check,
'chevron-down': ChevronDown,
'chevron-left': ChevronLeft,
'chevron-right': ChevronRight,
circle: Circle,
'circle-alert': CircleAlert,
'circle-check': CircleCheck,
'circle-question-mark': CircleQuestionMark,
'clipboard-list': ClipboardList,
clock: Clock,
cloud: Cloud,
code: Code,
copy: Copy,
'credit-card': CreditCard,
database: Database,
disc: Disc,
'disc-3': Disc3,
download: Download,
'external-link': ExternalLink,
file: File,
'file-text': FileText,
files: Files,
flag: Flag,
folder: Folder,
funnel: Funnel,
gauge: Gauge,
globe: Globe,
'graduation-cap': GraduationCap,
heart: Heart,
house: House,
image: Image,
inbox: Inbox,
info: Info,
key: Key,
languages: Languages,
'layout-grid': LayoutGrid,
leaf: Leaf,
library: Library,
'life-buoy': LifeBuoy,
link: Link,
list: List,
'list-ordered': ListOrdered,
lock: Lock,
'log-out': LogOut,
mail: Mail,
'map-pin': MapPin,
'message-circle': MessageCircle,
'mic-vocal': MicVocal,
minus: Minus,
network: Network,
palette: Palette,
'panel-left-close': PanelLeftClose,
'panel-left-open': PanelLeftOpen,
paperclip: Paperclip,
pause: Pause,
pencil: Pencil,
phone: Phone,
play: Play,
plus: Plus,
puzzle: Puzzle,
'refresh-cw': RefreshCw,
search: Search,
'search-x': SearchX,
settings: Settings,
'shield-check': ShieldCheck,
'shopping-cart': ShoppingCart,
star: Star,
sun: Sun,
table: Table,
tags: Tags,
'trash-2': Trash2,
'triangle-alert': TriangleAlert,
trophy: Trophy,
upload: Upload,
user: User,
'user-minus': UserMinus,
'user-plus': UserPlus,
users: Users,
'users-round': UsersRound,
'wand-sparkles': WandSparkles,
wrench: Wrench,
x: X,
}
/** Winter backend icon classes mapped to lucide names. */
export const winterIcons: Readonly<Record<string, string>> = {
'icon-archive': 'archive',
'icon-bar-chart': 'chart-bar',
'icon-bell': 'bell',
'icon-book': 'book',
'icon-bookmark': 'bookmark',
'icon-briefcase': 'briefcase',
'icon-building': 'building',
'icon-calendar': 'calendar',
'icon-camera': 'camera',
'icon-check': 'check',
'icon-check-circle': 'circle-check',
'icon-circle': 'circle',
'icon-list-ul': 'list',
'icon-tags': 'tags',
'icon-user': 'user',
'icon-search': 'search',
'icon-clock': 'clock',
'icon-clock-o': 'clock',
'icon-cloud': 'cloud',
'icon-code': 'code',
'icon-cog': 'settings',
'icon-cogs': 'settings',
'icon-comment': 'message-circle',
'icon-comments': 'message-circle',
'icon-comments-o': 'message-circle',
'icon-copy': 'copy',
'icon-credit-card': 'credit-card',
'icon-cube': 'box',
'icon-dashboard': 'gauge',
'icon-database': 'database',
'icon-download': 'download',
'icon-edit': 'pencil',
'icon-envelope': 'mail',
'icon-envelope-o': 'mail',
'icon-exclamation-triangle': 'triangle-alert',
'icon-external-link': 'external-link',
'icon-file': 'file',
'icon-file-o': 'file',
'icon-file-text': 'file-text',
'icon-file-text-o': 'file-text',
'icon-files-o': 'files',
'icon-filter': 'funnel',
'icon-flag': 'flag',
'icon-folder': 'folder',
'icon-folder-o': 'folder',
'icon-globe': 'globe',
'icon-graduation-cap': 'graduation-cap',
'icon-group': 'users',
'icon-heart': 'heart',
'icon-home': 'house',
'icon-inbox': 'inbox',
'icon-info-circle': 'info',
'icon-key': 'key',
'icon-language': 'languages',
'icon-leaf': 'leaf',
'icon-life-ring': 'life-buoy',
'icon-link': 'link',
'icon-list': 'list',
'icon-list-alt': 'clipboard-list',
'icon-list-ol': 'list-ordered',
'icon-list-ul': 'list',
'icon-lock': 'lock',
'icon-magic': 'wand-sparkles',
'icon-map-marker': 'map-pin',
'icon-minus': 'minus',
'icon-paperclip': 'paperclip',
'icon-pause': 'pause',
'icon-pencil': 'pencil',
'icon-phone': 'phone',
'icon-photo': 'image',
'icon-picture-o': 'image',
'icon-play': 'play',
'icon-plus': 'plus',
'icon-question': 'circle-question-mark',
'icon-question-circle': 'circle-question-mark',
'icon-refresh': 'refresh-cw',
'icon-search': 'search',
'icon-shopping-cart': 'shopping-cart',
'icon-sign-out': 'log-out',
'icon-sitemap': 'network',
'icon-star': 'star',
'icon-table': 'table',
'icon-tachometer': 'gauge',
'icon-tags': 'tags',
'icon-th': 'layout-grid',
'icon-th-large': 'layout-grid',
'icon-times': 'x',
'icon-trash': 'trash-2',
'icon-trash-o': 'trash-2',
'icon-trophy': 'trophy',
'icon-upload': 'upload',
'icon-user': 'user',
'icon-users': 'users',
'icon-warning': 'triangle-alert',
'icon-wrench': 'wrench',
}
/** Neutral icon for names neither lucide map knows. */

View File

@@ -1,5 +1,28 @@
import { describe, expect, it } from 'vitest'
import { Archive, Disc3, Settings, Square, Tags } from '@lucide/vue'
import {
Archive,
Book,
Bookmark,
Calendar,
CircleQuestionMark,
Clock,
Copy,
Disc3,
FileText,
Flag,
GraduationCap,
House,
Leaf,
LifeBuoy,
List,
Mail,
Network,
Pencil,
Settings,
Square,
Tags,
WandSparkles,
} from '@lucide/vue'
import { fallbackIcon, iconFor, icons, winterIcons } from '../../src/app/icons'
describe('iconFor', () => {
@@ -7,11 +30,33 @@ describe('iconFor', () => {
expect(iconFor('disc-3')).toBe(Disc3)
expect(iconFor('tags')).toBe(Tags)
expect(iconFor(' tags ')).toBe(Tags)
expect(iconFor('book')).toBe(Book)
expect(iconFor(' graduation-cap ')).toBe(GraduationCap)
})
it('resolves Winter icon aliases', () => {
expect(iconFor('icon-archive')).toBe(Archive)
expect(iconFor('icon-cog')).toBe(Settings)
expect(iconFor('icon-graduation-cap')).toBe(GraduationCap)
expect(iconFor('icon-book')).toBe(Book)
expect(iconFor('icon-bookmark')).toBe(Bookmark)
expect(iconFor('icon-calendar')).toBe(Calendar)
expect(iconFor('icon-clock-o')).toBe(Clock)
expect(iconFor('icon-flag')).toBe(Flag)
expect(iconFor('icon-file-text-o')).toBe(FileText)
expect(iconFor('icon-tags')).toBe(Tags)
expect(iconFor('icon-pencil')).toBe(Pencil)
expect(iconFor('icon-question-circle')).toBe(CircleQuestionMark)
expect(iconFor('icon-list')).toBe(List)
expect(iconFor('icon-home')).toBe(House)
expect(iconFor('icon-magic')).toBe(WandSparkles)
expect(iconFor('icon-life-ring')).toBe(LifeBuoy)
expect(iconFor('icon-sitemap')).toBe(Network)
expect(iconFor('icon-envelope-o')).toBe(Mail)
expect(iconFor('icon-copy')).toBe(Copy)
expect(iconFor('icon-leaf')).toBe(Leaf)
expect(iconFor('icon-cogs')).toBe(Settings)
expect(iconFor('icon-edit')).toBe(Pencil)
})
it('falls back to the neutral square for unknown, empty or missing names', () => {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -6,7 +6,7 @@
<meta name="robots" content="noindex, nofollow" />
<meta name="summer-admin-base" content="__SUMMER_ADMIN_BASE__" />
<title>SummerCMS</title>
<script type="module" crossorigin src="./assets/index-CbohsCp4.js"></script>
<script type="module" crossorigin src="./assets/index-z_LzMU8D.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-CqzF_Nki.css">
</head>
<body>