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:
OpenCode
2026-05-29 19:07:56 +08:00
commit 09ef1f000f
6521 changed files with 867163 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
import { useMemo } from 'react'
import axios from 'axios'
import type { AxiosInstance, AxiosResponse, CreateAxiosDefaults } from 'axios'
export interface AxiosAPI {
get: <T>(url: string) => Promise<AxiosResponse<T, never>>
post: <T, D>(url: string, data?: D) => Promise<AxiosResponse<T, D>>
del: <T>(url: string) => Promise<AxiosResponse<T, never>>
axiosInstance: AxiosInstance
}
const debugRequest = async <T, D = never>(
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
url: string,
doRequest: Promise<AxiosResponse<T, D>>,
data?: D
): Promise<AxiosResponse<T, D>> => {
console.debug(`${method} ${url}`, ...data ? [data] : [])
const response = await doRequest
console.debug('Response', response)
return response
}
export const useAxios = (defaultConfig: CreateAxiosDefaults): AxiosAPI => {
const axiosInstance = useMemo(() => axios.create(defaultConfig), [defaultConfig])
return useMemo((): AxiosAPI => ({
get: <T>(url: string): Promise<AxiosResponse<T, never>> =>
debugRequest('GET', url, axiosInstance.get<T, AxiosResponse<T, never>, never>(url)),
post: <T, D>(url: string, data?: D) =>
debugRequest('POST', url, axiosInstance.post<T, AxiosResponse<T, D>, D>(url, data), data),
del: <T>(url: string) =>
debugRequest('DELETE', url, axiosInstance.delete<T, AxiosResponse<T, never>, never>(url)),
axiosInstance
}), [axiosInstance])
}

View File

@@ -0,0 +1,87 @@
import { useMemo } from 'react'
import { useAxios } from './useAxios'
import type { AxiosResponse, CreateAxiosDefaults } from 'axios'
export interface FileUploadRequest {
files: FileList
}
export interface FileParseResponse {
snippets: ImportableSnippet[]
total_count: number
message: string
warnings?: string[]
}
export interface ImportableSnippet {
id?: number
name: string
desc?: string
description?: string
code: string
tags?: string[]
scope?: string
source_file?: string
table_data: {
id: number | string
title: string
scope: string
tags: string
description: string
type: string
}
}
export interface SnippetImportRequest {
snippets: ImportableSnippet[]
duplicate_action: 'ignore' | 'replace' | 'skip'
network?: boolean
}
export interface SnippetImportResponse {
imported: number
imported_ids: number[]
message: string
}
const ROUTE_BASE = `${window.CODE_SNIPPETS?.restAPI.base}code-snippets/v1/`
const AXIOS_CONFIG: CreateAxiosDefaults = {
headers: { 'X-WP-Nonce': window.CODE_SNIPPETS?.restAPI.nonce }
}
export interface FileUploadAPI {
parseFiles: (request: FileUploadRequest) => Promise<AxiosResponse<FileParseResponse>>
importSnippets: (request: SnippetImportRequest) => Promise<AxiosResponse<SnippetImportResponse>>
}
export const useFileUploadAPI = (): FileUploadAPI => {
const { axiosInstance } = useAxios(AXIOS_CONFIG)
return useMemo((): FileUploadAPI => ({
parseFiles: (request: FileUploadRequest) => {
const formData = new FormData()
for (let i = 0; i < request.files.length; i++) {
formData.append('files[]', request.files[i])
}
return axiosInstance.post<FileParseResponse>(
`${ROUTE_BASE}file-upload/parse`,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
}
}
)
},
importSnippets: (request: SnippetImportRequest) => {
return axiosInstance.post<SnippetImportResponse>(
`${ROUTE_BASE}file-upload/import`,
request
)
}
}), [axiosInstance])
}

View File

