Skip to content

Commit c04d443

Browse files
GaryJonesclaude
andcommitted
refactor(editorial-metadata): replace unmaintained timepicker addon with native inputs
Remove the jquery-ui-timepicker-addon library (~1900 lines) which is no longer maintained, replacing it with WordPress core jQuery UI Datepicker combined with HTML5 native time inputs. Key changes: - Date fields now use separate date picker + time input instead of combined widget - Added REST API support via register_post_meta() for Gutenberg compatibility - JavaScript syncs field changes to Gutenberg data store for proper saving - Calendar inline editing uses HTML5 datetime-local input Existing stored data is fully backwards compatible - dates are still stored as Unix timestamps and no migration is required. Closes #660 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 53ef52e commit c04d443

File tree

6 files changed

+320
-1991
lines changed

6 files changed

+320
-1991
lines changed

common/js/ef_date.js

Lines changed: 181 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,192 @@
1-
/* global document, jQuery, ef_week_first_day */
1+
/* global document, jQuery, ef_week_first_day, wp */
22

3-
jQuery( document ).ready( function() {
4-
const dateTimePicks = jQuery( '.date-time-pick' );
3+
jQuery( document ).ready( function( $ ) {
4+
/**
5+
* Check if we're in the Gutenberg editor.
6+
*
7+
* @return {boolean} True if Gutenberg is available.
8+
*/
9+
function isGutenberg() {
10+
return typeof wp !== 'undefined' && wp.data && wp.data.dispatch && wp.data.select;
11+
}
512

6-
dateTimePicks.each( function() {
7-
const $dTP = jQuery( this );
13+
/**
14+
* Check if Gutenberg post entity is ready for meta updates.
15+
*
16+
* @return {boolean} True if ready.
17+
*/
18+
function isGutenbergReady() {
19+
if ( ! isGutenberg() ) {
20+
return false;
21+
}
22+
try {
23+
const postType = wp.data.select( 'core/editor' ).getCurrentPostType();
24+
return !! postType;
25+
} catch ( e ) {
26+
return false;
27+
}
28+
}
829

9-
$dTP.datetimepicker( {
10-
dateFormat: 'M dd yy',
11-
firstDay: ef_week_first_day,
12-
alwaysSetTime: false,
13-
controlType: 'select',
14-
altField: '#' + $dTP.prop( 'id' ) + '_hidden',
15-
altFieldTimeOnly: false,
16-
altFormat: 'yy-mm-dd',
17-
altTimeFormat: 'HH:mm',
18-
} );
19-
} );
30+
/**
31+
* Update Gutenberg's data store with the meta value.
32+
* This ensures that when the user saves, the REST API includes our updated meta.
33+
*
34+
* @param {string} metaKey The post meta key.
35+
* @param {*} metaValue The value to save (will be converted to string).
36+
*/
37+
function updateGutenbergMeta( metaKey, metaValue ) {
38+
if ( ! isGutenbergReady() ) {
39+
return;
40+
}
41+
42+
const meta = {};
43+
// Convert to string since REST API expects string type.
44+
meta[ metaKey ] = String( metaValue );
45+
46+
wp.data.dispatch( 'core/editor' ).editPost( { meta: meta } );
47+
}
48+
49+
/**
50+
* Update the hidden field with combined date and time values.
51+
* The hidden field stores the value in 'Y-m-d H:i' format for PHP processing.
52+
* Also updates Gutenberg's data store with the Unix timestamp.
53+
*
54+
* @param {jQuery} $dateInput The date input element.
55+
* @param {boolean} updateGutenberg Whether to update Gutenberg store (default true).
56+
*/
57+
function updateHiddenField( $dateInput, updateGutenberg ) {
58+
if ( typeof updateGutenberg === 'undefined' ) {
59+
updateGutenberg = true;
60+
}
61+
62+
// Derive related element IDs from the date input ID.
63+
// Date input: {key}_date, Time input: {key}_time, Hidden: {key}_hidden
64+
const baseId = $dateInput.attr( 'id' ).replace( /_date$/, '' );
65+
const $timeInput = $( '#' + baseId + '_time' );
66+
const $hiddenInput = $( '#' + baseId + '_hidden' );
67+
68+
if ( ! $hiddenInput.length ) {
69+
return;
70+
}
2071

21-
const datePicks = jQuery( '.date-pick' );
22-
datePicks.each( function() {
23-
const $datePicker = jQuery( this );
72+
// Get the date value from the datepicker's altField mechanism or parse it.
73+
let dateValue = $dateInput.datepicker( 'getDate' );
74+
if ( ! dateValue ) {
75+
$hiddenInput.val( '' );
76+
if ( updateGutenberg ) {
77+
updateGutenbergMeta( baseId, '' );
78+
}
79+
return;
80+
}
81+
82+
// Format date as Y-m-d.
83+
const year = dateValue.getFullYear();
84+
const month = String( dateValue.getMonth() + 1 ).padStart( 2, '0' );
85+
const day = String( dateValue.getDate() ).padStart( 2, '0' );
86+
const formattedDate = year + '-' + month + '-' + day;
87+
88+
// Get time value (HH:mm format from HTML5 time input).
89+
let timeValue = $timeInput.val() || '00:00';
90+
91+
// Combine into 'Y-m-d H:i' format for the hidden field.
92+
$hiddenInput.val( formattedDate + ' ' + timeValue );
93+
94+
if ( updateGutenberg ) {
95+
// Calculate Unix timestamp for Gutenberg.
96+
const timeParts = timeValue.split( ':' );
97+
const hours = parseInt( timeParts[ 0 ], 10 ) || 0;
98+
const minutes = parseInt( timeParts[ 1 ], 10 ) || 0;
99+
100+
// Create a new Date object with the combined date and time.
101+
const combinedDate = new Date( year, dateValue.getMonth(), dateValue.getDate(), hours, minutes, 0 );
102+
const timestamp = Math.floor( combinedDate.getTime() / 1000 );
103+
104+
// Update Gutenberg's data store with the Unix timestamp (as string).
105+
updateGutenbergMeta( baseId, timestamp );
106+
}
107+
}
108+
109+
// Initialize jQuery UI datepicker on .date-pick elements.
110+
const $datePicks = $( '.date-pick' );
111+
112+
$datePicks.each( function() {
113+
const $datePicker = $( this );
24114

25115
$datePicker.datepicker( {
116+
dateFormat: 'M dd yy',
26117
firstDay: ef_week_first_day,
27-
altField: '#' + $datePicker.prop( 'id' ) + '_hidden',
28-
altFormat: 'yy-mm-dd',
29118
showButtonPanel: true,
119+
onSelect: function() {
120+
updateHiddenField( $datePicker, true );
121+
},
30122
} );
123+
124+
// Update hidden field when date input changes (e.g., manual input).
125+
$datePicker.on( 'change', function() {
126+
updateHiddenField( $datePicker, true );
127+
} );
128+
} );
129+
130+
// Update hidden field when time input changes.
131+
$( '.time-pick' ).on( 'change', function() {
132+
const $timeInput = $( this );
133+
// Derive the date input from the time input ID.
134+
const baseId = $timeInput.attr( 'id' ).replace( /_time$/, '' );
135+
const $dateInput = $( '#' + baseId + '_date' );
136+
137+
if ( $dateInput.length ) {
138+
updateHiddenField( $dateInput, true );
139+
}
31140
} );
141+
142+
// Initialize hidden fields with current values on page load.
143+
// Do NOT update Gutenberg here - just set up the hidden field for form submission.
144+
$datePicks.each( function() {
145+
const $datePicker = $( this );
146+
if ( $datePicker.val() ) {
147+
updateHiddenField( $datePicker, false );
148+
}
149+
} );
150+
151+
/**
152+
* Sync all Editorial Metadata fields to Gutenberg.
153+
* This handles text, paragraph, checkbox, user, number, and location fields.
154+
*/
155+
function setupMetaboxSync() {
156+
const $metaBox = $( '#ef_editorial_meta_meta_box' );
157+
158+
if ( ! $metaBox.length || ! isGutenberg() ) {
159+
return;
160+
}
161+
162+
// Text, paragraph, number, and location inputs.
163+
$metaBox.find( 'input[type="text"]:not(.date-pick), textarea, select' ).on( 'change input', function() {
164+
const $input = $( this );
165+
const name = $input.attr( 'name' );
166+
167+
// Skip hidden fields and fields without names.
168+
if ( ! name || name.endsWith( '_hidden' ) ) {
169+
return;
170+
}
171+
172+
updateGutenbergMeta( name, $input.val() );
173+
} );
174+
175+
// Checkbox inputs.
176+
$metaBox.find( 'input[type="checkbox"]' ).on( 'change', function() {
177+
const $input = $( this );
178+
const name = $input.attr( 'name' );
179+
180+
if ( ! name ) {
181+
return;
182+
}
183+
184+
// Store as '1' or '' to match PHP behavior.
185+
const value = $input.is( ':checked' ) ? '1' : '';
186+
updateGutenbergMeta( name, value );
187+
} );
188+
}
189+
190+
// Set up metabox sync for Gutenberg.
191+
setupMetaboxSync();
32192
} );

0 commit comments

Comments
 (0)