PluginProbe
Code Snippets / trunk
Code Snippets vtrunk
4.0.0-beta.2 3.10.2 3.10.1 3.10.0 3.10.0-beta.2 3.10.0-beta.1 4.0.0-beta.1 3.9.6 trunk 2.10.0 2.10.1 2.12.0 2.12.1 2.13.0 2.13.1 2.13.2 2.13.3 2.14.0 2.14.1 2.14.2 2.14.3 2.14.4 2.14.5 2.14.6 3.0.0 All 65 releases
code-snippets / js / components / ManageMenu / SnippetsTable / TableColumns.tsx

TableColumns.tsx in Code Snippets trunk, at js/components/ManageMenu/SnippetsTable/TableColumns.tsx

264 lines 8.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { humanTimeDiff } from '@wordpress/date'
2 import { RawHTML } from '@wordpress/element'
3 import { __, sprintf } from '@wordpress/i18n'
4 import React, { Fragment } from 'react'
5 import { useSnippetsAPI } from '../../../hooks/useSnippetsAPI'
6 import { useSnippetsList } from '../../../hooks/useSnippetsList'
7 import { handleUnknownError } from '../../../utils/errors'
8 import { isNetworkAdmin } from '../../../utils/screen'
9 import { getSnippetDisplayName, getSnippetEditUrl, getSnippetType } from '../../../utils/snippets/snippets'
10 import { getRunOnceNonce } from '../../../utils/restAPI'
11 import { buildUrl } from '../../../utils/urls'
12 import { Badge } from '../../common/Badge'
13 import { SnippetPriorityInput } from '../../common/snippets/SnippetPriorityInput'
14 import { Tooltip } from '../../common/Tooltip'
15 import { RowActions } from './RowActions'
16 import { useFilteredSnippets } from './WithFilteredSnippetsContext'
17 import { useSnippetsFilters } from './WithSnippetsTableFilters'
18 import type { Key } from 'react'
19 import type { Snippet } from '../../../types/Snippet'
20 import type { ListTableColumn } from '../../common/ListTable'
21
22 interface ColumnProps {
23 snippet: Snippet
24 }
25
26 const runOnceUrl = (snippet: Snippet, nonce: string): string =>
27 buildUrl(window.location.href, {
28 action: 'run-once',
29 snippet: snippet.id,
30 network: snippet.network ? 'true' : 'false',
31 _wpnonce: nonce
32 })
33
34 // The rendered link carries the nonce from page load. Before any navigation
35 // starts, whether a click, a middle-click or "open in new tab", the href is
36 // rebuilt from the nonce the Heartbeat has refreshed since, so a page left open
37 // still works.
38 const RunOnceButton: React.FC<ColumnProps> = ({ snippet }) =>
39 <a
40 className="snippet-execution-button"
41 title={__('Run Once', 'code-snippets')}
42 href={runOnceUrl(snippet, window.CODE_SNIPPETS_MANAGE?.runOnceNonce ?? '')}
43 onMouseDown={event => {
44 event.currentTarget.href = runOnceUrl(snippet, getRunOnceNonce())
45 }}
46 onClick={event => {
47 event.preventDefault()
48 window.location.assign(runOnceUrl(snippet, getRunOnceNonce()))
49 }}
50 >
51 <span className="screen-reader-text">{__('Run Once', 'code-snippets')}</span>
52 <span aria-hidden="true">&nbsp;</span>
53 </a>
54
55 const ActivationSwitch: React.FC<ColumnProps> = ({ snippet }) => {
56 const { activate, deactivate } = useSnippetsAPI()
57 const { refreshSnippetsList } = useSnippetsList()
58
59 const actionText = snippet.network && !snippet.shared_network
60 ? snippet.active ? __('Network Deactivate', 'code-snippets') : __('Network Activate', 'code-snippets')
61 : snippet.active ? __('Deactivate', 'code-snippets') : __('Activate', 'code-snippets')
62
63 return (
64 <input
65 id={`snippet-${snippet.id}-switch`}
66 type="checkbox"
67 role="switch"
68 checked={snippet.active}
69 aria-checked={snippet.active}
70 className="switch"
71 title={actionText}
72 aria-label={actionText}
73 onChange={() => {
74 (snippet.active ? deactivate(snippet) : activate(snippet))
75 .then(refreshSnippetsList)
76 .catch(handleUnknownError)
77 }}
78 />
79 )
80 }
81
82 export const ActivateColumn: React.FC<ColumnProps> = ({ snippet }) => {
83 const { activeByCondition } = useFilteredSnippets()
84
85 if (snippet.trashed) {
86 return ''
87 }
88
89 switch (snippet.scope) {
90 case 'single-use':
91 return <RunOnceButton snippet={snippet} />
92
93 case 'condition':
94 return (
95 <a className="snippet-condition-count" href={getSnippetEditUrl(snippet)}>
96 {activeByCondition.get(snippet.id)?.length ?? 0}
97 </a>
98 )
99
100 default:
101 return <ActivationSwitch snippet={snippet} />
102 }
103 }
104
105 export const SnippetExtraIcons: React.FC<ColumnProps> = ({ snippet }) =>
106 <div className="extra-icons">
107 {snippet.locked && (
108 <Tooltip
109 inline
110 end
111 label={__('About snippet lock', 'code-snippets')}
112 icon={<span className="dashicons dashicons-lock" aria-hidden="true"></span>}
113 >
114 {__('This snippet is locked and cannot be modified.', 'code-snippets')}
115 </Tooltip>)}
116 </div>
117
118 export const SnippetName: React.FC<ColumnProps> = ({ snippet }) =>
119 <span className="snippet-name-group">
120 {!snippet.trashed && (isNetworkAdmin() || !snippet.network || window.CODE_SNIPPETS_MANAGE?.hasNetworkCap)
121 ? <a
122 href={getSnippetEditUrl(snippet)}
123 className="snippet-name"
124 title={getSnippetDisplayName(snippet)}
125 >
126 {getSnippetDisplayName(snippet)}
127 </a>
128 : <span className="snippet-name" title={getSnippetDisplayName(snippet)}>
129 {getSnippetDisplayName(snippet)}
130 </span>}
131
132 {snippet.shared_network && <span className="badge">{__('Shared on Network', 'code-snippets')}</span>}
133 </span>
134
135 const NameColumn: React.FC<ColumnProps> = ({ snippet }) =>
136 <>
137 <SnippetExtraIcons snippet={snippet} />
138 <SnippetName snippet={snippet} />
139 <RowActions snippet={snippet} />
140 </>
141
142 export const TypeColumn: React.FC<ColumnProps> = ({ snippet }) => {
143 const { setCurrentType } = useSnippetsFilters()
144 const type = getSnippetType(snippet)
145
146 return (
147 <a
148 href={buildUrl(window.location.href, { type })}
149 onClick={event => {
150 event.preventDefault()
151 setCurrentType(type)
152 }}
153 >
154 <Badge name={type} />
155 </a>
156 )
157 }
158
159 export const TagsColumn: React.FC<ColumnProps> = ({ snippet }) =>
160 snippet.tags.map((tag, index) =>
161 <Fragment key={tag}>
162 <a key={tag} href={buildUrl(window.location.href, { tag })}>
163 {tag}
164 </a>
165 {index < snippet.tags.length - 1 ? ', ' : ''}
166 </Fragment>)
167
168 export const DateColumn: React.FC<ColumnProps> = ({ snippet }) =>
169 snippet.modified
170 ? <span className="modified-column-content" title={snippet.modified}>
171 <time dateTime={snippet.modified}>
172 {humanTimeDiff(snippet.modified, undefined)}
173 </time>
174 </span>
175 : <>&#8212;</>
176
177 export const PriorityColumn: React.FC<ColumnProps> = ({ snippet }) =>
178 <SnippetPriorityInput snippet={snippet} />
179
180 export const getAuthorDisplayName = (snippet: Snippet): string =>
181 (snippet.updatedBy ?? snippet.createdBy)?.displayName ?? ''
182
183 const AuthorColumn: React.FC<ColumnProps> = ({ snippet }) => {
184 const creator = snippet.createdBy
185 const updater = snippet.updatedBy
186 const primary = updater ?? creator
187
188 if (!primary) {
189 return <span className="snippet-author is-unknown">&#8212;</span>
190 }
191
192 const tooltip = creator && updater && creator.id !== updater.id
193 ? sprintf(__('Created by %1$s ยท Last edited by %2$s', 'code-snippets'), creator.displayName, updater.displayName)
194 : creator
195 ? sprintf(__('Created by %s', 'code-snippets'), creator.displayName)
196 : sprintf(__('Last edited by %s', 'code-snippets'), primary.displayName)
197
198 return (
199 <span className="snippet-author" title={tooltip}>
200 {primary.avatarUrl &&
201 <img className="snippet-author__avatar" src={primary.avatarUrl} alt="" width={20} height={20} />}
202 <span className="snippet-author__name">{primary.displayName}</span>
203 </span>
204 )
205 }
206
207 const baseTableColumns: ListTableColumn<Snippet>[] = [
208 {
209 id: 'activate',
210 title: <span className="screen-reader-text">{__('Activate', 'code-snippets')}</span>,
211 render: snippet => <ActivateColumn snippet={snippet} />
212 },
213 {
214 id: 'id',
215 title: __('ID', 'code-snippets'),
216 sortedValue: snippet => snippet.id,
217 render: snippet => snippet.id
218 },
219 {
220 id: 'name',
221 title: __('Name', 'code-snippets'),
222 isPrimary: true,
223 sortedValue: snippet => getSnippetDisplayName(snippet).toLowerCase(),
224 render: snippet => <NameColumn snippet={snippet} />
225 },
226 {
227 id: 'type',
228 title: __('Type', 'code-snippets'),
229 sortedValue: snippet => getSnippetType(snippet),
230 render: snippet => <TypeColumn snippet={snippet} />
231 },
232 {
233 id: 'desc',
234 title: __('Description', 'code-snippets'),
235 render: snippet => <div className="snippet-description-content"><RawHTML>{snippet.desc}</RawHTML></div>
236 },
237 {
238 id: 'tags',
239 title: __('Tags', 'code-snippets'),
240 render: snippet => <TagsColumn snippet={snippet} />
241 },
242 {
243 id: 'date',
244 title: __('Modified', 'code-snippets'),
245 sortedValue: snippet => snippet.modified ? new Date(snippet.modified).toISOString() : '',
246 render: snippet => <DateColumn snippet={snippet} />
247 },
248 {
249 id: 'priority',
250 title: __('Priority', 'code-snippets'),
251 sortedValue: snippet => snippet.priority,
252 render: snippet => <SnippetPriorityInput snippet={snippet} />
253 },
254 {
255 id: 'author',
256 title: __('Author', 'code-snippets'),
257 sortedValue: snippet => getAuthorDisplayName(snippet).toLowerCase(),
258 render: snippet => <AuthorColumn snippet={snippet} />
259 }
260 ]
261
262 export const getTableColumns = (hiddenColumns: Set<Key>): ListTableColumn<Snippet>[] =>
263 baseTableColumns.map(column => ({ ...column, isHidden: hiddenColumns.has(column.id) }))
264