From 8d369f8a999fbf12648b4a7db4df637d0d5ddb5f Mon Sep 17 00:00:00 2001 From: Imants Date: Tue, 14 Jul 2026 20:05:44 +0300 Subject: [PATCH 01/49] feat: align whats new page typography with admin shell --- src/css/welcome.scss | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/css/welcome.scss b/src/css/welcome.scss index 9d4c0702c..9e83aa4d6 100644 --- a/src/css/welcome.scss +++ b/src/css/welcome.scss @@ -7,10 +7,12 @@ $breakpoint: 1060px; .code-snippets-welcome { + // Page and section titles follow the shared page-header typography. h2 { - font-size: 1.5rem; - font-weight: 400; - line-height: 1.3; + font-size: 32px; + font-weight: 510; + line-height: 1.25; + color: #2c3337; padding: 0; margin-block: 50px 1rem; } From 657582bd279f4a14c619c7b3b7acd00c16cd645e Mon Sep 17 00:00:00 2001 From: Imants Date: Wed, 15 Jul 2026 20:50:29 +0300 Subject: [PATCH 02/49] feat: extract snippet priority input into common component --- .../ManageMenu/SnippetsTable/TableColumns.tsx | 41 ++------------ .../common/SnippetPriorityInput.tsx | 53 +++++++++++++++++++ 2 files changed, 57 insertions(+), 37 deletions(-) create mode 100644 src/js/components/common/SnippetPriorityInput.tsx diff --git a/src/js/components/ManageMenu/SnippetsTable/TableColumns.tsx b/src/js/components/ManageMenu/SnippetsTable/TableColumns.tsx index ee2c15ab6..b53f91039 100644 --- a/src/js/components/ManageMenu/SnippetsTable/TableColumns.tsx +++ b/src/js/components/ManageMenu/SnippetsTable/TableColumns.tsx @@ -1,7 +1,7 @@ import { humanTimeDiff } from '@wordpress/date' import { RawHTML } from '@wordpress/element' import { __ } from '@wordpress/i18n' -import React, { Fragment, useState } from 'react' +import React, { Fragment } from 'react' import { useSnippetsAPI } from '../../../hooks/useSnippetsAPI' import { useSnippetsList } from '../../../hooks/useSnippetsList' import { handleUnknownError } from '../../../utils/errors' @@ -9,6 +9,7 @@ import { isNetworkAdmin } from '../../../utils/screen' import { getSnippetDisplayName, getSnippetEditUrl, getSnippetType } from '../../../utils/snippets/snippets' import { buildUrl } from '../../../utils/urls' import { Badge } from '../../common/Badge' +import { SnippetPriorityInput } from '../../common/SnippetPriorityInput' import { Tooltip } from '../../common/Tooltip' import { RowActions } from './RowActions' import { useFilteredSnippets } from './WithFilteredSnippetsContext' @@ -153,42 +154,8 @@ export const DateColumn: React.FC = ({ snippet }) => : <>— -export const PriorityColumn: React.FC = ({ snippet }) => { - const [value, setValue] = useState(snippet.priority) - const snippetsAPI = useSnippetsAPI() - const { refreshSnippetsList } = useSnippetsList() - const id = `snippet-${snippet.id}-priority` - - const handleUpdate = () => { - snippetsAPI.update({ ...snippet, priority: value }) - .then(response => { - if (response.id === snippet.id) { - setValue(response.priority) - } - }) - .then(refreshSnippetsList) - .catch(handleUnknownError) - } - - return ( -
{ - event.preventDefault() - handleUpdate() - }}> - setValue(Number(event.target.value))} - disabled={snippet.locked || snippet.trashed} - /> -
- ) -} +export const PriorityColumn: React.FC = ({ snippet }) => + const baseTableColumns: ListTableColumn[] = [ { diff --git a/src/js/components/common/SnippetPriorityInput.tsx b/src/js/components/common/SnippetPriorityInput.tsx new file mode 100644 index 000000000..15a48c7c0 --- /dev/null +++ b/src/js/components/common/SnippetPriorityInput.tsx @@ -0,0 +1,53 @@ +import { __ } from '@wordpress/i18n' +import React, { useState } from 'react' +import { useSnippetsAPI } from '../../hooks/useSnippetsAPI' +import { useSnippetsList } from '../../hooks/useSnippetsList' +import { handleUnknownError } from '../../utils/errors' +import type { Snippet } from '../../types/Snippet' + +export interface SnippetPriorityInputProps { + snippet: Snippet + inputId?: string +} + +/** + * Inline number field for updating a snippet's priority, saving on blur or + * form submission. Shared between the snippets list table, card kebab menu, + * and preview modal. Pass `inputId` to keep element IDs unique when the same + * snippet is rendered in more than one place at once. + */ +export const SnippetPriorityInput: React.FC = ({ snippet, inputId }) => { + const [value, setValue] = useState(snippet.priority) + const snippetsAPI = useSnippetsAPI() + const { refreshSnippetsList } = useSnippetsList() + + const handleUpdate = () => { + snippetsAPI.update({ ...snippet, priority: value }) + .then(response => { + if (response.id === snippet.id) { + setValue(response.priority) + } + }) + .then(refreshSnippetsList) + .catch(handleUnknownError) + } + + return ( +
{ + event.preventDefault() + handleUpdate() + }}> + setValue(Number(event.target.value))} + disabled={snippet.locked || snippet.trashed} + /> +
+ ) +} From 721ec5d53774ea447134ad89ba321b2eb866b2dd Mon Sep 17 00:00:00 2001 From: Imants Date: Wed, 15 Jul 2026 20:50:45 +0300 Subject: [PATCH 03/49] feat: add snippet actions footer to preview modal --- .../SnippetsTable/ManageSnippetCard.tsx | 1 + .../ManageMenu/SnippetsTable/RowActions.tsx | 1 + .../components/common/SnippetPreviewModal.tsx | 123 ++++++++++++++++-- 3 files changed, 116 insertions(+), 9 deletions(-) diff --git a/src/js/components/ManageMenu/SnippetsTable/ManageSnippetCard.tsx b/src/js/components/ManageMenu/SnippetsTable/ManageSnippetCard.tsx index 055fa2415..2dcb7524c 100644 --- a/src/js/components/ManageMenu/SnippetsTable/ManageSnippetCard.tsx +++ b/src/js/components/ManageMenu/SnippetsTable/ManageSnippetCard.tsx @@ -42,6 +42,7 @@ const CardPreviewButton: React.FC = ({ snippet }) => { type={getSnippetType(snippet)} isOpen={isPreviewOpen} setIsOpen={setIsPreviewOpen} + snippet={snippet} /> ) diff --git a/src/js/components/ManageMenu/SnippetsTable/RowActions.tsx b/src/js/components/ManageMenu/SnippetsTable/RowActions.tsx index f55ce5689..9bf2d4298 100644 --- a/src/js/components/ManageMenu/SnippetsTable/RowActions.tsx +++ b/src/js/components/ManageMenu/SnippetsTable/RowActions.tsx @@ -32,6 +32,7 @@ const PreviewLink: React.FC = ({ snippet }) => { type={getSnippetType(snippet)} isOpen={isPreviewOpen} setIsOpen={setIsPreviewOpen} + snippet={snippet} /> ) diff --git a/src/js/components/common/SnippetPreviewModal.tsx b/src/js/components/common/SnippetPreviewModal.tsx index 751236a93..a9177852b 100644 --- a/src/js/components/common/SnippetPreviewModal.tsx +++ b/src/js/components/common/SnippetPreviewModal.tsx @@ -1,9 +1,18 @@ -import React, { useEffect, useRef } from 'react' import { Modal } from '@wordpress/components' import { __ } from '@wordpress/i18n' +import React, { useEffect, useRef } from 'react' +import { useSnippetsAPI } from '../../hooks/useSnippetsAPI' +import { useSnippetsList } from '../../hooks/useSnippetsList' +import { handleUnknownError } from '../../utils/errors' +import { downloadSnippetExportFile } from '../../utils/files' +import { cloneSnippetObject, getSnippetEditUrl } from '../../utils/snippets/snippets' import { Badge } from './Badge' +import { Button } from './Button' +import { useDeleteSnippet } from './DeleteButton' +import { SnippetPriorityInput } from './SnippetPriorityInput' import type { BadgeName } from './Badge' import type { EditorFromTextArea } from 'codemirror' +import type { Snippet } from '../../types/Snippet' export interface SnippetPreviewModalProps { title: string @@ -11,6 +20,7 @@ export interface SnippetPreviewModalProps { type: string isOpen: boolean setIsOpen: (isOpen: boolean) => void + snippet?: Snippet } // Mirrors the type-to-mode mapping used by the live editor in SnippetTypeInput. @@ -21,17 +31,106 @@ const EDITOR_MODES: Record = { html: 'application/x-httpd-php' } +interface SnippetPreviewActionsProps { + snippet: Snippet + closeModal: () => void +} + +interface SnippetPreviewButtonsProps extends SnippetPreviewActionsProps { + requestDelete: () => void +} + +const SnippetPreviewButtons: React.FC = ({ snippet, closeModal, requestDelete }) => { + const api = useSnippetsAPI() + const { refreshSnippetsList } = useSnippetsList() + + return ( +
+ {snippet.locked + ? null + : } + + + + + + + {snippet.locked ? __('View', 'code-snippets') : __('Edit', 'code-snippets')} + +
+ ) +} + +/** + * Footer action bar for previews of local snippets. Requires the snippets API + * and snippets list contexts, so it is only rendered when the modal receives a + * full snippet object rather than bare title/code/type values. + */ +const SnippetPreviewActions: React.FC = ({ snippet, closeModal }) => { + const { refreshSnippetsList } = useSnippetsList() + const priorityInputId = `snippet-${snippet.id}-preview-priority` + + const { requestDelete, confirmDialog } = useDeleteSnippet({ + snippet, + onSuccess: () => { + closeModal() + return refreshSnippetsList() + }, + onError: handleUnknownError + }) + + return ( +
+
+ + +
+ + + + {confirmDialog} +
+ ) +} + /** * Modal for quickly viewing a snippet's code in a read-only CodeMirror editor, * without navigating to the edit page. Shared between local snippets and cloud - * snippet previews. + * snippet previews. Passing a full snippet object adds a footer of snippet + * actions, which requires the snippets API and snippets list contexts. */ export const SnippetPreviewModal: React.FC = ({ title, code, type, isOpen, - setIsOpen + setIsOpen, + snippet }) => { const textareaRef = useRef(null) @@ -65,12 +164,18 @@ export const SnippetPreviewModal: React.FC = ({ } > -