Skip to content

Commit a39c4d8

Browse files
committed
Use editorId for WP editor components
Add editorId support and stronger typings for the WP editor Alpine component. The TypeScript component now exposes a WPEditorComponent interface, accepts editorId in the config, and uses editorId (instead of name) when looking up TinyMCE, textarea and QuickTags elements. Moved global tinymce/quicktags declarations into declaration.d.ts. Added a form reset listener (tutor-form-reset) to restore default editor content on form resets, and updated sync/set/get methods and type annotations accordingly. Also updated the PHP component to pass editorId into the Alpine initializer.
1 parent bc7372e commit a39c4d8

3 files changed

Lines changed: 65 additions & 29 deletions

File tree

assets/core/ts/components/wp-editor.ts

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,5 @@
11
import { type AlpineComponentMeta } from '@Core/ts/types';
22

3-
// Global type declarations for WordPress editor
4-
declare global {
5-
interface Window {
6-
tinymce?: {
7-
get(id: string): TinyMCEEditor | null;
8-
};
9-
quicktags?: unknown;
10-
}
11-
}
12-
133
interface TinyMCEEditor {
144
getContent(): string;
155
setContent(content: string): void;
@@ -22,22 +12,44 @@ interface TinyMCEEditor {
2212

2313
interface WPEditorConfig {
2414
name: string;
15+
editorId: string;
2516
placeholder?: string;
2617
}
2718

2819
interface AlpineComponent {
2920
$el: HTMLElement;
3021
}
3122

23+
interface WPEditorComponent {
24+
name: string;
25+
editorId: string;
26+
placeholder: string;
27+
editorInstance: TinyMCEEditor | null;
28+
isVisualMode: boolean;
29+
initialized: boolean;
30+
init(this: AlpineComponent & WPEditorComponent): void;
31+
setupTinyMCE(this: AlpineComponent & WPEditorComponent): void;
32+
setupTextarea(this: AlpineComponent & WPEditorComponent): void;
33+
setupQuickTags(this: AlpineComponent & WPEditorComponent): void;
34+
setupFormResetListener(this: AlpineComponent & WPEditorComponent): void;
35+
syncEditorToForm(this: AlpineComponent & WPEditorComponent): void;
36+
syncTextareaToForm(this: AlpineComponent & WPEditorComponent): void;
37+
updateFormValue(this: AlpineComponent & WPEditorComponent, content: string): void;
38+
triggerBlur(this: AlpineComponent & WPEditorComponent): void;
39+
getContent(this: AlpineComponent & WPEditorComponent): string;
40+
setContent(this: AlpineComponent & WPEditorComponent, content: string): void;
41+
}
42+
3243
/**
3344
* WordPress Editor Alpine.js Component
3445
* Integrates wp_editor (TinyMCE/QuickTags) with tutorForm validation system
3546
*/
36-
export const wpEditor = (config: WPEditorConfig) => {
37-
const { name, placeholder = '' } = config;
47+
export const wpEditor = (config: WPEditorConfig): WPEditorComponent => {
48+
const { name, editorId, placeholder = '' } = config;
3849

3950
return {
4051
name,
52+
editorId,
4153
placeholder,
4254
editorInstance: null as TinyMCEEditor | null,
4355
isVisualMode: true,
@@ -57,13 +69,17 @@ export const wpEditor = (config: WPEditorConfig) => {
5769
this.setupQuickTags();
5870
}
5971

72+
// Listen for form reset events
73+
this.setupFormResetListener();
74+
6075
this.initialized = true;
6176
},
6277

63-
setupTinyMCE(this: AlpineComponent & ReturnType<typeof wpEditor>) {
78+
setupTinyMCE(this: AlpineComponent & WPEditorComponent) {
6479
// TinyMCE might not be initialized immediately
6580
const checkEditor = () => {
66-
const editor = window.tinymce?.get(this.name);
81+
// Use editorId instead of name to support multiple editors
82+
const editor = window.tinymce?.get(this.editorId);
6783

6884
if (editor) {
6985
this.editorInstance = editor;
@@ -109,8 +125,8 @@ export const wpEditor = (config: WPEditorConfig) => {
109125
checkEditor();
110126
},
111127

112-
setupTextarea(this: AlpineComponent & ReturnType<typeof wpEditor>) {
113-
const textarea = document.getElementById(this.name) as HTMLTextAreaElement;
128+
setupTextarea(this: AlpineComponent & WPEditorComponent) {
129+
const textarea = document.getElementById(this.editorId) as HTMLTextAreaElement;
114130

115131
if (textarea) {
116132
// Set placeholder
@@ -135,15 +151,14 @@ export const wpEditor = (config: WPEditorConfig) => {
135151
}
136152
},
137153

138-
setupQuickTags(this: AlpineComponent & ReturnType<typeof wpEditor>) {
154+
setupQuickTags(this: AlpineComponent & WPEditorComponent) {
139155
// QuickTags buttons trigger changes in text mode
140-
const textarea = document.getElementById(this.name) as HTMLTextAreaElement;
156+
const textarea = document.getElementById(this.editorId) as HTMLTextAreaElement;
141157

142158
if (textarea) {
143159
// Monitor for QuickTags button clicks
144-
// Monitor for QuickTags button clicks
145-
// QuickTags toolbars usually have ID "qt_{name}_toolbar"
146-
const toolbarId = `qt_${this.name}_toolbar`;
160+
// QuickTags toolbars usually have ID "qt_{editorId}_toolbar"
161+
const toolbarId = `qt_${this.editorId}_toolbar`;
147162
const toolbar = document.getElementById(toolbarId);
148163

149164
if (toolbar) {
@@ -159,21 +174,37 @@ export const wpEditor = (config: WPEditorConfig) => {
159174
}
160175
},
161176

162-
syncEditorToForm(this: AlpineComponent & ReturnType<typeof wpEditor>) {
177+
setupFormResetListener(this: AlpineComponent & WPEditorComponent) {
178+
// Listen for form reset events from tutorForm
179+
window.addEventListener('tutor-form-reset', (event: Event) => {
180+
const customEvent = event as CustomEvent;
181+
const { formId, defaultValues } = customEvent.detail || {};
182+
183+
// Check if this reset event is for our form by checking if our element is inside the form
184+
const form = this.$el.closest('form');
185+
if (form && form.getAttribute('x-data')?.includes(`id: '${formId}'`)) {
186+
// Reset editor content to default value
187+
const defaultValue = defaultValues?.[this.name] || '';
188+
this.setContent(defaultValue);
189+
}
190+
});
191+
},
192+
193+
syncEditorToForm(this: AlpineComponent & WPEditorComponent) {
163194
if (this.editorInstance) {
164195
const content = this.editorInstance.getContent();
165196
this.updateFormValue(content);
166197
}
167198
},
168199

169-
syncTextareaToForm(this: AlpineComponent & ReturnType<typeof wpEditor>) {
200+
syncTextareaToForm(this: AlpineComponent & WPEditorComponent) {
170201
const textarea = document.getElementById(this.name) as HTMLTextAreaElement;
171202
if (textarea) {
172203
this.updateFormValue(textarea.value);
173204
}
174205
},
175206

176-
updateFormValue(this: AlpineComponent & ReturnType<typeof wpEditor>, content: string) {
207+
updateFormValue(this: AlpineComponent & WPEditorComponent, content: string) {
177208
// Get the hidden input that's bound to the form
178209
const hiddenInput = this.$el.querySelector(`input[name="${this.name}"]`) as HTMLInputElement;
179210

@@ -186,15 +217,15 @@ export const wpEditor = (config: WPEditorConfig) => {
186217
}
187218
},
188219

189-
triggerBlur(this: AlpineComponent & ReturnType<typeof wpEditor>) {
220+
triggerBlur(this: AlpineComponent & WPEditorComponent) {
190221
const hiddenInput = this.$el.querySelector(`input[name="${this.name}"]`) as HTMLInputElement;
191222

192223
if (hiddenInput) {
193224
hiddenInput.dispatchEvent(new Event('blur', { bubbles: true }));
194225
}
195226
},
196227

197-
getContent(this: AlpineComponent & ReturnType<typeof wpEditor>): string {
228+
getContent(this: AlpineComponent & WPEditorComponent): string {
198229
if (this.isVisualMode && this.editorInstance) {
199230
return this.editorInstance.getContent();
200231
}
@@ -203,7 +234,7 @@ export const wpEditor = (config: WPEditorConfig) => {
203234
return textarea ? textarea.value : '';
204235
},
205236

206-
setContent(this: AlpineComponent & ReturnType<typeof wpEditor>, content: string) {
237+
setContent(this: AlpineComponent & WPEditorComponent, content: string) {
207238
if (this.isVisualMode && this.editorInstance) {
208239
this.editorInstance.setContent(content);
209240
}

assets/core/ts/declaration.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ declare global {
8484
};
8585
};
8686

87+
// WordPress editor (TinyMCE and QuickTags)
88+
tinymce?: {
89+
get(id: string): () => unknown;
90+
};
91+
quicktags?: unknown;
92+
8793
// Tutor object from PHP (extend existing type, don't redeclare)
8894
_tutorobject?: Record<string, unknown> & {
8995
nonce_key?: string;

components/WPEditor.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515

1616
defined( 'ABSPATH' ) || exit;
1717

18-
use TUTOR\Icon;
19-
2018
/**
2119
* WPEditor Component Class.
2220
*
@@ -344,6 +342,7 @@ public function get(): string {
344342
<?php echo $wrapper_attr_str; // phpcs:ignore ?>
345343
x-data="tutorWPEditor({
346344
name: '<?php echo esc_js( $this->name ); ?>',
345+
editorId: '<?php echo esc_js( $editor_id ); ?>',
347346
placeholder: '<?php echo esc_js( $this->placeholder ); ?>'
348347
})"
349348
x-init="init()"

0 commit comments

Comments
 (0)