Skip to content

Commit cabec6c

Browse files
committed
feat: block에 children이 있는 경우 재귀적으로 하위 블록을 가져온다
1 parent 046dcf7 commit cabec6c

File tree

4 files changed

+228
-20
lines changed

4 files changed

+228
-20
lines changed

apps/storybook/scripts/fetchNotionBlocks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ async function fetchAndSaveBlocks() {
5454
const blocks = await client.getPageBlocks(PAGE_ID);
5555

5656
// JSON 파일로 저장
57-
const outputPath = join(__dirname, '../src/data/notionBlocks.json');
57+
const outputPath = join(__dirname, '../src/sample-data/notionBlocks.json');
5858
console.log(`저장 경로: ${outputPath}`);
5959
writeFileSync(outputPath, JSON.stringify(blocks, null, 2), 'utf8');
6060

apps/storybook/src/sample-data/notionBlocks.json

Lines changed: 159 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@
6868
"caption": [],
6969
"type": "file",
7070
"file": {
71-
"url": "https://prod-files-secure.s3.us-west-2.amazonaws.com/cd7314a5-d906-43b0-81e7-42eff82c02a3/566f127b-9e73-491d-bee6-5afd075653a2/image.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB46642FEXGJE%2F20250309%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250309T124627Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjECwaCXVzLXdlc3QtMiJGMEQCIHre8dI1bt9zihac4jNVFS9ZoA4%2FqBBhU%2Bsw3jR0caAdAiBDcl0HlDuZqjgCCKQWhkPc9B%2FbWrKrSZKUoD9n2R7CICr%2FAwh1EAAaDDYzNzQyMzE4MzgwNSIMgY84eNgY%2FzHhPyToKtwDJe421SwJIJO2JANOUC%2F3En1FCiUP4p6N%2BMiudGw2X1JPh9q9AeFupOuALCcEmH7OQNLGOW2UBDh66ps9RLn11JVLbBanBUm50R74F2kfbJwUuS2JfUGiZK%2BOn3qaDtDsc6Cd49hEG0reQS8QuKZQuKtf5G%2Fu7znv8e43n5Qq%2BzGBCvPS6BqdRxSr47XQw8%2FrpMeBBsAkT8QP%2BkQKTcgFgTcFkAcfpBlZZLKI%2FjXq6gQI1EqlFL9YCwJttBLWWxKYYEZB2Ap74s5k3GKIS6I%2BaNGQP9gEE55%2B1gIcYSwjSxl9u%2Blqjk6cWUJaWFq43E651fWMCy%2FyqW6nbzSJ0wbZBnnK353FPbC91Y9fQr8EtfBN1rgFLXZi7UD0ihBRtkFVEJM%2F7pcQtYe3NxhpK40jAsE1C4uZp17XR%2FDXWaeMIiwD%2FBvqKlm6IwG%2FB3bASijsjhLFnHgs9ajr37HBzg59eiYDgYvqVTRm6PwsRdQNziWWEDz0WJonYIfb%2Bl2xw0rbgCi9AP8Rj8TKEzMXsyGxaur9R94%2FEEsYW6Ez7cB4nSdFLXWxg4fOZbdrmfk6iceGUNCeI2odYnJRgLZNu94AKmVinswTpp0OoHA1NqhD3uLhuTg7YRy0KzcgqHgw0oe2vgY6pgGL8c7PfNR76%2F%2BrB%2B73ArTFHkYAtGJfcFDTvAKlQ5bVdNY%2Bp9vxsI%2BFcEKiQ9Z7Nd2OoPPwMMkcuf3kDwYYU%2F1kmCniDrbQr1rMAketnKg5YovFuHwCNNE%2FRIHBNfH2JgSrscT0%2B6iYEUfFUSsxnusQ6PyMftHlOS7fqagoclVgCaTl43tJRAwoFrEozjNkywhgMXxFbpQYDpFrxsjtwLDKuVemDfL3&X-Amz-Signature=51b7fcbc935acff583bd3383dd9f6e610a8f7c47d5e507966b840f2c7d66ce8c&X-Amz-SignedHeaders=host&x-id=GetObject",
72-
"expiry_time": "2025-03-09T13:46:27.277Z"
71+
"url": "https://prod-files-secure.s3.us-west-2.amazonaws.com/cd7314a5-d906-43b0-81e7-42eff82c02a3/566f127b-9e73-491d-bee6-5afd075653a2/image.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466STLNN5SA%2F20250309%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250309T131205Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjECsaCXVzLXdlc3QtMiJGMEQCIG94TkWa3mK1%2B5nVswsMblZQyl7j7VaT%2BqotVsKeSggXAiAQYbbvAf2NGeiVYD52%2FaNMKZ4YTg1%2BWJpH3%2Bmpc9xspir%2FAwh0EAAaDDYzNzQyMzE4MzgwNSIM%2F1rPCZwhKhp3eLk6KtwDZy4%2B20E51YbCCaXojGLfYiQw9vPtTNXK6tdO8gaTMKtEi5Nywg04k%2B9Jek8QTwgn1IM420f%2Bd%2B7ZeQxkGCZgYidrz1sKcxbWajR2%2B%2BsfabgkHxJePiFy5I%2FOxbBXdvwVrt5jDSn7KwwK9e1qUM40iTf3aDS1TBiTHdYFqed%2BE7vUg0%2Ffy7Qxo1huc%2BV0l0zWCzkqIykzPOJlFayl55TLK7%2B%2FXjcUD6ofLoCFQSXa6LvNMDcfSWKWbVMC05tnE9yBGsgKh23njD6DcbvtBFZM3NL4N0%2FCvxY2KuegsVlCY4598rhoogs%2BK%2B9GhXaCqnylT3iC8aR3ce3PNpEdO%2FUUnv1H1KZiFZh9tYblBdGcRocUnItGTq%2BPQJy89ooWIYkO0cfkPZiOtkVkhvc8JFHDRMsafTi92RLimnHP9pS%2FobUcQ5sLaLse96pISiT%2BdBJCTLJ%2FkK%2BmLHAb4deSmonck3VFr28Gdg33O8k0hlrlVDKnRYyhW%2BBrTg4R7VJkd6S9wOLvCgCNwBiwpPNy3gKNwTdnegDxLca5VV7EzC%2BeUWvCwCUxztpwyssi8SKks98Nib20n37ZbGamMWHLVzBfUtIhUvbwNCTqnug1uZEoe85mAFVMNvKDbJFH9CYw0tq1vgY6pgG1EFVVrhMNjCsu4q1c8btf3gVyMiqDnyPKQVm%2BGFD9CMIGHOQzTnYYdek5JJDDlQHzf5YxMVmDj9xFwb70UvCTf0NETvihF8T8s04Aik2m5t0%2FpSeKZbsZFoPvv16QeSS5BI9gaJJp7%2F1wDciv2yWG02uAxgp0kTlBZzYwCmsbAoBJhzuqppOnQ7MNLPOhPYtJCye%2BOjzgHukv19fC4vyZQCMcOof6&X-Amz-Signature=16278af11d7907fbb4ac58410f358cfce25f65b020be49ddfca13828dc47c7e9&X-Amz-SignedHeaders=host&x-id=GetObject",
72+
"expiry_time": "2025-03-09T14:12:04.810Z"
7373
}
7474
}
7575
},
@@ -4152,7 +4152,53 @@
41524152
}
41534153
],
41544154
"color": "default"
4155-
}
4155+
},
4156+
"children": [
4157+
{
4158+
"object": "block",
4159+
"id": "1809c6bf-2b17-80c0-a3fa-eb27906b54ed",
4160+
"parent": {
4161+
"type": "block_id",
4162+
"block_id": "1809c6bf-2b17-806c-8e9b-ccb1156e3441"
4163+
},
4164+
"created_time": "2025-01-19T10:48:00.000Z",
4165+
"last_edited_time": "2025-01-19T10:48:00.000Z",
4166+
"created_by": {
4167+
"object": "user",
4168+
"id": "5146391e-8b65-47f2-83b6-2bfe81194f32"
4169+
},
4170+
"last_edited_by": {
4171+
"object": "user",
4172+
"id": "5146391e-8b65-47f2-83b6-2bfe81194f32"
4173+
},
4174+
"has_children": false,
4175+
"archived": false,
4176+
"in_trash": false,
4177+
"type": "bulleted_list_item",
4178+
"bulleted_list_item": {
4179+
"rich_text": [
4180+
{
4181+
"type": "text",
4182+
"text": {
4183+
"content": "별도의 복잡한 설정 없이도 사용 가능",
4184+
"link": null
4185+
},
4186+
"annotations": {
4187+
"bold": false,
4188+
"italic": false,
4189+
"strikethrough": false,
4190+
"underline": false,
4191+
"code": false,
4192+
"color": "default"
4193+
},
4194+
"plain_text": "별도의 복잡한 설정 없이도 사용 가능",
4195+
"href": null
4196+
}
4197+
],
4198+
"color": "default"
4199+
}
4200+
}
4201+
]
41564202
},
41574203
{
41584204
"object": "block",
@@ -4196,7 +4242,114 @@
41964242
}
41974243
],
41984244
"color": "default"
4199-
}
4245+
},
4246+
"children": [
4247+
{
4248+
"object": "block",
4249+
"id": "1809c6bf-2b17-80fa-a85a-fb53dc7663e1",
4250+
"parent": {
4251+
"type": "block_id",
4252+
"block_id": "1809c6bf-2b17-80c6-9ec1-f27e61b7e5ee"
4253+
},
4254+
"created_time": "2025-01-19T10:48:00.000Z",
4255+
"last_edited_time": "2025-01-19T11:15:00.000Z",
4256+
"created_by": {
4257+
"object": "user",
4258+
"id": "5146391e-8b65-47f2-83b6-2bfe81194f32"
4259+
},
4260+
"last_edited_by": {
4261+
"object": "user",
4262+
"id": "5146391e-8b65-47f2-83b6-2bfe81194f32"
4263+
},
4264+
"has_children": false,
4265+
"archived": false,
4266+
"in_trash": false,
4267+
"type": "bulleted_list_item",
4268+
"bulleted_list_item": {
4269+
"rich_text": [
4270+
{
4271+
"type": "text",
4272+
"text": {
4273+
"content": "esbuild",
4274+
"link": null
4275+
},
4276+
"annotations": {
4277+
"bold": false,
4278+
"italic": false,
4279+
"strikethrough": false,
4280+
"underline": false,
4281+
"code": true,
4282+
"color": "default"
4283+
},
4284+
"plain_text": "esbuild",
4285+
"href": null
4286+
},
4287+
{
4288+
"type": "text",
4289+
"text": {
4290+
"content": " 를 기반으로 하여 매우 빠른 빌드 속도를 제공",
4291+
"link": null
4292+
},
4293+
"annotations": {
4294+
"bold": false,
4295+
"italic": false,
4296+
"strikethrough": false,
4297+
"underline": false,
4298+
"code": false,
4299+
"color": "default"
4300+
},
4301+
"plain_text": " 를 기반으로 하여 매우 빠른 빌드 속도를 제공",
4302+
"href": null
4303+
}
4304+
],
4305+
"color": "default"
4306+
}
4307+
},
4308+
{
4309+
"object": "block",
4310+
"id": "1809c6bf-2b17-8016-ad06-f8e9edc855f2",
4311+
"parent": {
4312+
"type": "block_id",
4313+
"block_id": "1809c6bf-2b17-80c6-9ec1-f27e61b7e5ee"
4314+
},
4315+
"created_time": "2025-01-19T10:48:00.000Z",
4316+
"last_edited_time": "2025-01-19T10:48:00.000Z",
4317+
"created_by": {
4318+
"object": "user",
4319+
"id": "5146391e-8b65-47f2-83b6-2bfe81194f32"
4320+
},
4321+
"last_edited_by": {
4322+
"object": "user",
4323+
"id": "5146391e-8b65-47f2-83b6-2bfe81194f32"
4324+
},
4325+
"has_children": false,
4326+
"archived": false,
4327+
"in_trash": false,
4328+
"type": "bulleted_list_item",
4329+
"bulleted_list_item": {
4330+
"rich_text": [
4331+
{
4332+
"type": "text",
4333+
"text": {
4334+
"content": "병렬 처리를 통해 모든 CPU 코어를 최대한 활용",
4335+
"link": null
4336+
},
4337+
"annotations": {
4338+
"bold": false,
4339+
"italic": false,
4340+
"strikethrough": false,
4341+
"underline": false,
4342+
"code": false,
4343+
"color": "default"
4344+
},
4345+
"plain_text": "병렬 처리를 통해 모든 CPU 코어를 최대한 활용",
4346+
"href": null
4347+
}
4348+
],
4349+
"color": "default"
4350+
}
4351+
}
4352+
]
42004353
},
42014354
{
42024355
"object": "block",
@@ -4407,8 +4560,8 @@
44074560
"caption": [],
44084561
"type": "file",
44094562
"file": {
4410-
"url": "https://prod-files-secure.s3.us-west-2.amazonaws.com/cd7314a5-d906-43b0-81e7-42eff82c02a3/10d9f59d-d3e2-429a-ad72-c8b15b1f536b/image.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB46642FEXGJE%2F20250309%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250309T124627Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjECwaCXVzLXdlc3QtMiJGMEQCIHre8dI1bt9zihac4jNVFS9ZoA4%2FqBBhU%2Bsw3jR0caAdAiBDcl0HlDuZqjgCCKQWhkPc9B%2FbWrKrSZKUoD9n2R7CICr%2FAwh1EAAaDDYzNzQyMzE4MzgwNSIMgY84eNgY%2FzHhPyToKtwDJe421SwJIJO2JANOUC%2F3En1FCiUP4p6N%2BMiudGw2X1JPh9q9AeFupOuALCcEmH7OQNLGOW2UBDh66ps9RLn11JVLbBanBUm50R74F2kfbJwUuS2JfUGiZK%2BOn3qaDtDsc6Cd49hEG0reQS8QuKZQuKtf5G%2Fu7znv8e43n5Qq%2BzGBCvPS6BqdRxSr47XQw8%2FrpMeBBsAkT8QP%2BkQKTcgFgTcFkAcfpBlZZLKI%2FjXq6gQI1EqlFL9YCwJttBLWWxKYYEZB2Ap74s5k3GKIS6I%2BaNGQP9gEE55%2B1gIcYSwjSxl9u%2Blqjk6cWUJaWFq43E651fWMCy%2FyqW6nbzSJ0wbZBnnK353FPbC91Y9fQr8EtfBN1rgFLXZi7UD0ihBRtkFVEJM%2F7pcQtYe3NxhpK40jAsE1C4uZp17XR%2FDXWaeMIiwD%2FBvqKlm6IwG%2FB3bASijsjhLFnHgs9ajr37HBzg59eiYDgYvqVTRm6PwsRdQNziWWEDz0WJonYIfb%2Bl2xw0rbgCi9AP8Rj8TKEzMXsyGxaur9R94%2FEEsYW6Ez7cB4nSdFLXWxg4fOZbdrmfk6iceGUNCeI2odYnJRgLZNu94AKmVinswTpp0OoHA1NqhD3uLhuTg7YRy0KzcgqHgw0oe2vgY6pgGL8c7PfNR76%2F%2BrB%2B73ArTFHkYAtGJfcFDTvAKlQ5bVdNY%2Bp9vxsI%2BFcEKiQ9Z7Nd2OoPPwMMkcuf3kDwYYU%2F1kmCniDrbQr1rMAketnKg5YovFuHwCNNE%2FRIHBNfH2JgSrscT0%2B6iYEUfFUSsxnusQ6PyMftHlOS7fqagoclVgCaTl43tJRAwoFrEozjNkywhgMXxFbpQYDpFrxsjtwLDKuVemDfL3&X-Amz-Signature=95b66cfbe39d99251377bc25a54c58a31b314be02588641b930a70a940803fcd&X-Amz-SignedHeaders=host&x-id=GetObject",
4411-
"expiry_time": "2025-03-09T13:46:27.795Z"
4563+
"url": "https://prod-files-secure.s3.us-west-2.amazonaws.com/cd7314a5-d906-43b0-81e7-42eff82c02a3/10d9f59d-d3e2-429a-ad72-c8b15b1f536b/image.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466STLNN5SA%2F20250309%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250309T131205Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjECsaCXVzLXdlc3QtMiJGMEQCIG94TkWa3mK1%2B5nVswsMblZQyl7j7VaT%2BqotVsKeSggXAiAQYbbvAf2NGeiVYD52%2FaNMKZ4YTg1%2BWJpH3%2Bmpc9xspir%2FAwh0EAAaDDYzNzQyMzE4MzgwNSIM%2F1rPCZwhKhp3eLk6KtwDZy4%2B20E51YbCCaXojGLfYiQw9vPtTNXK6tdO8gaTMKtEi5Nywg04k%2B9Jek8QTwgn1IM420f%2Bd%2B7ZeQxkGCZgYidrz1sKcxbWajR2%2B%2BsfabgkHxJePiFy5I%2FOxbBXdvwVrt5jDSn7KwwK9e1qUM40iTf3aDS1TBiTHdYFqed%2BE7vUg0%2Ffy7Qxo1huc%2BV0l0zWCzkqIykzPOJlFayl55TLK7%2B%2FXjcUD6ofLoCFQSXa6LvNMDcfSWKWbVMC05tnE9yBGsgKh23njD6DcbvtBFZM3NL4N0%2FCvxY2KuegsVlCY4598rhoogs%2BK%2B9GhXaCqnylT3iC8aR3ce3PNpEdO%2FUUnv1H1KZiFZh9tYblBdGcRocUnItGTq%2BPQJy89ooWIYkO0cfkPZiOtkVkhvc8JFHDRMsafTi92RLimnHP9pS%2FobUcQ5sLaLse96pISiT%2BdBJCTLJ%2FkK%2BmLHAb4deSmonck3VFr28Gdg33O8k0hlrlVDKnRYyhW%2BBrTg4R7VJkd6S9wOLvCgCNwBiwpPNy3gKNwTdnegDxLca5VV7EzC%2BeUWvCwCUxztpwyssi8SKks98Nib20n37ZbGamMWHLVzBfUtIhUvbwNCTqnug1uZEoe85mAFVMNvKDbJFH9CYw0tq1vgY6pgG1EFVVrhMNjCsu4q1c8btf3gVyMiqDnyPKQVm%2BGFD9CMIGHOQzTnYYdek5JJDDlQHzf5YxMVmDj9xFwb70UvCTf0NETvihF8T8s04Aik2m5t0%2FpSeKZbsZFoPvv16QeSS5BI9gaJJp7%2F1wDciv2yWG02uAxgp0kTlBZzYwCmsbAoBJhzuqppOnQ7MNLPOhPYtJCye%2BOjzgHukv19fC4vyZQCMcOof6&X-Amz-Signature=0b6ab1f158ebad0d10a5b604d79f9a916effea79013e4b9e7926dfc69f4c912e&X-Amz-SignedHeaders=host&x-id=GetObject",
4564+
"expiry_time": "2025-03-09T14:12:05.218Z"
44124565
}
44134566
}
44144567
},

