このコミットが含まれているのは:
@@ -211,6 +211,15 @@ export const apiDownload = async (
|
||||
const { token } = await apiPost<{ token: string }> (`${path}/prepare`, body, { locale })
|
||||
if (typeof token !== 'string' || token.length === 0)
|
||||
throw new Error ('Missing download token')
|
||||
startAPIDownload (path, token, onError)
|
||||
}
|
||||
|
||||
|
||||
export const startAPIDownload = (
|
||||
path: string,
|
||||
token: string,
|
||||
onError?: () => void,
|
||||
): void => {
|
||||
const frame = document.createElement ('iframe')
|
||||
frame.hidden = true
|
||||
frame.addEventListener ('load', () => {
|
||||
|
||||
+33
-11
@@ -206,10 +206,16 @@ export const selectedCountColumns = (
|
||||
params: URLSearchParams,
|
||||
definition?: CountsDefinition,
|
||||
): CountColumn[] => {
|
||||
const selected = params.has ('columns') ? params.get ('columns')!.split (',') :
|
||||
columnsForCounts (resource, definition).filter (key => ![
|
||||
'thumbnail', 'original_created_from', 'original_created_before']
|
||||
.includes (key))
|
||||
const common = ['resource_id', 'version_no', 'event_type', 'user', 'created_at']
|
||||
const defaults: Record<CountsResource, string[]> = {
|
||||
posts: ['title', 'url', 'tags', 'parents', 'video_ms'],
|
||||
tags: ['name', 'category', 'aliases', 'parents'],
|
||||
nico_tags: ['name', 'linked_tags'],
|
||||
tag_descriptions: ['language_code', 'body', 'source_language_code'],
|
||||
tag_groups: ['name', 'description', 'members'],
|
||||
materials: ['url', 'source_kind', 'tag_id'] }
|
||||
const selected = params.has ('columns') ? params.get ('columns')!.split (',')
|
||||
: [...common, ...defaults[resource]]
|
||||
return orderedCountFields (fieldsForCounts (resource, definition), params)
|
||||
.map (field => field.key as CountColumn).filter (key => selected.includes (key))
|
||||
}
|
||||
@@ -223,9 +229,8 @@ export const countChildPaths = (fields: CountField[], prefix = ''): string[] =>
|
||||
|
||||
|
||||
export const selectedCountChildren = (params: URLSearchParams): Set<string> =>
|
||||
new Set (params.has ('children') ? params.get ('children')!.split (',') :
|
||||
Object.entries (nested).flatMap (([key, fields]) =>
|
||||
countChildPaths (fields ?? [], key)))
|
||||
new Set (params.has ('children') ? params.get ('children')!.split (',')
|
||||
: ['tags.name', 'parents.id', 'aliases.name', 'members.tagId'])
|
||||
|
||||
|
||||
export const readCountSort = (value: string | null): CountSort[] =>
|
||||
@@ -342,15 +347,32 @@ export const exportCounts = (
|
||||
headers: string[],
|
||||
labels: Record<string, string>,
|
||||
onError?: () => void,
|
||||
): Promise<void> => {
|
||||
): Promise<void> => apiDownload ('/counts/export', countsExportPayload (
|
||||
resource, params, format, columns, headers, labels), locale, onError)
|
||||
|
||||
|
||||
export const countsExportPayload = (
|
||||
resource: CountsResource,
|
||||
params: URLSearchParams,
|
||||
format: 'json' | 'csv',
|
||||
columns: string[],
|
||||
headers: string[],
|
||||
labels: Record<string, string>,
|
||||
definition?: CountsDefinition,
|
||||
): Record<string, string> => {
|
||||
const payload = new URLSearchParams (params)
|
||||
if (!payload.has ('children'))
|
||||
{
|
||||
const selected = selectedCountChildren (params)
|
||||
payload.set ('children', countChildPaths (fieldsForCounts (resource, definition))
|
||||
.filter (path => selected.has (path)).join (','))
|
||||
}
|
||||
payload.delete ('current')
|
||||
payload.delete ('row_numbers')
|
||||
if (isCountsState (params) && countsUseLatest (params)) payload.delete ('at')
|
||||
return apiDownload ('/counts/export', {
|
||||
...Object.fromEntries (payload), resource, format,
|
||||
return { ...Object.fromEntries (payload), resource, format,
|
||||
columns: columns.join (','), headers: JSON.stringify (headers),
|
||||
labels: JSON.stringify (labels) }, locale, onError)
|
||||
labels: JSON.stringify (labels) }
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -194,6 +194,38 @@ export const countsNestedExport = (
|
||||
}
|
||||
|
||||
|
||||
// A chain with one selected scalar leaf is displayed and copied as one cell.
|
||||
export const countsNestedIsScalar = (
|
||||
fields: CountField[],
|
||||
path: string,
|
||||
params: URLSearchParams,
|
||||
): boolean => {
|
||||
const selected = selectedCountChildren (params)
|
||||
const columns = fields.filter (field => selected.has (`${path}.${field.key}`))
|
||||
if (columns.length !== 1) return false
|
||||
const field = columns[0]
|
||||
return field.children == null
|
||||
|| countsNestedIsScalar (field.children, `${path}.${field.key}`, params)
|
||||
}
|
||||
|
||||
|
||||
export const countsNestedScalarText = (
|
||||
rows: CountRecord[],
|
||||
fields: CountField[],
|
||||
path: string,
|
||||
params: URLSearchParams,
|
||||
locale: LocaleCode,
|
||||
): string => {
|
||||
const selected = selectedCountChildren (params)
|
||||
const field = fields.find (field => selected.has (`${path}.${field.key}`))
|
||||
if (field == null) return ''
|
||||
return countsNestedOrder (rows, path, params, locale).map (row => field.children == null
|
||||
? countsLogicalName (row, field.key, locale).text
|
||||
: countsNestedScalarText (row[field.key] as CountRecord[] ?? [], field.children,
|
||||
`${path}.${field.key}`, params, locale)).join (' ')
|
||||
}
|
||||
|
||||
|
||||
export const countsNestedText = (
|
||||
rows: CountRecord[],
|
||||
fields: CountField[],
|
||||
@@ -201,6 +233,8 @@ export const countsNestedText = (
|
||||
params: URLSearchParams,
|
||||
locale: LocaleCode,
|
||||
): string => {
|
||||
if (countsNestedIsScalar (fields, path, params))
|
||||
return countsNestedScalarText (rows, fields, path, params, locale)
|
||||
const selected = selectedCountChildren (params)
|
||||
const columns = orderedCountFields (fields, params, path)
|
||||
.filter (field => selected.has (`${path}.${field.key}`))
|
||||
@@ -224,9 +258,11 @@ export const countsCellCopy = (
|
||||
): string => {
|
||||
if (field.children == null) return countsCellText (row, field.key as CountColumn, locale, params)
|
||||
const rows = countsNestedRows (row, field.key)
|
||||
if (countsNestedIsScalar (field.children, field.key, params))
|
||||
return countsNestedScalarText (rows, field.children, field.key, params, locale)
|
||||
return params.get ('nested_json') === '1'
|
||||
? JSON.stringify (
|
||||
countsNestedExport (rows, field.children, field.key, params, locale), null, 2)
|
||||
countsNestedExport (rows, field.children, field.key, params, locale))
|
||||
: countsNestedText (rows, field.children, field.key, params, locale)
|
||||
}
|
||||
|
||||
@@ -297,7 +333,9 @@ const clipboardColumns = (
|
||||
return orderedCountFields (fields, params, path)
|
||||
.filter (field => selected.has (`${path}.${field.key}`))
|
||||
.map (field => ({ label: translateUIMessage (field.label, 'counts', locale).text,
|
||||
children: field.children == null ? undefined : clipboardColumns (
|
||||
children: (field.children == null
|
||||
|| countsNestedIsScalar (field.children, `${path}.${field.key}`, params))
|
||||
? undefined : clipboardColumns (
|
||||
field.children, `${path}.${field.key}`, params, locale) }))
|
||||
}
|
||||
|
||||
@@ -316,8 +354,11 @@ export const countsNestedClipboardTable = (
|
||||
columns: clipboardColumns (fields, path, params, locale),
|
||||
rows: countsNestedOrder (rows, path, params, locale).map (row => columns.map (field =>
|
||||
field.children == null ? countsLogicalName (row, field.key, locale).text
|
||||
: countsNestedClipboardTable (row[field.key] as CountRecord[] ?? [],
|
||||
field.children, `${path}.${field.key}`, params, locale))) }
|
||||
: (countsNestedIsScalar (field.children, `${path}.${field.key}`, params)
|
||||
? countsNestedScalarText (row[field.key] as CountRecord[] ?? [],
|
||||
field.children, `${path}.${field.key}`, params, locale)
|
||||
: countsNestedClipboardTable (row[field.key] as CountRecord[] ?? [],
|
||||
field.children, `${path}.${field.key}`, params, locale)))) }
|
||||
}
|
||||
|
||||
|
||||
@@ -330,7 +371,9 @@ export const countsDetailClipboardTable = (
|
||||
): CountsClipboardTable => {
|
||||
const columns = fields.map (field => ({
|
||||
label: translateUIMessage (field.label, 'counts', locale).text,
|
||||
children: field.children == null ? undefined : clipboardColumns (
|
||||
children: (field.children == null
|
||||
|| countsNestedIsScalar (field.children, field.key, params))
|
||||
? undefined : clipboardColumns (
|
||||
field.children, field.key, params, locale) }))
|
||||
if (rowStart != null)
|
||||
columns.unshift ({ label: translateUIMessage ('rowNumbers', 'counts', locale).text,
|
||||
@@ -338,8 +381,11 @@ export const countsDetailClipboardTable = (
|
||||
return { columns, rows: rows.map ((row, index) => {
|
||||
const cells: CountsClipboardCell[] = fields.map (field => field.children == null
|
||||
? countsCellText (row, field.key, locale, params)
|
||||
: countsNestedClipboardTable (countsNestedRows (row, field.key), field.children,
|
||||
field.key, params, locale))
|
||||
: (countsNestedIsScalar (field.children, field.key, params)
|
||||
? countsNestedScalarText (countsNestedRows (row, field.key), field.children,
|
||||
field.key, params, locale)
|
||||
: countsNestedClipboardTable (countsNestedRows (row, field.key), field.children,
|
||||
field.key, params, locale)))
|
||||
return rowStart == null ? cells : [String (rowStart + index), ...cells]
|
||||
}) }
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ type DialogueFormOptions = { title: ReactNode
|
||||
description?: ReactNode
|
||||
body: (controls: DialogueFormControls) => ReactNode
|
||||
cancelText?: ReactNode
|
||||
onCancel?: () => Promise<boolean | void> | boolean | void
|
||||
footerLayout?: 'inline'
|
||||
size?: 'default' | 'large' | 'viewport' }
|
||||
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする