@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,115 +0,0 @@
|
|
|
1
|
-
import { useMutation } from "@tanstack/react-query"
|
|
2
|
-
import { useNavigate } from "@tanstack/react-router"
|
|
3
|
-
import { ArrowLeft } from "lucide-react"
|
|
4
|
-
import { useState, useEffect } from "react"
|
|
5
|
-
import { ReloadButton } from "@/components/reload-button"
|
|
6
|
-
import { SidebarButton } from "@/components/sidebar-button"
|
|
7
|
-
import { Button } from "@/components/ui/button"
|
|
8
|
-
import { Input } from "@/components/ui/input"
|
|
9
|
-
import { VscodeButton } from "@/components/vscode-button"
|
|
10
|
-
import { apiClient } from "@/lib/api-client"
|
|
11
|
-
import { normalizePath } from "@/lib/path-utils"
|
|
12
|
-
|
|
13
|
-
type Props = {
|
|
14
|
-
filePath: string
|
|
15
|
-
fileData: {
|
|
16
|
-
path: string
|
|
17
|
-
title: string | null
|
|
18
|
-
}
|
|
19
|
-
cwd: string
|
|
20
|
-
onReload: () => void
|
|
21
|
-
isLoading: boolean
|
|
22
|
-
children?: React.ReactNode
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function FileHeader(props: Props) {
|
|
26
|
-
const navigate = useNavigate()
|
|
27
|
-
|
|
28
|
-
const getInitialTitle = () => {
|
|
29
|
-
if (props.fileData.title) return props.fileData.title
|
|
30
|
-
return props.filePath.split("/").pop()?.replace(/\.md$/, "") || ""
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const [title, setTitle] = useState(getInitialTitle())
|
|
34
|
-
|
|
35
|
-
// propsが変更されたときにタイトルを更新
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
setTitle(getInitialTitle())
|
|
38
|
-
}, [props.fileData.title, props.filePath])
|
|
39
|
-
|
|
40
|
-
const updateTitleMutation = useMutation({
|
|
41
|
-
async mutationFn(newTitle: string) {
|
|
42
|
-
const normalizedPath = normalizePath(props.filePath)
|
|
43
|
-
|
|
44
|
-
await apiClient.api.files[":path{.+}"].$put({
|
|
45
|
-
param: {
|
|
46
|
-
path: normalizedPath,
|
|
47
|
-
},
|
|
48
|
-
json: {
|
|
49
|
-
title: newTitle,
|
|
50
|
-
properties: null,
|
|
51
|
-
content: null,
|
|
52
|
-
description: null,
|
|
53
|
-
isArchived: null,
|
|
54
|
-
},
|
|
55
|
-
})
|
|
56
|
-
},
|
|
57
|
-
onSuccess() {
|
|
58
|
-
props.onReload()
|
|
59
|
-
},
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
63
|
-
const newTitle = e.target.value
|
|
64
|
-
setTitle(newTitle)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const handleTitleBlur = async () => {
|
|
68
|
-
if (props.fileData.path.endsWith(".md")) {
|
|
69
|
-
updateTitleMutation.mutate(title)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const handleBackClick = () => {
|
|
74
|
-
const pathSegments = props.filePath.split("/")
|
|
75
|
-
pathSegments.pop()
|
|
76
|
-
|
|
77
|
-
// アーカイブディレクトリ(_)を削除
|
|
78
|
-
const filteredSegments = pathSegments.filter((segment) => segment !== "_")
|
|
79
|
-
const directoryPath = filteredSegments.join("/")
|
|
80
|
-
|
|
81
|
-
navigate({ to: `/${directoryPath}` })
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return (
|
|
85
|
-
<>
|
|
86
|
-
<div className="flex items-center gap-2">
|
|
87
|
-
<SidebarButton />
|
|
88
|
-
<VscodeButton
|
|
89
|
-
cwd={props.cwd}
|
|
90
|
-
filePath={props.fileData.path}
|
|
91
|
-
size="icon"
|
|
92
|
-
variant="outline"
|
|
93
|
-
/>
|
|
94
|
-
<Button onClick={handleBackClick} size="icon" variant="outline">
|
|
95
|
-
<ArrowLeft className="h-4 w-4" />
|
|
96
|
-
</Button>
|
|
97
|
-
|
|
98
|
-
<Input
|
|
99
|
-
value={title}
|
|
100
|
-
onChange={handleTitleChange}
|
|
101
|
-
onBlur={handleTitleBlur}
|
|
102
|
-
placeholder="タイトルを入力"
|
|
103
|
-
className="flex-1"
|
|
104
|
-
/>
|
|
105
|
-
<ReloadButton
|
|
106
|
-
onReload={props.onReload}
|
|
107
|
-
size="icon"
|
|
108
|
-
variant="outline"
|
|
109
|
-
disabled={props.isLoading}
|
|
110
|
-
/>
|
|
111
|
-
{props.children}
|
|
112
|
-
</div>
|
|
113
|
-
</>
|
|
114
|
-
)
|
|
115
|
-
}
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import { useState } from "react"
|
|
2
|
-
import { CsvFileView } from "@/components/file-view/csv-file-view"
|
|
3
|
-
import { DefaultFileViewer } from "@/components/file-view/default-file-view"
|
|
4
|
-
import { JsonFileEditor } from "@/components/file-view/json-file-editor"
|
|
5
|
-
import { MarkdownFileView } from "@/components/file-view/markdown-file-view"
|
|
6
|
-
import { useDirectoryQuery } from "@/hooks/use-directory-query"
|
|
7
|
-
import { useFilePropertiesMutation } from "@/hooks/use-file-properties-mutation"
|
|
8
|
-
import { useFileQuery } from "@/hooks/use-file-query"
|
|
9
|
-
import { apiClient } from "@/lib/api-client"
|
|
10
|
-
import { normalizePath } from "@/lib/path-utils"
|
|
11
|
-
|
|
12
|
-
type Props = {
|
|
13
|
-
filePath: string
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function FilePageView(props: Props) {
|
|
17
|
-
const fileQuery = useFileQuery(props.filePath)
|
|
18
|
-
|
|
19
|
-
const directoryQuery = useDirectoryQuery(props.filePath)
|
|
20
|
-
|
|
21
|
-
const fileData = fileQuery.data
|
|
22
|
-
|
|
23
|
-
const directorySchema =
|
|
24
|
-
directoryQuery.data?.indexFile?.content?.frontMatter?.schema || {}
|
|
25
|
-
|
|
26
|
-
const relations = directoryQuery.data?.relations || []
|
|
27
|
-
|
|
28
|
-
// contentがオブジェクトの場合はbodyプロパティを使用
|
|
29
|
-
const initialContent = typeof fileData?.content === 'string'
|
|
30
|
-
? fileData.content
|
|
31
|
-
: fileData?.content?.body || ""
|
|
32
|
-
const [currentContent, setCurrentContent] = useState(initialContent)
|
|
33
|
-
|
|
34
|
-
const updateProperties = useFilePropertiesMutation()
|
|
35
|
-
|
|
36
|
-
const onChange = async (newContent: string) => {
|
|
37
|
-
const normalizedPath = normalizePath(props.filePath)
|
|
38
|
-
|
|
39
|
-
const result = await apiClient.api.files[":path{.+}"].$put({
|
|
40
|
-
param: { path: normalizedPath },
|
|
41
|
-
json: {
|
|
42
|
-
title: null,
|
|
43
|
-
properties: null,
|
|
44
|
-
content: newContent,
|
|
45
|
-
description: null,
|
|
46
|
-
isArchived: null,
|
|
47
|
-
},
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
const data = await result.json()
|
|
51
|
-
if ("content" in data && typeof data.content === "string") {
|
|
52
|
-
setCurrentContent(data.content)
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const handleReload = async () => {
|
|
57
|
-
const result = await fileQuery.refetch()
|
|
58
|
-
if (result.data === undefined) return
|
|
59
|
-
const content = typeof result.data.content === 'string'
|
|
60
|
-
? result.data.content
|
|
61
|
-
: result.data.content?.body || ""
|
|
62
|
-
setCurrentContent(content)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const handleFrontMatterUpdate = async (key: string, value: unknown) => {
|
|
66
|
-
await updateProperties.mutateAsync({
|
|
67
|
-
path: props.filePath,
|
|
68
|
-
field: key,
|
|
69
|
-
value: value,
|
|
70
|
-
})
|
|
71
|
-
// フロントマター更新後にファイル内容を再取得
|
|
72
|
-
handleReload()
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (!fileData) {
|
|
76
|
-
return <div>ファイルが見つかりません</div>
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// pathオブジェクトから実際のパス文字列を取得
|
|
80
|
-
const filePath = typeof fileData.path === 'string' ? fileData.path : fileData.path?.path
|
|
81
|
-
|
|
82
|
-
if (filePath?.endsWith(".md")) {
|
|
83
|
-
return (
|
|
84
|
-
<main className="p-2">
|
|
85
|
-
<MarkdownFileView
|
|
86
|
-
filePath={props.filePath}
|
|
87
|
-
fileData={{ path: filePath, title: fileData.content.title || null }}
|
|
88
|
-
cwd={directoryQuery.data.cwd}
|
|
89
|
-
content={currentContent}
|
|
90
|
-
onChange={onChange}
|
|
91
|
-
frontMatter={(() => {
|
|
92
|
-
// frontMatterはcontent内にある
|
|
93
|
-
const fm = fileData.content.frontMatter || {}
|
|
94
|
-
// スキーマやアイコンなどの複雑なオブジェクトを除外し、プリミティブ値のみを返す
|
|
95
|
-
const filtered: Record<
|
|
96
|
-
string,
|
|
97
|
-
string | number | boolean | string[] | number[] | boolean[] | null
|
|
98
|
-
> = {}
|
|
99
|
-
for (const [key, value] of Object.entries(fm)) {
|
|
100
|
-
if (key === "schema" || key === "icon") continue
|
|
101
|
-
if (
|
|
102
|
-
typeof value === "string" ||
|
|
103
|
-
typeof value === "number" ||
|
|
104
|
-
typeof value === "boolean" ||
|
|
105
|
-
value === null ||
|
|
106
|
-
(Array.isArray(value) &&
|
|
107
|
-
value.every(
|
|
108
|
-
(v) =>
|
|
109
|
-
typeof v === "string" ||
|
|
110
|
-
typeof v === "number" ||
|
|
111
|
-
typeof v === "boolean",
|
|
112
|
-
))
|
|
113
|
-
) {
|
|
114
|
-
filtered[key] = value as
|
|
115
|
-
| string
|
|
116
|
-
| number
|
|
117
|
-
| boolean
|
|
118
|
-
| string[]
|
|
119
|
-
| number[]
|
|
120
|
-
| boolean[]
|
|
121
|
-
| null
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return filtered
|
|
125
|
-
})()}
|
|
126
|
-
onFrontMatterUpdate={handleFrontMatterUpdate}
|
|
127
|
-
onReload={handleReload}
|
|
128
|
-
isLoading={fileQuery.isLoading}
|
|
129
|
-
schema={directorySchema}
|
|
130
|
-
relations={relations}
|
|
131
|
-
/>
|
|
132
|
-
</main>
|
|
133
|
-
)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (filePath?.endsWith(".csv")) {
|
|
137
|
-
return (
|
|
138
|
-
<main className="p-2">
|
|
139
|
-
<CsvFileView
|
|
140
|
-
filePath={props.filePath}
|
|
141
|
-
fileData={{ path: filePath, title: fileData.content.title || null }}
|
|
142
|
-
cwd={directoryQuery.data.cwd}
|
|
143
|
-
content={currentContent}
|
|
144
|
-
onChange={onChange}
|
|
145
|
-
onReload={handleReload}
|
|
146
|
-
isLoading={fileQuery.isLoading}
|
|
147
|
-
/>
|
|
148
|
-
</main>
|
|
149
|
-
)
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (filePath?.endsWith(".json")) {
|
|
153
|
-
return (
|
|
154
|
-
<main className="p-2">
|
|
155
|
-
<JsonFileEditor content={currentContent} />
|
|
156
|
-
</main>
|
|
157
|
-
)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return (
|
|
161
|
-
<main className="p-2">
|
|
162
|
-
<DefaultFileViewer content={currentContent} />
|
|
163
|
-
</main>
|
|
164
|
-
)
|
|
165
|
-
}
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react"
|
|
2
|
-
import { Input } from "@/components/ui/input"
|
|
3
|
-
import {
|
|
4
|
-
Select,
|
|
5
|
-
SelectContent,
|
|
6
|
-
SelectItem,
|
|
7
|
-
SelectTrigger,
|
|
8
|
-
SelectValue,
|
|
9
|
-
} from "@/components/ui/select"
|
|
10
|
-
import type { DocRelationFile, DocSchemaField } from "@/lib/types"
|
|
11
|
-
import { MultiRelationSelect } from "./multi-relation-select"
|
|
12
|
-
import { SingleRelationSelect } from "./single-relation-select"
|
|
13
|
-
|
|
14
|
-
type Props = {
|
|
15
|
-
fieldKey: string
|
|
16
|
-
value: unknown
|
|
17
|
-
originalValue: unknown
|
|
18
|
-
onValueChange: (key: string, value: string) => void
|
|
19
|
-
onBlur: (key: string, value: string) => void
|
|
20
|
-
schemaField?: DocSchemaField
|
|
21
|
-
relationOptions?: DocRelationFile[]
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Front Matter用の入力フィールドを表示するコンポーネント
|
|
26
|
-
*/
|
|
27
|
-
export function FrontMatterInputField(props: Props) {
|
|
28
|
-
const [localValue, setLocalValue] = useState(() => {
|
|
29
|
-
if (Array.isArray(props.value)) {
|
|
30
|
-
return props.value.join(", ")
|
|
31
|
-
}
|
|
32
|
-
return String(props.value)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
if (Array.isArray(props.value)) {
|
|
37
|
-
setLocalValue(props.value.join(", "))
|
|
38
|
-
} else {
|
|
39
|
-
setLocalValue(String(props.value))
|
|
40
|
-
}
|
|
41
|
-
}, [props.value])
|
|
42
|
-
|
|
43
|
-
const _getDisplayValue = (value: unknown): string => {
|
|
44
|
-
if (Array.isArray(value)) {
|
|
45
|
-
return value.join(", ")
|
|
46
|
-
}
|
|
47
|
-
return String(value)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// リレーション型の場合
|
|
51
|
-
if (
|
|
52
|
-
props.schemaField &&
|
|
53
|
-
props.schemaField.type === "relation" &&
|
|
54
|
-
props.schemaField.path
|
|
55
|
-
) {
|
|
56
|
-
return (
|
|
57
|
-
<SingleRelationSelect
|
|
58
|
-
value={(props.value as string) || ""}
|
|
59
|
-
relationOptions={props.relationOptions}
|
|
60
|
-
onValueChange={(value) => props.onValueChange(props.fieldKey, value)}
|
|
61
|
-
/>
|
|
62
|
-
)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// 配列リレーション型の場合
|
|
66
|
-
if (
|
|
67
|
-
props.schemaField &&
|
|
68
|
-
props.schemaField.type === "multi-relation" &&
|
|
69
|
-
props.schemaField.path
|
|
70
|
-
) {
|
|
71
|
-
// 値を配列に正規化
|
|
72
|
-
let normalizedValue: string[] = []
|
|
73
|
-
if (Array.isArray(props.value)) {
|
|
74
|
-
normalizedValue = props.value.filter(
|
|
75
|
-
(v) => typeof v === "string" && v.trim() !== "",
|
|
76
|
-
)
|
|
77
|
-
} else if (typeof props.value === "string" && props.value.trim() !== "") {
|
|
78
|
-
normalizedValue = props.value
|
|
79
|
-
.split(",")
|
|
80
|
-
.map((v) => v.trim())
|
|
81
|
-
.filter((v) => v !== "")
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return (
|
|
85
|
-
<MultiRelationSelect
|
|
86
|
-
value={normalizedValue}
|
|
87
|
-
relationOptions={props.relationOptions}
|
|
88
|
-
onValueChange={(value) =>
|
|
89
|
-
props.onValueChange(props.fieldKey, value.join(", "))
|
|
90
|
-
}
|
|
91
|
-
wrap={true}
|
|
92
|
-
/>
|
|
93
|
-
)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (typeof props.originalValue === "boolean") {
|
|
97
|
-
return (
|
|
98
|
-
<Select
|
|
99
|
-
value={String(props.value)}
|
|
100
|
-
onValueChange={(value) => props.onValueChange(props.fieldKey, value)}
|
|
101
|
-
>
|
|
102
|
-
<SelectTrigger className="w-full">
|
|
103
|
-
<SelectValue />
|
|
104
|
-
</SelectTrigger>
|
|
105
|
-
<SelectContent>
|
|
106
|
-
<SelectItem value="true">true</SelectItem>
|
|
107
|
-
<SelectItem value="false">false</SelectItem>
|
|
108
|
-
</SelectContent>
|
|
109
|
-
</Select>
|
|
110
|
-
)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (typeof props.originalValue === "number") {
|
|
114
|
-
return (
|
|
115
|
-
<Input
|
|
116
|
-
type="number"
|
|
117
|
-
value={localValue}
|
|
118
|
-
onChange={(e) => setLocalValue(e.target.value)}
|
|
119
|
-
onBlur={(e) => props.onBlur(props.fieldKey, e.target.value)}
|
|
120
|
-
className="flex-1"
|
|
121
|
-
placeholder={`${props.fieldKey}の値を入力`}
|
|
122
|
-
/>
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (Array.isArray(props.originalValue)) {
|
|
127
|
-
const placeholder =
|
|
128
|
-
props.originalValue.length > 0 &&
|
|
129
|
-
typeof props.originalValue[0] === "number"
|
|
130
|
-
? "カンマ区切りで数値を入力 (例: 1, 2, 3)"
|
|
131
|
-
: props.originalValue.length > 0 &&
|
|
132
|
-
typeof props.originalValue[0] === "boolean"
|
|
133
|
-
? "カンマ区切りでtrue/falseを入力 (例: true, false, true)"
|
|
134
|
-
: "カンマ区切りで入力 (例: item1, item2, item3)"
|
|
135
|
-
|
|
136
|
-
return (
|
|
137
|
-
<Input
|
|
138
|
-
value={localValue}
|
|
139
|
-
onChange={(e) => setLocalValue(e.target.value)}
|
|
140
|
-
onBlur={(e) => props.onBlur(props.fieldKey, e.target.value)}
|
|
141
|
-
className="flex-1"
|
|
142
|
-
placeholder={placeholder}
|
|
143
|
-
/>
|
|
144
|
-
)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// string型の場合
|
|
148
|
-
return (
|
|
149
|
-
<Input
|
|
150
|
-
value={localValue}
|
|
151
|
-
onChange={(e) => setLocalValue(e.target.value)}
|
|
152
|
-
onBlur={(e) => props.onBlur(props.fieldKey, e.target.value)}
|
|
153
|
-
className="flex-1"
|
|
154
|
-
placeholder={`${props.fieldKey}の値を入力`}
|
|
155
|
-
/>
|
|
156
|
-
)
|
|
157
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { Card } from "@/components/ui/card"
|
|
2
|
-
import { RenderFrontMatterValue } from "./render-front-matter-value"
|
|
3
|
-
|
|
4
|
-
type Props = {
|
|
5
|
-
frontMatter: Record<string, string | string[]> | null
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Front-matterをより見やすく表示するコンポーネント(表示のみ)
|
|
10
|
-
*/
|
|
11
|
-
export function FrontMatterView(props: Props) {
|
|
12
|
-
const frontMatter = props.frontMatter
|
|
13
|
-
// front-matterが空の場合は表示しない
|
|
14
|
-
if (!frontMatter || Object.keys(frontMatter).length === 0) {
|
|
15
|
-
return null
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return (
|
|
19
|
-
<Card className="gap-0 p-2">
|
|
20
|
-
{Object.entries(frontMatter).map(([key, value]) => (
|
|
21
|
-
<div key={key}>
|
|
22
|
-
<div className="flex gap-x-2">
|
|
23
|
-
<span className="font-bold">{key}</span>
|
|
24
|
-
<RenderFrontMatterValue value={value} />
|
|
25
|
-
</div>
|
|
26
|
-
</div>
|
|
27
|
-
))}
|
|
28
|
-
</Card>
|
|
29
|
-
)
|
|
30
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react"
|
|
2
|
-
|
|
3
|
-
type Props = {
|
|
4
|
-
content: string
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function JsonFileEditor(props: Props) {
|
|
8
|
-
const [formattedJson, setFormattedJson] = useState<string>("")
|
|
9
|
-
const [error, setError] = useState<string | null>(null)
|
|
10
|
-
|
|
11
|
-
useEffect(() => {
|
|
12
|
-
try {
|
|
13
|
-
const jsonData = JSON.parse(props.content)
|
|
14
|
-
setFormattedJson(JSON.stringify(jsonData, null, 2))
|
|
15
|
-
setError(null)
|
|
16
|
-
} catch (_e) {
|
|
17
|
-
setError(
|
|
18
|
-
"JSONのパースに失敗しました。有効なJSONではない可能性があります。",
|
|
19
|
-
)
|
|
20
|
-
setFormattedJson(props.content) // JSONのパースに失敗した場合はそのまま表示
|
|
21
|
-
}
|
|
22
|
-
}, [props.content])
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<div>
|
|
26
|
-
{error && (
|
|
27
|
-
<div className="mb-4 rounded border border-red-700 bg-red-900/30 p-2 text-red-300 text-sm">
|
|
28
|
-
{error}
|
|
29
|
-
</div>
|
|
30
|
-
)}
|
|
31
|
-
<div className="overflow-x-auto rounded bg-gray-800 p-4 text-white">
|
|
32
|
-
<pre className="whitespace-pre-wrap font-mono text-green-300">
|
|
33
|
-
{formattedJson}
|
|
34
|
-
</pre>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
)
|
|
38
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { marked } from "marked"
|
|
2
|
-
import type { ReactNode } from "react"
|
|
3
|
-
import { ArchiveRestoreBanner } from "@/components/file-view/archive-restore-banner"
|
|
4
|
-
import { EditableFrontMatterView } from "@/components/file-view/editable-front-matter-view"
|
|
5
|
-
import { FileHeader } from "@/components/file-view/file-header"
|
|
6
|
-
import { Card } from "@/components/ui/card"
|
|
7
|
-
import type { DocRelation, DocSchemaRecord, DocFileMdFrontMatter } from "@/lib/types"
|
|
8
|
-
|
|
9
|
-
import "github-markdown-css"
|
|
10
|
-
|
|
11
|
-
type Props = {
|
|
12
|
-
filePath: string
|
|
13
|
-
fileData: {
|
|
14
|
-
path: string
|
|
15
|
-
title: string | null
|
|
16
|
-
}
|
|
17
|
-
cwd: string
|
|
18
|
-
content: string
|
|
19
|
-
onChange(content: string): void
|
|
20
|
-
frontMatter: DocFileMdFrontMatter
|
|
21
|
-
onFrontMatterUpdate: (key: string, value: unknown) => void
|
|
22
|
-
onReload: () => void
|
|
23
|
-
isLoading: boolean
|
|
24
|
-
schema?: DocSchemaRecord
|
|
25
|
-
relations?: DocRelation[]
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function MarkdownFileView(props: Props): ReactNode {
|
|
29
|
-
const frontMatter = props.frontMatter
|
|
30
|
-
|
|
31
|
-
const hasFrontMatter =
|
|
32
|
-
frontMatter !== null && Object.keys(frontMatter || {}).length > 0
|
|
33
|
-
|
|
34
|
-
const html = marked.parse(props.content)
|
|
35
|
-
|
|
36
|
-
// アーカイブファイルかどうかを判定
|
|
37
|
-
const isArchived = props.filePath.includes("/_/")
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<div className="h-full space-y-2">
|
|
41
|
-
{isArchived && (
|
|
42
|
-
<ArchiveRestoreBanner
|
|
43
|
-
filePath={props.filePath}
|
|
44
|
-
onRestore={props.onReload}
|
|
45
|
-
/>
|
|
46
|
-
)}
|
|
47
|
-
<FileHeader
|
|
48
|
-
filePath={props.filePath}
|
|
49
|
-
fileData={props.fileData}
|
|
50
|
-
cwd={props.cwd}
|
|
51
|
-
onReload={props.onReload}
|
|
52
|
-
isLoading={props.isLoading}
|
|
53
|
-
/>
|
|
54
|
-
{hasFrontMatter && (
|
|
55
|
-
<EditableFrontMatterView
|
|
56
|
-
frontMatter={frontMatter}
|
|
57
|
-
onUpdate={props.onFrontMatterUpdate}
|
|
58
|
-
schema={props.schema}
|
|
59
|
-
relations={props.relations}
|
|
60
|
-
/>
|
|
61
|
-
)}
|
|
62
|
-
<Card className="overflow-hidden rounded-md p-0">
|
|
63
|
-
<div
|
|
64
|
-
className="markdown-body p-4"
|
|
65
|
-
// biome-ignore lint/security/noDangerouslySetInnerHtml: <explanation>
|
|
66
|
-
dangerouslySetInnerHTML={{ __html: html }}
|
|
67
|
-
/>
|
|
68
|
-
</Card>
|
|
69
|
-
</div>
|
|
70
|
-
)
|
|
71
|
-
}
|