PluginProbe
Code Snippets / 4.0.0-beta.1
Code Snippets v4.0.0-beta.1
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 4.0.0-beta.1, at js/components/ManageMenu/SnippetsTable/TableColumns.tsx

298 lines 8.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import classnames from 'classnames'
2 import React, { Fragment, useState } from 'react'
3 import { __ } from '@wordpress/i18n'
4 import { humanTimeDiff } from '@wordpress/date'
5 import { RawHTML } from '@wordpress/element'
6 import { useSnippetsAPI } from '../../../hooks/useSnippetsAPI'
7 import { useSnippetsList } from '../../../hooks/useSnippetsList'
8 import { handleUnknownError } from '../../../utils/errors'
9 import { downloadSnippetExportFile } from '../../../utils/files'
10 import { isNetworkAdmin } from '../../../utils/screen'
11 import { cloneSnippetObject, getSnippetDisplayName, getSnippetEditUrl, getSnippetType } from '../../../utils/snippets/snippets'
12 import { buildUrl } from '../../../utils/urls'
13 import { Badge } from '../../common/Badge'
14 import { Button } from '../../common/Button'
15 import { DeleteButton } from '../../common/DeleteButton'
16 import { Tooltip } from '../../common/Tooltip'
17 import { useSnippetsFilters } from './WithSnippetsTableFilters'
18 import { useFilteredSnippets } from './WithFilteredSnippetsContext'
19 import type { Key, ReactNode } from 'react'
20 import type { Snippet } from '../../../types/Snippet'
21 import type { ListTableColumn } from '../../common/ListTable'
22
23 interface ColumnProps {
24 snippet: Snippet
25 }
26
27 const RunOnceButton: React.FC<ColumnProps> = ({ snippet }) =>
28 <a
29 className="snippet-execution-button"
30 title={__('Run Once', 'code-snippets')}
31 href={buildUrl(window.location.href, { action: 'run-once', snippet: snippet.id })}
32 >
33 <span className="screen-reader-text">{__('Run Once', 'code-snippets')}</span>
34 <span aria-hidden="true">&nbsp;</span>
35 </a>
36
37 const ActivationSwitch: React.FC<ColumnProps> = ({ snippet }) => {
38 const { activate, deactivate } = useSnippetsAPI()
39 const { refreshSnippetsList } = useSnippetsList()
40
41 const actionText = snippet.network && !snippet.shared_network
42 ? snippet.active ? __('Network Deactivate', 'code-snippets') : __('Network Activate', 'code-snippets')
43 : snippet.active ? __('Deactivate', 'code-snippets') : __('Activate', 'code-snippets')
44
45 return (
46 <input
47 id={`snippet-${snippet.id}-switch`}
48 type="checkbox"
49 role="switch"
50 checked={snippet.active}
51 aria-checked={snippet.active}
52 className="switch"
53 title={actionText}
54 aria-label={actionText}
55 onChange={() => {
56 (snippet.active ? deactivate(snippet) : activate(snippet))
57 .then(refreshSnippetsList)
58 .catch(handleUnknownError)
59 }}
60 />
61 )
62 }
63
64 const ActivateColumn: React.FC<ColumnProps> = ({ snippet }) => {
65 const { activeByCondition } = useFilteredSnippets()
66
67 if (snippet.trashed) {
68 return ''
69 }
70
71 switch (snippet.scope) {
72 case 'single-use':
73 return <RunOnceButton snippet={snippet} />
74
75 case 'condition':
76 return (
77 <a className="snippet-condition-count" href={getSnippetEditUrl(snippet)}>
78 {activeByCondition.get(snippet.id)?.length ?? 0}
79 </a>
80 )
81
82 default:
83 return <ActivationSwitch snippet={snippet} />
84 }
85 }
86
87 const ActionLinks = ({ snippet }: { snippet: Snippet }) => {
88 const api = useSnippetsAPI()
89 const { refreshSnippetsList } = useSnippetsList()
90
91 const Edit = !snippet.trashed && (() =>
92 <a href={getSnippetEditUrl(snippet)}>
93 {snippet.locked ? __('View', 'code-snippets') : __('Edit', 'code-snippets')}
94 </a>)
95
96 const Clone = !snippet.trashed && (() =>
97 <Button link onClick={() => {
98 api.create(cloneSnippetObject(snippet))
99 .then(refreshSnippetsList)
100 .catch(handleUnknownError)
101 }}>
102 {__('Clone', 'code-snippets')}
103 </Button>)
104
105 const Export = !snippet.trashed && (() =>
106 <Button link onClick={() => {
107 api.export(snippet)
108 .then(response => downloadSnippetExportFile(response, snippet))
109 .catch(handleUnknownError)
110 }}>
111 {__('Export', 'code-snippets')}
112 </Button>)
113
114 const Restore = snippet.trashed && (() =>
115 <Button link onClick={() => {
116 api.restore(snippet)
117 .then(refreshSnippetsList)
118 .catch(handleUnknownError)
119 }}>
120 {__('Restore', 'code-snippets')}
121 </Button>)
122
123 const Delete = (!snippet.locked || snippet.trashed) && (() =>
124 <DeleteButton link className="delete" snippet={snippet} onSuccess={refreshSnippetsList} />)
125
126 return (
127 <>
128 {[Edit, Clone, Restore, Export, Delete]
129 .filter(Action => false !== Action)
130 .reduce<ReactNode>(
131 (Actions, Action) =>
132 null === Actions ? <Action /> : <>{Actions} | <Action /></>,
133 null)}
134 </>
135 )
136 }
137
138 const RowActions: React.FC<ColumnProps> = ({ snippet }) => {
139 if (!isNetworkAdmin() && snippet.network && !snippet.shared_network) {
140 return (
141 <div className="row-actions visible">
142 {snippet.active
143 ? <span className="network-active">{__('Network Active', 'code-snippets')}</span>
144 : <span className="network-only">{__('Network Only', 'code-snippets')}</span>}
145 </div>
146 )
147 }
148
149 if (snippet.shared_network && !window.CODE_SNIPPETS_MANAGE?.hasNetworkCap) {
150 return undefined
151 }
152
153 return (
154 <div className={classnames('row-actions', { visible: !snippet.trashed })}>
155 <ActionLinks snippet={snippet} />
156 </div>
157 )
158 }
159
160 const NameColumn: React.FC<ColumnProps> = ({ snippet }) =>
161 <>
162 {snippet.locked && (
163 <Tooltip
164 inline
165 end
166 label={__('About snippet lock', 'code-snippets')}
167 icon={<span className="dashicons dashicons-lock" aria-hidden="true"></span>}
168 >
169 {__('This snippet is locked and cannot be modified.', 'code-snippets')}
170 </Tooltip>)}
171
172 {!snippet.trashed && (isNetworkAdmin() || !snippet.network || window.CODE_SNIPPETS_MANAGE?.hasNetworkCap)
173 ? <a href={getSnippetEditUrl(snippet)} className="snippet-name">{getSnippetDisplayName(snippet)}</a>
174 : getSnippetDisplayName(snippet)}
175
176 {snippet.shared_network && <span className="badge">{__('Shared on Network', 'code-snippets')}</span>}
177
178 <RowActions snippet={snippet} />
179 </>
180
181 const TypeColumn: React.FC<ColumnProps> = ({ snippet }) => {
182 const { setCurrentType } = useSnippetsFilters()
183 const type = getSnippetType(snippet)
184
185 return (
186 <a
187 href={buildUrl(window.location.href, { type })}
188 onClick={event => {
189 event.preventDefault()
190 setCurrentType(type)
191 }}
192 >
193 <Badge name={type} />
194 </a>
195 )
196 }
197
198 const TagsColumn: React.FC<ColumnProps> = ({ snippet }) =>
199 snippet.tags.map((tag, index) =>
200 <Fragment key={tag}>
201 <a key={tag} href={buildUrl(window.location.href, { tag })}>
202 {tag}
203 </a>
204 {index < snippet.tags.length - 1 ? ', ' : ''}
205 </Fragment>)
206
207 const DateColumn: React.FC<ColumnProps> = ({ snippet }) =>
208 snippet.modified
209 ? <span className="modified-column-content" title={snippet.modified}>
210 <time dateTime={snippet.modified}>
211 {humanTimeDiff(snippet.modified, undefined)}
212 </time>
213 </span>
214 : <>&#8212;</>
215
216 const PriorityColumn: React.FC<ColumnProps> = ({ snippet }) => {
217 const [value, setValue] = useState(snippet.priority)
218 const snippetsAPI = useSnippetsAPI()
219 const { refreshSnippetsList } = useSnippetsList()
220 const id = `snippet-${snippet.id}-priority`
221
222 const handleUpdate = () => {
223 snippetsAPI.update({ ...snippet, priority: value })
224 .then(response => {
225 if (response.id === snippet.id) {
226 setValue(response.priority)
227 }
228 })
229 .then(refreshSnippetsList)
230 .catch(handleUnknownError)
231 }
232
233 return (
234 <form onSubmit={event => {
235 event.preventDefault()
236 handleUpdate()
237 }}>
238 <input
239 id={id}
240 type="number"
241 className="snippet-priority"
242 value={value}
243 step="1"
244 onBlur={handleUpdate}
245 aria-label={__('Snippet priority', 'code-snippets')}
246 onChange={event => setValue(Number(event.target.value))}
247 disabled={snippet.locked || snippet.trashed}
248 />
249 </form>
250 )
251 }
252
253 const baseTableColumns: ListTableColumn<Snippet>[] = [
254 {
255 id: 'activate',
256 title: <span className="screen-reader-text">{__('Activate', 'code-snippets')}</span>,
257 render: snippet => <ActivateColumn snippet={snippet} />
258 },
259 {
260 id: 'name',
261 title: __('Name', 'code-snippets'),
262 isPrimary: true,
263 sortedValue: snippet => getSnippetDisplayName(snippet).toLowerCase(),
264 render: snippet => <NameColumn snippet={snippet} />
265 },
266 {
267 id: 'type',
268 title: __('Type', 'code-snippets'),
269 sortedValue: snippet => getSnippetType(snippet),
270 render: snippet => <TypeColumn snippet={snippet} />
271 },
272 {
273 id: 'desc',
274 title: __('Description', 'code-snippets'),
275 render: snippet => <div className="snippet-description-content"><RawHTML>{snippet.desc}</RawHTML></div>
276 },
277 {
278 id: 'tags',
279 title: __('Tags', 'code-snippets'),
280 render: snippet => <TagsColumn snippet={snippet} />
281 },
282 {
283 id: 'date',
284 title: __('Modified', 'code-snippets'),
285 sortedValue: snippet => snippet.modified ? new Date(snippet.modified).toISOString() : '',
286 render: snippet => <DateColumn snippet={snippet} />
287 },
288 {
289 id: 'priority',
290 title: __('Priority', 'code-snippets'),
291 sortedValue: snippet => snippet.priority,
292 render: snippet => <PriorityColumn snippet={snippet} />
293 }
294 ]
295
296 export const getTableColumns = (hiddenColumns: Set<Key>): ListTableColumn<Snippet>[] =>
297 baseTableColumns.map(column => ({ ...column, isHidden: hiddenColumns.has(column.id) }))
298