@@ -0,0 +1,52 @@
import { useMemo } from 'react'
import { useAxios } from './useAxios'
import type { AxiosResponse, CreateAxiosDefaults } from 'axios'
export interface Importer {
name: string
title: string
is_active: boolean
}
export interface ImportableSnippet {
id: number
title: string
table_data: {
id: number
title: string
}
}
export interface ImportRequest {
ids: number[]
network?: boolean
auto_add_tags?: boolean
tag_value?: string
}
export interface ImportResponse {
imported: number[]
}
const ROUTE_BASE = `${window.CODE_SNIPPETS?.restAPI.base}code-snippets/v1/`
const AXIOS_CONFIG: CreateAxiosDefaults = {
headers: { 'X-WP-Nonce': window.CODE_SNIPPETS?.restAPI.nonce }
}
export interface ImportersAPI {
fetchAll: () => Promise<AxiosResponse<Importer[]>>
fetchSnippets: (importerName: string) => Promise<AxiosResponse<ImportableSnippet[]>>
importSnippets: (importerName: string, request: ImportRequest) => Promise<AxiosResponse<ImportResponse>>
}
export const useImportersAPI = (): ImportersAPI => {
const { get, post } = useAxios(AXIOS_CONFIG)
return useMemo((): ImportersAPI => ({
fetchAll: () => get<Importer[]>(`${ROUTE_BASE}importers`),
fetchSnippets: (importerName: string) => get<ImportableSnippet[]>(`${ROUTE_BASE}${importerName}`),
importSnippets: (importerName: string, request: ImportRequest) =>
post<ImportResponse, ImportRequest>(`${ROUTE_BASE}${importerName}/import`, request)
}), [get, post])
}

View File

@@ -0,0 +1,60 @@
import React, { useMemo } from 'react'
import axios from 'axios'
import { createContextHook } from '../utils/hooks'
import { REST_API_AXIOS_CONFIG } from '../utils/restAPI'
import { buildSnippetsAPI } from '../utils/snippets/api'
import type { SnippetsAPI } from '../utils/snippets/api'
import type { PropsWithChildren } from 'react'
import type { AxiosInstance, AxiosResponse } from 'axios'
export interface RestAPIContext {
api: RestAPI
snippetsAPI: SnippetsAPI
axiosInstance: AxiosInstance
}
export interface RestAPI {
get: <T>(url: string) => Promise<T>
post: <T>(url: string, data?: object) => Promise<T>
put: <T>(url: string, data?: object) => Promise<T>
del: <T>(url: string) => Promise<T>
}
const debugRequest = async <T, D = never>(
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
url: string,
doRequest: Promise<AxiosResponse<T, D>>,
data?: D
): Promise<T> => {
console.debug(`${method} ${url}`, ...data ? [data] : [])
const response = await doRequest
console.debug('Response', response)
return response.data
}
const buildRestAPI = (axiosInstance: AxiosInstance): RestAPI => ({
get: <T, >(url: string): Promise<T> =>
debugRequest('GET', url, axiosInstance.get<T, AxiosResponse<T, never>, never>(url)),
post: <T, >(url: string, data?: object): Promise<T> =>
debugRequest('POST', url, axiosInstance.post<T, AxiosResponse<T, typeof data>, typeof data>(url, data), data),
del: <T, >(url: string): Promise<T> =>
debugRequest('DELETE', url, axiosInstance.delete<T, AxiosResponse<T, never>, never>(url)),
put: <T, >(url: string, data?: object): Promise<T> =>
debugRequest('PUT', url, axiosInstance.put<T, AxiosResponse<T, typeof data>, typeof data>(url, data), data)
})
export const [RestAPIContext, useRestAPI] = createContextHook<RestAPIContext>('RestAPI')
export const WithRestAPIContext: React.FC<PropsWithChildren> = ({ children }) => {
const axiosInstance = useMemo(() => axios.create(REST_API_AXIOS_CONFIG), [])
const api = useMemo(() => buildRestAPI(axiosInstance), [axiosInstance])
const snippetsAPI = useMemo(() => buildSnippetsAPI(api), [api])
const value: RestAPIContext = { api, snippetsAPI, axiosInstance }
return <RestAPIContext.Provider value={value}>{children}</RestAPIContext.Provider>
}

View File

