From 2a630228ad40bfbf691c45d9fd6f03cb76f500f0 Mon Sep 17 00:00:00 2001 From: Martin Slachta Date: Fri, 12 Jun 2026 11:25:57 +0200 Subject: [PATCH] (#1) - WebPack bundling of JS and CSS --- .gitignore | 5 +- assets/js/RsvApi.js | 2 +- assets/js/datasource/RsvDataSource.js | 2 +- .../datasource/RsvFormDefinitionResource.js | 4 +- assets/js/datasource/RsvReservationClient.js | 2 +- .../js/datasource/RsvReservationResource.js | 4 +- .../RsvTimetableCapacityResource.js | 4 +- .../RsvTimetableReservationResource.js | 4 +- assets/js/datasource/RsvTimetableResource.js | 4 +- assets/js/elements/RsvCalendar.js | 2 +- assets/js/elements/RsvDatagrid.js | 6 +- assets/js/elements/RsvReservationSelector.js | 2 + assets/js/elements/RsvTimeline.js | 2 + assets/js/forms/RsvAdminForm.js | 5 +- assets/js/forms/RsvFormEncoder.js | 2 +- assets/js/forms/RsvFormSender.js | 2 +- assets/js/forms/RsvInlineFormBuilder.js | 2 +- assets/js/services/RsvTimetableService.js | 4 +- includes/RsvAssetsDefinition.php | 64 ++++--------------- src/admin.js | 22 +++++++ src/client.js | 20 ++++++ src/components/admin.js | 4 +- webpack.config.js | 17 +++++ 23 files changed, 115 insertions(+), 70 deletions(-) create mode 100644 src/admin.js create mode 100644 src/client.js create mode 100644 webpack.config.js diff --git a/.gitignore b/.gitignore index b07ac6c..125ec0e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ node_modules -build +build/ vendor/ -dist +dist/ # Editors .claude/ .idea/ +.zed/ .pytest_cache/ diff --git a/assets/js/RsvApi.js b/assets/js/RsvApi.js index eab7d7f..a78dc7b 100644 --- a/assets/js/RsvApi.js +++ b/assets/js/RsvApi.js @@ -2,6 +2,6 @@ * Utilities for calling the API */ -function get_rest_url(resource) { +export function get_rest_url(resource) { return ReservairServiceAPI.restUrl + '/' + resource; } diff --git a/assets/js/datasource/RsvDataSource.js b/assets/js/datasource/RsvDataSource.js index 72c6da4..599d921 100644 --- a/assets/js/datasource/RsvDataSource.js +++ b/assets/js/datasource/RsvDataSource.js @@ -1,4 +1,4 @@ -const RsvDataSource = { +export const RsvDataSource = { create_rsv_resource(base_url, { nonce } = {}) { function request(url, method, body) { const headers = { 'Content-Type': 'application/json' }; diff --git a/assets/js/datasource/RsvFormDefinitionResource.js b/assets/js/datasource/RsvFormDefinitionResource.js index 577fc00..7bef762 100644 --- a/assets/js/datasource/RsvFormDefinitionResource.js +++ b/assets/js/datasource/RsvFormDefinitionResource.js @@ -1,2 +1,4 @@ -const RsvFormDefinitionResource = () => +import { RsvDataSource } from './RsvDataSource.js'; + +export const RsvFormDefinitionResource = () => RsvDataSource.create_rsv_resource(ReservairServiceAPI.restUrl + '/form-definition'); diff --git a/assets/js/datasource/RsvReservationClient.js b/assets/js/datasource/RsvReservationClient.js index 11de9a0..ccf3dce 100644 --- a/assets/js/datasource/RsvReservationClient.js +++ b/assets/js/datasource/RsvReservationClient.js @@ -1,4 +1,4 @@ -const RsvReservationClient = { +export const RsvReservationClient = { accept(reservation_id) { return this._post(reservation_id, 'accept'); }, diff --git a/assets/js/datasource/RsvReservationResource.js b/assets/js/datasource/RsvReservationResource.js index 6421fae..1a20c95 100644 --- a/assets/js/datasource/RsvReservationResource.js +++ b/assets/js/datasource/RsvReservationResource.js @@ -1,2 +1,4 @@ -const RsvReservationResource = () => +import { RsvDataSource } from './RsvDataSource.js'; + +export const RsvReservationResource = () => RsvDataSource.create_rsv_resource(ReservairServiceAPI.restUrl + '/reservation'); diff --git a/assets/js/datasource/RsvTimetableCapacityResource.js b/assets/js/datasource/RsvTimetableCapacityResource.js index 43a31ce..4901c37 100644 --- a/assets/js/datasource/RsvTimetableCapacityResource.js +++ b/assets/js/datasource/RsvTimetableCapacityResource.js @@ -1,2 +1,4 @@ -const RsvTimetableCapacityResource = (id) => +import { RsvDataSource } from './RsvDataSource.js'; + +export const RsvTimetableCapacityResource = (id) => RsvDataSource.create_rsv_resource(ReservairServiceAPI.restUrl + `/timetable/${id}/capacity`); diff --git a/assets/js/datasource/RsvTimetableReservationResource.js b/assets/js/datasource/RsvTimetableReservationResource.js index 1320572..70dc1d2 100644 --- a/assets/js/datasource/RsvTimetableReservationResource.js +++ b/assets/js/datasource/RsvTimetableReservationResource.js @@ -1,2 +1,4 @@ -const RsvTimetableReservationResource = (id) => +import { RsvDataSource } from './RsvDataSource.js'; + +export const RsvTimetableReservationResource = (id) => RsvDataSource.create_rsv_resource(ReservairServiceAPI.restUrl + `/timetable/${id}/reservation`); diff --git a/assets/js/datasource/RsvTimetableResource.js b/assets/js/datasource/RsvTimetableResource.js index c26c9fe..f4a4544 100644 --- a/assets/js/datasource/RsvTimetableResource.js +++ b/assets/js/datasource/RsvTimetableResource.js @@ -1,2 +1,4 @@ -const RsvTimetableResource = () => +import { RsvDataSource } from './RsvDataSource.js'; + +export const RsvTimetableResource = () => RsvDataSource.create_rsv_resource(ReservairServiceAPI.restUrl + '/timetable'); diff --git a/assets/js/elements/RsvCalendar.js b/assets/js/elements/RsvCalendar.js index 25424c2..1bf16c1 100644 --- a/assets/js/elements/RsvCalendar.js +++ b/assets/js/elements/RsvCalendar.js @@ -1,4 +1,4 @@ -const RsvCalendarPicker = (() => { +export const RsvCalendarPicker = (() => { function get_first_day_of_month(date) { const day = new Date(date.getFullYear(), date.getMonth(), 1).getDay(); diff --git a/assets/js/elements/RsvDatagrid.js b/assets/js/elements/RsvDatagrid.js index baa77f2..b87cd96 100644 --- a/assets/js/elements/RsvDatagrid.js +++ b/assets/js/elements/RsvDatagrid.js @@ -2,7 +2,7 @@ * RSV Dynamic datagrid * Allows fetching with JS instead of page reload. */ -window.RsvDataGrid = window.RsvDataGrid || { +const RsvDataGrid = { create_header(self, columns, has_actions) { let thead = document.createElement('thead'); @@ -419,4 +419,6 @@ window.RsvDataGrid = window.RsvDataGrid || { return state; } -} +}; + +export { RsvDataGrid }; diff --git a/assets/js/elements/RsvReservationSelector.js b/assets/js/elements/RsvReservationSelector.js index 7f405a0..de6baf4 100644 --- a/assets/js/elements/RsvReservationSelector.js +++ b/assets/js/elements/RsvReservationSelector.js @@ -1,3 +1,5 @@ +import { RsvCalendarPicker } from './RsvCalendar.js'; + class RsvReservationSelector extends HTMLElement { static get observedAttributes() { return ['timetable-id', 'name', 'price-per-block']; diff --git a/assets/js/elements/RsvTimeline.js b/assets/js/elements/RsvTimeline.js index 2ad7d01..499e5ef 100644 --- a/assets/js/elements/RsvTimeline.js +++ b/assets/js/elements/RsvTimeline.js @@ -1,3 +1,5 @@ +import { RsvTimetableService } from '../services/RsvTimetableService.js'; + class RsvTimeline extends HTMLElement { static get observedAttributes() { return ['timetable-id', 'date']; diff --git a/assets/js/forms/RsvAdminForm.js b/assets/js/forms/RsvAdminForm.js index a3e2d7d..30cb60b 100644 --- a/assets/js/forms/RsvAdminForm.js +++ b/assets/js/forms/RsvAdminForm.js @@ -1,3 +1,6 @@ +import { RsvFormEncoder } from './RsvFormEncoder.js'; +import { show_notice } from '../../../src/components/admin.js'; + /* * RsvAdminForm — shared submit handler for wp-admin forms. * @@ -13,7 +16,7 @@ * refresh: () => my_datagrid.refresh(), * }); */ -const RsvAdminForm = { +export const RsvAdminForm = { // Attach a submit listener that sends the form as JSON. bind(form, options = {}) { if (!form) return; diff --git a/assets/js/forms/RsvFormEncoder.js b/assets/js/forms/RsvFormEncoder.js index c824d59..d015515 100644 --- a/assets/js/forms/RsvFormEncoder.js +++ b/assets/js/forms/RsvFormEncoder.js @@ -1,4 +1,4 @@ -const RsvFormEncoder = { +export const RsvFormEncoder = { // Serialize form element into a plain JS object supporting arrays. // - Nested keys supported with dot notation: 'meta.email' // - Array notation supported with trailing [] (e.g. 'times[]') or multiple inputs with same name diff --git a/assets/js/forms/RsvFormSender.js b/assets/js/forms/RsvFormSender.js index c707c9a..e8465fd 100644 --- a/assets/js/forms/RsvFormSender.js +++ b/assets/js/forms/RsvFormSender.js @@ -1,4 +1,4 @@ -const RsvFormSender = { +export const RsvFormSender = { get_form_url(form_id) { return ReservairServiceAPI.restUrl + '/form/' + form_id; }, diff --git a/assets/js/forms/RsvInlineFormBuilder.js b/assets/js/forms/RsvInlineFormBuilder.js index 9d21f61..5c04abf 100644 --- a/assets/js/forms/RsvInlineFormBuilder.js +++ b/assets/js/forms/RsvInlineFormBuilder.js @@ -1,4 +1,4 @@ -const RsvInlineFormBuilder = { +export const RsvInlineFormBuilder = { match_p(name, value) { return (form) => String(form[name]) === String(value); }, diff --git a/assets/js/services/RsvTimetableService.js b/assets/js/services/RsvTimetableService.js index ce81b74..c0348df 100644 --- a/assets/js/services/RsvTimetableService.js +++ b/assets/js/services/RsvTimetableService.js @@ -1,4 +1,6 @@ -const RsvTimetableService = { +import { get_rest_url } from '../RsvApi.js'; + +export const RsvTimetableService = { get_all() { return fetch(get_rest_url('timetable'), { method: 'GET' }) .then(r => { diff --git a/includes/RsvAssetsDefinition.php b/includes/RsvAssetsDefinition.php index fd56794..7cebf39 100644 --- a/includes/RsvAssetsDefinition.php +++ b/includes/RsvAssetsDefinition.php @@ -5,44 +5,21 @@ * admin and the default user. */ -function rsv_asset_url(string $relative): string { - return plugin_dir_url(__FILE__) . '../assets/' . $relative; +function rsv_build_url(string $file): string { + return plugin_dir_url(__FILE__) . '../build/' . $file; } -function rsv_asset_file(string $relative): string { - return plugin_dir_path(__FILE__) . '../assets/' . $relative; +function rsv_build_file(string $file): string { + return plugin_dir_path(__FILE__) . '../build/' . $file; } -function rsv_js(string $handle, string $relative, array $deps = []): void { - wp_enqueue_script($handle, rsv_asset_url($relative), $deps, filemtime(rsv_asset_file($relative))); -} - -function rsv_css(string $handle, string $relative): void { - wp_enqueue_style($handle, rsv_asset_url($relative), [], filemtime(rsv_asset_file($relative))); -} - -// --- Shared between frontend and admin --- - -function rsv_enqueue_shared_assets(): void { - rsv_js('rsv_calendar', 'js/elements/RsvCalendar.js'); - rsv_js('rsv_timeline', 'js/elements/RsvTimeline.js'); - rsv_js('rsv_api', 'js/RsvApi.js'); - rsv_js('reservation_selector', 'js/elements/RsvReservationSelector.js'); - rsv_js('rsv_reservation_summary', 'js/elements/RsvReservationSummary.js'); - rsv_js('rsv_data_source', 'js/datasource/RsvDataSource.js'); - rsv_js('rsv_reservation_resource', 'js/datasource/RsvReservationResource.js'); - rsv_js('rsv_form_definition_resource', 'js/datasource/RsvFormDefinitionResource.js'); - rsv_js('rsv_timetable_resource', 'js/datasource/RsvTimetableResource.js'); - rsv_js('rsv_timetable_capacity_resource', 'js/datasource/RsvTimetableCapacityResource.js'); - rsv_js('rsv_timetable_reservation_resource', 'js/datasource/RsvTimetableReservationResource.js'); - rsv_js('rsv_reservation_client', 'js/datasource/RsvReservationClient.js'); - - wp_localize_script('rsv_api', 'ReservairServiceAPI', [ +function rsv_localize_api(string $handle): void { + wp_localize_script($handle, 'ReservairServiceAPI', [ 'restUrl' => rest_url('reservations/v1'), 'nonce' => wp_create_nonce('wp_rest'), ]); - wp_localize_script('rsv_api', 'ReservairStrings', [ + wp_localize_script($handle, 'ReservairStrings', [ 'timeline' => [ 'not_reservable' => 'Tento objekt nelze rezervovat.', 'no_blocks' => 'Tento den není dostupný žádný blok. Vyberte jiné datum.', @@ -63,35 +40,20 @@ function rsv_enqueue_shared_assets(): void { 'error_generic' => 'Něco se pokazilo. Zkuste to prosím znovu.', ], ]); - - rsv_css('reservations-styles', 'css/RsvMainStyle.css'); - rsv_css('rsv-form-summary-styles', 'css/components/RsvFormSummaryStyles.css'); - rsv_css('rsv-calendar-styles', 'css/components/RsvCalendarStyles.css'); - rsv_css('rsv-form-styles', 'css/components/RsvFormStyles.css'); - rsv_css('rsv-time-slot-styles', 'css/components/RsvTimeSlotsStyles.css'); } // --- Public hooks --- function rsv_enqueue_assets(): void { - rsv_enqueue_shared_assets(); + wp_enqueue_script('rsv-client', rsv_build_url('client.js'), [], filemtime(rsv_build_file('client.js'))); + wp_enqueue_style('rsv-client', rsv_build_url('client.css'), [], filemtime(rsv_build_file('client.css'))); - rsv_js('rsv_timetable_service', 'js/services/RsvTimetableService.js'); - rsv_js('rsv_form_sender', 'js/forms/RsvFormSender.js'); - rsv_js('rsv_form_encoder', 'js/forms/RsvFormEncoder.js'); + rsv_localize_api('rsv-client'); } function rsv_enqueue_admin_assets(): void { - rsv_enqueue_shared_assets(); + wp_enqueue_script('rsv-admin', rsv_build_url('admin.js'), [], filemtime(rsv_build_file('admin.js'))); + wp_enqueue_style('rsv-admin', rsv_build_url('admin.css'), [], filemtime(rsv_build_file('admin.css'))); - $admin_js = plugin_dir_path(__FILE__) . '../src/components/admin.js'; - wp_enqueue_script('admin', plugin_dir_url(__FILE__) . '../src/components/admin.js', [], filemtime($admin_js)); - - rsv_js('rsv_inline_form_builder', 'js/forms/RsvInlineFormBuilder.js'); - rsv_js('datagrid', 'js/elements/RsvDatagrid.js'); - rsv_js('rsv_form_encoder', 'js/forms/RsvFormEncoder.js'); - // RsvAdminForm needs the encoder, the localized nonce (rsv_api), and - // show_notice() (admin) — declare them as deps so load order is correct. - rsv_js('rsv_admin_form', 'js/forms/RsvAdminForm.js', ['rsv_form_encoder', 'rsv_api', 'admin']); - rsv_css('rsv-admin-style', 'css/RsvAdminStyle.css'); + rsv_localize_api('rsv-admin'); } diff --git a/src/admin.js b/src/admin.js new file mode 100644 index 0000000..3a78892 --- /dev/null +++ b/src/admin.js @@ -0,0 +1,22 @@ +import './client.js'; + +import '../assets/css/RsvAdminStyle.css'; + +import { RsvDataGrid } from '../assets/js/elements/RsvDatagrid.js'; +import { RsvInlineFormBuilder } from '../assets/js/forms/RsvInlineFormBuilder.js'; +import './components/admin.js'; +import { RsvAdminForm } from '../assets/js/forms/RsvAdminForm.js'; +import { RsvReservationResource } from '../assets/js/datasource/RsvReservationResource.js'; +import { RsvTimetableResource } from '../assets/js/datasource/RsvTimetableResource.js'; +import { RsvTimetableCapacityResource } from '../assets/js/datasource/RsvTimetableCapacityResource.js'; +import { RsvTimetableReservationResource } from '../assets/js/datasource/RsvTimetableReservationResource.js'; +import { RsvReservationClient } from '../assets/js/datasource/RsvReservationClient.js'; + +window.RsvDataGrid = RsvDataGrid; +window.RsvInlineFormBuilder = RsvInlineFormBuilder; +window.RsvAdminForm = RsvAdminForm; +window.RsvReservationResource = RsvReservationResource; +window.RsvTimetableResource = RsvTimetableResource; +window.RsvTimetableCapacityResource = RsvTimetableCapacityResource; +window.RsvTimetableReservationResource = RsvTimetableReservationResource; +window.RsvReservationClient = RsvReservationClient; diff --git a/src/client.js b/src/client.js new file mode 100644 index 0000000..db50ca5 --- /dev/null +++ b/src/client.js @@ -0,0 +1,20 @@ +import '../assets/css/RsvMainStyle.css'; +import '../assets/css/components/RsvCalendarStyles.css'; +import '../assets/css/components/RsvFormStyles.css'; +import '../assets/css/components/RsvFormSummaryStyles.css'; +import '../assets/css/components/RsvTimeSlotsStyles.css'; + +import '../assets/js/elements/RsvTimeline.js'; +import '../assets/js/elements/RsvReservationSelector.js'; +import '../assets/js/elements/RsvReservationSummary.js'; + +import { RsvReservationResource } from '../assets/js/datasource/RsvReservationResource.js'; +import { RsvFormDefinitionResource } from '../assets/js/datasource/RsvFormDefinitionResource.js'; +import { RsvTimetableResource } from '../assets/js/datasource/RsvTimetableResource.js'; +import { RsvTimetableCapacityResource } from '../assets/js/datasource/RsvTimetableCapacityResource.js'; +import { RsvTimetableReservationResource } from '../assets/js/datasource/RsvTimetableReservationResource.js'; +import { RsvReservationClient } from '../assets/js/datasource/RsvReservationClient.js'; +import { RsvFormEncoder } from '../assets/js/forms/RsvFormEncoder.js'; +import { RsvFormSender } from '../assets/js/forms/RsvFormSender.js'; + +window.RsvFormSender = RsvFormSender; diff --git a/src/components/admin.js b/src/components/admin.js index 7999738..6e0c35b 100644 --- a/src/components/admin.js +++ b/src/components/admin.js @@ -1,3 +1,5 @@ +import { get_rest_url } from '../../assets/js/RsvApi.js'; + async function fetch_reservations_to_confirm(object_id) { const url = `/wordpress/wp-json/reservations/v1/object/${object_id}/timetable/reservation/unconfirmed`; return await fetch(url, { @@ -100,7 +102,7 @@ function create_notice(id, type, mesg) { return container; } -function show_notice(target, type, mesg) { +export function show_notice(target, type, mesg) { target.querySelectorAll('.notice').forEach(x => x.remove()); const notice = create_notice('test', type, mesg); target.prepend(notice); diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..fa91b9f --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,17 @@ +const defaultConfig = require('@wordpress/scripts/config/webpack.config'); + +const defaultEntry = defaultConfig.entry; + +module.exports = { + ...defaultConfig, + entry: async () => { + const entries = typeof defaultEntry === 'function' + ? await defaultEntry() + : defaultEntry; + return { + ...entries, + client: './src/client.js', + admin: './src/admin.js', + }; + }, +}; -- 2.52.0