このコミットが含まれているのは:
2026-10-26 18:59:25 +09:00
コミット 88711d2bd8
10個のファイルの変更、183行の追加、39行の削除
+7 -4
ファイルの表示
@@ -2,7 +2,7 @@ import SortableList from '@/components/common/SortableList'
import { countsAxisFields, countsFilterFields, readCountsJSON, useCountsDefinition }
from '@/lib/countsDefinitions'
import { useClientMotion } from '@/lib/useClientMotion'
import { inputClass } from '@/lib/utils'
import { cn, inputClass } from '@/lib/utils'
import { useUITranslation } from '@/lib/useUITranslation'
import type { CountsAxis } from '@/lib/countsDefinitions'
@@ -43,8 +43,12 @@ const CountsAxes = ({ params, update }: {
<div className="rounded border dark:border-slate-700">{content}</div>
</fieldset>} renderItem={(id, group) => {
const axis = axes.find (axis => axis.field === id)
return <div className="flex flex-wrap items-center gap-2 text-sm">
{group === 'available' && <span className="inline-flex gap-1">
return <div className={cn ('flex items-center gap-2 text-sm',
group !== 'available' && 'flex-wrap')}>
<span className="min-w-0 flex-1 break-words">
{label (available.find (field => field.key === id)?.label ?? 'axis')}
</span>
{group === 'available' && <span className="ml-auto inline-flex shrink-0 gap-1">
{(['rows', 'cols'] as const).map (target => <button key={target} type="button"
className="h-7 w-7 rounded-full border border-slate-300 bg-slate-100
text-xs text-slate-700 hover:bg-blue-100 hover:text-blue-900
@@ -58,7 +62,6 @@ const CountsAxes = ({ params, update }: {
lang={message ('axisPlace').language} onClick={() => place (id, target)}>
{label (target)}</button>)}
</span>}
{label (available.find (field => field.key === id)?.label ?? 'axis')}
{group !== 'available' && <>
{countsFilterFields (definition)[id] === 'datetime' && <select
className={inputClass (false, 'w-auto py-0')}
+20 -15
ファイルの表示
@@ -64,23 +64,28 @@ const CountsControls = (
execute (normaliseCountsParams (resource, next))
}}>
<div className="flex flex-wrap items-center gap-2 border-b pb-1 dark:border-slate-700">
{history ? <>
{['from', 'through'].map (key => <label key={key}
className="flex flex-wrap items-center gap-1 text-sm">
{label (key)}<DateTimeField precision="second" value={draft.get (key) ?? ''}
aria-label={message (key).text} onChange={value => update (key, value ?? '')}/>
</label>)}
</> : <div className="flex flex-wrap items-center gap-1 text-sm">
{history ? <div className="flex min-w-0 max-w-full items-center gap-1">
<DateTimeField precision="second" value={draft.get ('from') ?? ''}
aria-label={message ('from').text} lang={message ('from').language}
className="min-w-0 flex-1" onChange={value => update ('from', value ?? '')}/>
<span className="shrink-0">{label ('periodSeparator')}</span>
<DateTimeField precision="second" value={draft.get ('through') ?? ''}
aria-label={message ('through').text} lang={message ('through').language}
className="min-w-0 flex-1" onChange={value => update ('through', value ?? '')}/>
</div> : <div className="flex flex-wrap items-center gap-1 text-sm">
{label ('referenceAt')}<HelpPopover label={message ('referenceAt')}>
{label ('referenceHelp')}</HelpPopover>
<label className="inline-flex items-center gap-1">
<input type="checkbox" checked={countsUseLatest (draft)}
onChange={event => update ('current', event.target.checked ? '1' : '0')}/>
{label ('latest')}</label>
<DateTimeField precision="second" disabled={countsUseLatest (draft)}
value={draft.get ('at') ?? ''}
aria-label={message ('referenceAt').text} lang={message ('referenceAt').language}
onChange={value => update ('at', value ?? '')}/>
<div className="flex min-w-0 max-w-full items-center gap-1">
<DateTimeField precision="second" disabled={countsUseLatest (draft)}
className="min-w-0 flex-1"
value={draft.get ('at') ?? ''}
aria-label={message ('referenceAt').text} lang={message ('referenceAt').language}
onChange={value => update ('at', value ?? '')}/>
<label className="inline-flex shrink-0 items-center gap-1">
<input type="checkbox" checked={countsUseLatest (draft)}
onChange={event => update ('current', event.target.checked ? '1' : '0')}/>
{label ('latest')}</label>
</div>
</div>}
</div>
<div className="flex flex-wrap items-center gap-2 border-b py-1 dark:border-slate-700">
+2 -1
ファイルの表示
@@ -4,7 +4,7 @@ import CountsSortHeader from '@/components/counts/CountsSortHeader'
import CountsSelectableTable from '@/components/counts/CountsSelectableTable'
import { changeCountSort, readCountSort, selectedCountChildren,
writeCountSort, orderedCountFields } from '@/lib/counts'
import { countsLogicalName, countsNestedExport,
import { countsLogicalName, countsNestedClipboardTable, countsNestedExport,
countsNestedOrder, countsNestedText } from '@/lib/countsDisplay'
import { useLocale } from '@/lib/useLocale'
import { useUITranslation } from '@/lib/useUITranslation'
@@ -67,6 +67,7 @@ const CountsNestedTable = (
className="relative isolate max-h-72 w-full min-w-0 overflow-auto">
<CountsSelectableTable family={path} columns={columns.map (field => field.key)}
identity={`${path}:${params}`} name={title}
copyTable={() => countsNestedClipboardTable (rows, fields, path, params, locale)}
className="text-xs">
<thead className="sticky top-0 z-10 bg-background text-foreground
dark:bg-background dark:text-foreground"><tr>
+5 -2
ファイルの表示
@@ -8,8 +8,8 @@ import CountsSelectableTable from '@/components/counts/CountsSelectableTable'
import PostThumbnailPreview from '@/components/posts/PostThumbnailPreview'
import { changeCountSort, countColumns, countsRequestParams, fieldsForCounts,
readCountSort, selectedCountColumns, writeCountSort } from '@/lib/counts'
import { countsCellCopy, countsCellText, countsImageURL, countsNestedRows }
from '@/lib/countsDisplay'
import { countsCellCopy, countsCellText, countsDetailClipboardTable,
countsImageURL, countsNestedRows } from '@/lib/countsDisplay'
import useDialogue from '@/lib/dialogues/useDialogue'
import { useLocale } from '@/lib/useLocale'
import { useCountsDefinition } from '@/lib/countsDefinitions'
@@ -101,6 +101,9 @@ const CountsResults = (
{tableColumns.length === 0 ? <p>{label ('nested.noColumns')}</p> : (
<CountsSelectableTable family={resource}
columns={tableColumns}
copyTable={() => countsDetailClipboardTable (data.versions ?? [],
columns.map (column => fields.find (field => field.key === column)!),
params, locale, rowNumbers ? (data.page - 1) * data.limit + 1 : null)}
identity={`${resource}:${countsRequestParams (params)}:${tableColumns.join (',')}:${
(data.versions ?? []).map (row => row.id)}`}
className="text-left text-sm">
+12 -2
ファイルの表示
@@ -8,6 +8,7 @@ import { useUITranslation } from '@/lib/useUITranslation'
import type { ClipboardEvent, ReactNode } from 'react'
import type { CountsClipboardTable } from '@/lib/countsClipboard'
import type { CountsMenuAction } from '@/components/counts/CountsCellMenu'
type Point = { row: number; column: number }
@@ -20,13 +21,14 @@ const cellAt = (table: HTMLTableElement, point: Point) =>
const CountsSelectableTable = (
{ family, columns, identity, children, className, name }: {
{ family, columns, identity, children, className, name, copyTable }: {
family: string
columns: string[]
identity: string
children: ReactNode
className?: string
name?: { text: string; language: string } },
name?: { text: string; language: string }
copyTable?: () => CountsClipboardTable },
) => {
const id = useId ()
const root = useRef<HTMLTableElement> (null)
@@ -103,6 +105,14 @@ const CountsSelectableTable = (
}
const clipboard = (headers = false) => {
if (bounds == null || root.current == null) return null
if (copyTable != null)
{
const table = copyTable ()
return countsClipboard ({
columns: table.columns.slice (bounds.left, bounds.right + 1),
rows: table.rows.slice (bounds.top, bounds.bottom + 1)
.map (row => row.slice (bounds.left, bounds.right + 1)) }, headers)
}
const rows: string[][] = []
for (let row = bounds.top; row <= bounds.bottom; ++row)
{