@@ -0,0 +1,69 @@
import { isAxiosError } from 'axios'
import React, { useCallback, useMemo, useState } from 'react'
import { createContextHook } from '../utils/hooks'
import { isLicensed } from '../utils/screen'
import { isProSnippet } from '../utils/snippets/snippets'
import type { Dispatch, PropsWithChildren, SetStateAction } from 'react'
import type { ScreenNotice } from '../types/ScreenNotice'
import type { Snippet } from '../types/Snippet'
import type { CodeEditorInstance } from '../types/WordPressCodeEditor'
export interface SnippetFormContext {
snippet: Snippet
isWorking: boolean
isReadOnly: boolean
setSnippet: Dispatch<SetStateAction<Snippet>>
updateSnippet: Dispatch<SetStateAction<Snippet>>
setIsWorking: Dispatch<SetStateAction<boolean>>
currentNotice: ScreenNotice | undefined
setCurrentNotice: Dispatch<SetStateAction<ScreenNotice | undefined>>
codeEditorInstance: CodeEditorInstance | undefined
handleRequestError: (error: unknown, message?: string) => void
setCodeEditorInstance: Dispatch<SetStateAction<CodeEditorInstance | undefined>>
}
export const [SnippetFormContext, useSnippetForm] = createContextHook<SnippetFormContext>('SnippetForm')
export interface WithSnippetFormContextProps extends PropsWithChildren {
initialSnippet: () => Snippet
}
export const WithSnippetFormContext: React.FC<WithSnippetFormContextProps> = ({ children, initialSnippet }) => {
const [snippet, setSnippet] = useState<Snippet>(initialSnippet)
const [isWorking, setIsWorking] = useState(false)
const [currentNotice, setCurrentNotice] = useState<ScreenNotice>()
const [codeEditorInstance, setCodeEditorInstance] = useState<CodeEditorInstance>()
const isReadOnly = useMemo(() => !isLicensed() && isProSnippet({ scope: snippet.scope }), [snippet.scope])
const handleRequestError = useCallback((error: unknown, message?: string) => {
console.error('Request failed', error)
setIsWorking(false)
setCurrentNotice(['error', [message, isAxiosError(error) ? error.message : ''].filter(Boolean).join(' ')])
}, [setIsWorking, setCurrentNotice])
const updateSnippet: Dispatch<SetStateAction<Snippet>> = useCallback((value: SetStateAction<Snippet>) => {
setSnippet(previous => {
const updated = 'object' === typeof value ? value : value(previous)
codeEditorInstance?.codemirror.setValue(updated.code)
window.tinymce?.activeEditor.setContent(updated.desc)
return updated
})
}, [codeEditorInstance?.codemirror])
const value: SnippetFormContext = {
snippet,
isWorking,
isReadOnly,
setSnippet,
setIsWorking,
updateSnippet,
currentNotice,
setCurrentNotice,
codeEditorInstance,
handleRequestError,
setCodeEditorInstance
}
return <SnippetFormContext.Provider value={value}>{children}</SnippetFormContext.Provider>
}

View File

@@ -0,0 +1,42 @@
import React, { useCallback, useEffect, useState } from 'react'
import { createContextHook } from '../utils/hooks'
import { isNetworkAdmin } from '../utils/screen'
import { useRestAPI } from './useRestAPI'
import type { PropsWithChildren } from 'react'
import type { Snippet } from '../types/Snippet'
export interface SnippetsListContext {
snippetsList: readonly Snippet[] | undefined
refreshSnippetsList: () => Promise<void>
}
const [SnippetsListContext, useSnippetsList] = createContextHook<SnippetsListContext>('SnippetsList')
export const WithSnippetsListContext: React.FC<PropsWithChildren> = ({ children }) => {
const { snippetsAPI: { fetchAll } } = useRestAPI()
const [snippetsList, setSnippetsList] = useState<Snippet[]>()
const refreshSnippetsList = useCallback(async (): Promise<void> => {
try {
console.info('Fetching snippets list')
const response = await fetchAll(isNetworkAdmin())
setSnippetsList(response)
} catch (error: unknown) {
console.error('Error fetching snippets list', error)
}
}, [fetchAll])
useEffect(() => {
refreshSnippetsList()
.catch(() => undefined)
}, [refreshSnippetsList])
const value: SnippetsListContext = {
snippetsList,
refreshSnippetsList
}
return <SnippetsListContext.Provider value={value}>{children}</SnippetsListContext.Provider>
}
export { useSnippetsList }

View File

