@@ -366,6 +366,68 @@ $(document).on('click', '[data-yform-be-relation-moveup], [data-yform-be-relatio
366366 } , 100 ) ;
367367} ) ;
368368
369+ /**
370+ * Groups style formats by their profile assignments and styleset.
371+ * Adds separators with profile+styleset labels between different groups.
372+ *
373+ * @param {Array } formatsWithProfiles - Array with {format: {...}, profiles: [...], styleset: "..."}
374+ * @returns {Array } - Grouped formats with separators
375+ */
376+ function groupFormatsByProfiles ( formatsWithProfiles ) {
377+ if ( ! Array . isArray ( formatsWithProfiles ) || formatsWithProfiles . length === 0 ) {
378+ return [ ] ;
379+ }
380+
381+ // Group by profile string + styleset (empty profiles = all profiles)
382+ let groupedByKey = { } ;
383+
384+ formatsWithProfiles . forEach ( function ( item ) {
385+ if ( ! item . format ) return ;
386+
387+ // Convert profiles array to a key (sort for consistent grouping)
388+ let profileKey = '' ;
389+ if ( item . profiles && item . profiles . length > 0 ) {
390+ profileKey = item . profiles . sort ( ) . join ( ', ' ) ;
391+ }
392+
393+ let styleset = item . styleset || 'Default' ;
394+ let groupKey = styleset + '|' + profileKey ;
395+
396+ if ( ! groupedByKey [ groupKey ] ) {
397+ groupedByKey [ groupKey ] = {
398+ profileKey : profileKey ,
399+ styleset : styleset ,
400+ formats : [ ]
401+ } ;
402+ }
403+ groupedByKey [ groupKey ] . formats . push ( item . format ) ;
404+ } ) ;
405+
406+ // Convert to ordered array with separators
407+ let result = [ ] ;
408+ let groupKeys = Object . keys ( groupedByKey ) . sort ( ) ;
409+
410+ groupKeys . forEach ( function ( key , index ) {
411+ let group = groupedByKey [ key ] ;
412+
413+ // Add separator with profile label (except for first group)
414+ if ( index > 0 ) {
415+ let profileLabel = group . profileKey === '' ? 'All Profiles' : group . profileKey ;
416+ let label = group . styleset + ' (' + profileLabel + ')' ;
417+ result . push ( {
418+ title : '─ ' + label + ' ─' ,
419+ isDisabled : true ,
420+ onSelect : function ( ) { return false ; }
421+ } ) ;
422+ }
423+
424+ // Add formats from this group
425+ result = result . concat ( group . formats ) ;
426+ } ) ;
427+
428+ return result ;
429+ }
430+
369431function tiny_init ( container ) {
370432 let profiles = { } ;
371433
@@ -466,14 +528,79 @@ function tiny_init(container) {
466528 // This ensures style_formats are available when TinyMCE registers the 'styles' button
467529 if ( typeof rex !== 'undefined' && rex . tinyGlobalOptions ) {
468530 let globalOpts = rex . tinyGlobalOptions ;
531+ let profileNamesById = ( typeof rex . tinyProfileNamesById === 'object' && rex . tinyProfileNamesById ) ? rex . tinyProfileNamesById : { } ;
532+
533+ function normalizeProfileName ( value ) {
534+ if ( typeof value !== 'string' ) {
535+ return '' ;
536+ }
537+ return value . trim ( ) . toLowerCase ( ) ;
538+ }
539+
540+ function getNormalizedProfileCandidates ( profileValue ) {
541+ let candidates = [ ] ;
542+ let profileRaw = ( profileValue === undefined || profileValue === null ) ? '' : String ( profileValue ) . trim ( ) ;
543+ let normalizedProfile = normalizeProfileName ( profileRaw ) ;
544+ if ( normalizedProfile !== '' ) {
545+ candidates . push ( normalizedProfile ) ;
546+ }
547+
548+ if ( profileRaw !== '' && Object . prototype . hasOwnProperty . call ( profileNamesById , profileRaw ) ) {
549+ let mappedName = normalizeProfileName ( String ( profileNamesById [ profileRaw ] ) ) ;
550+ if ( mappedName !== '' && candidates . indexOf ( mappedName ) === - 1 ) {
551+ candidates . push ( mappedName ) ;
552+ }
553+ }
554+
555+ return candidates ;
556+ }
557+
558+ let profileCandidates = getNormalizedProfileCandidates ( profile ) ;
559+
560+ function normalizeStylesetsInToolbar ( toolbarValue ) {
561+ if ( typeof toolbarValue === 'string' ) {
562+ return toolbarValue . replace ( / \b s t y l e s \b / g, 'stylesets' ) ;
563+ }
564+
565+ if ( Array . isArray ( toolbarValue ) ) {
566+ return toolbarValue . map ( function ( row ) {
567+ if ( typeof row !== 'string' ) {
568+ return row ;
569+ }
570+ return row . replace ( / \b s t y l e s \b / g, 'stylesets' ) ;
571+ } ) ;
572+ }
573+
574+ return toolbarValue ;
575+ }
469576
470577 // Helper function to check if a Style-Set applies to this profile
471578 // Empty profiles array means it applies to ALL profiles
472579 function appliesToProfile ( profilesList ) {
473580 if ( ! profilesList || profilesList . length === 0 ) {
474581 return true ; // Empty = applies to all profiles
475582 }
476- return profilesList . indexOf ( profile ) !== - 1 ;
583+
584+ for ( let i = 0 ; i < profilesList . length ; i ++ ) {
585+ let profileEntry = String ( profilesList [ i ] || '' ) . trim ( ) ;
586+ if ( profileEntry === '' ) {
587+ continue ;
588+ }
589+
590+ let normalizedEntry = normalizeProfileName ( profileEntry ) ;
591+ if ( profileCandidates . indexOf ( normalizedEntry ) !== - 1 ) {
592+ return true ;
593+ }
594+
595+ if ( Object . prototype . hasOwnProperty . call ( profileNamesById , profileEntry ) ) {
596+ let mappedEntry = normalizeProfileName ( String ( profileNamesById [ profileEntry ] ) ) ;
597+ if ( profileCandidates . indexOf ( mappedEntry ) !== - 1 ) {
598+ return true ;
599+ }
600+ }
601+ }
602+
603+ return false ;
477604 }
478605
479606 // Merge content_css (array) - filter by profile
@@ -514,39 +641,40 @@ function tiny_init(container) {
514641 }
515642 }
516643
517- // Merge style_formats - filter by profile
644+ // Merge style_formats - filter by profile, group by profile with separators
518645 // New format: [{format: {...}, profiles: ["uikit"] }]
519- // Legacy format: [ {title: "...", items: [...]}]
646+ // Format can be a group: {title: "...", items: [...]}
520647 if ( globalOpts . style_formats && globalOpts . style_formats . length > 0 ) {
521- let filteredFormats = [ ] ;
648+ let formatsWithProfiles = [ ] ;
649+ let legacyFormats = [ ] ;
650+
522651 globalOpts . style_formats . forEach ( function ( item ) {
523652 if ( item . format && appliesToProfile ( item . profiles ) ) {
524- // New format with profile filter
525- filteredFormats . push ( item . format ) ;
653+ // New format with profile info - keep for grouping
654+ formatsWithProfiles . push ( item ) ;
526655 } else if ( item . title ) {
527656 // Legacy format (has title = is a format group) - always include
528- filteredFormats . push ( item ) ;
657+ legacyFormats . push ( item ) ;
529658 }
530659 } ) ;
531660
532- if ( filteredFormats . length > 0 ) {
661+ if ( formatsWithProfiles . length > 0 || legacyFormats . length > 0 ) {
533662 // Enable merging with default formats (Headings, Inline, Blocks, Align)
534663 options . style_formats_merge = true ;
535664
536665 if ( ! options . style_formats ) {
537666 options . style_formats = [ ] ;
538667 }
539- // Append filtered style formats to existing ones
540- options . style_formats = options . style_formats . concat ( filteredFormats ) ;
541668
542- // Replace 'styles' with 'stylesets' in toolbar (our custom button)
543- if ( options . toolbar && typeof options . toolbar === 'string' ) {
544- // Replace existing 'styles' with 'stylesets'
545- options . toolbar = options . toolbar . replace ( / \b s t y l e s \b / g, 'stylesets' ) ;
546- // If neither exists, add stylesets at the beginning
547- if ( options . toolbar . indexOf ( 'stylesets' ) === - 1 ) {
548- options . toolbar = 'stylesets ' + options . toolbar ;
549- }
669+ // Group new formats by profile and add separators
670+ let groupedFormats = groupFormatsByProfiles ( formatsWithProfiles ) ;
671+
672+ // Append grouped formats first, then legacy formats
673+ options . style_formats = options . style_formats . concat ( groupedFormats ) . concat ( legacyFormats ) ;
674+
675+ // Keep user-defined toolbar order; only normalize legacy 'styles' -> 'stylesets'.
676+ if ( options . toolbar ) {
677+ options . toolbar = normalizeStylesetsInToolbar ( options . toolbar ) ;
550678 }
551679
552680 // Add stylesets to Format menu
@@ -558,6 +686,15 @@ function tiny_init(container) {
558686 items : 'bold italic underline strikethrough superscript subscript codeformat | stylesets blocks fontfamily fontsize align lineheight | forecolor backcolor | removeformat'
559687 } ;
560688
689+ // Add stylesets to context menu if available
690+ if ( ! options . contextmenu ) {
691+ options . contextmenu = 'link image table' ;
692+ }
693+ // Add stylesets to existing contextmenu if not already there
694+ if ( typeof options . contextmenu === 'string' && options . contextmenu . indexOf ( 'stylesets' ) === - 1 ) {
695+ options . contextmenu = options . contextmenu + ' | stylesets' ;
696+ }
697+
561698 }
562699 }
563700 }
0 commit comments