This utility allows developers to browse and search for icons provided by the be_style addon (including FontAwesome, Glyphicons, and .rex-icon). It parses style.css to generate a visual gallery where users can filter by icon group, search by name, and double-click an icon name to copy its HTML tag to the clipboard.
Important Caveat: Since the transition to FontAwesome 6, this method has very limited functionality because icon information is now distributed across multiple files. A revised version is not currently planned.
// Save this script as «domain»/redaxo/src/addons/project/pages/icons.php
// (Create the 'pages' folder if it does not exist)
$types = ['FontAwesome'=>'fa','Glyphicons'=>'glyphicon','REX'=>'rex-icon'];
$filename = $this->getCachePath('icons.html');
$stylename = rex_path::addonAssets('be_style','css/styles.css');
if( !is_readable($stylename) ) return;
$html = false;
if( is_readable($filename) && filemtime($filename) < filemtime($stylename) ){
$html = @file_get_contents($filename);
}
$html = false;
if( !$html ) {
// Extract icons from be_style CSS
$css = file_get_contents( $stylename );
$typeset = implode('|',$types);
$p = preg_match_all(
'/((\.('.$typeset.')\-[a-z0-9_-]+)((\:before,\.('.$typeset.')\-[a-z0-9_-]+))*)\:before{content:"(?<marker>(.*?))"}/m',
$css,
$match,
);
$icons = [];
foreach( $match['marker'] as $k=>$code ) {
$code = sprintf("\\%04X",mb_ord($code));
$unicode = $result[$code] ?? [];
preg_match_all( '/(?<class>\.(?<type>('.$typeset.'))-(?<name>[a-z0-9_-]+))/',$match[1][$k],$data );
foreach( $data['class'] as $i => $class ){
$type = $data['type'][$i];
$name = $data['name'][$i];
$unicode['class'][$class] = $class;
$unicode['name'][$name] = $name;
$unicode['type'][$type] = 'x-'.$type;
if( !isset($unicode['html']) ) $unicode['html'] = "<i class=\"$type $type-$name\"></i>";
}
$icons[$code] = $unicode;
}
unset( $match );
ksort($icons);
// Build UI using rex_fragment
$umfang = new rex_fragment();
$elements = [];
foreach( $types as $k=>$v ) {
$elements[] = [
'label' => '<label class="control-label">' . $k . '</label>',
'field' => '<input onchange="f_select(this);" type="checkbox" id="scope-' . $v . '" value="x-' . $v . '" checked />',
];
}
$umfang->setVar('elements', $elements, false);
$umfang->setVar('grouped', true);
$umfang->setVar('inline', true);
$search = new rex_fragment();
$elements = [
'field' => '<input oninput="f_search(this.value)" type="text" class="form-control" placeholder="Search for..." id="search">',
'right' => '<button onclick="f_reset()"class="btn btn-default" type="button"><i class="fa fa-close"></i></button>',
];
$search->setVar('elements', [$elements], false);
$row1 = new rex_fragment();
$row1->setVar('content', [$umfang->parse('core/form/checkbox.php'),$search->parse('core/form/input_group.php')], false);
$row2 = new rex_fragment();
$row2->setVar('content', array_map(function($v){return "<i class=\"$v $v-«name»\"></i>";},$types), false);
$section = new rex_fragment();
$content = '<div ondblclick="f_copy(event)" class="'.implode(' ',array_map(function($v){return "x-$v";},$types)).'" id="iconlist">';
foreach( $icons as $k=>$v ) {
$type = implode(' ',$v['type']);
$name = implode(',',$v['name']);
$class = implode('</span><br><span>',$v['class']);
$content .= "<div class=\"panel panel-default $type\" data-search=\"$name\"><div class=\"panel-heading\"><small>$k</small>{$v['html']}</div><div class=\"panel-body\"><span>$class</span></div></div>";
}
$content .= '</div>';
$section->setVar('heading', $row1->parse('core/page/grid.php').$row2->parse('core/page/grid.php'), false);
$section->setVar('body',$content,false);
$html = $section->parse('core/page/section.php');
rex_dir::create($this->getCachePath());
file_put_contents($filename, $html);
}
echo $html;
?>
<style>
#iconlist { display: flex; flex-wrap: wrap; justify-content: center;}
#iconlist .panel { display: none; margin: 1em; width: 14em; font-size: 1em; }
#iconlist .panel-heading { font-size: 2rem; text-align: center; position: relative; }
#iconlist .panel-body { padding: 0.1rem 0.2rem; }
#iconlist small { padding: 0.1rem; position: absolute; top:0; left:0; font-size: 1rem; }
<?= implode(',',array_map(function($v){return "#iconlist.x-$v .panel.x-$v";},$types)) ?> { display: block; }
</style>
<script>
var iconlist = document.getElementById('iconlist');
var search = document.getElementById('search');
var typeList = ['<?= implode('\',\',$types) ?>'];
function f_select( node ) { iconlist.classList.toggle(node.value,node.checked); }
function f_reset() { search.value = ''; f_search( search.value ); }
function f_search( pattern ) {
if( pattern ) {
for( var node of iconlist.children ) {
var hidden = !node.dataset.search.includes(pattern.toLowerCase());
if( node.classList.contains('hidden') != hidden ) node.classList.toggle('hidden',hidden);
}
return;
}
for( var node of iconlist.children ) { if( node.classList.contains('hidden') ) node.classList.remove('hidden'); }
}
function f_copy( event ) {
var target = event.target;
if( target.tagName.toLowerCase() != 'span' ) return;
for( var type of typeList ) {
if( target.innerText.startsWith('.'+type) ) {
var node = document.createElement('textarea');
node.value = '<i class="'+type+' '+target.innerText+'"></i>';
node.style = {position: 'absolute', left: '-9999px'};
document.body.appendChild(node);
node.select();
document.execCommand('copy');
alert('HTML for "'+target.innerText+'" copied to clipboard');
document.body.removeChild(node);
event.preventDefault();
return;
}
}
}
</script>