Skip to content

Commit 8c0b606

Browse files
authored
Merge pull request #251 from codesnippetspro/feat/version-switch
feat: Implement version switching functionality in plugin settings
2 parents 2f606b6 + 2741138 commit 8c0b606

File tree

10 files changed

+701
-7
lines changed

10 files changed

+701
-7
lines changed

src/css/settings.scss

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
@use 'common/codemirror';
22

3-
$sections: general, editor, debug;
3+
$sections: general, editor, debug, version-switch;
44

55
p.submit {
66
display: flex;
@@ -127,3 +127,87 @@ body.js {
127127
.cloud-settings tbody tr:nth-child(n+5) {
128128
display: none;
129129
}
130+
131+
// Version Switch Styles
132+
.code-snippets-version-switch {
133+
.current-version {
134+
font-family: monospace;
135+
font-size: 1.1em;
136+
font-weight: bold;
137+
color: #0073aa;
138+
background: #f0f6fc;
139+
padding: 2px 8px;
140+
border-radius: 3px;
141+
border: 1px solid #c3c4c7;
142+
}
143+
144+
#target_version {
145+
min-width: 200px;
146+
margin-inline-start: 8px;
147+
}
148+
149+
#switch-version-btn {
150+
&[disabled] {
151+
opacity: 0.6;
152+
cursor: not-allowed;
153+
background-color: #f0f0f1 !important;
154+
color: #a7aaad !important;
155+
border-color: #dcdcde !important;
156+
}
157+
}
158+
159+
// Warning box styling
160+
#version-switch-warning {
161+
margin-top: 20px !important;
162+
padding: 12px 16px;
163+
border-left: 4px solid #dba617;
164+
background: #fff8e5;
165+
border-radius: 4px;
166+
167+
p {
168+
margin: 0;
169+
color: #8f6914;
170+
171+
strong {
172+
color: #8f6914;
173+
}
174+
}
175+
}
176+
177+
#version-switch-result {
178+
margin-block-start: 12px;
179+
180+
&.notice {
181+
padding: 8px 12px;
182+
border-radius: 4px;
183+
}
184+
}
185+
186+
.notice {
187+
&.notice {
188+
&-success {
189+
border-left-color: #00a32a;
190+
}
191+
192+
&-error {
193+
border-left-color: #d63638;
194+
}
195+
196+
&-warning {
197+
border-left-color: #dba617;
198+
}
199+
200+
&-info {
201+
border-left-color: #72aee6;
202+
}
203+
}
204+
}
205+
}
206+
207+
.version-switch-settings {
208+
.form-table {
209+
th {
210+
width: 180px;
211+
}
212+
}
213+
}

