Initial commit: WordPress wp-content (themes, plugins, languages)
- Theme: momentry (custom theme with REST API routes) - Plugins: code-snippets (contains all API proxies) - Languages: zh_TW translations - Excludes: cache, backups, uploads, logs
This commit is contained in:
79
plugins/code-snippets/js/services/manage/activation.ts
Normal file
79
plugins/code-snippets/js/services/manage/activation.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { __ } from '@wordpress/i18n'
|
||||
import { updateSnippet } from './requests'
|
||||
import type { Snippet } from '../../types/Snippet'
|
||||
|
||||
/**
|
||||
* Update the snippet count of a specific view
|
||||
* @param element
|
||||
* @param increment
|
||||
*/
|
||||
const updateViewCount = (element: HTMLElement | null, increment: boolean) => {
|
||||
if (element?.textContent) {
|
||||
let count = parseInt(element.textContent.replace(/\((?<count>\d+)\)/, '$1'), 10)
|
||||
count += increment ? 1 : -1
|
||||
element.textContent = `(${count})`
|
||||
} else {
|
||||
console.error('Could not update view count.', element)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate an inactive snippet, or deactivate an active snippet
|
||||
* @param link
|
||||
* @param event
|
||||
*/
|
||||
export const toggleSnippetActive = (link: HTMLAnchorElement, event: Event) => {
|
||||
const row = link.parentElement?.parentElement // Switch < cell < row
|
||||
if (!row) {
|
||||
console.error('Could not toggle snippet active status.', row)
|
||||
return
|
||||
}
|
||||
|
||||
const match = /\b(?:in)?active-snippet\b/.exec(row.className)
|
||||
if (!match) {
|
||||
return
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
const activating = 'inactive-snippet' === match[0]
|
||||
const snippet: Partial<Snippet> = { active: activating }
|
||||
|
||||
updateSnippet('active', row, snippet, response => {
|
||||
const button: HTMLAnchorElement | null = row.querySelector('.snippet-activation-switch')
|
||||
|
||||
if (response.success) {
|
||||
row.className = activating
|
||||
? row.className.replace(/\binactive-snippet\b/, 'active-snippet')
|
||||
: row.className.replace(/\bactive-snippet\b/, 'inactive-snippet')
|
||||
|
||||
const views = document.querySelector('.subsubsub')
|
||||
const activeCount = views?.querySelector<HTMLElement>('.active .count')
|
||||
const inactiveCount = views?.querySelector<HTMLElement>('.inactive .count')
|
||||
|
||||
if (activeCount) {
|
||||
updateViewCount(activeCount, activating)
|
||||
}
|
||||
|
||||
if (inactiveCount) {
|
||||
updateViewCount(inactiveCount, activating)
|
||||
}
|
||||
|
||||
if (button) {
|
||||
button.title = activating ? __('Deactivate', 'code-snippets') : __('Activate', 'code-snippets')
|
||||
}
|
||||
} else {
|
||||
row.className += ' erroneous-snippet'
|
||||
|
||||
if (button) {
|
||||
button.title = __('An error occurred when attempting to activate', 'code-snippets')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const handleSnippetActivationSwitches = () => {
|
||||
for (const link of document.getElementsByClassName('snippet-activation-switch')) {
|
||||
link.addEventListener('click', event => toggleSnippetActive(<HTMLAnchorElement> link, event))
|
||||
}
|
||||
}
|
||||
45
plugins/code-snippets/js/services/manage/cloud.ts
Normal file
45
plugins/code-snippets/js/services/manage/cloud.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import Prism from 'prismjs'
|
||||
import 'prismjs/components/prism-clike'
|
||||
import 'prismjs/components/prism-javascript'
|
||||
import 'prismjs/components/prism-css'
|
||||
import 'prismjs/components/prism-php'
|
||||
import 'prismjs/components/prism-markup'
|
||||
import 'prismjs/plugins/keep-markup/prism-keep-markup'
|
||||
|
||||
/**
|
||||
* Handle clicks on snippet preview button.
|
||||
*/
|
||||
export const handleShowCloudPreview = () => {
|
||||
const previewButtons = document.querySelectorAll('.cloud-snippet-preview')
|
||||
|
||||
previewButtons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
const snippetId = button.getAttribute('data-snippet')
|
||||
const snippetLanguage = button.getAttribute('data-lang')
|
||||
|
||||
const snippetCodeInput = <HTMLInputElement | null> document.getElementById(`cloud-snippet-code-${snippetId}`)
|
||||
const snippetCodeModalTag = document.getElementById('snippet-code-thickbox')
|
||||
|
||||
if (!snippetCodeModalTag || !snippetCodeInput) {
|
||||
return
|
||||
}
|
||||
|
||||
snippetCodeModalTag.classList.remove(...snippetCodeModalTag.classList)
|
||||
snippetCodeModalTag.classList.add(`language-${snippetLanguage}`)
|
||||
snippetCodeModalTag.textContent = snippetCodeInput.value
|
||||
|
||||
if ('markup' === snippetLanguage) {
|
||||
snippetCodeModalTag.innerHTML = `<xmp>${snippetCodeInput.value}</xmp>`
|
||||
}
|
||||
|
||||
if ('php' === snippetLanguage) {
|
||||
// Check if there is an opening php tag if not add it.
|
||||
if (!snippetCodeInput.value.startsWith('<?php')) {
|
||||
snippetCodeModalTag.textContent = `<?php\n${snippetCodeInput.value}`
|
||||
}
|
||||
}
|
||||
|
||||
Prism.highlightElement(snippetCodeModalTag)
|
||||
})
|
||||
})
|
||||
}
|
||||
3
plugins/code-snippets/js/services/manage/index.ts
Normal file
3
plugins/code-snippets/js/services/manage/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { handleSnippetActivationSwitches } from './activation'
|
||||
export { handleSnippetPriorityChanges } from './priority'
|
||||
export { handleShowCloudPreview } from './cloud'
|
||||
22
plugins/code-snippets/js/services/manage/priority.ts
Normal file
22
plugins/code-snippets/js/services/manage/priority.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { updateSnippet } from './requests'
|
||||
import type { Snippet } from '../../types/Snippet'
|
||||
|
||||
/**
|
||||
* Update the priority of a snippet
|
||||
*/
|
||||
export const updateSnippetPriority = (element: HTMLInputElement) => {
|
||||
const row = element.parentElement?.parentElement
|
||||
const snippet: Partial<Snippet> = { priority: parseFloat(element.value) }
|
||||
if (row) {
|
||||
updateSnippet('priority', row, snippet)
|
||||
} else {
|
||||
console.error('Could not update snippet information.', snippet, row)
|
||||
}
|
||||
}
|
||||
|
||||
export const handleSnippetPriorityChanges = () => {
|
||||
for (const field of <HTMLCollectionOf<HTMLInputElement>> document.getElementsByClassName('snippet-priority')) {
|
||||
field.addEventListener('input', () => updateSnippetPriority(field))
|
||||
field.disabled = false
|
||||
}
|
||||
}
|
||||
56
plugins/code-snippets/js/services/manage/requests.ts
Normal file
56
plugins/code-snippets/js/services/manage/requests.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { isNetworkAdmin } from '../../utils/screen'
|
||||
import type { SnippetSchema } from '../../types/schema/SnippetSchema'
|
||||
import type { Snippet, SnippetScope } from '../../types/Snippet'
|
||||
|
||||
export interface ResponseData<T = unknown> {
|
||||
success: boolean
|
||||
data?: T
|
||||
}
|
||||
|
||||
export type SuccessCallback = (response: ResponseData) => void
|
||||
|
||||
const sendSnippetRequest = (query: string, onSuccess?: SuccessCallback) => {
|
||||
const request = new XMLHttpRequest()
|
||||
request.open('POST', window.ajaxurl, true)
|
||||
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8')
|
||||
|
||||
request.onload = () => {
|
||||
const success = 200
|
||||
const errorStart = 400
|
||||
if (success > request.status || errorStart <= request.status) {
|
||||
return
|
||||
}
|
||||
|
||||
console.info(request.responseText)
|
||||
onSuccess?.(<ResponseData> JSON.parse(request.responseText))
|
||||
}
|
||||
|
||||
request.send(query)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the data of a given snippet using AJAX
|
||||
* @param field
|
||||
* @param row
|
||||
* @param snippet
|
||||
* @param successCallback
|
||||
*/
|
||||
export const updateSnippet = (field: keyof Snippet, row: Element, snippet: Partial<Snippet>, successCallback?: SuccessCallback) => {
|
||||
const nonce = <HTMLInputElement | null> document.getElementById('code_snippets_ajax_nonce')
|
||||
const columnId = row.querySelector('.column-id')
|
||||
|
||||
if (!nonce || !columnId?.textContent || !parseInt(columnId.textContent, 10)) {
|
||||
return
|
||||
}
|
||||
|
||||
const updatedSnippet: Partial<SnippetSchema> = {
|
||||
id: parseInt(columnId.textContent, 10),
|
||||
shared_network: null !== /\bshared-network-snippet\b/.exec(row.className),
|
||||
network: snippet.shared_network ?? isNetworkAdmin(),
|
||||
scope: <SnippetScope | null> row.getAttribute('data-snippet-scope') ?? snippet.scope,
|
||||
...snippet
|
||||
}
|
||||
|
||||
const queryString = `action=update_code_snippet&_ajax_nonce=${nonce.value}&field=${field}&snippet=${JSON.stringify(updatedSnippet)}`
|
||||
sendSnippetRequest(queryString, successCallback)
|
||||
}
|
||||
55
plugins/code-snippets/js/services/settings/editor-preview.ts
Normal file
55
plugins/code-snippets/js/services/settings/editor-preview.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import '../../editor'
|
||||
|
||||
const parseSelect = (select: HTMLSelectElement) => select.options[select.selectedIndex].value
|
||||
const parseCheckbox = (checkbox: HTMLInputElement) => checkbox.checked
|
||||
const parseNumber = (input: HTMLInputElement) => parseInt(input.value, 10)
|
||||
|
||||
const initialiseCodeMirror = () => {
|
||||
const { codeEditor } = window.wp
|
||||
const textarea = document.getElementById('code_snippets_editor_preview')
|
||||
|
||||
if (textarea) {
|
||||
window.code_snippets_editor_preview = codeEditor.initialize(textarea)
|
||||
return window.code_snippets_editor_preview.codemirror
|
||||
}
|
||||
|
||||
console.error('Could not initialise CodeMirror on textarea.', textarea)
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const handleEditorPreviewUpdates = () => {
|
||||
const editor = initialiseCodeMirror()
|
||||
const editorSettings = window.code_snippets_editor_settings
|
||||
|
||||
for (const setting of editorSettings) {
|
||||
const element = document.querySelector(`[name="code_snippets_settings[editor][${setting.name}]"]`)
|
||||
|
||||
element?.addEventListener('change', () => {
|
||||
const opt = setting.codemirror
|
||||
|
||||
const value = (() => {
|
||||
switch (setting.type) {
|
||||
case 'select':
|
||||
return parseSelect(<HTMLSelectElement> element)
|
||||
case 'checkbox':
|
||||
return parseCheckbox(<HTMLInputElement> element)
|
||||
case 'number':
|
||||
return parseNumber(<HTMLInputElement> element)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
})()
|
||||
|
||||
if (null !== value) {
|
||||
if ('font_size' === setting.name) {
|
||||
const codeElement = document.querySelector('.CodeMirror-code')
|
||||
if (codeElement && codeElement instanceof HTMLElement) {
|
||||
codeElement.style.fontSize = `${value}px`
|
||||
}
|
||||
} else {
|
||||
editor?.setOption(opt, value)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
3
plugins/code-snippets/js/services/settings/index.ts
Normal file
3
plugins/code-snippets/js/services/settings/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { handleSettingsTabs } from './tabs'
|
||||
export { handleEditorPreviewUpdates } from './editor-preview'
|
||||
export { initVersionSwitch } from './version'
|
||||
50
plugins/code-snippets/js/services/settings/tabs.ts
Normal file
50
plugins/code-snippets/js/services/settings/tabs.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
const selectTab = (tabsWrapper: Element, tab: Element, section: string) => {
|
||||
// Swap the active tab class from the previously active tab to the current one.
|
||||
tabsWrapper.querySelector('.nav-tab-active')?.classList.remove('nav-tab-active')
|
||||
tab.classList.add('nav-tab-active')
|
||||
|
||||
// Update the current active tab attribute so that only the active tab is displayed.
|
||||
tabsWrapper.closest('.wrap')?.setAttribute('data-active-tab', section)
|
||||
}
|
||||
|
||||
// Refresh the editor preview if we're viewing the editor section.
|
||||
const refreshEditorPreview = (section: string) => {
|
||||
if ('editor' === section) {
|
||||
window.code_snippets_editor_preview?.codemirror.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
// Update the http referer value so that any redirections lead back to this tab.
|
||||
const updateHttpReferer = (section: string) => {
|
||||
const httpReferer = document.querySelector<HTMLInputElement>('input[name=_wp_http_referer]')
|
||||
if (!httpReferer) {
|
||||
console.error('could not find http referer')
|
||||
return
|
||||
}
|
||||
|
||||
const newReferer = httpReferer.value.replace(/(?<base>[&?]section=)[^&]+/, `$1${section}`)
|
||||
httpReferer.value = newReferer + (newReferer === httpReferer.value ? `§ion=${section}` : '')
|
||||
}
|
||||
|
||||
export const handleSettingsTabs = () => {
|
||||
const tabsWrapper = document.getElementById('settings-sections-tabs')
|
||||
if (!tabsWrapper) {
|
||||
console.error('Could not find snippets tabs')
|
||||
return
|
||||
}
|
||||
|
||||
const tabs = tabsWrapper.querySelectorAll('.nav-tab')
|
||||
|
||||
for (const tab of tabs) {
|
||||
tab.addEventListener('click', event => {
|
||||
event.preventDefault()
|
||||
const section = tab.getAttribute('data-section')
|
||||
|
||||
if (section) {
|
||||
selectTab(tabsWrapper, tab, section)
|
||||
refreshEditorPreview(section)
|
||||
updateHttpReferer(section)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
182
plugins/code-snippets/js/services/settings/version.ts
Normal file
182
plugins/code-snippets/js/services/settings/version.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
// Handles version switching UI on the settings screen.
|
||||
// Exported init function so callers can opt-in like other settings modules.
|
||||
// Uses vanilla DOM APIs and the global `code_snippets_version_switch` config
|
||||
// injected by PHP via wp_add_inline_script.
|
||||
|
||||
interface VersionConfig {
|
||||
ajaxurl?: string
|
||||
nonce_switch?: string
|
||||
nonce_refresh?: string
|
||||
|
||||
}
|
||||
|
||||
interface AjaxResponse {
|
||||
success?: boolean
|
||||
data?: {
|
||||
message?: string
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
code_snippets_version_switch?: VersionConfig
|
||||
__code_snippets_i18n?: Record<string, string>
|
||||
}
|
||||
}
|
||||
|
||||
const el = (id: string): HTMLElement | null => document.getElementById(id)
|
||||
|
||||
const getConfig = (): VersionConfig => {
|
||||
const w = <{ code_snippets_version_switch?: VersionConfig }><unknown>window
|
||||
return w.code_snippets_version_switch ?? {}
|
||||
}
|
||||
|
||||
const getCurrentVersion = (): string => (document.querySelector('.current-version')?.textContent ?? '').trim()
|
||||
|
||||
const getI18n = (key: string, fallback: string): string => window.__code_snippets_i18n?.[key] ?? fallback
|
||||
|
||||
const bindDropdown = (
|
||||
dropdown: HTMLSelectElement,
|
||||
button: HTMLButtonElement | null,
|
||||
currentVersion: string,
|
||||
): void => {
|
||||
dropdown.addEventListener('change', (): void => {
|
||||
const selectedVersion = dropdown.value
|
||||
if (!button) {
|
||||
return
|
||||
}
|
||||
if (!selectedVersion || selectedVersion === currentVersion) {
|
||||
button.disabled = true
|
||||
const warn = el('version-switch-warning')
|
||||
if (warn) { warn.setAttribute('style', 'display: none;') }
|
||||
} else {
|
||||
button.disabled = false
|
||||
const warn = el('version-switch-warning')
|
||||
if (warn) { warn.setAttribute('style', '') }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const SUCCESS_RELOAD_MS = 3000
|
||||
|
||||
const postForm = async (data: Record<string, string>, cfg: VersionConfig): Promise<AjaxResponse> => {
|
||||
const body = new URLSearchParams()
|
||||
Object.keys(data).forEach(k => body.append(k, data[k]))
|
||||
const resp = await fetch(cfg.ajaxurl ?? '/wp-admin/admin-ajax.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||
body: body.toString(),
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
const json = <AjaxResponse> await resp.json()
|
||||
return json
|
||||
}
|
||||
|
||||
const bindSwitch = (
|
||||
button: HTMLButtonElement,
|
||||
dropdown: HTMLSelectElement,
|
||||
result: HTMLDivElement,
|
||||
cfg: VersionConfig,
|
||||
currentVersion: string,
|
||||
): void => {
|
||||
button.addEventListener('click', (): void => {
|
||||
void (async (): Promise<void> => {
|
||||
const targetVersion = dropdown.value
|
||||
if (!targetVersion || targetVersion === currentVersion) {
|
||||
result.className = 'notice notice-warning'
|
||||
result.innerHTML = `<p>${getI18n('selectDifferent', 'Please select a different version to switch to.')}</p>`
|
||||
result.style.display = ''
|
||||
return
|
||||
}
|
||||
|
||||
button.disabled = true
|
||||
const originalText = button.textContent ?? ''
|
||||
button.textContent = getI18n('switching', 'Switching...')
|
||||
|
||||
result.className = 'notice notice-info'
|
||||
result.innerHTML = `<p>${getI18n('processing', 'Processing version switch. Please wait...')}</p>`
|
||||
result.style.display = ''
|
||||
|
||||
try {
|
||||
const response = await postForm({
|
||||
action: 'code_snippets_switch_version',
|
||||
target_version: targetVersion,
|
||||
nonce: cfg.nonce_switch ?? '',
|
||||
}, cfg)
|
||||
|
||||
if (response.success) {
|
||||
result.className = 'notice notice-success'
|
||||
result.innerHTML = `<p>${response.data?.message ?? ''}</p>`
|
||||
setTimeout(() => window.location.reload(), SUCCESS_RELOAD_MS)
|
||||
return
|
||||
}
|
||||
|
||||
result.className = 'notice notice-error'
|
||||
result.innerHTML = `<p>${response.data?.message ?? getI18n('error', 'An error occurred.')}</p>`
|
||||
button.disabled = false
|
||||
button.textContent = originalText
|
||||
} catch (_err) {
|
||||
result.className = 'notice notice-error'
|
||||
result.innerHTML = `<p>${getI18n('errorSwitch', 'An error occurred while switching versions. Please try again.')}</p>`
|
||||
button.disabled = false
|
||||
button.textContent = originalText
|
||||
}
|
||||
})()
|
||||
})
|
||||
}
|
||||
|
||||
const REFRESH_RELOAD_MS = 1000
|
||||
|
||||
const bindRefresh = (
|
||||
btn: HTMLButtonElement,
|
||||
cfg: VersionConfig,
|
||||
): void => {
|
||||
btn.addEventListener('click', (): void => {
|
||||
void (async (): Promise<void> => {
|
||||
const original = btn.textContent ?? ''
|
||||
btn.disabled = true
|
||||
btn.textContent = getI18n('refreshing', 'Refreshing...')
|
||||
|
||||
try {
|
||||
await postForm({
|
||||
action: 'code_snippets_refresh_versions',
|
||||
nonce: cfg.nonce_refresh ?? '',
|
||||
}, cfg)
|
||||
|
||||
btn.textContent = getI18n('refreshed', 'Refreshed!')
|
||||
setTimeout(() => {
|
||||
btn.disabled = false
|
||||
btn.textContent = original
|
||||
window.location.reload()
|
||||
}, REFRESH_RELOAD_MS)
|
||||
} catch {
|
||||
btn.disabled = false
|
||||
btn.textContent = original
|
||||
}
|
||||
})()
|
||||
})
|
||||
}
|
||||
|
||||
export const initVersionSwitch = (): void => {
|
||||
const cfg = getConfig()
|
||||
const currentVersion = getCurrentVersion()
|
||||
|
||||
const button = <HTMLButtonElement | null> el('switch-version-btn')
|
||||
const dropdown = <HTMLSelectElement | null> el('target_version')
|
||||
const result = <HTMLDivElement | null> el('version-switch-result')
|
||||
const refreshBtn = <HTMLButtonElement | null> el('refresh-versions-btn')
|
||||
|
||||
if (dropdown) {
|
||||
bindDropdown(dropdown, button, currentVersion)
|
||||
}
|
||||
|
||||
if (button && dropdown && result) {
|
||||
bindSwitch(button, dropdown, result, cfg, currentVersion)
|
||||
}
|
||||
|
||||
if (refreshBtn) {
|
||||
bindRefresh(refreshBtn, cfg)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user