diff --git a/package-lock.json b/package-lock.json index 1fc9131a33c8c3..20dc65f7ed872c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34395,6 +34395,16 @@ "signal-exit": "^3.0.2" } }, + "node_modules/locutus": { + "version": "2.0.32", + "resolved": "https://registry.npmjs.org/locutus/-/locutus-2.0.32.tgz", + "integrity": "sha512-fr7OCpbE4xeefhHqfh6hM2/l9ZB3XvClHgtgFnQNImrM/nqL950o6FO98vmUH8GysfQRCcyBYtZ4C8GcY52Edw==", + "license": "MIT", + "engines": { + "node": ">= 10", + "yarn": ">= 1" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -54764,7 +54774,8 @@ "@wordpress/icons": "*", "@wordpress/private-apis": "*", "@wordpress/rich-text": "*", - "@wordpress/url": "*" + "@wordpress/url": "*", + "locutus": "2.0.32" }, "engines": { "node": ">=18.12.0", diff --git a/packages/format-library/package.json b/packages/format-library/package.json index ee1dd8efd1fe95..d23987579b6081 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -39,7 +39,8 @@ "@wordpress/icons": "*", "@wordpress/private-apis": "*", "@wordpress/rich-text": "*", - "@wordpress/url": "*" + "@wordpress/url": "*", + "locutus": "2.0.32" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/format-library/src/default-formats.js b/packages/format-library/src/default-formats.js index 3ccfc92cb40c18..0c9bfa4c9adece 100644 --- a/packages/format-library/src/default-formats.js +++ b/packages/format-library/src/default-formats.js @@ -9,6 +9,7 @@ import { link } from './link'; import { strikethrough } from './strikethrough'; import { underline } from './underline'; import { textColor } from './text-color'; +import { time } from './time'; import { subscript } from './subscript'; import { superscript } from './superscript'; import { keyboard } from './keyboard'; @@ -25,6 +26,7 @@ export default [ strikethrough, underline, textColor, + time, subscript, superscript, keyboard, diff --git a/packages/format-library/src/time/index.js b/packages/format-library/src/time/index.js new file mode 100644 index 00000000000000..8bf51d54ccf4a9 --- /dev/null +++ b/packages/format-library/src/time/index.js @@ -0,0 +1,98 @@ +/** + * External dependencies + */ +import { gmdate, strtotime } from 'locutus/php/datetime'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { toggleFormat } from '@wordpress/rich-text'; +import { + RichTextToolbarButton, + RichTextShortcut, +} from '@wordpress/block-editor'; +import { postDate as icon } from '@wordpress/icons'; + +const name = 'core/time'; +const title = __( 'Time' ); + +export const time = { + name, + title, + tagName: 'time', + className: null, + attributes: { + datetime: 'datetime', + }, + edit( { isActive, value, onChange, onFocus } ) { + function onClick() { + const dateDescription = value.text + .slice( value.start, value.end ) + .trim(); + + // Exit early if no selection or format is already active + if ( ! dateDescription || isActive ) { + onChange( + toggleFormat( value, { + type: name, + } ) + ); + return; + } + + // Clean up the date string + const dateCleaned = dateDescription + .replace( 'at ', '' ) // Remove "at" + .replace( 'UTC', 'GMT' ); // Replace "UTC" with "GMT" + + // Parse the date string using strtotime + const timestamp = strtotime( dateCleaned ); + + // If parsing fails, toggle format without enhancement + if ( ! timestamp ) { + onChange( + toggleFormat( value, { + type: name, + } ) + ); + return; + } + + // Format the datetime attributes using gmdate + const datetime = gmdate( 'c', timestamp ); // ISO 8601 format + const datetimeISO = gmdate( 'Ymd\\THi', timestamp ); // Compact ISO format + + // Apply the format with parsed datetime attributes + onChange( + toggleFormat( value, { + type: name, + attributes: { + datetime, + 'data-iso': datetimeISO, + style: 'text-decoration: underline; text-decoration-style: dotted', + }, + } ) + ); + + onFocus(); + } + + return ( + <> + + + + ); + }, +};