src/js/services/settings/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { handleSettingsTabs } from './tabs'
22
export { handleEditorPreviewUpdates } from './editor-preview'
3+
export { initVersionSwitch } from './version'
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
// Handles version switching UI on the settings screen.
2+
// Exported init function so callers can opt-in like other settings modules.
3+
// Uses vanilla DOM APIs and the global `code_snippets_version_switch` config
4+
// injected by PHP via wp_add_inline_script.
5+
6+
interface VersionConfig {
7+
ajaxurl?: string
8+
nonce_switch?: string
9+
nonce_refresh?: string
10+
11+
}
12+
13+
interface AjaxResponse {
14+
success?: boolean
15+
data?: {
16+
message?: string
17+
}
18+
}
19+
20+
declare global {
21+
interface Window {
22+
code_snippets_version_switch?: VersionConfig
23+
__code_snippets_i18n?: Record<string, string>
24+
}
25+
}
26+
27+
const el = (id: string): HTMLElement | null => document.getElementById(id)
28+
29+
const getConfig = (): VersionConfig => {
30+
const w = <{ code_snippets_version_switch?: VersionConfig }><unknown>window
31+
return w.code_snippets_version_switch ?? {}
32+
}
33+
34+
const getCurrentVersion = (): string => (document.querySelector('.current-version')?.textContent ?? '').trim()
35+
36+
const getI18n = (key: string, fallback: string): string => window.__code_snippets_i18n?.[key] ?? fallback
37+
38+
const bindDropdown = (
39+
dropdown: HTMLSelectElement,
40+
button: HTMLButtonElement | null,
41+
currentVersion: string,
42+
): void => {
43+
dropdown.addEventListener('change', (): void => {
44+
const selectedVersion = dropdown.value
45+
if (!button) {
46+
return
47+
}
48+
if (!selectedVersion || selectedVersion === currentVersion) {
49+
button.disabled = true
50+
const warn = el('version-switch-warning')
51+
if (warn) { warn.setAttribute('style', 'display: none;') }
52+
} else {
53+
button.disabled = false
54+
const warn = el('version-switch-warning')
55+
if (warn) { warn.setAttribute('style', '') }
56+
}
57+
})
58+
}
59+
60+
const SUCCESS_RELOAD_MS = 3000
61+
62+
const postForm = async (data: Record<string, string>, cfg: VersionConfig): Promise<AjaxResponse> => {
63+
const body = new URLSearchParams()
64+
Object.keys(data).forEach(k => body.append(k, data[k]))
65+
const resp = await fetch(cfg.ajaxurl ?? '/wp-admin/admin-ajax.php', {
66+
method: 'POST',
67+
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
68+
body: body.toString(),
69+
credentials: 'same-origin',
70+
})
71+
const json = <AjaxResponse> await resp.json()
72+
return json
73+
}
74+
75+
const bindSwitch = (
76+
button: HTMLButtonElement,
77+
dropdown: HTMLSelectElement,
78+
result: HTMLDivElement,
79+
cfg: VersionConfig,
80+
currentVersion: string,
81+
): void => {
82+
button.addEventListener('click', (): void => {
83+
void (async (): Promise<void> => {
84+
const targetVersion = dropdown.value
85+
if (!targetVersion || targetVersion === currentVersion) {
86+
result.className = 'notice notice-warning'
87+
result.innerHTML = `<p>${getI18n('selectDifferent', 'Please select a different version to switch to.')}</p>`
88+
result.style.display = ''
89+
return
90+
}
91+
92+
button.disabled = true
93+
const originalText = button.textContent ?? ''
94+
button.textContent = getI18n('switching', 'Switching...')
95+
96+
result.className = 'notice notice-info'
97+
result.innerHTML = `<p>${getI18n('processing', 'Processing version switch. Please wait...')}</p>`
98+
result.style.display = ''
99+
100+
try {
101+
const response = await postForm({
102+
action: 'code_snippets_switch_version',
103+
target_version: targetVersion,
104+
nonce: cfg.nonce_switch ?? '',
105+
}, cfg)
106+
107+
if (response.success) {
108+
result.className = 'notice notice-success'
109+
result.innerHTML = `<p>${response.data?.message ?? ''}</p>`
110+
setTimeout(() => window.location.reload(), SUCCESS_RELOAD_MS)
111+
return
112+
}
113+
114+
result.className = 'notice notice-error'
115+
result.innerHTML = `<p>${response.data?.message ?? getI18n('error', 'An error occurred.')}</p>`
116+
button.disabled = false
117+
button.textContent = originalText
118+
} catch (_err) {
119+
result.className = 'notice notice-error'
120+
result.innerHTML = `<p>${getI18n('errorSwitch', 'An error occurred while switching versions. Please try again.')}</p>`
121+
button.disabled = false
122+
button.textContent = originalText
123+
}
124+
})()
125+
})
126+
}
127+
128+
const REFRESH_RELOAD_MS = 1000
129+
130+
const bindRefresh = (
131+
btn: HTMLButtonElement,
132+
cfg: VersionConfig,
133+
): void => {
134+
btn.addEventListener('click', (): void => {
135+
void (async (): Promise<void> => {
136+
const original = btn.textContent ?? ''
137+
btn.disabled = true
138+
btn.textContent = getI18n('refreshing', 'Refreshing...')
139+
140+
try {
141+
await postForm({
142+
action: 'code_snippets_refresh_versions',
143+
nonce: cfg.nonce_refresh ?? '',
144+
}, cfg)
145+
146+
btn.textContent = getI18n('refreshed', 'Refreshed!')
147+
setTimeout(() => {
148+
btn.disabled = false
149+
btn.textContent = original
150+
window.location.reload()
151+
}, REFRESH_RELOAD_MS)
152+
} catch {
153+
btn.disabled = false
154+
btn.textContent = original
155+
}
156+
})()
157+
})
158+
}
159+
160+
export const initVersionSwitch = (): void => {
161+
const cfg = getConfig()
162+
const currentVersion = getCurrentVersion()
163+
164+
const button = <HTMLButtonElement | null> el('switch-version-btn')
165+
const dropdown = <HTMLSelectElement | null> el('target_version')
166+
const result = <HTMLDivElement | null> el('version-switch-result')
167+
const refreshBtn = <HTMLButtonElement | null> el('refresh-versions-btn')
168+
169+
if (dropdown) {
170+
bindDropdown(dropdown, button, currentVersion)
171+
}
172+
173+
if (button && dropdown && result) {
174+
bindSwitch(button, dropdown, result, cfg, currentVersion)
175+
}
176+
177+
if (refreshBtn) {
178+
bindRefresh(refreshBtn, cfg)
179+
}
180+
}
181+
182+

src/js/settings.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { handleEditorPreviewUpdates, handleSettingsTabs } from './services/settings'
1+
import { handleEditorPreviewUpdates, handleSettingsTabs, initVersionSwitch } from './services/settings'
22

33
handleSettingsTabs()
44
handleEditorPreviewUpdates()
5+
initVersionSwitch()

src/php/class-plugin.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ public function load_plugin() {
132132
// Settings component.
133133
require_once $includes_path . '/settings/settings-fields.php';
134134
require_once $includes_path . '/settings/editor-preview.php';
135+
require_once $includes_path . '/settings/class-version-switch.php';
135136
require_once $includes_path . '/settings/settings.php';
136137

137138
// Cloud List Table shared functions.

src/php/settings/class-setting-field.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,11 @@ public function render() {
117117
* Render a callback field.
118118
*/
119119
public function render_callback_field() {
120-
call_user_func( $this->render_callback );
120+
if ( ! is_callable( $this->render_callback ) ) {
121+
return;
122+
}
123+
124+
call_user_func( $this->render_callback, $this->args );
121125
}
122126

123127
/**

0 commit comments

Comments
 (0)