このコミットが含まれているのは:
@@ -340,7 +340,7 @@ class CountsCondition
|
||||
return @definition.alias_source(root) if root.start_with?('aliases_')
|
||||
return [nil, @definition.name_expression(root)]
|
||||
end
|
||||
return [nil, "(#{ @population.latest_sql })"] if root == 'latest'
|
||||
return [nil, "(#{ @population.current_latest_sql })"] if root == 'latest'
|
||||
if root == 'user' && parts.any?
|
||||
expression = parts.first == 'id' ? 'v.created_by_user_id' :
|
||||
'(SELECT actor.name FROM users actor WHERE actor.id = v.created_by_user_id)'
|
||||
|
||||
@@ -20,7 +20,7 @@ class CountsDefaults
|
||||
details = details.transform_values do |value|
|
||||
value.split(' ').flat_map do |token|
|
||||
if resource == 'tags' && ['name', 'parents.name'].include?(token)
|
||||
token == 'name' ? name : ["parents.#{ name }", "parents.#{ aliases }"]
|
||||
token == 'name' ? name : "parents.#{ name }"
|
||||
elsif resource == 'tags' && token == 'aliases'
|
||||
aliases
|
||||
elsif resource == 'tags' && token.start_with?('aliases.')
|
||||
|
||||
@@ -41,8 +41,7 @@ class CountsPopulation
|
||||
raise ArgumentError
|
||||
end
|
||||
|
||||
def latest_sql(row = 'v', cutoff = nil)
|
||||
cutoff ||= history ? at : timestamp(params['at']) || at
|
||||
def latest_at_sql(row, cutoff)
|
||||
keys = definition.keys.map { "newer.#{ _1 } = #{ row }.#{ _1 }" }.join(' AND ')
|
||||
bound = definition.model.connection.quote(cutoff)
|
||||
<<~SQL.squish
|
||||
@@ -52,6 +51,14 @@ class CountsPopulation
|
||||
SQL
|
||||
end
|
||||
|
||||
def current_latest_sql(row = 'v')
|
||||
keys = definition.keys.map { "newer.#{ _1 } = #{ row }.#{ _1 }" }.join(' AND ')
|
||||
<<~SQL.squish
|
||||
NOT EXISTS (SELECT 1 FROM #{ definition.table } newer
|
||||
WHERE #{ keys } AND newer.version_no > #{ row }.version_no)
|
||||
SQL
|
||||
end
|
||||
|
||||
def changed_sql(key)
|
||||
column = definition.column(key)
|
||||
"(previous.id IS NOT NULL AND NOT (v.#{ column } <=> previous.#{ column }))"
|
||||
@@ -125,7 +132,7 @@ class CountsPopulation
|
||||
raise ArgumentError if point > at
|
||||
|
||||
@scope = @scope.where("v.#{ definition.time_column } <= ?", point)
|
||||
.where(Arel.sql(latest_sql('v', point))).where.not('v.event_type' => 'discard')
|
||||
.where(Arel.sql(latest_at_sql('v', point))).where.not('v.event_type' => 'discard')
|
||||
return
|
||||
end
|
||||
first = timestamp(params['from'])
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Common entrypoint for six version resources; legacy endpoints keep their contracts.
|
||||
class CountsTableQuery
|
||||
SQL_HINTS = ['MAX_EXECUTION_TIME(10000)',
|
||||
"SET_VAR(optimizer_switch='semijoin=off')"].freeze
|
||||
MAX_COLUMNS = 100
|
||||
class TooManyColumns < ArgumentError; end
|
||||
|
||||
@@ -127,7 +129,7 @@ class CountsTableQuery
|
||||
numbered AS (SELECT row_groups.*,
|
||||
ROW_NUMBER() OVER (ORDER BY #{ order_rows.join(', ') }) AS position FROM row_groups),
|
||||
group_totals AS (SELECT COUNT(*) AS group_count FROM row_groups)
|
||||
SELECT /*+ MAX_EXECUTION_TIME(10000) */ cells.*,
|
||||
SELECT /*+ #{ SQL_HINTS.join(' ') } */ cells.*,
|
||||
page_rows.position AS counts_position, group_totals.group_count AS counts_group_count
|
||||
FROM group_totals LEFT JOIN numbered page_rows
|
||||
ON page_rows.position > #{ (page - 1) * limit }
|
||||
@@ -175,7 +177,7 @@ class CountsTableQuery
|
||||
|
||||
def details
|
||||
query = population.scope.select(Arel.sql(
|
||||
"(#{ population.latest_sql }) AS counts_latest"))
|
||||
"(#{ population.current_latest_sql }) AS counts_latest"))
|
||||
order = sort_sql { detail_expression(_1) }
|
||||
query = query.order(Arel.sql((order + ['v.id ASC']).join(', ')))
|
||||
.limit(limit).offset((page - 1) * limit)
|
||||
@@ -253,11 +255,11 @@ class CountsTableQuery
|
||||
|
||||
if HistoryAccess.admin? || mode == 'summary'
|
||||
total = population.scope.except(:select)
|
||||
.optimizer_hints('MAX_EXECUTION_TIME(10000)').count
|
||||
.optimizer_hints(*SQL_HINTS).count
|
||||
return @record_counts = { total:, visible: total, denied: 0 }
|
||||
end
|
||||
total, denied = unrestricted_population.scope.except(:select)
|
||||
.optimizer_hints('MAX_EXECUTION_TIME(10000)').pick(Arel.sql('COUNT(*)'),
|
||||
.optimizer_hints(*SQL_HINTS).pick(Arel.sql('COUNT(*)'),
|
||||
Arel.sql("COALESCE(SUM(CASE WHEN #{ HistoryAccess.sql(@definition.model, 'v') } " +
|
||||
'THEN 0 ELSE 1 END), 0)'))
|
||||
# Related/previous-version predicates may differ under public authority.
|
||||
@@ -267,7 +269,7 @@ class CountsTableQuery
|
||||
total.to_i - denied.to_i
|
||||
else
|
||||
population.scope.except(:select)
|
||||
.optimizer_hints('MAX_EXECUTION_TIME(10000)').count
|
||||
.optimizer_hints(*SQL_HINTS).count
|
||||
end
|
||||
@record_counts = { total: total.to_i, denied: denied.to_i, visible: }
|
||||
end
|
||||
@@ -309,7 +311,7 @@ class CountsTableQuery
|
||||
prefix = "WITH cells AS (#{ sql })"
|
||||
@connection.select_value(<<~SQL).to_i
|
||||
#{ prefix }
|
||||
SELECT /*+ MAX_EXECUTION_TIME(10000) */ COUNT(*) FROM (
|
||||
SELECT /*+ #{ SQL_HINTS.join(' ') } */ COUNT(*) FROM (
|
||||
SELECT #{ keys.join(', ') } FROM cells GROUP BY #{ keys.join(', ') }
|
||||
) output_groups
|
||||
SQL
|
||||
@@ -335,7 +337,7 @@ class CountsTableQuery
|
||||
end
|
||||
values = @connection.select_rows(<<~SQL)
|
||||
#{ prefix }
|
||||
SELECT /*+ MAX_EXECUTION_TIME(10000) */ DISTINCT #{ keys.join(', ') } FROM cells
|
||||
SELECT /*+ #{ SQL_HINTS.join(' ') } */ DISTINCT #{ keys.join(', ') } FROM cells
|
||||
ORDER BY #{ order } LIMIT #{ MAX_COLUMNS + 1 }
|
||||
SQL
|
||||
raise TooManyColumns if values.length > MAX_COLUMNS
|
||||
@@ -395,7 +397,7 @@ class CountsTableQuery
|
||||
end
|
||||
|
||||
def detail_expression(key)
|
||||
return "(#{ population.latest_sql })" if key == 'latest'
|
||||
return "(#{ population.current_latest_sql })" if key == 'latest'
|
||||
return @definition.name_expression(key) if @definition.resource == 'tags' &&
|
||||
@definition.name_column(key)
|
||||
aliases = { 'tags' => 'tags_json', 'parents' => @definition.resource == 'posts' ?
|
||||
|
||||
@@ -91,6 +91,34 @@ RSpec.describe 'Counts table API', type: :request do
|
||||
expect(versions(query(predicate.merge('conditions.0.value' => 'true')))).to eq([3])
|
||||
end
|
||||
|
||||
it 'distinguishes the snapshot cutoff from the current latest flag and condition' do
|
||||
past = { definition: 'latest', at: '2024-01-31T16:00:00Z' }
|
||||
result = query(past)
|
||||
expect(versions(result)).to eq([2])
|
||||
expect(result['versions'].first['latest']).to be(false)
|
||||
predicate = { conditions: '', 'conditions.0.field' => 'latest',
|
||||
'conditions.0.operator' => 'eq', 'conditions.0.value' => 'false' }
|
||||
expect(versions(query(past.merge(predicate)))).to eq([2])
|
||||
expect(versions(query(past.merge(predicate).merge('conditions.0.value' => 'true'))))
|
||||
.to eq([])
|
||||
unchanged = query(definition: 'latest', at: '2024-02-01T00:00:00Z')
|
||||
expect(versions(unchanged)).to eq([3])
|
||||
expect(unchanged['versions'].first['latest']).to be(true)
|
||||
expect(versions(query(sort: 'latest:desc resource_id:asc version_no:asc'))).to eq([3, 1, 2])
|
||||
end
|
||||
|
||||
it 'checks the current latest flag beyond the execution timestamp' do
|
||||
PostVersion.create!(post: record, version_no: 4, event_type: 'update',
|
||||
url: record.url, title: 'after execution', tags: '', tags_json: [],
|
||||
parent_post_ids: '', created_at: Time.utc(2025, 2, 1))
|
||||
result = query
|
||||
expect(versions(result)).to eq([1, 2, 3])
|
||||
expect(result['versions'].map { _1['latest'] }).to eq([false, false, false])
|
||||
snapshot = query(definition: 'latest')
|
||||
expect(versions(snapshot)).to eq([3])
|
||||
expect(snapshot['versions'].first['latest']).to be(false)
|
||||
end
|
||||
|
||||
it 'keeps ANY, ALL and NONE distinct without multiplying parent records' do
|
||||
values = { conditions: '', 'conditions.0.field' => 'tags',
|
||||
'conditions.0.values' => '1', condition_values: '',
|
||||
|
||||
@@ -28,7 +28,7 @@ bundle exec rspec spec/requests/counts_table_spec.rb spec/requests/counts_downlo
|
||||
frontend ディレクトリで提案する対象(未実行):
|
||||
|
||||
```sh
|
||||
npm run test:run -- src/lib/counts.test.ts src/lib/api.test.ts src/pages/CountsResourcePage.test.tsx src/pages/help/HelpChangesPage.test.tsx src/components/counts/CountsControls.test.tsx src/components/counts/CountsSummaryResults.test.tsx src/components/counts/CountsExportProgress.test.tsx
|
||||
npm run test:run -- src/lib/counts.test.ts src/lib/api.test.ts src/pages/counts/CountsResourcePage.test.tsx src/pages/help/HelpChangesPage.test.tsx src/components/counts/CountsControls.test.tsx src/components/counts/CountsSummaryResults.test.tsx src/components/counts/CountsExportProgress.test.tsx
|
||||
```
|
||||
|
||||
SSR は frontend/server.ssr.test.mjs が dist/server/entry-server.js を読み込む。
|
||||
@@ -104,7 +104,7 @@ SQL の速度改善・索引追加は別 Issue とし、今回 migration は追
|
||||
- frontend/src/components/counts/CountsSummaryResults.tsx:行番号コードの撤去、私的セルの
|
||||
ドリルダウン抑止、公開列のソート維持。
|
||||
- frontend/src/components/counts/CountsSortHeader.tsx:禁止座標のソート抑止と伏字の理由表示。
|
||||
- frontend/src/pages/CountsResourcePage.tsx:専用エラーと、初回失敗後にも再実行できる状態。
|
||||
- frontend/src/pages/counts/CountsResourcePage.tsx:専用エラーと、初回失敗後にも再実行できる状態。
|
||||
- frontend/src/locales/ja/counts.json:承認されたエラー文言。
|
||||
- frontend/src/locales/ain-Kana/counts.json:同じキーと未翻訳・日本語 fallback の管理。
|
||||
|
||||
@@ -121,7 +121,7 @@ SQL の速度改善・索引追加は別 Issue とし、今回 migration は追
|
||||
再描画、開始受付失敗、通信断、独立期限、取消の競合・再試行、unmount、iframe 失敗。
|
||||
- frontend/src/components/counts/CountsSummaryResults.test.tsx(新規):伏字・理由・コピー・旧行番号。
|
||||
- frontend/src/components/counts/CountsControls.test.tsx:summary 行番号廃止と表示件数選択。
|
||||
- frontend/src/pages/CountsResourcePage.test.tsx:初期エラーからの再実行、サイト名付き title。
|
||||
- frontend/src/pages/counts/CountsResourcePage.test.tsx:初期エラーからの再実行、サイト名付き title。
|
||||
- frontend/src/lib/api.test.ts:共有 CSRF 待機中の中断、iframe の解放・重複失敗通知。
|
||||
- frontend/src/lib/counts.test.ts:理由別エラーと任意のサーバー文字列を表示しないこと。
|
||||
- frontend/src/test/setup.ts:時刻が進む非同期 RAF と Canvas のテスト専用 fallback。
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ const MaterialListPage = lazyPage (() => import ('@/pages/materials/MaterialList
|
||||
const MaterialNewPage = lazyPage (() => import ('@/pages/materials/MaterialNewPage'))
|
||||
const MaterialSyncSuppressionsPage = lazyPage (
|
||||
() => import ('@/pages/materials/MaterialSyncSuppressionsPage'))
|
||||
const CountsResourcePage = lazyPage (() => import ('@/pages/CountsResourcePage'))
|
||||
const CountsResourcePage = lazyPage (() => import ('@/pages/counts/CountsResourcePage'))
|
||||
const MorePage = lazyPage (() => import ('@/pages/MorePage'))
|
||||
const GekanatorUnavailablePage = lazyPage (() => import ('@/pages/GekanatorUnavailablePage'))
|
||||
const NicoTagListPage = lazyPage (() => import ('@/pages/tags/NicoTagListPage'))
|
||||
|
||||
@@ -120,7 +120,8 @@ export const menuOutline = (
|
||||
{ ...message ('entertainment'), visible: false, subMenu: [
|
||||
{ ...message ('theatre'), to: '/theatres/1' },
|
||||
{ ...message ('gekanator'), to: '/gekanator' }] },
|
||||
{ ...message ('counts'), visible: false, subMenu: [
|
||||
{ ...message ('counts'),
|
||||
name: `${ message ('counts').name } (β)`, visible: false, subMenu: [
|
||||
{ ...message ('posts'), to: '/counts/posts' },
|
||||
{ ...message ('tags'), to: '/counts/tags' },
|
||||
{ ...message ('externalTags'), to: '/counts/nico-tags' }] },
|
||||
|
||||
@@ -49,8 +49,9 @@ const CountsAxes = ({ params, update }: {
|
||||
commit (next)
|
||||
}
|
||||
return <SortableList groups={groups} name={name} onChange={commit}
|
||||
renderGroup={(id, content, over) => <fieldset className="mb-2 min-w-0">
|
||||
<legend>{label (id === 'available' ? 'axisItems' : id)}</legend>
|
||||
renderGroup={(id, content, over) => <fieldset className="mx-2 mb-2 min-w-0">
|
||||
<legend className="mb-1 font-semibold">
|
||||
{label (id === 'available' ? 'axisItems' : id)}</legend>
|
||||
<div className={cn ('rounded border px-1 dark:border-slate-700',
|
||||
over && 'ring-1 ring-blue-500 dark:ring-blue-400')}>{content}</div>
|
||||
</fieldset>} renderItem={(id, group) => {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
.ainu-ui-word
|
||||
{
|
||||
display: inline-block;
|
||||
display: inline;
|
||||
white-space: nowrap !important;
|
||||
word-break: keep-all !important;
|
||||
overflow-wrap: normal !important;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"_untranslated": ["languageNames.en"],
|
||||
"languageNames": {
|
||||
"ja": "シサㇺ イタㇰ",
|
||||
"ain": "ヤウンモシルン アイヌ イタㇰ"
|
||||
"ain": "ヤウンモシルン アイヌ イタㇰ",
|
||||
"en": "イングリㇱ イタㇰ"
|
||||
},
|
||||
"loadingLabel": "$t(common:loading)",
|
||||
"countLabel": "チピㇱキㇷ゚",
|
||||
|
||||
@@ -1,28 +1,311 @@
|
||||
{
|
||||
"_untranslated": [
|
||||
"*",
|
||||
"outputRows",
|
||||
"maskedReason",
|
||||
"downloadErrors",
|
||||
"axisLimit",
|
||||
"privateCondition",
|
||||
"errors"
|
||||
],
|
||||
"outputRows": "行数:{{value}} 件",
|
||||
"maskedReason": "閲覧権限のない履歴に由来するため、この項目の値は表示できません。集計件数には含まれます。",
|
||||
"downloadErrors": {
|
||||
"unavailable": "ダウンロードの認可が失われたか、ファイルの有効期限が切れています。",
|
||||
"failed": "ダウンロード応答を開始できませんでした。",
|
||||
"timeout": "ダウンロード応答の開始確認がタイムアウトしました。"
|
||||
"titles": {
|
||||
"posts": "ミンタㇻ ウコピㇱキ",
|
||||
"tags": "シロシ ウコピㇱキ",
|
||||
"nico_tags": "ソイタ シロシ ウコピㇱキ",
|
||||
"tag_descriptions": "シロシ オルㇱペ ウコピㇱキ ネワ オルㇱペ",
|
||||
"tag_groups": "シロシ ウコ イソネカ ウコピㇱキ ネワ オルㇱペ",
|
||||
"materials": "チカㇻペ ウコピㇱキ ネワ オルㇱペ"
|
||||
},
|
||||
"axisLimit": "集計軸は最大{{max}}個です。行・列の項目を減らしてください。",
|
||||
"privateCondition": "非公開情報を保護するため、この抽出条件では集計できません。",
|
||||
"target": "ウコピㇱキ イアヌ",
|
||||
"localeName": "レ({{language}})",
|
||||
"localeNameWithScript": "レ({{language}}・{{script}})",
|
||||
"localeAliases": "モㇱマ レ({{language}})",
|
||||
"localeAliasesWithScript": "モㇱマ レ({{language}}・{{script}})",
|
||||
"run": "ウコピㇱキ キ",
|
||||
"columnLimit": {
|
||||
"title": "コラム チピㇱキㇷ゚ イヨッタ ポロ ネ",
|
||||
"message": "ウコピㇱキ テーブル コラム {{max}} パㇰノ アエイワンケ エアㇱカイ。コラム イヌㇺケ シンナ カㇻ ネ ヤ、イフナラ クニ ペ ポンノ カㇻ ヤン。"
|
||||
},
|
||||
"conditions": "ウコピㇱキ イアヌ",
|
||||
"output": "アサンケ クニ オルㇱペ",
|
||||
"filters": "イフナラ イアヌ",
|
||||
"mode": "アヌカレ",
|
||||
"exportProgress": {
|
||||
"title": "エクスポート",
|
||||
"format": "イソネカ:{{format}}",
|
||||
"starting": "アオハシ コㇿ アン",
|
||||
"queued": "アテレ コㇿ アン",
|
||||
"running": "アカㇻ コㇿ アン",
|
||||
"aggregating": "ウコピㇱキ コㇿ アン",
|
||||
"writing": "アサンケ コㇿ アン",
|
||||
"cancelling": "ソモ キ クス イカㇻ コㇿ アン",
|
||||
"cancelled": "ソモ キ オケレ",
|
||||
"ready": "チヌイェㇷ゚ アカㇻ オケレ・ダウンロード アオハシ(アウカオ オケレ ヤ ソモ アエラマン)",
|
||||
"failed": "ハイタ",
|
||||
"processed": "{{count}} ペ アカㇻ オケレ",
|
||||
"measured": "{{processed}} / {{total}} ペ({{percent}}%)"
|
||||
},
|
||||
"displaySettings": "アヌカレ イアヌ",
|
||||
"summary": "ウコピㇱキ",
|
||||
"details": "オルㇱペ",
|
||||
"axis": "ウコピㇱキ アㇰシㇱ",
|
||||
"axes": {
|
||||
"month": "チュㇷ゚ ネ ウシンナ(UTC)",
|
||||
"user": "$t(history:actor)",
|
||||
"tags": "シロシ ネ ウシンナ"
|
||||
},
|
||||
"metric": "フㇱコアンペ レコード チピㇱキㇷ゚",
|
||||
"total": "フㇱコアンペ レコード チピㇱキㇷ゚:{{count}}",
|
||||
"csv": "CSV",
|
||||
"csvScope": "タネ アヌカレ ソㇱ ネワ アヌㇺケ コラム CSV ウン アサンケ",
|
||||
"empty": "ネ フㇱコアンペ イサㇺ。",
|
||||
"columns": {
|
||||
"changedAt": "イカㇻ ト ヘㇺ トキ",
|
||||
"version": "ウェㇽソン",
|
||||
"name": "レ",
|
||||
"resourceId": "Id.",
|
||||
"postTitle": "レ",
|
||||
"postURL": "URL",
|
||||
"tags": "シロシ",
|
||||
"deprecated": "エイワンケ ソモ エアㇱカイ シㇼキ",
|
||||
"latest": "イヨッタ アシㇼ シロシ",
|
||||
"thumbnailURL": "ポン ノカ モト URL",
|
||||
"originalFrom": "ホㇱキ アオ ト ヘㇺ トキ(オㇿ ワノ)",
|
||||
"originalBefore": "ホㇱキ アオ ト ヘㇺ トキ(エトコ ウン)",
|
||||
"language": "イタㇰ",
|
||||
"kind": "ウㇱケ オㇿ・ソイタ",
|
||||
"sections": "シロシ アオ トキ ウシ",
|
||||
"names": "イタㇰ ネ ウシンナ レ",
|
||||
"thumbnailDisplay": "$t(shared:labels.thumbnail)"
|
||||
},
|
||||
"fields": {
|
||||
"from": "アオハシ(エコㇿ マシン トキ・オㇿ ワノ)",
|
||||
"before": "オケレ(エコㇿ マシン トキ・エトコ ウン)",
|
||||
"post": "ミンタㇻ Id.",
|
||||
"tag": "シロシ レ ネ ヤ Id.",
|
||||
"actor": "イカㇻ クㇽ Id.",
|
||||
"limit": "アヌカレ ペ チピㇱキㇷ゚"
|
||||
},
|
||||
"latestOnly": "イヨッタ アシㇼ ペ パテㇰ",
|
||||
"truthValues": {
|
||||
"true": "アニ",
|
||||
"false": "ソモ"
|
||||
},
|
||||
"boolean": {
|
||||
"true": "ルウェ ウン",
|
||||
"false": "ソモ ネ"
|
||||
},
|
||||
"kinds": {
|
||||
"internal": "ウㇱケ オㇿ シロシ",
|
||||
"external": "ソイタ シロシ"
|
||||
},
|
||||
"nested": {
|
||||
"toggle": "テーブル オㇿ テーブル アㇱマカ・アセㇱケ",
|
||||
"selection": "サパ ペ OFF ネ ヤッカ ポン ペ イヌㇺケ アウカオ コㇿ アン。",
|
||||
"noColumns": "アサンケ クニ オルㇱペ ソモ アヌㇺケ。",
|
||||
"asJSON": "オㇱケ テーブル JSON アニ アヌカレ"
|
||||
},
|
||||
"sort": {
|
||||
"action": "クリッㇰ コㇿ シネ ソート。Ctrl ネ ヤ タンネ アオサンケ コㇿ キー アオ・シンナ・アウㇱカ。NULL イヨㇱノ。",
|
||||
"structured": "サパ テーブル オㇿ ポロンノ アン ペ アピㇱキ ワ、アウカオ ノカ イタㇰ シㇼ ネ アウコシㇼキ。Ctrl ネ ヤ タンネ アオサンケ コㇿ ポロンノ キー ソート エアㇱカイ。ポン テーブル ソート サパ テーブル ウン エラㇺシㇰ ソモ キ。",
|
||||
"label": "{{column}}:{{direction}}、ホㇱキ ネ イキㇼ {{priority}}",
|
||||
"none": "ソート イサㇺ",
|
||||
"ascending": "ポン ペ ホㇱキ",
|
||||
"descending": "ポロ ペ ホㇱキ",
|
||||
"unsortedLabel": "{{column}}:ソート イサㇺ",
|
||||
"singleLabel": "{{column}}:{{direction}}"
|
||||
},
|
||||
"notRegistered": "レ ソモ アヌイェ",
|
||||
"selection": {
|
||||
"table": "ウコピㇱキ ネワ オルㇱペ テーブル",
|
||||
"textMode": "イタㇰ イヌㇺケ コㇿ アン",
|
||||
"hint": "クリッㇰ コㇿ セル アヌㇺケ、ドラッグ ネ ヤ Shift+クリッㇰ コㇿ ウシ アヌㇺケ。タッチ ネ セル タンネ アオサンケ ワ ドラッグ。Ctrl+C/Cmd+C コㇿ コピー。アㇻㇺプヤラ ケイ コㇿ オマン、Shift+アㇻㇺプヤラ ケイ コㇿ ウシ ポロ カㇻ、Escape コㇿ アウㇱカ。",
|
||||
"copy": "コピー",
|
||||
"copyHeaders": "サパ レ コㇿ コピー",
|
||||
"row": "イキㇼ アヌㇺケ",
|
||||
"column": "コラム アヌㇺケ",
|
||||
"all": "オピッタ アヌㇺケ"
|
||||
},
|
||||
"resize": {
|
||||
"column": "{{column}} コラム サクマ シンナ。イキㇻ ドラッグ、タッチ ネ タンネ アオサンケ ワ ドラッグ。アㇻㇺプヤラ ケイ アニ カ シンナ エアㇱカイ。"
|
||||
},
|
||||
"latestValue": "イヨッタ アシㇼ",
|
||||
"sections": {
|
||||
"range": "{{begin}}~{{end}}",
|
||||
"separator": "、"
|
||||
},
|
||||
"reorder": "{{column}}:アサンケ クニ オルㇱペ イキㇼ シンナ",
|
||||
"state": {
|
||||
"list": "シロシ ネ ウシンナ ミンタㇻ チピㇱキㇷ゚ イキㇼ",
|
||||
"trend": "シロシ ネ ウシンナ ミンタㇻ チピㇱキㇷ゚ シンナ シㇼキ",
|
||||
"details": "ネ トキ コㇿ オルㇱペ",
|
||||
"history": "フㇱコアンペ ウコピㇱキ",
|
||||
"historyDetails": "フㇱコアンペ オルㇱペ",
|
||||
"at": "ウコピㇱキ トキ",
|
||||
"boundary": "ウコピㇱキ トキ(UTC)",
|
||||
"period": "トキ(UTC)",
|
||||
"grain": "トキ イソネカ(UTC)",
|
||||
"grains": {
|
||||
"day": "ト ネ ウシンナ",
|
||||
"month": "チュㇷ゚ ネ ウシンナ",
|
||||
"year": "パ ネ ウシンナ"
|
||||
},
|
||||
"postCount": "ミンタㇻ チピㇱキㇷ゚",
|
||||
"totals": {
|
||||
"posts": "ミンタㇻ チピㇱキㇷ゚ オピッタ",
|
||||
"tags": "ウㇱケ オㇿ シロシ チピㇱキㇷ゚ オピッタ",
|
||||
"assignments": "シロシ アオ ペ チピㇱキㇷ゚ オピッタ"
|
||||
}
|
||||
},
|
||||
"measure": "アピㇱキ ペ",
|
||||
"export": "エクスポート",
|
||||
"exportFormat": "エクスポート イソネカ",
|
||||
"exportFormats": {
|
||||
"json": "JSON",
|
||||
"csv": "CSV"
|
||||
},
|
||||
"resources": {
|
||||
"posts": "ミンタㇻ",
|
||||
"tags": "シロシ",
|
||||
"nico_tags": "ソイタ シロシ",
|
||||
"tag_descriptions": "シロシ オルㇱペ",
|
||||
"tag_groups": "シロシ ウコ イソネカ",
|
||||
"materials": "チカㇻペ"
|
||||
},
|
||||
"latest": "イヨッタ アシㇼ",
|
||||
"history": "フㇱコアンペ",
|
||||
"referenceAt": "アㇱパ ト ヘㇺ トキ",
|
||||
"referenceHelp": "エヌㇺケ トキ タ イヨッタ アシㇼ アン ペ アウコピㇱキ ワ アヌカレ。ソモ エヌイェ コㇿ タネ イヨッタ アシㇼ ペ アエイワンケ。",
|
||||
"from": "アオハシ",
|
||||
"through": "オケレ",
|
||||
"axisItems": "オルㇱペ イキㇼ",
|
||||
"rows": "イキㇼ",
|
||||
"cols": "コラム",
|
||||
"axisPlace": "{{field}} {{target}} ウン アオ",
|
||||
"field": "オルㇱペ",
|
||||
"operator": "オペレーター",
|
||||
"add": "アオ",
|
||||
"remove": "アウㇱカ",
|
||||
"value": "アヌイェ ペ",
|
||||
"blank": "エンプティ",
|
||||
"additionalConditions": "モㇱマ イアヌ",
|
||||
"changedField": "シンナ アカㇻ オルㇱペ",
|
||||
"eventType": "シンナ イソネカ",
|
||||
"count": "チピㇱキㇷ゚",
|
||||
"outputTotal": "チピㇱキㇷ゚:{{value}} ペ",
|
||||
"excludedRecords": "(アヌカㇻ エアイカㇷ゚ レコード {{count}} ペ アサンケ ソモ キ)",
|
||||
"recordTotal": "アヌカㇻ エアㇱカイ ペ チピㇱキㇷ゚:{{count}}",
|
||||
"grain": "トキ イソネカ",
|
||||
"grains": {
|
||||
"year": "パ",
|
||||
"quarter": "パ 4 イキㇼ オㇿ シネ ペ",
|
||||
"month": "チュㇷ゚",
|
||||
"week": "シネ ウィーㇰ",
|
||||
"day": "ト",
|
||||
"hour": "トキ"
|
||||
},
|
||||
"operatorSymbols": {
|
||||
"in": "IN",
|
||||
"not_in": "NOT IN",
|
||||
"any": "ANY",
|
||||
"all": "ALL",
|
||||
"none": "NONE",
|
||||
"eq": "=",
|
||||
"ne": "≠",
|
||||
"gt": ">",
|
||||
"lt": "<",
|
||||
"ge": "≥",
|
||||
"le": "≤",
|
||||
"like": "LIKE",
|
||||
"range": "~"
|
||||
},
|
||||
"operators": {
|
||||
"in": "IN",
|
||||
"not_in": "NOT IN",
|
||||
"any": "アヌㇺケ ペ オㇿ シネ ペ カ アン",
|
||||
"all": "アヌㇺケ ペ オピッタ アン",
|
||||
"none": "アヌㇺケ ペ シネ ペ カ イサㇺ",
|
||||
"eq": "ウコシノㇳ",
|
||||
"ne": "ウコシノㇳ ソモ キ",
|
||||
"gt": "イヨッタ ポロ",
|
||||
"lt": "ポン ワ アン",
|
||||
"ge": "ポロ ネ ヤ ウコシノㇳ",
|
||||
"le": "ポン ネ ヤ ウコシノㇳ",
|
||||
"like": "パターン",
|
||||
"range": "ウシ"
|
||||
},
|
||||
"pattern": {
|
||||
"input": "ソㇱ アヌカレ イソネカ",
|
||||
"single": "シネ ペ エヌイェ",
|
||||
"multiple": "ポロンノ エヌイェ",
|
||||
"list": "ポロンノ アヌㇺケ イキㇼ",
|
||||
"pattern": "データ パターン",
|
||||
"link": "データ ウコテㇷ゚",
|
||||
"columns": "アヌカレ コラム",
|
||||
"value": "アエイワンケ ペ",
|
||||
"selection": "イヌㇺケ"
|
||||
},
|
||||
"events": {
|
||||
"create": "アシㇼ アカㇻ",
|
||||
"update": "シンナ アカㇻ",
|
||||
"discard": "アウㇱカ",
|
||||
"restore": "アホシピレ"
|
||||
},
|
||||
"attributes": {
|
||||
"sourceLanguage": "イタㇰ シンナ カㇻ モト イタㇰ",
|
||||
"source_language_code": "イタㇰ シンナ カㇻ モト イタㇰ",
|
||||
"source_version_no": "モト ウェㇽソン チピㇱキㇷ゚",
|
||||
"event_type": "シンナ イソネカ",
|
||||
"body": "ヌイェㇷ゚",
|
||||
"linked_tags": "ウコテㇷ゚ シロシ",
|
||||
"description": "オルㇱペ",
|
||||
"members": "ウコオマレ ペ",
|
||||
"placements": "イアヌ ウシ",
|
||||
"parent_id": "オナ チカㇻペ",
|
||||
"tag_id": "シロシ",
|
||||
"tag_name": "シロシ レ",
|
||||
"tag_category": "シロシ イソネカ",
|
||||
"discarded_at": "アウㇱカ ト ヘㇺ トキ",
|
||||
"updated_at": "フㇱコアンペ イキㇼ アシㇼ アカㇻ ト ヘㇺ トキ",
|
||||
"updated_by_user_id": "フㇱコアンペ イキㇼ アシㇼ カㇻ クㇽ",
|
||||
"export_paths_json": "アサンケ パㇱ",
|
||||
"file_blob_id": "チヌイェㇷ゚ Id.",
|
||||
"file_filename": "チヌイェㇷ゚ レ",
|
||||
"file_content_type": "チヌイェㇷ゚ イソネカ",
|
||||
"file_byte_size": "チヌイェㇷ゚ サイズ",
|
||||
"file_checksum": "チェックサム",
|
||||
"file_sha256": "SHA-256",
|
||||
"source_kind": "モト イソネカ",
|
||||
"source_uri": "モト URI",
|
||||
"source_path": "モト パㇱ",
|
||||
"source_file_id": "モト チヌイェㇷ゚ Id.",
|
||||
"normalized_source_key": "モト イアヌ キー",
|
||||
"parent_post_ids": "サパ ミンタㇻ",
|
||||
"parent_tag_ids": "サパ シロシ",
|
||||
"video_ms": "チモイェノカ トキ",
|
||||
"original_created_from": "ホㇱキ アカㇻ ト ヘㇺ トキ(オㇿ ワノ)",
|
||||
"original_created_before": "ホㇱキ アカㇻ ト ヘㇺ トキ(エトコ ウン)",
|
||||
"deprecated_at": "エイワンケ ソモ エアㇱカイ アカㇻ ト",
|
||||
"section_name": "イキㇻ レ",
|
||||
"children": "ポン ペ",
|
||||
"profile": "アサンケ イアヌ",
|
||||
"path": "アサンケ パㇱ"
|
||||
},
|
||||
"rowNumberHeading": "No.",
|
||||
"subtotal": "ポン ウコピㇱキ",
|
||||
"singleTotal": "ウコピㇱキ オピッタ",
|
||||
"grandTotal": "オピッタ ウコピㇱキ",
|
||||
"rowNumbers": "イキㇼ チピㇱキㇷ゚",
|
||||
"periodSeparator": "〜",
|
||||
"bot": "$t(history:bot)",
|
||||
"actorWithId": "{{name}} (#{{id}})",
|
||||
"axisWithGrain": "{{field}}({{grain}})",
|
||||
"outputRows": "イキㇼ チピㇱキㇷ゚:{{value}} ペ",
|
||||
"maskedReason": "エヌカㇻ エアイカㇷ゚ フㇱコアンペ オㇿ ワ アウㇰ ペ クス、タン オルㇱペ アヌカレ エアイカㇷ゚。ウコピㇱキ チピㇱキㇷ゚ オㇿ アン。",
|
||||
"downloadErrors": {
|
||||
"unavailable": "ダウンロード エアㇱカイ イレンカ イサㇺ ネ ヤ、チヌイェㇷ゚ エイワンケ エアㇱカイ トキ オケレ。",
|
||||
"failed": "ダウンロード ホㇱキ アオハシ カ エアイカㇷ゚。",
|
||||
"timeout": "ダウンロード アオハシ アエヌカㇻ トキ オケレ。"
|
||||
},
|
||||
"axisLimit": "ウコピㇱキ アㇰシㇱ {{max}} パㇰノ ネ。イキㇼ ネワ コラム オルㇱペ ポンノ カㇻ ヤン。",
|
||||
"privateCondition": "ソモ アヌカレ オルㇱペ アシッケウ クス、タン イフナラ イアヌ アニ ウコピㇱキ エアイカㇷ゚。",
|
||||
"errors": {
|
||||
"queryTimeout": "集計処理が時間制限を超えました。抽出条件や集計軸を減らして再実行してください。",
|
||||
"workerUnavailable": "エクスポートワーカーが稼働していません。",
|
||||
"stopped": "エクスポート処理が停止しました。再実行してください。",
|
||||
"input": "入力条件が正しくありません。",
|
||||
"permission": "この操作を実行する権限がありません。",
|
||||
"authentication": "認証状態を確認して再実行してください。"
|
||||
"queryTimeout": "ウコピㇱキ イカㇻ トキ オケレ。イフナラ イアヌ ネ ヤ ウコピㇱキ アㇰシㇱ ポンノ カㇻ ワ スイ キ ヤン。",
|
||||
"workerUnavailable": "エクスポート ワーカー イカㇻ ソモ キ。",
|
||||
"stopped": "エクスポート イカㇻ オケレ ソモ キ。スイ キ ヤン。",
|
||||
"input": "エヌイェㇷ゚ シㇼキ ピㇼカ ソモ ネ。",
|
||||
"permission": "タン イカㇻ キ クス イレンカ エコㇿ ソモ キ。",
|
||||
"authentication": "エアヌカㇻ シㇼキ エヌカㇻ ワ スイ キ ヤン。"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
{
|
||||
"_untranslated": [
|
||||
"exportPath",
|
||||
"restoreScope",
|
||||
"nameChanges.title",
|
||||
"nameChanges.script",
|
||||
"nameChanges.automatic",
|
||||
"nameChanges.certified",
|
||||
"nameChanges.otherLanguages",
|
||||
"nameChanges.changed",
|
||||
"nameChanges.currentReference",
|
||||
"nameChanges.noNameRecord",
|
||||
"nameChanges.uncertified"
|
||||
],
|
||||
"exportPath": "export_path",
|
||||
"exportPath": "アサンケ パㇱ",
|
||||
"postTitle": "$t(tags:history)",
|
||||
"tagTitle": "シロシ イヌイェㇷ゚ フㇱコアンペ",
|
||||
"materialTitle": "チカㇻペ フㇱコアンペ",
|
||||
@@ -32,17 +19,17 @@
|
||||
"confirmTitle": "アホシピレ エヌカㇻ",
|
||||
"confirmPost": "「{{name}}」 ウェㇽソン {{version}} ウン アホシピレ ヤ?\nピㇼカ ヤ?",
|
||||
"confirmTag": "シロシ「{{name}}」 ウェㇽソン {{version}} ウン アホシピレ ヤ?\nピㇼカ ヤ?",
|
||||
"restoreScope": "名称・別名は現在表示中の言語だけを差し戻します。全言語の名称は復元しません。",
|
||||
"restoreScope": "レ ネワ モㇱマ レ タネ アヌカレ イタㇰ パテㇰ アホシピレ。イタㇰ オピッタ コㇿ レ アホシピレ ソモ キ。",
|
||||
"nameChanges": {
|
||||
"currentReference": "現行名・参考",
|
||||
"noNameRecord": "この版には名称記録なし",
|
||||
"otherLanguages": "他言語情報",
|
||||
"changed": "(異動あり)",
|
||||
"uncertified": "(未公証)",
|
||||
"title": "言語別の変更",
|
||||
"script": "文字体系",
|
||||
"automatic": "自動生成",
|
||||
"certified": "公証済み"
|
||||
"currentReference": "タネ レ・アエヌカㇻ クニ ペ",
|
||||
"noNameRecord": "タン ウェㇽソン タ レ アヌイェ ペ イサㇺ",
|
||||
"otherLanguages": "モㇱマ イタㇰ オルㇱペ",
|
||||
"changed": "(シンナ アカㇻ)",
|
||||
"uncertified": "(ナ ソモ アヌカㇻ オケレ)",
|
||||
"title": "イタㇰ ネ ウシンナ シンナ アカㇻ ペ",
|
||||
"script": "ヌイェ イソネカ",
|
||||
"automatic": "ヤイウㇰ アカㇻ",
|
||||
"certified": "(アヌカㇻ オケレ)"
|
||||
},
|
||||
"revert": "アホシピレ",
|
||||
"restored": "アホシピレ オケレ!",
|
||||
|
||||
@@ -24,5 +24,7 @@
|
||||
"settings": "イアヌ",
|
||||
"legal": "イレンカ",
|
||||
"terms": "エイワンケ イレンカ",
|
||||
"more": "モㇱマ アン ペ »"
|
||||
"more": "モㇱマ アン ペ »",
|
||||
"counts": "ウコピㇱキ",
|
||||
"externalTags": "ソイタ シロシ"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"head": { "description": "$t(posts:list.tagline)" },
|
||||
"head": {
|
||||
"description": "$t(posts:list.tagline)"
|
||||
},
|
||||
"dialogue": {
|
||||
"confirm": "エヌカㇻ ワ ピㇼカ",
|
||||
"close": "$t(settings:actions.close)"
|
||||
@@ -56,7 +58,8 @@
|
||||
"latest": "(イヨッタ アシㇼ)",
|
||||
"diff": "ウシンナイペ",
|
||||
"restore": "ホㇱピ",
|
||||
"show": "ヌカㇻ"
|
||||
"show": "ヌカㇻ",
|
||||
"count": "チピㇱキㇷ゚"
|
||||
},
|
||||
"messages": {
|
||||
"loading": "アウㇰ コㇿ アン…",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"languageNames": {
|
||||
"ja": "日本語",
|
||||
"ain": "北海道アイヌ語",
|
||||
"en": "英語"
|
||||
"en": "イングランド語"
|
||||
},
|
||||
"loadingLabel": "Loading",
|
||||
"countLabel": "件数",
|
||||
|
||||
@@ -70,11 +70,11 @@
|
||||
"thumbnailDisplay": "$t(shared:labels.thumbnail)"
|
||||
},
|
||||
"fields": {
|
||||
"from": "開始(端末の時刻帯・以上)",
|
||||
"before": "終了(端末の時刻帯・未満)",
|
||||
"post": "投稿 ID",
|
||||
"tag": "タグ名または ID",
|
||||
"actor": "操作者 ID",
|
||||
"from": "開始(以上)",
|
||||
"before": "終了(未満)",
|
||||
"post": "投稿 Id.",
|
||||
"tag": "タグ名または Id.",
|
||||
"actor": "操作者 Id.",
|
||||
"limit": "表示件数"
|
||||
},
|
||||
"latestOnly": "最新のみ",
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import CountsResourcePage from '@/pages/CountsResourcePage'
|
||||
|
||||
|
||||
const CountsPage = () => <CountsResourcePage resource="posts"/>
|
||||
|
||||
export default CountsPage
|
||||
+1
-1
@@ -6,7 +6,7 @@ import { HelmetProvider } from 'react-helmet-async'
|
||||
import { createMemoryRouter, RouterProvider, useLocation } from 'react-router-dom'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import CountsResourcePage from '@/pages/CountsResourcePage'
|
||||
import CountsResourcePage from '@/pages/counts/CountsResourcePage'
|
||||
import DialogueProvider from '@/components/dialogues/DialogueProvider'
|
||||
import { countsKeys } from '@/lib/queryKeys'
|
||||
import SortableColumns from '@/components/common/SortableColumns'
|
||||
+25
-16
@@ -222,7 +222,8 @@ const CountsResourceContent = (
|
||||
}
|
||||
catch (failure)
|
||||
{
|
||||
if (pendingRun.current !== pending) return
|
||||
if (pendingRun.current !== pending)
|
||||
return
|
||||
pendingRun.current = null
|
||||
setRunning (false)
|
||||
setRunFailure (failure)
|
||||
@@ -252,7 +253,8 @@ const CountsResourceContent = (
|
||||
if (key === 'measures' || key.startsWith ('measures.')) next.delete (key)
|
||||
for (const [key, value] of Object.entries (extra)) next.set (key, value)
|
||||
}
|
||||
if (key === 'sort') next.delete ('page')
|
||||
if (key === 'sort')
|
||||
next.delete ('page')
|
||||
setParams (next)
|
||||
}
|
||||
const download = async (format: 'csv' | 'json') => {
|
||||
@@ -321,8 +323,11 @@ const CountsResourceContent = (
|
||||
aria-expanded={expanded.has (target)} aria-controls={`counts-definition-${target}`}
|
||||
onClick={() => setExpanded (previous => {
|
||||
const next = new Set (previous)
|
||||
if (next.has (target)) next.delete (target)
|
||||
else next.add (target)
|
||||
if (next.has (target))
|
||||
next.delete (target)
|
||||
else
|
||||
next.add (target)
|
||||
|
||||
return next
|
||||
})}>
|
||||
<span aria-hidden className="inline-block" style={{
|
||||
@@ -331,18 +336,22 @@ const CountsResourceContent = (
|
||||
{label (`resources.${target}`)}
|
||||
</button>
|
||||
<AnimatedCollapse open={expanded.has (target)} id={`counts-definition-${target}`}>
|
||||
<ul className="ml-3">
|
||||
{(['latest', 'history'] as const).map (definition => {
|
||||
const next = countsDefinitionParams (definition, params)
|
||||
const active = target === resource
|
||||
&& params.get ('definition') === definition
|
||||
return <li key={definition}><PrefetchLink
|
||||
to={`/counts/${target}?${next}`}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
className={cn ('block py-1', active && 'font-bold underline')}>
|
||||
{label (definition)}</PrefetchLink></li>
|
||||
})}
|
||||
</ul>
|
||||
<ul className="ml-3">
|
||||
{(['latest', 'history'] as const).map (definition => {
|
||||
const next = countsDefinitionParams (definition, params)
|
||||
const active = target === resource
|
||||
&& params.get ('definition') === definition
|
||||
return (
|
||||
<li key={definition}>
|
||||
<PrefetchLink
|
||||
to={`/counts/${target}?${next}`}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
className={cn ('block py-1', active && 'font-bold underline')}>
|
||||
{label (definition)}
|
||||
</PrefetchLink>
|
||||
</li>)
|
||||
})}
|
||||
</ul>
|
||||
</AnimatedCollapse>
|
||||
</li>)}
|
||||
</ul>
|
||||
新しいイシューから参照
ユーザーをブロックする