@interactive-inc/docs-client 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.html +13 -0
- package/package.json +5 -2
- package/.tanstack/tmp/router-generator-Eq20Yx/50b11a0f488e61cf92364a7e585c1448 +0 -123
- package/.tanstack/tmp/router-generator-KQIYSa/aecb094e4891f9475b91de590abc8809 +0 -117
- package/.tanstack/tmp/router-generator-ekW2pI/aecb094e4891f9475b91de590abc8809 +0 -117
- package/app/components/archived-file-list-view.tsx +0 -99
- package/app/components/directory-file-list-view.tsx +0 -32
- package/app/components/directory-file-tree-node.tsx +0 -96
- package/app/components/directory-layout-sidebar.tsx +0 -181
- package/app/components/directory-page-view.tsx +0 -208
- package/app/components/directory-table-view.tsx +0 -383
- package/app/components/editable-table-cell/boolean-editable-cell.tsx +0 -44
- package/app/components/editable-table-cell/multi-boolean-editable-cell.tsx +0 -59
- package/app/components/editable-table-cell/multi-number-editable-cell.tsx +0 -62
- package/app/components/editable-table-cell/multi-relation-editable-cell.tsx +0 -19
- package/app/components/editable-table-cell/multi-select-number-editable-cell.tsx +0 -82
- package/app/components/editable-table-cell/multi-select-text-editable-cell.tsx +0 -82
- package/app/components/editable-table-cell/multi-text-editable-cell.tsx +0 -62
- package/app/components/editable-table-cell/number-editable-cell.tsx +0 -54
- package/app/components/editable-table-cell/relation-editable-cell.tsx +0 -20
- package/app/components/editable-table-cell/select-number-editable-cell.tsx +0 -43
- package/app/components/editable-table-cell/select-text-editable-cell.tsx +0 -33
- package/app/components/editable-table-cell/string-editable-cell.tsx +0 -42
- package/app/components/editable-table-cell.tsx +0 -129
- package/app/components/file-card.tsx +0 -120
- package/app/components/file-tree-icon.tsx +0 -26
- package/app/components/file-view/archive-restore-banner.tsx +0 -86
- package/app/components/file-view/csv-file-view.tsx +0 -50
- package/app/components/file-view/default-file-view.tsx +0 -11
- package/app/components/file-view/editable-csv-cell.tsx +0 -37
- package/app/components/file-view/editable-front-matter-view.tsx +0 -105
- package/app/components/file-view/file-csv-columns-view.tsx +0 -181
- package/app/components/file-view/file-csv-table.tsx +0 -142
- package/app/components/file-view/file-header.tsx +0 -115
- package/app/components/file-view/file-page-view.tsx +0 -165
- package/app/components/file-view/front-matter-input-field.tsx +0 -157
- package/app/components/file-view/front-matter-view.tsx +0 -30
- package/app/components/file-view/json-file-editor.tsx +0 -38
- package/app/components/file-view/markdown-file-view.tsx +0 -71
- package/app/components/file-view/multi-relation-select.tsx +0 -129
- package/app/components/file-view/render-front-matter-value.tsx +0 -57
- package/app/components/file-view/single-relation-select.tsx +0 -41
- package/app/components/file-view/sortable-csv-row.tsx +0 -57
- package/app/components/file-view/sortable-header.tsx +0 -30
- package/app/components/file-view/utils.ts +0 -8
- package/app/components/milestones-editor.tsx +0 -95
- package/app/components/page-header.tsx +0 -20
- package/app/components/project-view/feature-item.tsx +0 -139
- package/app/components/project-view/project-page-group.tsx +0 -125
- package/app/components/project-view/project-view.tsx +0 -230
- package/app/components/project-view/unlinked-features-section.tsx +0 -73
- package/app/components/providers.tsx +0 -21
- package/app/components/reload-button.tsx +0 -25
- package/app/components/sidebar-button.tsx +0 -13
- package/app/components/ui/alert.tsx +0 -66
- package/app/components/ui/badge.tsx +0 -46
- package/app/components/ui/button.tsx +0 -59
- package/app/components/ui/card.tsx +0 -92
- package/app/components/ui/dialog.tsx +0 -133
- package/app/components/ui/emoji-picker.tsx +0 -133
- package/app/components/ui/icons.tsx +0 -3
- package/app/components/ui/input.tsx +0 -21
- package/app/components/ui/select.tsx +0 -183
- package/app/components/ui/separator.tsx +0 -26
- package/app/components/ui/sheet.tsx +0 -137
- package/app/components/ui/sidebar.tsx +0 -724
- package/app/components/ui/simple-sidebar.tsx +0 -50
- package/app/components/ui/skeleton.tsx +0 -13
- package/app/components/ui/sonner.tsx +0 -23
- package/app/components/ui/table.tsx +0 -114
- package/app/components/ui/tabs.tsx +0 -53
- package/app/components/ui/textarea.tsx +0 -18
- package/app/components/ui/tooltip.tsx +0 -59
- package/app/components/vscode-button.tsx +0 -23
- package/app/hooks/use-directory-query.ts +0 -22
- package/app/hooks/use-file-properties-mutation.ts +0 -38
- package/app/hooks/use-file-query.ts +0 -21
- package/app/hooks/use-mobile.ts +0 -19
- package/app/main.css +0 -122
- package/app/main.tsx +0 -38
- package/app/routes/$.index.tsx +0 -25
- package/app/routes/$.tsx +0 -20
- package/app/routes/__root.tsx +0 -20
- package/app/routes/_index.tsx +0 -17
- package/app/routes/apps.client.$.tsx +0 -21
- package/components.json +0 -21
- package/tsconfig.index.json +0 -17
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { X } from "lucide-react"
|
|
2
|
-
import { Button } from "@/components/ui/button"
|
|
3
|
-
import {
|
|
4
|
-
Select,
|
|
5
|
-
SelectContent,
|
|
6
|
-
SelectItem,
|
|
7
|
-
SelectTrigger,
|
|
8
|
-
SelectValue,
|
|
9
|
-
} from "@/components/ui/select"
|
|
10
|
-
import type { DocRelationFile } from "@/lib/types"
|
|
11
|
-
|
|
12
|
-
type Props = {
|
|
13
|
-
value: string[]
|
|
14
|
-
onValueChange: (value: string[]) => void
|
|
15
|
-
relationOptions?: DocRelationFile[]
|
|
16
|
-
wrap?: boolean
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* 複数リレーション用のSelectコンポーネント
|
|
21
|
-
*/
|
|
22
|
-
export function MultiRelationSelect(props: Props) {
|
|
23
|
-
const options = props.relationOptions || []
|
|
24
|
-
const selectedValues = Array.isArray(props.value) ? props.value : []
|
|
25
|
-
|
|
26
|
-
// 選択済みの値を除外した利用可能なオプション
|
|
27
|
-
const availableOptions = options.filter(
|
|
28
|
-
(option) => !selectedValues.includes(option.name),
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
const addValue = (newValue: string) => {
|
|
32
|
-
if (newValue && !selectedValues.includes(newValue)) {
|
|
33
|
-
const updatedValues = [...selectedValues, newValue]
|
|
34
|
-
props.onValueChange(updatedValues)
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const removeValue = (valueToRemove: string) => {
|
|
39
|
-
const updatedValues = selectedValues.filter((val) => val !== valueToRemove)
|
|
40
|
-
props.onValueChange(updatedValues)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// wrapありの場合(ファイル詳細用)
|
|
44
|
-
if (props.wrap) {
|
|
45
|
-
return (
|
|
46
|
-
<div className="space-y-2">
|
|
47
|
-
{/* 新しい値を追加するSelect */}
|
|
48
|
-
{availableOptions.length > 0 && (
|
|
49
|
-
<Select onValueChange={addValue}>
|
|
50
|
-
<SelectTrigger className="w-full">
|
|
51
|
-
<SelectValue placeholder="項目を追加" />
|
|
52
|
-
</SelectTrigger>
|
|
53
|
-
<SelectContent>
|
|
54
|
-
{availableOptions.map((option) => (
|
|
55
|
-
<SelectItem key={option.name} value={option.name}>
|
|
56
|
-
{option.label}
|
|
57
|
-
</SelectItem>
|
|
58
|
-
))}
|
|
59
|
-
</SelectContent>
|
|
60
|
-
</Select>
|
|
61
|
-
)}
|
|
62
|
-
{/* 選択済みの値を表示(wrap対応) */}
|
|
63
|
-
<div className="flex flex-wrap gap-2">
|
|
64
|
-
{selectedValues.toReversed().map((value) => {
|
|
65
|
-
const option = options.find((opt) => opt.name === value)
|
|
66
|
-
const label = option?.label || value
|
|
67
|
-
return (
|
|
68
|
-
<Button
|
|
69
|
-
key={value}
|
|
70
|
-
variant={"outline"}
|
|
71
|
-
onClick={() => removeValue(value)}
|
|
72
|
-
className="h-auto break-words text-left"
|
|
73
|
-
>
|
|
74
|
-
<span className="flex-1">{label}</span>
|
|
75
|
-
<X className="ml-2 w-4 flex-shrink-0 text-muted-foreground" />
|
|
76
|
-
</Button>
|
|
77
|
-
)
|
|
78
|
-
})}
|
|
79
|
-
</div>
|
|
80
|
-
{selectedValues.length === 0 && availableOptions.length === 0 && (
|
|
81
|
-
<div className="text-muted-foreground text-sm">
|
|
82
|
-
利用可能なオプションがありません
|
|
83
|
-
</div>
|
|
84
|
-
)}
|
|
85
|
-
</div>
|
|
86
|
-
)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// wrapなしの場合(テーブル用)
|
|
90
|
-
return (
|
|
91
|
-
<div className="flex gap-2">
|
|
92
|
-
{/* 新しい値を追加するSelect */}
|
|
93
|
-
{availableOptions.length > 0 && (
|
|
94
|
-
<Select onValueChange={addValue}>
|
|
95
|
-
<SelectTrigger className="min-w-24">
|
|
96
|
-
<SelectValue placeholder="項目を追加" />
|
|
97
|
-
</SelectTrigger>
|
|
98
|
-
<SelectContent>
|
|
99
|
-
{availableOptions.map((option) => (
|
|
100
|
-
<SelectItem key={option.name} value={option.name}>
|
|
101
|
-
{option.label}
|
|
102
|
-
</SelectItem>
|
|
103
|
-
))}
|
|
104
|
-
</SelectContent>
|
|
105
|
-
</Select>
|
|
106
|
-
)}
|
|
107
|
-
{/* 選択済みの値を表示(横並び) */}
|
|
108
|
-
{selectedValues.toReversed().map((value) => {
|
|
109
|
-
const option = options.find((opt) => opt.name === value)
|
|
110
|
-
const label = option?.label || value
|
|
111
|
-
return (
|
|
112
|
-
<Button
|
|
113
|
-
key={value}
|
|
114
|
-
variant={"outline"}
|
|
115
|
-
onClick={() => removeValue(value)}
|
|
116
|
-
>
|
|
117
|
-
<span>{label}</span>
|
|
118
|
-
<X className="w-4 text-muted-foreground" />
|
|
119
|
-
</Button>
|
|
120
|
-
)
|
|
121
|
-
})}
|
|
122
|
-
{selectedValues.length === 0 && availableOptions.length === 0 && (
|
|
123
|
-
<div className="text-muted-foreground text-sm">
|
|
124
|
-
利用可能なオプションがありません
|
|
125
|
-
</div>
|
|
126
|
-
)}
|
|
127
|
-
</div>
|
|
128
|
-
)
|
|
129
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
type Props = {
|
|
2
|
-
value: unknown
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* フロントマターの値を再帰的にレンダリングする
|
|
7
|
-
*/
|
|
8
|
-
export function RenderFrontMatterValue(props: Props) {
|
|
9
|
-
const value = props.value
|
|
10
|
-
|
|
11
|
-
if (value === null || value === undefined) {
|
|
12
|
-
return <span>null</span>
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if (typeof value === "boolean") {
|
|
16
|
-
return <span>{value.toString()}</span>
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (typeof value === "number") {
|
|
20
|
-
return <span>{value}</span>
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (typeof value === "string") {
|
|
24
|
-
return <span>"{value}"</span>
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (Array.isArray(value)) {
|
|
28
|
-
return (
|
|
29
|
-
<div>
|
|
30
|
-
{value.map((item, index) => (
|
|
31
|
-
<div
|
|
32
|
-
key={`item-${index}-${String(item).substring(0, 8)}`}
|
|
33
|
-
className="flex"
|
|
34
|
-
>
|
|
35
|
-
<span className="mr-2">-</span>
|
|
36
|
-
<RenderFrontMatterValue value={item} />
|
|
37
|
-
</div>
|
|
38
|
-
))}
|
|
39
|
-
</div>
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (typeof value === "object") {
|
|
44
|
-
return (
|
|
45
|
-
<div>
|
|
46
|
-
{Object.entries(value as Record<string, unknown>).map(([key, val]) => (
|
|
47
|
-
<div key={key} className="flex">
|
|
48
|
-
<span className="mr-2">{key}:</span>
|
|
49
|
-
<RenderFrontMatterValue value={val} />
|
|
50
|
-
</div>
|
|
51
|
-
))}
|
|
52
|
-
</div>
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return <span>{String(value)}</span>
|
|
57
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Select,
|
|
3
|
-
SelectContent,
|
|
4
|
-
SelectItem,
|
|
5
|
-
SelectTrigger,
|
|
6
|
-
SelectValue,
|
|
7
|
-
} from "@/components/ui/select"
|
|
8
|
-
|
|
9
|
-
import type { DocRelationFile } from "@/lib/types"
|
|
10
|
-
|
|
11
|
-
type Props = {
|
|
12
|
-
value: string
|
|
13
|
-
onValueChange: (value: string) => void
|
|
14
|
-
relationOptions?: DocRelationFile[]
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* 単一リレーション用のSelectコンポーネント
|
|
19
|
-
*/
|
|
20
|
-
export function SingleRelationSelect(props: Props) {
|
|
21
|
-
const options = props.relationOptions || []
|
|
22
|
-
|
|
23
|
-
const handleValueChange = (value: string) => {
|
|
24
|
-
props.onValueChange(value)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<Select value={props.value || ""} onValueChange={handleValueChange}>
|
|
29
|
-
<SelectTrigger className="w-full bg-background">
|
|
30
|
-
<SelectValue placeholder="リレーションを選択" />
|
|
31
|
-
</SelectTrigger>
|
|
32
|
-
<SelectContent>
|
|
33
|
-
{options.map((option) => (
|
|
34
|
-
<SelectItem key={option.name} value={option.name}>
|
|
35
|
-
{option.label}
|
|
36
|
-
</SelectItem>
|
|
37
|
-
))}
|
|
38
|
-
</SelectContent>
|
|
39
|
-
</Select>
|
|
40
|
-
)
|
|
41
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { useSortable } from "@dnd-kit/sortable"
|
|
2
|
-
import { CSS } from "@dnd-kit/utilities"
|
|
3
|
-
import { GripVertical } from "lucide-react"
|
|
4
|
-
import { TableCell, TableRow } from "@/components/ui/table"
|
|
5
|
-
import { EditableCsvCell } from "./editable-csv-cell"
|
|
6
|
-
|
|
7
|
-
type Props = {
|
|
8
|
-
id: string
|
|
9
|
-
row: string[]
|
|
10
|
-
rowIndex: number
|
|
11
|
-
onCellUpdate: (rowIndex: number, columnIndex: number, value: string) => void
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function SortableCsvRow(props: Props) {
|
|
15
|
-
const sortable = useSortable({
|
|
16
|
-
id: props.id,
|
|
17
|
-
attributes: {
|
|
18
|
-
role: "button",
|
|
19
|
-
tabIndex: 0,
|
|
20
|
-
},
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
const style = {
|
|
24
|
-
transform: CSS.Transform.toString(sortable.transform),
|
|
25
|
-
opacity: sortable.isDragging ? 0.5 : 1,
|
|
26
|
-
touchAction: "none",
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function handleCellChange(
|
|
30
|
-
rowIndex: number,
|
|
31
|
-
columnIndex: number,
|
|
32
|
-
value: string,
|
|
33
|
-
) {
|
|
34
|
-
props.onCellUpdate(rowIndex, columnIndex, value)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<TableRow
|
|
39
|
-
ref={sortable.setNodeRef}
|
|
40
|
-
style={style}
|
|
41
|
-
className="hover:opacity-60"
|
|
42
|
-
>
|
|
43
|
-
<TableCell {...sortable.attributes} {...sortable.listeners}>
|
|
44
|
-
<GripVertical className="size-4" />
|
|
45
|
-
</TableCell>
|
|
46
|
-
{props.row.map((cell: string, cellIndex: number) => (
|
|
47
|
-
<EditableCsvCell
|
|
48
|
-
key={`cell-${props.rowIndex}-${cellIndex}-${cell}`}
|
|
49
|
-
content={cell}
|
|
50
|
-
rowIndex={props.rowIndex}
|
|
51
|
-
columnIndex={cellIndex}
|
|
52
|
-
onCellChange={handleCellChange}
|
|
53
|
-
/>
|
|
54
|
-
))}
|
|
55
|
-
</TableRow>
|
|
56
|
-
)
|
|
57
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { useSortable } from "@dnd-kit/sortable"
|
|
2
|
-
import { CSS } from "@dnd-kit/utilities"
|
|
3
|
-
|
|
4
|
-
type Props = {
|
|
5
|
-
id: string
|
|
6
|
-
index: number
|
|
7
|
-
children: React.ReactNode
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function CsvSortableHeader({ id, index, children }: Props) {
|
|
11
|
-
const { attributes, listeners, setNodeRef, transform, transition } =
|
|
12
|
-
useSortable({ id })
|
|
13
|
-
|
|
14
|
-
const style = {
|
|
15
|
-
transform: CSS.Transform.toString(transform),
|
|
16
|
-
transition,
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return (
|
|
20
|
-
<th
|
|
21
|
-
ref={setNodeRef}
|
|
22
|
-
style={style}
|
|
23
|
-
{...attributes}
|
|
24
|
-
{...listeners}
|
|
25
|
-
className="cursor-grab border border-gray-600 p-2 text-sm"
|
|
26
|
-
>
|
|
27
|
-
{children}
|
|
28
|
-
</th>
|
|
29
|
-
)
|
|
30
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { useSuspenseQuery } from "@tanstack/react-query"
|
|
2
|
-
import { Card } from "@/components/ui/card"
|
|
3
|
-
import { apiClient } from "@/lib/api-client"
|
|
4
|
-
import type { DocFile, DocFileMd } from "@/lib/types"
|
|
5
|
-
|
|
6
|
-
type Props = {
|
|
7
|
-
project: string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function MilestonesEditor(props: Props) {
|
|
11
|
-
const { data: milestonesData } = useSuspenseQuery({
|
|
12
|
-
queryKey: ["directories", `products/${props.project}/milestones`],
|
|
13
|
-
queryFn: async () => {
|
|
14
|
-
const response = await apiClient.api.directories[":path{.+}"].$get({
|
|
15
|
-
param: { path: `products/${props.project}/milestones` },
|
|
16
|
-
})
|
|
17
|
-
if (!response.ok) {
|
|
18
|
-
throw new Error("Failed to fetch milestones")
|
|
19
|
-
}
|
|
20
|
-
return response.json()
|
|
21
|
-
},
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
const { data: featuresData } = useSuspenseQuery({
|
|
25
|
-
queryKey: ["directories", `products/${props.project}/features`],
|
|
26
|
-
queryFn: async () => {
|
|
27
|
-
const response = await apiClient.api.directories[":path{.+}"].$get({
|
|
28
|
-
param: { path: `products/${props.project}/features` },
|
|
29
|
-
})
|
|
30
|
-
if (!response.ok) {
|
|
31
|
-
throw new Error("Failed to fetch features")
|
|
32
|
-
}
|
|
33
|
-
return response.json()
|
|
34
|
-
},
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
function isDocFileMd(file: DocFile): file is DocFileMd {
|
|
38
|
-
return file.type === 'markdown'
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function hasMarkdownContent(file: { content: unknown }): file is { content: { title: string; description: string; frontMatter: Record<string, unknown> } } {
|
|
42
|
-
return typeof file.content === 'object' && file.content !== null && 'frontMatter' in file.content
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const milestones = (milestonesData.files || []).filter(isDocFileMd)
|
|
46
|
-
const features = (featuresData.files || []).filter(isDocFileMd)
|
|
47
|
-
|
|
48
|
-
function getFeaturesByMilestone(milestoneId: string) {
|
|
49
|
-
return features.filter(
|
|
50
|
-
(feature) => {
|
|
51
|
-
if (!hasMarkdownContent(feature)) return false
|
|
52
|
-
const frontMatter = feature.content.frontMatter
|
|
53
|
-
return typeof frontMatter === 'object' && frontMatter !== null && 'milestone' in frontMatter && frontMatter.milestone === milestoneId
|
|
54
|
-
}
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return (
|
|
59
|
-
<div className="grid gap-2">
|
|
60
|
-
{milestones.map((milestone) => {
|
|
61
|
-
const milestoneFeatures = getFeaturesByMilestone(milestone.path.name)
|
|
62
|
-
return (
|
|
63
|
-
<Card key={milestone.path.name} className="gap-2 overflow-hidden p-2">
|
|
64
|
-
<div className="space-y-1">
|
|
65
|
-
<h2 className="font-semibold text-xl">
|
|
66
|
-
{hasMarkdownContent(milestone) ? milestone.content.title || milestone.path.name : milestone.path.name}
|
|
67
|
-
</h2>
|
|
68
|
-
<p className="text-muted-foreground text-sm">
|
|
69
|
-
{hasMarkdownContent(milestone) ? milestone.content.description : ''}
|
|
70
|
-
</p>
|
|
71
|
-
</div>
|
|
72
|
-
{milestoneFeatures.length > 0 ? (
|
|
73
|
-
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
|
74
|
-
{milestoneFeatures.map((feature) => (
|
|
75
|
-
<div key={feature.path.name} className="rounded border p-2">
|
|
76
|
-
<h3 className="font-medium">
|
|
77
|
-
{hasMarkdownContent(feature) ? feature.content.title || feature.path.name : feature.path.name}
|
|
78
|
-
</h3>
|
|
79
|
-
<p className="text-muted-foreground text-sm">
|
|
80
|
-
{hasMarkdownContent(feature) ? feature.content.description : ''}
|
|
81
|
-
</p>
|
|
82
|
-
</div>
|
|
83
|
-
))}
|
|
84
|
-
</div>
|
|
85
|
-
) : (
|
|
86
|
-
<p className="py-4 text-center text-muted-foreground">
|
|
87
|
-
このマイルストーンに関連する機能はありません
|
|
88
|
-
</p>
|
|
89
|
-
)}
|
|
90
|
-
</Card>
|
|
91
|
-
)
|
|
92
|
-
})}
|
|
93
|
-
</div>
|
|
94
|
-
)
|
|
95
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react"
|
|
2
|
-
import { SidebarButton } from "@/components/sidebar-button"
|
|
3
|
-
import { Input } from "@/components/ui/input"
|
|
4
|
-
|
|
5
|
-
type Props = {
|
|
6
|
-
filePath: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function PageHeader(props: Props): ReactNode {
|
|
10
|
-
const paths = props.filePath.split("/")
|
|
11
|
-
|
|
12
|
-
const fileName = paths[paths.length - 1]
|
|
13
|
-
|
|
14
|
-
return (
|
|
15
|
-
<div className="flex flex-1 items-center gap-2">
|
|
16
|
-
<SidebarButton />
|
|
17
|
-
<Input value={fileName} readOnly />
|
|
18
|
-
</div>
|
|
19
|
-
)
|
|
20
|
-
}
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { CheckCircle, X } from "lucide-react"
|
|
2
|
-
import { SingleRelationSelect } from "@/components/file-view/single-relation-select"
|
|
3
|
-
import { Button } from "@/components/ui/button"
|
|
4
|
-
import type { DocFile, DocFileMd, DocRelationFile } from "@/lib/types"
|
|
5
|
-
import { cn } from "@/lib/utils"
|
|
6
|
-
|
|
7
|
-
function isDocFileMd(file: DocFile): file is DocFileMd {
|
|
8
|
-
return file.type === 'markdown'
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
type Priority = "high" | "medium" | "low"
|
|
12
|
-
|
|
13
|
-
type Props = {
|
|
14
|
-
feature: DocFile
|
|
15
|
-
milestoneOptions?: DocRelationFile[]
|
|
16
|
-
onMilestoneUpdate?: (featurePath: string, milestone: string) => void
|
|
17
|
-
onPropertyUpdate?: (
|
|
18
|
-
featurePath: string,
|
|
19
|
-
field: string,
|
|
20
|
-
value: unknown,
|
|
21
|
-
) => void
|
|
22
|
-
onFeatureRemove?: (featurePath: string) => void
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function FeatureItem(props: Props) {
|
|
26
|
-
if (!isDocFileMd(props.feature)) {
|
|
27
|
-
return null
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const frontMatter = props.feature.content.frontMatter as Record<string, unknown>
|
|
31
|
-
|
|
32
|
-
const isDone = (frontMatter?.["is-done"] as boolean) === true
|
|
33
|
-
|
|
34
|
-
const priorityNumber = (frontMatter?.priority as number) || 0
|
|
35
|
-
|
|
36
|
-
const priority =
|
|
37
|
-
priorityNumber === 2 ? "high" : priorityNumber === 1 ? "medium" : "low"
|
|
38
|
-
|
|
39
|
-
const milestone = frontMatter?.milestone as string
|
|
40
|
-
|
|
41
|
-
const getCurrentValue = () => {
|
|
42
|
-
const matchingOption = props.milestoneOptions?.find(
|
|
43
|
-
(option) => option.label === milestone,
|
|
44
|
-
)
|
|
45
|
-
return matchingOption?.name || ""
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const handleMilestoneChange = (value: string) => {
|
|
49
|
-
if (!value) {
|
|
50
|
-
props.onMilestoneUpdate?.(props.feature.path.path, "")
|
|
51
|
-
return
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const selectedOption = props.milestoneOptions?.find(
|
|
55
|
-
(option) => option.name === value,
|
|
56
|
-
)
|
|
57
|
-
const labelToSave = selectedOption?.label || value
|
|
58
|
-
props.onMilestoneUpdate?.(props.feature.path.path, labelToSave)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const handleDoneToggle = () => {
|
|
62
|
-
props.onPropertyUpdate?.(props.feature.path.path, "is-done", !isDone)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const handlePriorityClick = () => {
|
|
66
|
-
const currentValue = (frontMatter?.priority as number) || 0
|
|
67
|
-
const nextValue = (currentValue + 1) % 3
|
|
68
|
-
props.onPropertyUpdate?.(props.feature.path.path, "priority", nextValue)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const getPriorityColor = (priority: Priority): string => {
|
|
72
|
-
switch (priority) {
|
|
73
|
-
case "high":
|
|
74
|
-
return "border-red-500"
|
|
75
|
-
case "medium":
|
|
76
|
-
return "border-yellow-500"
|
|
77
|
-
case "low":
|
|
78
|
-
return "border-green-500"
|
|
79
|
-
default:
|
|
80
|
-
return "border-gray-500"
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const getPriorityLabel = (priority: Priority): string => {
|
|
85
|
-
if (priority === "high") return "高"
|
|
86
|
-
if (priority === "medium") return "中"
|
|
87
|
-
return "低"
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return (
|
|
91
|
-
<div
|
|
92
|
-
className={cn("space-y-2 rounded-md border p-2", isDone && "opacity-75")}
|
|
93
|
-
>
|
|
94
|
-
<div className="flex items-center gap-3">
|
|
95
|
-
<Button
|
|
96
|
-
variant={isDone ? "default" : "secondary"}
|
|
97
|
-
size="sm"
|
|
98
|
-
onClick={handleDoneToggle}
|
|
99
|
-
>
|
|
100
|
-
<CheckCircle className="size-4" />
|
|
101
|
-
</Button>
|
|
102
|
-
<h3 className={`font-bold ${isDone ? "line-through opacity-80" : ""}`}>
|
|
103
|
-
{props.feature.content.title || props.feature.path.name}
|
|
104
|
-
</h3>
|
|
105
|
-
</div>
|
|
106
|
-
{props.feature.content.description && (
|
|
107
|
-
<p className="text-sm opacity-80">{props.feature.content.description}</p>
|
|
108
|
-
)}
|
|
109
|
-
<div className="flex flex-col items-end justify-between gap-2 lg:flex-row">
|
|
110
|
-
<div className="text-xs opacity-50">{props.feature.path.name}</div>
|
|
111
|
-
<div className="flex gap-2">
|
|
112
|
-
<SingleRelationSelect
|
|
113
|
-
value={getCurrentValue()}
|
|
114
|
-
relationOptions={props.milestoneOptions}
|
|
115
|
-
onValueChange={handleMilestoneChange}
|
|
116
|
-
/>
|
|
117
|
-
<Button
|
|
118
|
-
variant="outline"
|
|
119
|
-
className={cn("w-8", getPriorityColor(priority))}
|
|
120
|
-
onClick={handlePriorityClick}
|
|
121
|
-
>
|
|
122
|
-
{getPriorityLabel(priority)}
|
|
123
|
-
</Button>
|
|
124
|
-
{props.onFeatureRemove && (
|
|
125
|
-
<Button
|
|
126
|
-
variant="outline"
|
|
127
|
-
className="w-8"
|
|
128
|
-
onClick={() => {
|
|
129
|
-
props.onFeatureRemove?.(props.feature.path.path)
|
|
130
|
-
}}
|
|
131
|
-
>
|
|
132
|
-
<X className="size-3" />
|
|
133
|
-
</Button>
|
|
134
|
-
)}
|
|
135
|
-
</div>
|
|
136
|
-
</div>
|
|
137
|
-
</div>
|
|
138
|
-
)
|
|
139
|
-
}
|