@@ -0,0 +1,125 @@
import { __ } from '@wordpress/i18n'
import { addQueryArgs } from '@wordpress/url'
import { isAxiosError } from 'axios'
import { useCallback } from 'react'
import { createSnippetObject, isCondition } from '../utils/snippets/snippets'
import { useRestAPI } from './useRestAPI'
import { useSnippetForm } from './useSnippetForm'
import type { Snippet } from '../types/Snippet'
const snippetMessages = <const> {
addNew: __('Add New Snippet', 'code-snippets'),
edit: __('Edit Snippet', 'code-snippets'),
created: __('Snippet <strong>created</strong>.', 'code-snippets'),
updated: __('Snippet <strong>updated</strong>.', 'code-snippets'),
createdActivated: __('Snippet <strong>created</strong> and <strong>activated</strong>.', 'code-snippets'),
updatedActivated: __('Snippet <strong>updated</strong> and <strong>activated</strong>.', 'code-snippets'),
updatedDeactivated: __('Snippet <strong>updated</strong> and <strong>deactivated</strong>'),
updatedExecuted: __('Snippet <strong>updated</strong> and <strong>executed</strong>.', 'code-snippets'),
failedCreate: __('Could not create snippet.', 'code-snippets'),
failedUpdate: __('Could not update snippet.', 'code-snippets')
}
const conditionCreated = __('Condition <strong>created</strong>.', 'code-snippets')
const conditionUpdated = __('Condition <strong>updated</strong>.', 'code-snippets')
const conditionMessages: typeof snippetMessages = {
addNew: __('Add New Condition', 'code-snippets'),
edit: __('Edit Condition', 'code-snippets'),
created: conditionCreated,
updated: conditionUpdated,
createdActivated: conditionCreated,
updatedActivated: conditionUpdated,
updatedDeactivated: conditionUpdated,
updatedExecuted: conditionUpdated,
failedCreate: __('Could not create condition.', 'code-snippets'),
failedUpdate: __('Could not update condition.', 'code-snippets')
}
export enum SubmitSnippetAction {
SAVE = 'save_snippet',
SAVE_AND_ACTIVATE = 'save_snippet_activate',
SAVE_AND_EXECUTE = 'save_snippet_execute',
SAVE_AND_DEACTIVATE = 'save_snippet_deactivate'
}
const getSuccessNotice = (request: Snippet, response: Snippet, action: SubmitSnippetAction): string => {
const messages = 'condition' === request.scope ? conditionMessages : snippetMessages
const wasCreated = 0 === request.id
switch (action) {
case SubmitSnippetAction.SAVE:
return wasCreated ? messages.created : messages.updated
case SubmitSnippetAction.SAVE_AND_EXECUTE:
return messages.updatedExecuted
case SubmitSnippetAction.SAVE_AND_ACTIVATE:
if ('single-use' === response.scope) {
return messages.updatedExecuted
} else {
return wasCreated
? messages.createdActivated
: messages.updatedActivated
}
case SubmitSnippetAction.SAVE_AND_DEACTIVATE:
return messages.updatedDeactivated
}
}
const SUBMIT_ACTION_DELTA: Record<SubmitSnippetAction, Partial<Snippet>> = {
[SubmitSnippetAction.SAVE]: {},
[SubmitSnippetAction.SAVE_AND_ACTIVATE]: { active: true },
[SubmitSnippetAction.SAVE_AND_DEACTIVATE]: { active: false },
[SubmitSnippetAction.SAVE_AND_EXECUTE]: { active: true }
}
export interface UseSubmitSnippet {
submitSnippet: (action?: SubmitSnippetAction) => Promise<Snippet | undefined>
}
export const useSubmitSnippet = (): UseSubmitSnippet => {
const { snippetsAPI } = useRestAPI()
const { setIsWorking, setCurrentNotice, snippet, setSnippet } = useSnippetForm()
const submitSnippet = useCallback(async (action: SubmitSnippetAction = SubmitSnippetAction.SAVE) => {
setCurrentNotice(undefined)
const result = await (async (): Promise<Snippet | string | undefined> => {
try {
const request: Snippet = { ...snippet, ...SUBMIT_ACTION_DELTA[action] }
const response = await (0 === request.id ? snippetsAPI.create(request) : snippetsAPI.update(request))
setIsWorking(false)
return response.id ? response : undefined
} catch (error) {
setIsWorking(false)
return isAxiosError(error) ? error.message : undefined
}
})()
const messages = isCondition(snippet) ? conditionMessages : snippetMessages
if (undefined === result || 'string' === typeof result) {
const message = [
snippet.id ? messages.failedUpdate : messages.failedCreate,
result ?? __('The server did not send a valid response.', 'code-snippets')
]
setCurrentNotice(['error', message.filter(Boolean).join(' ')])
return undefined
} else {
setSnippet(createSnippetObject(result))
setCurrentNotice(['updated', getSuccessNotice(snippet, result, action)])
if (snippet.id && result.id) {
window.document.title = window.document.title.replace(snippetMessages.addNew, messages.edit)
window.history.replaceState({}, '', addQueryArgs(window.CODE_SNIPPETS?.urls.edit, { id: result.id }))
}
return result
}
}, [snippetsAPI, setIsWorking, setCurrentNotice, snippet, setSnippet])
return { submitSnippet }
}