JFIF $ d d 7 * Z qn )ZP 0 !1 A "#a$2Qr D8 a Ri [f \m I ykI w0 c u F c Rı?lO7 к _f˓ [C $殷WF< _W ԣ s Kc ëI zyQy /_LK ℂ ;C",p F A : /] =H ~ , l s / 9 ć: [=/#f ; ) x { ٛE Q )~ = 𘙲r*2~ a _V = ' ku m F D } KYYC )( { * g& f `툪 r y ` = ^ c J. I ]( * `wq 1dđ #̩ ͑ 0 ; H ] u 搂 @:~ ו K L N s h } O I R* 8 :2 ! lD JV o ( 3 = M ( z Ȱ+i *N A r6K n S l ) ! J J Ӂ* % ݉?|D } d5: eP0 R; {$X' x F @ .ÊB {,WJ uQ ɲRI ;9 Q E琯 6 2fT .DU J ;*c P A\IL Nj! J ۱+ O\ ͔ ] ޒ S J ȧ c%A N ol ՎprU L Zԛe r E2= X D Xg VQeӓ k y P7 U* o m Q I s ,K `) 6\G3t?pgjr m ۛجw luGt f h 9uyP 0D;Uڽ"OXl i f $ ) &|ML 0 Z rm1 [ H XP l P R0 'G=i 2N +0 e 2 ]] 9 VT P O 7 h(F * 癈 ' = Q VZD F,d ߬ ~ T X G[ `le 69C R(!S2! P < 0 x < !1AQ "Raq 02Br #S CTb ? Ζ "]mH5WR 7 k.ۛ ! } Q~ + yԏ z |@ T20 S~ K e k *zF f^2X *( @ 8 r ? C I uI|֓>^Ex LgNU Y+{.R Ѫ τV YT D I 6 2 '8Y2 7'\T P.6 d &˦ @V q i |8 -O Ε ] ʔ U= T L 8= ;6c | !qfF3 a ů& ~$ l} 'NW U s$U k^S V : U#6w + +s &r+n ڐ { @ 29gL u " T Ù M=6 (^ "7 r}=6Yݾ lC u hquymp Ǧ G jhs ǜNl ɻ} o 7 #S6 a w 4 ! OS r D 5 7 % | ?x> L | / nD 6?/8 w # [ ) L 7 +6〼T A T g ! %5 Mm Z /c -{1_Je "| ^$ 'O & ޱմ < H vNqϡQ$ < 6 &Nݛ ֨tǻN (9 n ru~'όN sMG& ,uH Kw *Ī 5@ u6 BԒ i YN * f 0 _ GhKW u lT <nj- 0{ M L~ | /a v\ E = ?1 \ɟuq Ђ Y O a K ޡP q $ H8o Q˼EV ~ k 0 h' L Pp M -M" m^ !6l nմ 쮝C U T . ! :vMǨb h ډ Vc r @~p # !1QAa q ? AL0L x LU 4 d ; x " 16ad0 7.\ߋ 0 = P l v/ " c A Ay A|2pO8"d MV #1] = \ " ˡ ] k v I9 d ۓ & q ) ǐq E ꞅ v "P )q X &Q З non]я u ג./ ; gZ$ M QР 2M ]h ni 0 P $ێ 3CɏGjώy : m c # S_ l@ '_ \m|C 2`K _ 1 cIvGc $? PCQ S+D ? K aײ k Fr b 8 ύ NEدg ĝ U 3 6 #Ik+ v { v i ( ވ ? a e) X xy/*@Q Ǽ Ծ ) $ IWO l c G; !i k{ gOŀ L ('x T rb $ w) R $ & N1E td U3Uȉ1p M"N* (DNy d96.(jQ) X 5 c QɎMy W ? Q *!R > 6=7)Xj 5`J ] e 8%t ! + ' ! 1Q 5 !1 AQaq ё #2 "0BRb ? G t^ ## .l lQ T $ v , ,m 㵜5u bV =sY+ @d { N ! dn O < .- B; _w J t 6;Q Jd. Q c%p{ 1 ,sND dF H I0 Гo X ш e黅X ۢ F :)[ F G X ƹ / w _c MeD ,ʡc c . W D tA $ j@ : ) - #u c1< @ۗ9F ) KJ- hpP] _ x [ q B lb p ʖ w q " LFGd ƶ * s +ډ_Zc " ? % t[I P 6 J ]# =ɺ V v v CGs Gh1 > )6 |e y ? L ӣ m,4 G W Ui ` ] uJVo VDG< SB 6ϏQ @ TiU l y O U 0 kfV ~ ~}S Z @ *WU Ui ## ; s /[ = ! 7}" W N ] ' (L ! ~ y5 g9T̅J kbM' +s :S +B) v @ M j e C f j E 0 Y\Qn z G 1 д~ W o{T 9?` Rm yhsy3 !HAD ] m c1 ~ 2 L S u7x T ;j$ `} 4-> L# vzŏ I L S ֭ T { r jGK C ; b pU= - ` Bs K. S F w4 M q] Z dH S 0 ) tLg
import { getSnippetType } from '../utils/snippets/snippets'
import type { SnippetScope } from '../types/Snippet'
export type PaginationStatus = 'active' | 'inactive'
export type PaginationAction = 'first' | 'prev' | 'next' | 'last'
export interface SnippetResponseItem {
id: number
scope: SnippetScope
name?: string
}
export interface AdminBarConfig {
restUrl: string
nonce: string
perPage: number
isNetwork: boolean
excludeTypes: string[]
snippetPlaceholder: string
editUrlBase: string
activeNodeId: string
inactiveNodeId: string
}
declare const CODE_SNIPPETS_ADMIN_BAR: AdminBarConfig | undefined
const config = 'undefined' === typeof CODE_SNIPPETS_ADMIN_BAR ? undefined : CODE_SNIPPETS_ADMIN_BAR
const getMenuNode = (status: PaginationStatus): HTMLElement | null => {
if (!config) {
return null
}
const nodeId = 'active' === status ? config.activeNodeId : config.inactiveNodeId
return document.getElementById(nodeId)
}
const getPaginationControls = (status: PaginationStatus): HTMLElement | null => {
const menuNode = getMenuNode(status)
if (!menuNode) {
return null
}
return menuNode.querySelector<HTMLElement>(`.code-snippets-pagination-controls[data-status="${status}"]`)
}
const getPaginationState = (controls: HTMLElement): { page: number; totalPages: number } => {
const page = Number.parseInt(controls.dataset.page ?? '1', 10)
const totalPages = Number.parseInt(controls.dataset.totalPages ?? '1', 10)
return {
page: Number.isFinite(page) && 0 < page ? page : 1,
totalPages: Number.isFinite(totalPages) && 0 < totalPages ? totalPages : 1
}
}
const setLoading = (controls: HTMLElement, loading: boolean) => {
controls.dataset.loading = loading ? 'true' : 'false'
}
const buildRequestUrl = (status: PaginationStatus, page: number): string => {
if (!config) {
return ''
}
const url = new URL(config.restUrl)
url.searchParams.set('status', status)
url.searchParams.set('page', String(page))
url.searchParams.set('per_page', String(config.perPage))
url.searchParams.set('orderby', 'display_name')
url.searchParams.set('order', 'asc')
if (config.isNetwork) {
url.searchParams.set('network', '1')
}
for (const excluded of config.excludeTypes) {
url.searchParams.append('exclude_types[]', excluded)
}
return url.toString()
}
const fetchSnippetsPage = async (status: PaginationStatus, page: number) => {
if (!config) {
throw new Error('Missing CODE_SNIPPETS_ADMIN_BAR config')
}
const response = await fetch(buildRequestUrl(status, page), {
credentials: 'same-origin',
headers: {
'X-WP-Nonce': config.nonce
}
})
if (!response.ok) {
throw new Error(`Failed to fetch snippets (${response.status})`)
}
const totalPagesHeader = response.headers.get('X-WP-TotalPages') ?? '1'
const totalPages = Number.parseInt(totalPagesHeader, 10) || 1
const snippets = <SnippetResponseItem[]> await response.json()
return { snippets, totalPages }
}
const buildEditUrl = (snippetId: number): string => {
if (!config) {
return '#'
}
try {
const url = new URL(config.editUrlBase, window.location.href)
url.searchParams.set('id', String(snippetId))
return url.toString()
} catch {
return '#'
}
}
const buildSnippetPlaceholder = (snippetId: number): string =>
config?.snippetPlaceholder.replace(/%(?:\d+\$)?d/, String(snippetId)) ??
`Snippet #${snippetId}`
const formatSnippetTitle = (snippet: SnippetResponseItem): string => {
const typeLabel = getSnippetType(snippet).toUpperCase()
const name = snippet.name?.trim()
const title = ('' === name ? undefined : name) ?? buildSnippetPlaceholder(snippet.id)
return `(${typeLabel}) ${title}`
}
const setControlsLinkDisabled = (controls: HTMLElement, action: PaginationAction, disabled: boolean): void => {
const link = controls.querySelector<HTMLAnchorElement>(`a[data-action="${action}"]`)
if (link) {
link.setAttribute('aria-disabled', disabled ? 'true' : 'false')
}
}
const updatePaginationHrefs = (controls: HTMLElement, page: number, totalPages: number, queryArg?: string): void => {
if (!queryArg) {
return
}
const firstLink = controls.querySelector<HTMLAnchorElement>('a[data-action="first"]')
const baseHref = firstLink?.href
if (!baseHref) {
return
}
const buildHref = (targetPage: number) => {
const url = new URL(baseHref)
if (1 >= targetPage) {
url.searchParams.delete(queryArg)
} else {
url.searchParams.set(queryArg, String(targetPage))
}
return url.toString()
}
const getLink = (action: PaginationAction) =>
controls.querySelector<HTMLAnchorElement>(`a[data-action="${action}"]`)
const first = getLink('first')
if (first) {
first.href = buildHref(1)
}
const prev = getLink('prev')
if (prev) {
prev.href = buildHref(Math.max(1, page - 1))
}
const next = getLink('next')
if (next) {
next.href = buildHref(Math.min(totalPages, page + 1))
}
const last = getLink('last')
if (last) {
last.href = buildHref(totalPages)
}
}
const updatePaginationControls = (controls: HTMLElement, page: number, totalPages: number) => {
controls.dataset.page = String(page)
controls.dataset.totalPages = String(totalPages)
const pageLabel = controls.querySelector<HTMLElement>('.code-snippets-pagination-page')
if (pageLabel) {
pageLabel.textContent = pageLabel.textContent?.replace(/\(\d+\/\d+\)/, `(${page}/${totalPages})`) ?? pageLabel.textContent
}
const disableFirstPrev = 1 >= page
const disableNextLast = page >= totalPages
setControlsLinkDisabled(controls, 'first', disableFirstPrev)
setControlsLinkDisabled(controls, 'prev', disableFirstPrev)
setControlsLinkDisabled(controls, 'next', disableNextLast)
setControlsLinkDisabled(controls, 'last', disableNextLast)
updatePaginationHrefs(controls, page, totalPages, controls.dataset.queryArg)
}
const replaceSnippetItems = (status: PaginationStatus, snippets: SnippetResponseItem[]) => {
const menuNode = getMenuNode(status)
if (!menuNode) {
return
}
const subMenu = menuNode.querySelector<HTMLUListElement>('ul.ab-submenu')
if (!subMenu) {
return
}
subMenu.querySelectorAll('li.code-snippets-snippet-item').forEach(node => node.remove())
const insertAfterId = 'active' === status
? 'wp-admin-bar-code-snippets-active-pagination'
: 'wp-admin-bar-code-snippets-inactive-pagination'
const insertAfter = subMenu.querySelector<HTMLLIElement>(`#${insertAfterId}`)
const fragment = document.createDocumentFragment()
for (const snippet of snippets) {
const li = document.createElement('li')
li.id = `wp-admin-bar-code-snippets-snippet-${snippet.id}`
li.className = 'code-snippets-snippet-item'
const a = document.createElement('a')
a.className = 'ab-item'
a.href = buildEditUrl(snippet.id)
a.textContent = formatSnippetTitle(snippet)
li.appendChild(a)
fragment.appendChild(li)
}
if (insertAfter?.parentNode === subMenu) {
subMenu.insertBefore(fragment, insertAfter.nextSibling)
} else {
subMenu.appendChild(fragment)
}
}
const navigateToPage = async (status: PaginationStatus, targetPage: number) => {
const controls = getPaginationControls(status)
if (!controls) {
return
}
const { totalPages: currentTotalPages } = getPaginationState(controls)
const page = Math.max(1, Math.min(targetPage, currentTotalPages))
setLoading(controls, true)
try {
const { snippets, totalPages } = await fetchSnippetsPage(status, page)
updatePaginationControls(controls, page, totalPages)
replaceSnippetItems(status, snippets)
} catch (error) {
console.error(error)
} finally {
setLoading(controls, false)
}
}
const handlePaginationClick = (event: MouseEvent) => {
const target = <Element | null> event.target
if (!target) {
return
}
const link = target.closest<HTMLAnchorElement>('.code-snippets-pagination-controls a[data-action]')
if (!link) {
return
}
const controls = link.closest<HTMLElement>('.code-snippets-pagination-controls')
if (!controls) {
return
}
if ('true' === controls.dataset.loading) {
return
}
const status = <PaginationStatus | undefined> controls.dataset.status
const action = <PaginationAction | undefined> link.dataset.action
if (!status || !action) {
return
}
event.preventDefault()
event.stopPropagation()
event.stopImmediatePropagation()
const { page, totalPages } = getPaginationState(controls)
let targetPage = page
switch (action) {
case 'first':
targetPage = 1
break
case 'prev':
targetPage = page - 1
break
case 'next':
targetPage = page + 1
break
case 'last':
targetPage = totalPages
break
}
if (targetPage === page || 1 > targetPage || targetPage > totalPages) {
return
}
void navigateToPage(status, targetPage)
}
if (config) {
document.addEventListener('click', handlePaginationClick, true)
}