packages/notion-to-utils/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
],
2828
"sideEffects": false,
2929
"scripts": {
30+
"dev": "tsup --watch",
3031
"test": "vitest run",
3132
"test:watch": "vitest --watch",
3233
"build": "tsup"

packages/notion-to-utils/src/client/getPageBlocks.ts

Lines changed: 67 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,43 @@
11
import { Client } from '@notionhq/client';
2-
import { ListBlockChildrenResponse } from '@notionhq/client/build/src/api-endpoints';
2+
import {
3+
ListBlockChildrenResponse,
4+
BlockObjectResponse,
5+
PartialBlockObjectResponse,
6+
} from '@notionhq/client/build/src/api-endpoints';
37

4-
type BlockAttributes = {
5-
numbered_list_item?: {
6-
number?: number;
7-
};
8-
};
8+
// 블록 타입 정의
9+
export type NotionBlock = BlockObjectResponse | PartialBlockObjectResponse;
910

10-
type ListBlockChildrenResponseResults = ListBlockChildrenResponse['results'] &
11-
BlockAttributes;
11+
// 하위 블록을 가진 블록 타입 (인덱스 시그니처 사용)
12+
export interface NotionBlockWithChildren extends Record<string, any> {
13+
id: string;
14+
children?: NotionBlockWithChildren[];
15+
}
16+
17+
// has_children 속성이 있는지 확인하는 타입 가드 함수
18+
function hasChildren(block: NotionBlock): block is BlockObjectResponse {
19+
return (
20+
'has_children' in block &&
21+
(block as BlockObjectResponse).has_children === true
22+
);
23+
}
1224

13-
export async function getPageBlocks(client: Client, pageId: string) {
25+
/**
26+
* 특정 블록의 모든 하위 블록을 재귀적으로 가져오는 함수
27+
*/
28+
async function fetchBlockChildren(
29+
client: Client,
30+
blockId: string
31+
): Promise<NotionBlockWithChildren[]> {
1432
try {
15-
// Get all children blocks with pagination
16-
let allBlocks: ListBlockChildrenResponseResults = [];
33+
// 페이지네이션을 처리하며 모든 하위 블록 가져오기
34+
let allBlocks: NotionBlock[] = [];
1735
let hasMore = true;
1836
let nextCursor = undefined;
1937

2038
while (hasMore) {
2139
const response = (await client.blocks.children.list({
22-
block_id: pageId,
40+
block_id: blockId,
2341
start_cursor: nextCursor,
2442
})) as ListBlockChildrenResponse;
2543

@@ -28,7 +46,43 @@ export async function getPageBlocks(client: Client, pageId: string) {
2846
nextCursor = response.next_cursor || undefined;
2947
}
3048

31-
return allBlocks;
49+
// 하위 블록이 있는 블록들에 대해 재귀적으로 처리
50+
const blocksWithChildren = await Promise.all(
51+
allBlocks.map(async (block) => {
52+
// 타입 단언을 사용하여 안전하게 변환
53+
const blockWithChildren = {
54+
...block,
55+
} as unknown as NotionBlockWithChildren;
56+
57+
// 타입 가드를 사용하여 has_children 속성 확인
58+
if (hasChildren(block)) {
59+
// 하위 블록 가져오기
60+
const children = await fetchBlockChildren(client, block.id);
61+
// 원본 블록에 children 속성 추가
62+
blockWithChildren.children = children;
63+
}
64+
65+
return blockWithChildren;
66+
})
67+
);
68+
69+
return blocksWithChildren;
70+
} catch (error) {
71+
console.error(`Error fetching children for block ${blockId}:`, error);
72+
return [];
73+
}
74+
}
75+
76+
/**
77+
* 페이지의 모든 블록을 재귀적으로 가져오는 함수
78+
*/
79+
export async function getPageBlocks(
80+
client: Client,
81+
pageId: string
82+
): Promise<NotionBlockWithChildren[]> {
83+
try {
84+
const blocks = await fetchBlockChildren(client, pageId);
85+
return blocks;
3286
} catch (error) {
3387
console.error('Error:', error);
3488
return [];

0 commit comments

Comments
 (0)