Skip to content

Commit 9246dd3

Browse files
committed
feat: use css text-transform instead of coverting text.
1 parent e6b8835 commit 9246dd3

File tree

3 files changed

+143
-102
lines changed

3 files changed

+143
-102
lines changed

blablablocks-formats.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,12 @@ function blablablocks_formats_enqueue_assets()
138138
wp_enqueue_style('blablablocks-formats-styles');
139139
}
140140

141-
$needs_marker = blablablocks_has_format('has-marker-format');
142-
$needs_infotip = blablablocks_has_format('has-infotip-format');
141+
$needs_marker = blablablocks_has_format('has-marker-format');
142+
$needs_infotip = blablablocks_has_format('has-infotip-format');
143+
$needs_changecase = blablablocks_has_format('has-change-case-format');
143144

144-
// If neither format is present, do nothing.
145-
if (! $needs_marker && ! $needs_infotip) {
145+
// If no format is present, do nothing.
146+
if (! $needs_marker && ! $needs_infotip && ! $needs_changecase) {
146147
return;
147148
}
148149

src/change-case/index.js

Lines changed: 126 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
/**
22
* WordPress dependencies
33
*/
4+
import { __ } from '@wordpress/i18n';
45
import { useState } from '@wordpress/element';
5-
import { Icon } from '@wordpress/icons';
6-
import { Popover, MenuItem } from '@wordpress/components';
76
import {
8-
create,
9-
insert,
10-
isCollapsed,
11-
useAnchorRef,
12-
toggleFormat,
13-
slice,
14-
getTextContent,
7+
reset,
8+
formatCapitalize,
9+
formatLowercase,
10+
formatUppercase,
11+
} from '@wordpress/icons';
12+
import {
13+
Popover,
14+
__experimentalToggleGroupControl as ToggleGroupControl,
15+
__experimentalToggleGroupControlOptionIcon as ToggleGroupControlOptionIcon,
16+
} from '@wordpress/components';
17+
import {
18+
useAnchor,
19+
applyFormat,
20+
removeFormat,
1521
registerFormatType,
1622
} from '@wordpress/rich-text';
1723
import { RichTextToolbarButton } from '@wordpress/block-editor';
@@ -20,137 +26,161 @@ import { RichTextToolbarButton } from '@wordpress/block-editor';
2026
* Internal dependencies
2127
*/
2228
import './editor.scss';
29+
import './style.scss';
2330

2431
const name = 'lubus/change-case';
2532
const title = 'Change Case';
2633

2734
/**
28-
* Icon
35+
* Main icon for toolbar button
2936
*/
30-
function formatIcon() {
31-
return (
32-
<Icon
33-
icon={
34-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
35-
<defs />
36-
<path fill="none" d="M0 0h24v24H0V0z" />
37-
<path d="M9 4v3h5v12h3V7h5V4H9zm-6 8h3v7h3v-7h3V9H3v3z" />
38-
</svg>
39-
}
40-
/>
41-
);
42-
}
37+
const ChangeCaseIcon = () => (
38+
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
39+
<path fill="none" d="M0 0h24v24H0V0z" />
40+
<path d="M9 4v3h5v12h3V7h5V4H9zm-6 8h3v7h3v-7h3V9H3v3z" fill="currentColor" />
41+
</svg>
42+
);
43+
44+
/**
45+
* Text transform options matching WordPress core
46+
*/
47+
const TEXT_TRANSFORMS = [
48+
{
49+
label: __( 'None' ),
50+
value: 'none',
51+
icon: reset,
52+
},
53+
{
54+
label: __( 'Uppercase' ),
55+
value: 'uppercase',
56+
icon: formatUppercase,
57+
},
58+
{
59+
label: __( 'Lowercase' ),
60+
value: 'lowercase',
61+
icon: formatLowercase,
62+
},
63+
{
64+
label: __( 'Capitalize' ),
65+
value: 'capitalize',
66+
icon: formatCapitalize,
67+
},
68+
];
4369

4470
/**
4571
* Format edit
4672
*/
4773
function EditButton( props ) {
48-
const { value, onChange, onFocus, isActive, contentRef } = props;
74+
const { value, onChange, onFocus, isActive, contentRef, activeAttributes } = props;
4975

50-
const [ isChangingCase, setIsChangingCase ] = useState( false );
76+
const [ isPopoverOpen, setIsPopoverOpen ] = useState( false );
5177

52-
const anchorRef = useAnchorRef( {
53-
ref: contentRef,
54-
value,
55-
settings: {
56-
title,
57-
},
78+
const anchor = useAnchor( {
79+
editableContentElement: contentRef.current,
80+
settings: { isActive },
5881
} );
5982

60-
function toTitleCase( str ) {
61-
const text = str
62-
.match(
63-
/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g
64-
)
65-
.map( ( x ) => x.charAt( 0 ).toUpperCase() + x.slice( 1 ) )
66-
.join( ' ' );
67-
68-
return text;
83+
// Get the currently active case type from the class attribute
84+
const activeClass = activeAttributes?.class || '';
85+
let activeCaseType = activeClass.replace( 'has-', '' ).replace( '-case', '' );
86+
87+
// Map our class names to WordPress core values
88+
if ( activeCaseType === 'titlecase' ) {
89+
activeCaseType = 'capitalize';
90+
}
91+
if ( !activeCaseType ) {
92+
activeCaseType = 'none';
6993
}
7094

71-
function onToggle( type ) {
72-
let text = getTextContent( slice( value ) );
73-
74-
switch ( type ) {
75-
case 'upper':
76-
text = text.toUpperCase();
77-
break;
78-
79-
case 'lower':
80-
text = text.toLowerCase();
81-
break;
95+
// Check if selection contains multiple words (for capitalize validation)
96+
const selectedText = value.text.slice( value.start, value.end );
97+
const hasMultipleWords = selectedText.trim().split( /\s+/ ).length > 1;
8298

83-
case 'title':
84-
text = toTitleCase( text );
85-
break;
99+
// Filter options based on selection
100+
const availableOptions = TEXT_TRANSFORMS.filter( ( option ) => {
101+
// Show capitalize only for multi-word selections
102+
if ( option.value === 'capitalize' && ! hasMultipleWords ) {
103+
return false;
86104
}
105+
return true;
106+
} );
87107

88-
const toInsert = toggleFormat(
89-
create( { text } ),
90-
{
91-
type: name,
92-
},
93-
0,
94-
text.length
95-
);
108+
function handleChange( newValue ) {
109+
// Remove any existing case format first
110+
let newFormattedValue = removeFormat( value, name );
96111

97-
onChange( insert( value, toInsert ) );
98-
}
112+
// If newValue is not 'none', apply the format
113+
if ( newValue && newValue !== 'none' ) {
114+
// Map WordPress core values to our class names
115+
let caseType = newValue;
116+
if ( newValue === 'capitalize' ) {
117+
caseType = 'titlecase';
118+
}
99119

100-
function onClick( type ) {
101-
if ( isCollapsed( value ) ) {
102-
return;
120+
const className = `has-${ caseType }-case`;
121+
newFormattedValue = applyFormat( newFormattedValue, {
122+
type: name,
123+
attributes: {
124+
class: className,
125+
},
126+
} );
103127
}
104128

105-
onToggle( type );
129+
onChange( newFormattedValue );
106130
onFocus();
107-
setIsChangingCase( false );
108-
}
109-
110-
function openOptions() {
111-
setIsChangingCase( true );
112-
}
113-
114-
function closeOptions() {
115-
setIsChangingCase( false );
131+
setIsPopoverOpen( false );
116132
}
117133

118134
return (
119135
<>
120136
<RichTextToolbarButton
121-
icon={ formatIcon }
137+
icon={ ChangeCaseIcon }
122138
title={ title }
123-
onClick={ openOptions }
139+
onClick={ () => setIsPopoverOpen( true ) }
124140
isActive={ isActive }
125141
/>
126-
{ isChangingCase && (
142+
{ isPopoverOpen && (
127143
<Popover
128-
position="bottom center"
129-
anchorRef={ anchorRef }
130-
onClose={ closeOptions }
131-
className="block-editor-format-toolbar__lubus-changecase-popover"
144+
anchor={ anchor }
145+
onClose={ () => setIsPopoverOpen( false ) }
146+
placement="bottom"
147+
shift
148+
offset={ 10 }
132149
>
133-
<MenuItem onClick={ () => onClick( 'upper' ) }>
134-
UPPER CASE
135-
</MenuItem>
136-
<MenuItem onClick={ () => onClick( 'lower' ) }>
137-
lower case
138-
</MenuItem>
139-
<MenuItem onClick={ () => onClick( 'title' ) }>
140-
Title Case
141-
</MenuItem>
150+
<div style={ { padding: '8px' } }>
151+
<ToggleGroupControl
152+
__next40pxDefaultSize
153+
__nextHasNoMarginBottom
154+
isBlock
155+
label={ __( 'Letter case' ) }
156+
value={ activeCaseType }
157+
onChange={ handleChange }
158+
>
159+
{ availableOptions.map( ( option ) => (
160+
<ToggleGroupControlOptionIcon
161+
key={ option.value }
162+
value={ option.value }
163+
icon={ option.icon }
164+
label={ option.label }
165+
/>
166+
) ) }
167+
</ToggleGroupControl>
168+
</div>
142169
</Popover>
143170
) }
144171
</>
145172
);
146173
}
147174

148175
/**
149-
* Register Richtext Color Format.
176+
* Register Change Case Format.
150177
*/
151178
registerFormatType( name, {
152179
title,
153-
tagName: 'ChangeText',
154-
className: null,
180+
tagName: 'span',
181+
className: 'has-change-case-format',
155182
edit: EditButton,
183+
attributes: {
184+
class: 'class',
185+
},
156186
} );

src/change-case/style.scss

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
/**
22
* The following styles get applied both on the front of your site
33
* and in the editor.
4-
*
5-
* Replace them with your own styles or remove the file completely.
64
*/
5+
6+
.has-uppercase-case {
7+
text-transform: uppercase;
8+
}
9+
10+
.has-lowercase-case {
11+
text-transform: lowercase;
12+
}
13+
14+
.has-titlecase-case {
15+
text-transform: capitalize;
16+
}

0 commit comments

Comments
 (0)