diff --git a/.gitignore b/.gitignore index b6bd0d6..947b90e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ book .env -cache .DS_Store diff --git a/cache/issues.json b/cache/issues.json new file mode 100644 index 0000000..c52953a --- /dev/null +++ b/cache/issues.json @@ -0,0 +1,3434 @@ +[ + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/1", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/1/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/1/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/1", + "id": 1207881268, + "node_id": "I_kwDOHCdFJM5H_s40", + "number": 1, + "title": "Chunk: Focus/Unfocus", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": { + "url": "https://api.github.com/repos/vi117/scrap-yard/milestones/1", + "html_url": "https://github.com/vi117/scrap-yard/milestone/1", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/milestones/1/labels", + "id": 7890719, + "node_id": "MI_kwDOHCdFJM4AeGcf", + "number": 1, + "title": "Minimum Viable Product", + "description": "최소 기능 제품", + "creator": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 0, + "closed_issues": 8, + "state": "open", + "created_at": "2022-04-19T10:28:38Z", + "updated_at": "2022-05-29T04:42:16Z", + "due_on": "2022-05-01T07:00:00Z", + "closed_at": null + }, + "comments": 0, + "created_at": "2022-04-19T07:23:17Z", + "updated_at": "2022-05-02T08:19:19Z", + "closed_at": "2022-05-02T08:19:19Z", + "author_association": "OWNER", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작 조건: Chunk를 편집할 수 있는 권한을 가져야 한다. \r\n목표: 지금 편집하고자 하는 Chunk를 보여준다.\r\n\r\n1. 사용자가 Chunk의 영역에 클릭을 했을때, Focus 된다. 그떄 다른 Chunk의 Focus를 사라지게 한다. \r\n2. Focus를 얻었을때, Focus를 얻은 Chunk을 눈에 띄이도록 표시한다. \r\n3. Focus가 사라졌을때, 변경되었으면 변경된 Chunk를 저장한다.\r\n\r\n```mermaid\r\nflowchart LR\r\n A((Start)) -->|On click| B([Get Focus])\r\n B --> C([Defocus Others])\r\n C -->|On Defocus| D[Update Chunk Signal]\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/1/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/1/timeline", + "performed_via_github_app": null, + "state_reason": "completed" + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/2", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/2/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/2/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/2/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/2", + "id": 1207894620, + "node_id": "I_kwDOHCdFJM5H_wJc", + "number": 2, + "title": "Chunk: remove", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": { + "url": "https://api.github.com/repos/vi117/scrap-yard/milestones/1", + "html_url": "https://github.com/vi117/scrap-yard/milestone/1", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/milestones/1/labels", + "id": 7890719, + "node_id": "MI_kwDOHCdFJM4AeGcf", + "number": 1, + "title": "Minimum Viable Product", + "description": "최소 기능 제품", + "creator": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 0, + "closed_issues": 8, + "state": "open", + "created_at": "2022-04-19T10:28:38Z", + "updated_at": "2022-05-29T04:42:16Z", + "due_on": "2022-05-01T07:00:00Z", + "closed_at": null + }, + "comments": 0, + "created_at": "2022-04-19T07:35:32Z", + "updated_at": "2022-05-02T08:19:27Z", + "closed_at": "2022-05-02T08:19:27Z", + "author_association": "OWNER", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작 조건: Chunk를 수정가능한 권한을 가지고 있어야함. \r\n목표: Chunk를 지운다.\r\n\r\n1. Chunk의 좌측 상단의 Context Menu에 삭제 아이콘을 클릭할 때나 빈 내용의 Chunk에서 BackspaceDel를 입력할 때 시작한다.\r\n2. 해당 Chunk를 삭제한다.\r\n3. 서버에서 그 Chunk를 삭제한다.\r\n4. 아래의 Chunk가 있다면 끌어 올린다.\r\n\r\n```mermaid\r\ngraph LR\r\n A((Start)) -->|On right click| B([Open Context Menu])\r\n B -->|On click| C([Remove Chunk])\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/2/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/2/timeline", + "performed_via_github_app": null, + "state_reason": "completed" + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/3", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/3/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/3/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/3/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/3", + "id": 1207895669, + "node_id": "I_kwDOHCdFJM5H_wZ1", + "number": 3, + "title": "Chunk: render", + "user": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": { + "url": "https://api.github.com/repos/vi117/scrap-yard/milestones/1", + "html_url": "https://github.com/vi117/scrap-yard/milestone/1", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/milestones/1/labels", + "id": 7890719, + "node_id": "MI_kwDOHCdFJM4AeGcf", + "number": 1, + "title": "Minimum Viable Product", + "description": "최소 기능 제품", + "creator": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 0, + "closed_issues": 8, + "state": "open", + "created_at": "2022-04-19T10:28:38Z", + "updated_at": "2022-05-29T04:42:16Z", + "due_on": "2022-05-01T07:00:00Z", + "closed_at": null + }, + "comments": 0, + "created_at": "2022-04-19T07:36:31Z", + "updated_at": "2022-05-02T08:19:34Z", + "closed_at": "2022-05-02T08:19:34Z", + "author_association": "COLLABORATOR", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작조건: 없음 \r\n목표: Chunk를 보여준다.\r\n\r\n1. 내용을 읽는다.\r\n2. 그 내용을 chunk안 영역에 사람이 보기 좋게 그 타입에 따라 렌더링한다.\r\n render하는 대상 목록은 다음과 같다.\r\n - markdown\r\n - latex\r\n - link (image, video, site)\r\n - FEN\r\n - etc\r\n\r\n대안 흐름:\r\n\r\nA. 렌더링 실패\r\n 1. 렌더링에 실패하면 실패의 이유를 보여준다.\r\n\r\n```mermaid\r\nflowchart LR\r\n A((Start)) --> Checktype{Check Type}\r\n Checktype --> markdown([markdown])\r\n Checktype --> latex([latex])\r\n Checktype --> link([link])\r\n Checktype --> FEN([FEN])\r\n Checktype --> etc([etc])\r\n subgraph Drawer\r\n markdown\r\n latex\r\n link\r\n FEN\r\n etc\r\n end\r\n markdown --> C\r\n latex --> C\r\n link --> C\r\n FEN --> C\r\n etc --> C\r\n C{ }\r\n C -->|Failed| D([DrawMessage])\r\n C -->|Success| E([Draw Result])\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/3/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/3/timeline", + "performed_via_github_app": null, + "state_reason": "completed" + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/4", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/4/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/4/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/4/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/4", + "id": 1207900808, + "node_id": "I_kwDOHCdFJM5H_xqI", + "number": 4, + "title": "Chunk: previews", + "user": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": null, + "comments": 1, + "created_at": "2022-04-19T07:42:00Z", + "updated_at": "2022-05-31T10:42:05Z", + "closed_at": "2022-05-31T10:42:05Z", + "author_association": "COLLABORATOR", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n사용조건: 편집 중일때 \r\n목표: 미리보기를 보여주어 편집을 편하게 한다.\r\n\r\n1. Chunk의 내용을 바꾸면 시작된다.\r\n2. 보기모드에서 어떻게 보여질지 미리보기 창을 띄워준다. 미리보기는 기본적으로 하단에 띄우고 밑에 공간이 없으면 상단에 띄운다.\r\n3. 내용이 바뀌면 미리보기 창의 내용도 갱신한다.\r\n\r\n```mermaid\r\ngraph LR\r\n A((Start)) -->|On Change| B([Draw Preview])\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/4/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/4/timeline", + "performed_via_github_app": null, + "state_reason": "completed" + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/5", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/5/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/5/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/5/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/5", + "id": 1207901800, + "node_id": "I_kwDOHCdFJM5H_x5o", + "number": 5, + "title": "Document: view Chunk", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": { + "url": "https://api.github.com/repos/vi117/scrap-yard/milestones/1", + "html_url": "https://github.com/vi117/scrap-yard/milestone/1", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/milestones/1/labels", + "id": 7890719, + "node_id": "MI_kwDOHCdFJM4AeGcf", + "number": 1, + "title": "Minimum Viable Product", + "description": "최소 기능 제품", + "creator": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 0, + "closed_issues": 8, + "state": "open", + "created_at": "2022-04-19T10:28:38Z", + "updated_at": "2022-05-29T04:42:16Z", + "due_on": "2022-05-01T07:00:00Z", + "closed_at": null + }, + "comments": 0, + "created_at": "2022-04-19T07:43:04Z", + "updated_at": "2022-05-24T14:45:43Z", + "closed_at": "2022-05-24T14:45:43Z", + "author_association": "OWNER", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작조건: 읽기 권한이 있어야 한다. \r\n목표: Chunk들을 보여준다.\r\n\r\n1. Document가 로딩되면 시작한다.\r\n2. 경로가 주어지면 Document Component에서 그 경로의 문서를 읽고 파싱한다. 그동안 로딩 바를 보여준다.\r\n3. 로딩이 완료되면 파싱된 결과물인 Chunk들을 보여준다.\r\n\r\n대안흐름:\r\n\r\nA. 읽기 실패:\r\n 1. 읽기에 실패한 경우 읽기에 실패한 이유를 띄운다.\r\n\r\nB. 파싱 실패:\r\n 1. 파싱에 실패한 경우 파싱에 실패한 이유를 띄우고 raw text가 담긴 Chunk로 렌더링한다.\r\n\r\n```mermaid\r\nflowchart LR\r\n A((Start)) --> reqdata([request data])\r\n reqdata --> tryread{ }\r\n tryread --> resdata([response data])\r\n tryread --> failedload([draw message])\r\n resdata --> parsedata([parse data])\r\n parsedata --> isparsefailed{ }\r\n isparsefailed --> drawcontent([draw chunk content])\r\n isparsefailed --> drawrawdata([draw raw data])\r\n isparsefailed --> drawnottext([draw 'it is not text'])\r\n subgraph client\r\n A\r\n failedload\r\n reqdata\r\n parsedata\r\n isparsefailed\r\n drawcontent\r\n drawrawdata\r\n drawnottext\r\n end\r\n subgraph server\r\n tryread\r\n resdata\r\n end\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/5/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/5/timeline", + "performed_via_github_app": null, + "state_reason": "completed" + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/6", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/6/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/6/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/6/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/6", + "id": 1207906490, + "node_id": "I_kwDOHCdFJM5H_zC6", + "number": 6, + "title": "Document: remove", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": null, + "comments": 1, + "created_at": "2022-04-19T07:47:59Z", + "updated_at": "2022-05-29T04:42:00Z", + "closed_at": "2022-05-29T04:41:26Z", + "author_association": "OWNER", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작조건: 문서를 삭제할 권한이 있어야함. \r\n목표: 앱에서 문서를 삭제한다.\r\n\r\n1. Document의 AppBar에 놓여있는 삭제 아이콘을 클릭하면 시작한다.\r\n2. 정말로 삭제하겠냐는 다이얼로고가 띄운다.\r\n3. 거기서 예스를 누르면 Document를 삭제한다.\r\n\r\n대안 흐름:\r\n1. 다이얼로그에서 아니오를 누르면 다이얼로고를 닫고 종료한다.\r\n\r\n```mermaid\r\ngraph LR\r\n start((start)) -->|click delete icon|dialogue([draw dialogue])\r\n dialogue --> S{ }\r\n S -->|Yes| remove([delete])\r\n S -->|No| enddial([end])\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/6/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/6/timeline", + "performed_via_github_app": null, + "state_reason": "completed" + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/7", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/7/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/7/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/7/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/7", + "id": 1207914418, + "node_id": "I_kwDOHCdFJM5H_0-y", + "number": 7, + "title": "Document: add/delete tag", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": null, + "comments": 0, + "created_at": "2022-04-19T07:52:41Z", + "updated_at": "2022-05-19T13:30:21Z", + "closed_at": "2022-05-19T13:30:21Z", + "author_association": "OWNER", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작조건: 태그 수정 권한이 있을 때 \r\n목표: 문서의 태그를 추가/수정/삭제한다.\r\n\r\n1. Document의 AppBar에 놓여있는 태그 수정 아이콘을 클릭하면 시작한다. 태그 수정 다이얼로그를 띄운다.\r\n2. 태그 수정 다이얼로그에서 태그를 생성, 삭제한다.\r\n3. 수정을 완료하고 저장 버튼을 누르면 태그 수정이 종료된다.\r\n\r\n대안 흐름:\r\n\r\n1. 취소 버튼을 누르면 다이얼로그를 닫고 종료한다.\r\n\r\n```mermaid\r\nflowchart LR\r\n Start((start)) --> drawdialogue([Draw Tag Dialogue])\r\n drawdialogue --> check{ }\r\n check --> cancel([cancel])\r\n check --> complete([complete])\r\n ```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/7/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/7/timeline", + "performed_via_github_app": null, + "state_reason": "completed" + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/8", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/8/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/8/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/8/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/8", + "id": 1207932203, + "node_id": "I_kwDOHCdFJM5H_5Ur", + "number": 8, + "title": "Document: Drag And Drop Upload", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": null, + "comments": 2, + "created_at": "2022-04-19T08:01:16Z", + "updated_at": "2022-05-24T14:13:18Z", + "closed_at": "2022-05-24T14:13:18Z", + "author_association": "OWNER", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작조건: 문서 수정 권한이 있어야 한다. \r\n목표: 업로드를 드래그 앤 드롭으로 한다.\r\n\r\n1. 웹사이트의 그림이나 비디오 등의 파일을 Drag And Drop 해서 Chunk 사이에 놓으면 시작된다.\r\n2. Drag And Drop된 파일을 서버에 업로드한다. 업로드시 파일 이름이 중복될 때 파일 이름이 밑줄과 숫자로 끝나지 않으면 이름의 뒤에 \"_1\"을 붙여 업로드 한다. 숫자로 끝나면 다음 숫자를 붙여서 업로드 한다. 파일 이름이 없다면 임의의 이름을 붙여서 업로드 한다.\r\n3. 그 파일을 새로운 Chunk로 추가한다. 이때 웹에서 표시가능한 파일(이미지, 동영상)이면 파일을 표시하는 Chunk를 추가하고 아니면 다운로드 링크를 가진 Chunk를 추가한다.\r\n\r\n대체흐름:\r\n\r\nA. 파일 사이즈 큼\r\n 1. 기본단계 2에서 파일 사이즈가 설정보다 크면 시작한다.\r\n 2. 파일 크기가 너무 크다는 메세지로 띄우고 종료한다.\r\n\r\nB. 작은 파일 사이즈\r\n 1. 기본단계 2에서 파일 사이즈가 설정보다 작으면 시작한다.\r\n 2. 파일을 base64로 인코딩해서 Chunk로 삽입한다.\r\n\r\n```mermaid\r\nflowchart LR\r\n Start((start)) -->|on drop| upload([upload])\r\n upload --> tryupload{try upload}\r\n tryupload -->|file too big| cancel([upload fail])\r\n tryupload -->|on success| complete([complete])\r\n tryupload -->|file small| inlining([inlining content])\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/8/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/8/timeline", + "performed_via_github_app": null, + "state_reason": "completed" + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/9", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/9/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/9/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/9/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/9", + "id": 1207938762, + "node_id": "I_kwDOHCdFJM5H_67K", + "number": 9, + "title": "Document: Auto-Refresh", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2022-04-19T08:05:56Z", + "updated_at": "2022-05-15T08:07:17Z", + "closed_at": "2022-05-15T08:07:17Z", + "author_association": "OWNER", + "active_lock_reason": null, + "body": "액터: 외부 편집기 \r\n시작조건: 없음 \r\n목적: 변화를 실시간으로 따라갈 수 있게 한다.\r\n\r\n1. Document나 Document가 포함하는 미디어의 파일이 다른 편집 프로그램에 의해서 변경되었을 시에 시작한다.\r\n2. 보고 있는 사용자의 Document 뷰를 변경 부분만 Refresh한다. 이때 보고 있던 스크롤이 변하지 않게 유의한다.\r\n\r\n```mermaid\r\nflowchart LR\r\n Start((start)) --> signal>on refresh signal]\r\n signal --> redraw([redraw chunk])\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/9/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/9/timeline", + "performed_via_github_app": null, + "state_reason": "completed" + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/10", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/10/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/10/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/10/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/10", + "id": 1207939021, + "node_id": "I_kwDOHCdFJM5H_6_N", + "number": 10, + "title": "Chunk: autocomplete", + "user": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2022-04-19T08:06:07Z", + "updated_at": "2022-05-15T08:05:24Z", + "closed_at": "2022-05-15T08:02:23Z", + "author_association": "COLLABORATOR", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작조건: chunk 수정를 하고 있어야 한다. \r\n목표: 링크 등을 자동으로 완성해서 편집을 편하게 한다.\r\n\r\n1. Ctrl+Space로 자동완성 창을 띄우는 명령을 내릴 때 시작한다.\r\n2. caret cursor위치를 찾아서 그 위치에 자동완성 창을 띄운다.\r\n3. 자동완성은 caret cursor 앞의 단어를 보고 문맥을 추론하여 추천 리스트를 만든다. 순서는 일반적으로는 abc순으로 한다.\r\n4. 추천 리스트 중 알맞은 것을 방향키로 고르게 한다.\r\n5. Tab이나 Enter를 통해 선택한다.\r\n6. 선택한 단어로 완성시킨다.\r\n\r\n```mermaid\r\nflowchart LR\r\n start((Start)) -->|\"Cnrl + Space\"| drawac([draw auto-complete window])\r\n drawac --> select([select word])\r\n select --> complete([complete])\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/10/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/10/timeline", + "performed_via_github_app": null, + "state_reason": "completed" + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/11", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/11/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/11/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/11/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/11", + "id": 1207941746, + "node_id": "I_kwDOHCdFJM5H_7py", + "number": 11, + "title": "Chunk: swap positions", + "user": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": null, + "comments": 0, + "created_at": "2022-04-19T08:07:59Z", + "updated_at": "2022-05-11T01:59:20Z", + "closed_at": "2022-05-11T01:59:19Z", + "author_association": "COLLABORATOR", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작 조건: Document 수정권한이 있어야 한다. \r\n목적: 앱에서 Chunk 위치를 바꿀 수 있다.\r\n\r\n1. Focus를 얻고 핸들 아이콘을 누를때 시작한다.\r\n2. 이때 오버레이를 표시해서 놓여졌을 때의 상황을 미리 볼 수 있게 한다.\r\n3. 드래그해서 원하는 장소에 놓으면 위치를 바꿀 수 있다.\r\n\r\n```mermaid\r\nflowchart LR\r\n start((Start)) -->|\"on mousedown\"| drawdc([draw droppable chunk])\r\n drawdc --> drawoverlay([draw overlay])\r\n drawoverlay --> |on mouse move| drawdc\r\n drawoverlay -->|on mouseup| swap([swap chunk])\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/11/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/11/timeline", + "performed_via_github_app": null, + "state_reason": "completed" + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/12", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/12/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/12/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/12/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/12", + "id": 1207942358, + "node_id": "I_kwDOHCdFJM5H_7zW", + "number": 12, + "title": "Document: Share", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": null, + "comments": 0, + "created_at": "2022-04-19T08:08:26Z", + "updated_at": "2022-06-07T10:16:48Z", + "closed_at": "2022-06-07T10:16:48Z", + "author_association": "OWNER", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작 조건: 문서를 공유할 수 있는 권한을 가져야한다. \r\n목표: 문서를 공유한다.\r\n\r\n1. Document의 AppBar에 놓여있는 공유 아이콘을 누르면 시작한다.\r\n2. 공유 링크를 복사한다. 그리고 공유 설정아이콘을 띄워준다. 여기서 종료할 수 있다.\r\n3. 공유 설정아이콘을 클릭하면 공유 설정 다이얼로그를 띄운다. 이 다이얼로그에서는 공유 기간과 편집 가능여부 등을 설정할 수 있고 공유 링크를 복사할 수 있다. 공유를 취소할 수도 있다.\r\n\r\n```mermaid\r\nflowchart TB\r\n start((Start)) -->|\"click share\"| share([copy share link])\r\n share --> shareend((end))\r\n share -->|\"click again\"| shareopt([open share option window])\r\n shareopt --> S{ }\r\n S --> shareexit([stop sharing])\r\n S --> chday([modify expired day])\r\n S --> chwritable([change modifiablity])\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/12/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/12/timeline", + "performed_via_github_app": null, + "state_reason": "completed" + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/13", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/13/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/13/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/13/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/13", + "id": 1207948025, + "node_id": "I_kwDOHCdFJM5H_9L5", + "number": 13, + "title": "Document: Navigator", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "open", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2022-04-19T08:11:41Z", + "updated_at": "2022-04-27T04:59:31Z", + "closed_at": null, + "author_association": "OWNER", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작 조건: 없음 \r\n목표: 문서들을 쉽게 이동할 수 있는 네비게이터를 보여준다.\r\n\r\n1. 만일 문서가 속하는 디렉토리에 Summary.md 가 있고 올바른 형식(링크와 리스트로 이루어져 있음)이면 시작한다.\r\n2. Document의 왼쪽에 Summary의 내용을 네비게이터 역할로 표시한다.\r\n\r\n```mermaid\r\nflowchart LR\r\n start((Start)) --> checkmd{check the\\nsummary.md}\r\n checkmd -->|exist| navigator([draw navigator])\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/13/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/13/timeline", + "performed_via_github_app": null, + "state_reason": null + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/14", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/14/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/14/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/14/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/14", + "id": 1208080430, + "node_id": "I_kwDOHCdFJM5IAdgu", + "number": 14, + "title": "File: create/delete/rename file", + "user": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": { + "url": "https://api.github.com/repos/vi117/scrap-yard/milestones/1", + "html_url": "https://github.com/vi117/scrap-yard/milestone/1", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/milestones/1/labels", + "id": 7890719, + "node_id": "MI_kwDOHCdFJM4AeGcf", + "number": 1, + "title": "Minimum Viable Product", + "description": "최소 기능 제품", + "creator": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 0, + "closed_issues": 8, + "state": "open", + "created_at": "2022-04-19T10:28:38Z", + "updated_at": "2022-05-29T04:42:16Z", + "due_on": "2022-05-01T07:00:00Z", + "closed_at": null + }, + "comments": 1, + "created_at": "2022-04-19T09:37:07Z", + "updated_at": "2022-05-19T14:04:38Z", + "closed_at": "2022-05-19T14:04:30Z", + "author_association": "COLLABORATOR", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작조건: 디렉토리에 대한 권한을 가지고 있어야한다. \r\n목표: 앱상에서 파일을 생성하거나 삭제 할 수 있어야 한다.\r\n\r\n1. Treeview에 포커스가 간 상태에서 시작한다.\r\n2. Treeview에서 오른쪽 클릭을 하면 Context Menu가 나오고 새파일을 클릭하면 이름을 지정해서 파일을 생성할 수 있다. \r\n3. Context Menu에서 삭제를 클릭하면 해당 파일을 삭제한다. \r\n4. 이름 바꾸기를 클릭하거나 Treeview에서 파일에 포커스가 간 상태에서 F2를 입력하면 이름을 바꿀 수 있도록 한다.\r\n\r\n```mermaid\r\ngraph TD\r\n start((start)) -->|on right click| context([draw context menu])\r\n context --> S{ }\r\n S -->|click new file| newfile([create new file])\r\n S -->|click remove file| rmfile([delete selected file])\r\n S -->|F2 or rename| renamefile([rename file])\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/14/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/14/timeline", + "performed_via_github_app": null, + "state_reason": "completed" + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/15", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/15/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/15/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/15/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/15", + "id": 1208083798, + "node_id": "I_kwDOHCdFJM5IAeVW", + "number": 15, + "title": "File: upload/download files", + "user": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": { + "url": "https://api.github.com/repos/vi117/scrap-yard/milestones/1", + "html_url": "https://github.com/vi117/scrap-yard/milestone/1", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/milestones/1/labels", + "id": 7890719, + "node_id": "MI_kwDOHCdFJM4AeGcf", + "number": 1, + "title": "Minimum Viable Product", + "description": "최소 기능 제품", + "creator": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 0, + "closed_issues": 8, + "state": "open", + "created_at": "2022-04-19T10:28:38Z", + "updated_at": "2022-05-29T04:42:16Z", + "due_on": "2022-05-01T07:00:00Z", + "closed_at": null + }, + "comments": 0, + "created_at": "2022-04-19T09:38:43Z", + "updated_at": "2022-05-29T04:42:16Z", + "closed_at": "2022-05-29T04:42:16Z", + "author_association": "COLLABORATOR", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작조건: 디렉토리의 권한이 있어야 한다. \r\n목표: 파일을 업로드하거나 다운로드 할 수 있어야 한다. \r\n\r\n1. Treeview의 Context menu에서 다운로드 버튼을 클릭해서 다운로드 할 수 있다. 아니면 Treeview 파일을 Drag and Drop 하는 것으로도 가능하다. \r\n2. Treeview에 파일을 드래그 앤 드롭하는 것으로 업로드 할 수 있다.\r\n\r\n```mermaid\r\ngraph TD\r\n start((start)) -->|on right click| context([draw context menu])\r\n start -->|drag out| download\r\n start -->|drag in| upload([upload file])\r\n context --> S{ }\r\n S -->|\"click download\"| download([download selected file])\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/15/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/15/timeline", + "performed_via_github_app": null, + "state_reason": "completed" + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/16", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/16/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/16/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/16/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/16", + "id": 1208088690, + "node_id": "I_kwDOHCdFJM5IAfhy", + "number": 16, + "title": "Search: Document Search", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "open", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2022-04-19T09:40:56Z", + "updated_at": "2022-04-28T06:08:08Z", + "closed_at": null, + "author_association": "OWNER", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작조건: 검색에는 문서를 읽을 수 있는 권한이 있어야 한다. \r\n목표: 문서들을 검색할 수 있다. \r\n\r\n1. Drawer에서 문서 검색 버튼을 누르면 문서 검색 창이 뜬다.\r\n2. 문서 검색창에서 범위를 지정한다. 기본 범위는 현재 보고 있는 문서의 디렉터리로 한다.\r\n3. 문서 검색 창에서 태그를 검색할지 내용으로 검색을 할지 지정한다. 기본값은 내용이다.\r\n4. 정규식을 사용할 것인지 지정한다. 기본값은 사용 안함이다.\r\n5. 검색 버튼을 누르면 해당 조건을 만족하는 문서를 리스트로 보여준다.\r\n\r\n```mermaid\r\ngraph TD\r\n A((Start))\r\n B([Search dialog])\r\n C([Select range])\r\n D([Select search type])\r\n E([Select regex use])\r\n F([Show search list])\r\n\r\n A -->|\"press search button\"| B;\r\n B --> C;\r\n C --> D;\r\n D --> E;\r\n E --> |\"press search button\"| F;\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/16/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/16/timeline", + "performed_via_github_app": null, + "state_reason": null + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/17", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/17/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/17/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/17/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/17", + "id": 1208099535, + "node_id": "I_kwDOHCdFJM5IAiLP", + "number": 17, + "title": "Stash: render", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": null, + "comments": 0, + "created_at": "2022-04-19T09:48:00Z", + "updated_at": "2022-05-20T17:49:56Z", + "closed_at": "2022-05-20T17:49:56Z", + "author_association": "OWNER", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작조건: 화면크기가 768px 이상일때 보여준다. \r\n목표: Stash를 보여준다.\r\n\r\n1. 오른쪽에 작은 버튼을 두고 버튼을 클릭하면 시작한다.\r\n2. 오른쪽 Drawer가 열려서 Stash의 내용을 보여준다. 최대 지정된 숫자만큼의 내용들을 보여준다.\r\n3. 다시 버튼을 누르면 Stash Drawer를 닫는다.\r\n\r\n```mermaid\r\ngraph LR\r\n A((Start))\r\n B([Show stash drawer])\r\n C([Close stash drawer])\r\n\r\n A -->|\"click stash button\"| B;\r\n B -->|\"click stash button\"| C;\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/17/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/17/timeline", + "performed_via_github_app": null, + "state_reason": "completed" + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/18", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/18/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/18/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/18/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/18", + "id": 1208102529, + "node_id": "I_kwDOHCdFJM5IAi6B", + "number": 18, + "title": "File: export document", + "user": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "open", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 2, + "created_at": "2022-04-19T09:50:42Z", + "updated_at": "2022-04-28T05:50:27Z", + "closed_at": null, + "author_association": "COLLABORATOR", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작조건: 문서를 읽을 수 있는 권한이 있어야 한다. 문서 타입에만 가능하다. \r\n목표: 문서를 보기 모드에서 보이는 것처럼 출력할 수 있다.\r\n\r\n1. 메뉴의 Context menu에서 접근 할 때 시작한다.\r\n2. 출력하기를 누르면 출력 다이얼로그가 뜬다.\r\n3. 출력할 문서 타입을 문서 타입을 설정한다. 출력할 수 있는 문서 타입은 다음과 같다.\r\n - pdf\r\n - html\r\n4. 출력하기를 누르면 출력된 문서를 다운로드한다.\r\n\r\n대안흐름:\r\n\r\nA. 취소\r\n 1. 단계 2에서 시작한다.\r\n 2. 취소하기를 누르면 다이얼로그 창을 닫고 종료한다.\r\n\r\n```mermaid\r\ngraph LR\r\n A((Start))\r\n B([Context menu])\r\n C([Show export dialog])\r\n D([Select export type])\r\n E([Export])\r\n\r\n A --> B;\r\n B -->|\"click export button\"| C;\r\n C --> D;\r\n D -->|\"pdf\"| E;\r\n D -->|\"html\"| E;\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/18/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/18/timeline", + "performed_via_github_app": null, + "state_reason": null + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/19", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/19/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/19/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/19/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/19", + "id": 1208105899, + "node_id": "I_kwDOHCdFJM5IAjur", + "number": 19, + "title": "Stash: add", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": null, + "comments": 0, + "created_at": "2022-04-19T09:53:50Z", + "updated_at": "2022-05-20T17:49:56Z", + "closed_at": "2022-05-20T17:49:56Z", + "author_association": "OWNER", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작 조건: Stash 창을 연 상태에서 포커스가 Stash 창에 주어져 있어야 한다. \r\n목표: Stash를 추가한다.\r\n\r\n1. \\<\\< 포함 #17 \\>\\>\r\n2. Ctrl+V를 누르면 Stash에 클립보드의 내용이 Stash로 추가되고 클립보드는 비워진다.\r\n3. Ctrl+Z를 눌러서 추가를 되돌릴 수 있다.\r\n\r\n```mermaid\r\ngraph LR\r\n A((Start))\r\n B([Stash])\r\n C([Add to stash])\r\n D([Clear clipboard])\r\n E([Undo add])\r\n\r\n A --> B\r\n B -->|\"press Ctrl-V\"| C;\r\n C --> D;\r\n D -->|\"press Ctrl-Z\"| E;\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/19/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/19/timeline", + "performed_via_github_app": null, + "state_reason": "completed" + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/20", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/20/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/20/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/20/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/20", + "id": 1208110130, + "node_id": "I_kwDOHCdFJM5IAkwy", + "number": 20, + "title": "Stash: remove", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": null, + "comments": 0, + "created_at": "2022-04-19T09:57:37Z", + "updated_at": "2022-05-20T17:49:56Z", + "closed_at": "2022-05-20T17:49:56Z", + "author_association": "OWNER", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작조건: Stash 창을 연 상태에서 포커스가 Stash 창에 주어져 있어야 한다. \r\n목표: 원하는 Stash를 삭제한다.\r\n\r\n1. Stash 창의 각각 항목의 삭제 버튼을 클릭하면 선택된 항목을 삭제한다.\r\n2. Stash에 붙어있는 삭제 버튼을 클릭하면 전체 항목을 삭제한다.\r\n3. Ctrl+Z를 눌르거나 실행취소 버튼을 눌러 삭제를 되돌릴 수 있다.\r\n\r\n```mermaid\r\ngraph TD\r\n A((Start))\r\n B([Delete stash])\r\n C([Clear stash])\r\n D([undo delete])\r\n\r\n A -->|\"click delete button\"| B\r\n A -->|\"click clear button\"| C\r\n A -->|\"press Ctrl-Z\"| D\r\n A -->|\"press undo button\"| D\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/20/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/20/timeline", + "performed_via_github_app": null, + "state_reason": "completed" + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/21", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/21/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/21/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/21/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/21", + "id": 1208115181, + "node_id": "I_kwDOHCdFJM5IAl_t", + "number": 21, + "title": "Stash: Drag and Drop to Document", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": null, + "comments": 0, + "created_at": "2022-04-19T10:02:03Z", + "updated_at": "2022-05-29T04:40:55Z", + "closed_at": "2022-05-29T04:40:55Z", + "author_association": "OWNER", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작 조건: 문서 편집 권한이 있어야 한다. \r\n목표: 드래그 앤 드롭으로 Chunk를 Stash에서 꺼내 삽입할 수 있다.\r\n\r\n1. Stash 창의 Stash을 Document에 드래그하면 시작된다.\r\n2. Document의 Chunk 사이에 Stash을 드롭하면 그 자리에 Chunk가 삽입된다. 사이의 결정은 제일 가까운 청크로 정한다. \r\n3. Stash을 그 위치로 업로드시킨다. 업로드는 #8 와 한 것 같이 한다.\r\n4. 해당 Stash를 Stash창에서 삭제한다.\r\n\r\n대안흐름:\r\n\r\nA. 취소\r\n 1. Stash 창에 놓으면 작업을 취소한다.\r\n\r\n```mermaid\r\ngraph TD\r\n A((Start))\r\n B(( ))\r\n C([Upload stash])\r\n D([Delete stash from stash window])\r\n E([Cancel upload])\r\n\r\n A -->|drag stash to document| B\r\n B -->|drop stash into document| C\r\n C --> D\r\n B -->|drop stash into stash window| E\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/21/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/21/timeline", + "performed_via_github_app": null, + "state_reason": "completed" + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/22", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/22/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/22/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/22/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/22", + "id": 1208118496, + "node_id": "I_kwDOHCdFJM5IAmzg", + "number": 22, + "title": "Management: Login", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2022-04-19T10:04:50Z", + "updated_at": "2022-06-06T08:03:58Z", + "closed_at": "2022-06-06T08:03:58Z", + "author_association": "OWNER", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작조건: 없음. \r\n목표: 사용자가 액세스하기 위해 로그인할 수 있다.\r\n\r\n1. 사용자가 로컬에서 지정된 프로그램이 아닌 외부에서 접근한다면 시작한다.\r\n2. 로그인 암호를 요구한다. 초기 로그인 암호는 환경변수에 의해서 결정된다.\r\n3. 알맞은 암호를 입력했다면 권한을 부여한다.\r\n\r\n```mermaid\r\ngraph LR\r\n A((Start))\r\n B([Login Prompt])\r\n C([Access Granted])\r\n\r\n A -->|external connection| B\r\n B -->|enter password| C\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/22/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/22/timeline", + "performed_via_github_app": null, + "state_reason": "completed" + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/23", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/23/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/23/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/23/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/23", + "id": 1208123921, + "node_id": "I_kwDOHCdFJM5IAoIR", + "number": 23, + "title": "Management: Configure", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "open", + "locked": false, + "assignee": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": null, + "comments": 0, + "created_at": "2022-04-19T10:08:08Z", + "updated_at": "2022-05-12T05:23:10Z", + "closed_at": null, + "author_association": "OWNER", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작조건: 없음. \r\n목표: 앱 정책등에 대해 앱에서 설정할 수 있다.\r\n\r\n1. 편집기 내부에서 설정 아이콘을 클릭하면 설정창을 보여준다. 설정창은 프로그램의 속성을 설정할 수 있고 로그를 볼 수 있다. 설정창은 다음 항목들을 포함한다.\r\n - 로그인 암호\r\n - 테마 설정\r\n - 언어 설정\r\n 이것은 권한에 따라 선택적으로 렌더링된다.\r\n2. 다시 설정 아이콘을 클릭하면 설정창을 닫는다.\r\n\r\n```mermaid\r\ngraph LR\r\n A((Start))\r\n B([Setting])\r\n\r\n subgraph Setting\r\n C([Theme])\r\n D([Language])\r\n E([Login password])\r\n end \r\n \r\n F{ }\r\n G([Exit setting])\r\n\r\n A --> |click setting button| B\r\n B --> C\r\n B --> D\r\n B --> E\r\n C --> F\r\n D --> F\r\n E --> F\r\n F --> |click setting button| G\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/23/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/23/timeline", + "performed_via_github_app": null, + "state_reason": null + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/24", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/24/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/24/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/24/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/24", + "id": 1208126279, + "node_id": "I_kwDOHCdFJM5IAotH", + "number": 24, + "title": "Management: Localization", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "open", + "locked": false, + "assignee": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": null, + "comments": 0, + "created_at": "2022-04-19T10:09:21Z", + "updated_at": "2022-05-12T05:23:11Z", + "closed_at": null, + "author_association": "OWNER", + "active_lock_reason": null, + "body": "> 어플리케이션을 여러가지 다양한 언어로 제공한다.\r\n> 다음은 다른 언어로 바꾸는 사용 사례이다.\r\n\r\n액터: 사용자 \r\n시작조건: 없음. \r\n목표: 설정창에서 다른 언어로 바꿀 수 있다.\r\n\r\n1. 설정창을 연다.\r\n2. 언어 항목으로 간다. 지원되는 언어 리스트 창이 놓여저있다.\r\n3. 언어 항목에서 다른 언어로 바꾼다. 이때 창을 다시 렌더링한다.\r\n\r\n```mermaid\r\ngraph LR\r\n A((Start))\r\n B([Setting])\r\n C([Language])\r\n D([Change language])\r\n E([Rerender window])\r\n\r\n A --> B --> C --> D --> E\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/24/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/24/timeline", + "performed_via_github_app": null, + "state_reason": null + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/25", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/25/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/25/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/25/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/25", + "id": 1208127708, + "node_id": "I_kwDOHCdFJM5IApDc", + "number": 25, + "title": "Management: Theme", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": null, + "comments": 0, + "created_at": "2022-04-19T10:10:33Z", + "updated_at": "2022-06-11T03:59:52Z", + "closed_at": "2022-06-11T03:59:52Z", + "author_association": "OWNER", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작조건: 없음. \r\n목표: 설정창에서 테마를 설정할 수 있다.\r\n\r\n1. 설정창에서 테마로 이동한다.\r\n2. 기본적으로 제공하는 밝은 테마과 어두운 테마를 고른다. 기본값은 밝은 색이다. 바꾸는 즉시 테마를 변경한다.\r\n3. 커스텀 css 를 올려서 테마로 등록한다.\r\n\r\n```mermaid\r\ngraph LR\r\n A((Start))\r\n B([Settting])\r\n C([Theme])\r\n D([Change theme])\r\n\r\n A --> B\r\n B --> C\r\n C --> D\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/25/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/25/timeline", + "performed_via_github_app": null, + "state_reason": "completed" + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/27", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/27/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/27/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/27/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/27", + "id": 1209357324, + "node_id": "I_kwDOHCdFJM5IFVQM", + "number": 27, + "title": "Chunk: edit", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "closed", + "locked": false, + "assignee": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": { + "url": "https://api.github.com/repos/vi117/scrap-yard/milestones/1", + "html_url": "https://github.com/vi117/scrap-yard/milestone/1", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/milestones/1/labels", + "id": 7890719, + "node_id": "MI_kwDOHCdFJM4AeGcf", + "number": 1, + "title": "Minimum Viable Product", + "description": "최소 기능 제품", + "creator": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 0, + "closed_issues": 8, + "state": "open", + "created_at": "2022-04-19T10:28:38Z", + "updated_at": "2022-05-29T04:42:16Z", + "due_on": "2022-05-01T07:00:00Z", + "closed_at": null + }, + "comments": 1, + "created_at": "2022-04-20T08:33:53Z", + "updated_at": "2022-05-02T08:19:30Z", + "closed_at": "2022-05-02T08:19:30Z", + "author_association": "OWNER", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작조건: Chunk의 Focus를 얻어야 한다. \r\n목표: Chunk안의 컨텐츠를 수정할 수 있다.\r\n\r\n1. 수정가능한 타입인지 확인한다.\r\n2. 타입에 맞는 에디터를 띄운다. 예를 들어 text 타입이면 해당 Chunk 안의 text를 수정할 수 있게 한다.\r\n\r\n```mermaid\r\ngraph LR\r\n A((Start))\r\n A --> B{\"check
focused
chunk is
editable\"}\r\n C([Open editor])\r\n D([Open tooltip 'cannot edit this chunk'])\r\n B -->|yes| C\r\n B -->|no| D\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/27/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/27/timeline", + "performed_via_github_app": null, + "state_reason": "completed" + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/28", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/28/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/28/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/28/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/28", + "id": 1209869793, + "node_id": "I_kwDOHCdFJM5IHSXh", + "number": 28, + "title": "Extension: API", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "open", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2022-04-20T15:43:12Z", + "updated_at": "2022-04-22T14:49:07Z", + "closed_at": null, + "author_association": "OWNER", + "active_lock_reason": null, + "body": "액터: API 사용자 \r\n시작조건: 없음. \r\n목표: 외부에서 API를 가지고 접근가능하도록 인터페이스를 제공한다.\r\n\r\n1. 설정에 들어가서 API 토큰을 발급받는다.\r\n2. api를 사용할때 Header에 발급받은 토큰을 넣고 통신한다.\r\n\r\n> API는 개발이 어느정도 진척되고 나서야 명세를 정할 수 있다.\r\n> 그러므로 일단은 통신 방식만 명세한다.", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/28/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/28/timeline", + "performed_via_github_app": null, + "state_reason": null + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/29", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/29/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/29/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/29/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/29", + "id": 1209876289, + "node_id": "I_kwDOHCdFJM5IHT9B", + "number": 29, + "title": "Extension: Plugin", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "open", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2022-04-20T15:47:50Z", + "updated_at": "2022-04-22T14:49:58Z", + "closed_at": null, + "author_association": "OWNER", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작 조건: 없음 \r\n목표: Plugin으로 편집환경을 확장 가능하게 한다.\r\n\r\n1. 플러그인을 폴더에 추가해 플러그인을 설치한다.\r\n2. 플러그인을 사용한다.\r\n\r\n> Plugin는 개발이 어느정도 진척되고 나서야 명세를 정할 수 있다.\r\n> 그러므로 사용한다는 것만 명세한다.", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/29/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/29/timeline", + "performed_via_github_app": null, + "state_reason": null + }, + { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/30", + "repository_url": "https://api.github.com/repos/vi117/scrap-yard", + "labels_url": "https://api.github.com/repos/vi117/scrap-yard/issues/30/labels{/name}", + "comments_url": "https://api.github.com/repos/vi117/scrap-yard/issues/30/comments", + "events_url": "https://api.github.com/repos/vi117/scrap-yard/issues/30/events", + "html_url": "https://github.com/vi117/scrap-yard/issues/30", + "id": 1212454772, + "node_id": "I_kwDOHCdFJM5IRJd0", + "number": 30, + "title": "Search: Find word", + "user": { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 3950545290, + "node_id": "LA_kwDOHCdFJM7reImK", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/duplicate", + "name": "duplicate", + "color": "cfd3d7", + "default": true, + "description": "This issue or pull request already exists" + }, + { + "id": 3950545291, + "node_id": "LA_kwDOHCdFJM7reImL", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/enhancement", + "name": "enhancement", + "color": "a2eeef", + "default": true, + "description": "New feature or request" + }, + { + "id": 4048616940, + "node_id": "LA_kwDOHCdFJM7xUP3s", + "url": "https://api.github.com/repos/vi117/scrap-yard/labels/feature", + "name": "feature", + "color": "1888DB", + "default": false, + "description": "" + } + ], + "state": "open", + "locked": false, + "assignee": { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "teyalem", + "id": 5918930, + "node_id": "MDQ6VXNlcjU5MTg5MzA=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918930?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/teyalem", + "html_url": "https://github.com/teyalem", + "followers_url": "https://api.github.com/users/teyalem/followers", + "following_url": "https://api.github.com/users/teyalem/following{/other_user}", + "gists_url": "https://api.github.com/users/teyalem/gists{/gist_id}", + "starred_url": "https://api.github.com/users/teyalem/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/teyalem/subscriptions", + "organizations_url": "https://api.github.com/users/teyalem/orgs", + "repos_url": "https://api.github.com/users/teyalem/repos", + "events_url": "https://api.github.com/users/teyalem/events{/privacy}", + "received_events_url": "https://api.github.com/users/teyalem/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "vi117", + "id": 5918999, + "node_id": "MDQ6VXNlcjU5MTg5OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/5918999?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vi117", + "html_url": "https://github.com/vi117", + "followers_url": "https://api.github.com/users/vi117/followers", + "following_url": "https://api.github.com/users/vi117/following{/other_user}", + "gists_url": "https://api.github.com/users/vi117/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vi117/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vi117/subscriptions", + "organizations_url": "https://api.github.com/users/vi117/orgs", + "repos_url": "https://api.github.com/users/vi117/repos", + "events_url": "https://api.github.com/users/vi117/events{/privacy}", + "received_events_url": "https://api.github.com/users/vi117/received_events", + "type": "User", + "site_admin": false + } + ], + "milestone": null, + "comments": 2, + "created_at": "2022-04-22T14:45:26Z", + "updated_at": "2022-06-10T16:17:39Z", + "closed_at": null, + "author_association": "OWNER", + "active_lock_reason": null, + "body": "액터: 사용자 \r\n시작조건: 문서를 읽을 수 있는 권한을 가져야한다. \r\n목표: 문서에서 단어를 검색한다.\r\n\r\n1. Ctrl+F를 누르면 작은 검색 창이 오른쪽 상단에 표시된다.\r\n2. 검색할 단어를 입력한다. 정규식도 가능하다.\r\n3. 검색된 단어의 배경에 색깔을 칠해 구별 가능하게 한다.\r\n\r\n```mermaid\r\ngraph TB\r\n A((Start))\r\n B([Search mode])\r\n C([Draw search mode])\r\n\r\n A --> |press Ctrl-F| B\r\n B --> |input word or regex| C\r\n```", + "reactions": { + "url": "https://api.github.com/repos/vi117/scrap-yard/issues/30/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/vi117/scrap-yard/issues/30/timeline", + "performed_via_github_app": null, + "state_reason": null + } +] \ No newline at end of file diff --git a/log.json b/log.json index 7241336..67169ab 100644 --- a/log.json +++ b/log.json @@ -1 +1 @@ -[{"root":"C:\\Users\\Monoid\\Desktop\\SRS\\SRS","config":{"book":{"authors":["monoid"],"language":"ko","multilingual":false,"src":"src","title":"Software Requirement Specification"},"output":{"html":{"additional-js":["mermaid.min.js","mermaid-init.js"],"live-reload-endpoint":"__livereload","site-url":"/"}},"preprocessor":{"etap":{"before":["mermaid"],"command":"deno run -A --no-check tools/preprop.ts"},"mermaid":{"command":"mdbook-mermaid"}}},"renderer":"html","mdbook_version":"0.4.18"},{"sections":[{"Chapter":{"name":"Introduction","content":"# 1. 소개(Introduction)\n\n> Version : 1.0.1\n\n 본 문서는 전북대학교 컴퓨터공학과의 Floor 팀에서 Scrap Yard라는 어플리케이션을 설계 및 구현하기 위한 소프트웨어 요구사항 명세서(SRS)이다.\n\n## 1.1. 목적(Purpose)\n\n본 문서의 목적은 프로젝트의 관련된 모든 아이디어들을 정리하고 분석해서 나열하는 것이다. 또한 프로젝트를 더 잘 이해하기 위해 이 제품이 어떻게 사용될지 예측하고 분류하고, 나중에 개발될 요소를 설명하고, 고려 중이지만 폐기될 수 있는 요구사항들을 문서화한다.\n\n## 1.2. 범위(Scope)\n\n본 문서의 범위는 ScrapYard의 기능들과 그 환경이다.\n\nScrapYard는 문서 작성 밎 문서를 아카이빙 할 수 있는 웹 어플리케이션이다. 같이 제공되는 확장기능을 통해 북마크(즐겨찾기)를 구조적으로 보관할 수 있고 미리보기를 보여줄 수 있다.\n\n또한 문서를 다른 사람과 링크로 공유할 수 있다.\n\n개인정보의 관리를 자기 자신이 제어할 수 있도록 파일과 문서에 대한 메타데이터가 어떠한 외부 DB가 있는 것이 아닌 파일에서 사람이 읽을 수 있는 형태로 관리된다. \n\n## 1.3. 용어 및 약어 정의(Definitions, acronyms and abbreviations)\n\n|용어 및 약어|정의|\n|---|----|\n|ScrapYard|현재 개발하는 앱의 명칭|\n|DnD|드래그 앤 드롭의 약자|\n\n## 1.4. 참고자료(References)\n\n- [repo](https://github.com/vi117/scrap-yard)\n- [react](https://reactjs.org/)\n- [recoil](https://recoiljs.org/)\n- [MUI](https://mui.com/)\n- [dndkit](https://docs.dndkit.com/)\n- [markdown](https://commonmark.org/)\n\n## 1.5. 개요(Overview)\n\n 2장에서는 종합적인 요구사항을 서술하고, 3장에서는 기능 및 UI에 대해서 상세한 요구사항을 설명한다.\n","number":[1],"sub_items":[],"path":"intro.md","source_path":"intro.md","parent_names":[]}},{"Chapter":{"name":"Overall Description","content":"# 2. 전체 시스템 개요(Overall description)\n\n### 2.1. 제품 관점(Product perspective)\n\n### 2.1.1. 시스템 인터페이스(System interfaces)\n\n본 시스템은 Cross-platform 소프트웨어이다. 다음과 같은 브라우저가 원활히 실행될 수 있는 시스템에서 동작 할 수 있다.\n- Chrome 버전 61 이상\n- Firefox 버전 60 이상\n- Edge 버전 79 이상\n- Safari 버전 11 이상\n- Chrome for Android 버전 100 이상\n- Samsung internet 버전 8.2 이상\n\n### 2.1.2. 사용자 인터페이스(User interfaces)\n\n웹으로 동작하는 GUI이다. 키보드와 마우스, 터치 인터페이스로 동작할 수 있다. GUI의 디자인은 Material Design이나 Metro Design 같이 플랫한 디자인을 추구한다.\n\n### 2.1.3. 하드웨어 인터페이스(Hardware interfaces)\n\n해당되지 않음.\n\n### 2.1.4. 소프트웨어 인터페이스(Software interfaces)\n\n이 프로젝트의 결과물은 클립보드를 통해서 여러 타입의 데이터를 import/export한다.\n\n### 2.1.5. 통신 인터페이스(Communications interfaces)\n\n해당되지 않음.\n\n### 2.1.6. 메모리 제약사항(Memory constraints)\n\n서버는 2GB 메모리 환경에서 정상 작동해야 한다.\n\n### 2.1.7. 운영(Operations)\n\n해당되지 않음.\n\n### 2.1.8. 사이트 적용 요건(Site adaption requirements)\n\n해당되지 않음.\n\n## 2.2. 제품 기능(Product functions)\n\n본 프로젝트의 결과물은 다음과 같은 기능을 수행한다.\n\n<%\n const table = new Map();\n it.issues.forEach((x)=>{\n const category = x.title.split(\":\")[0];\n if(!category) return;\n let c = table.get(category)\n if(!c){\n c = [];\n table.set(category,c);\n }\n c.push(x);\n })\n let index = 1;\n for (const [c,issues] of table) {\n%><%= `### 2.2.${index++} ${c} Operation\\n\\n` %><%\n let subIndex = 1;\n for (const i of issues) {\n%><%=`${subIndex++}. #${i.number} ${i.title}\\n` %><%\n }\n%>\n\n<%\n }\n%>\n\n## 2.3. 사용자 특성(User characteristics)\n\n사용자는 기본적인 GUI 조작을 할 줄 알며 인터넷 사용을 원활히 할 수 있고 기본적인 영어를 읽고 쓸 줄 알며, markdown을 작성할 수 있는 사용자로 한정한다. 일반적으로 13세 이상 65세 이하의 사람을 사용자로 가정한다.\n\n## 2.4. 제약사항(Constraints)\n\n- 이 프로젝트는 MIT License로 개발되고 있으므로 라이브러리의 라이센스도 신경을 쓴다.\n- XSS 공격에 안전해야 한다.\n\n## 2.5. 가정 및 의존성(Assumptions and dependencies)\n\n해당되지 않음.\n\n## 2.6. 단계별 요구사항(Apportioning of requirements)\n\n해당되지 않음.","number":[2],"sub_items":[],"path":"overall.md","source_path":"overall.md","parent_names":[]}},{"Chapter":{"name":"Specific Requirement","content":"# 3. 상세요구사항(Specific Requirements)\n\n## 3.1. 외부 인터페이스 요구사항(External interface requirements)\n\n해당되지 않음.\n## 3.2. 기능 요구사항(Functional requirements)\n\n<%~ it.issues.map(i => `### (#${i.number}) ${i.title}\\n${i.body.replaceAll(\"\\r\\n\",\"\\n\")}`).join(\"\\n\\n\") %>\n\n## 3.3. 성능 요구사항(Performance requirements)\n\n1. 최소 1000 RPS를 보장해야한다.\n2. 첫 로드후 로딩하면 0.5s 이내에 동작해야합니다\n3. 동시 편집 이용자를 5명까지는 허용해야 합니다.\n4. 적어도 400개의 파일을 관리할 수 있어야 합니다.\n\n## 3.4. 논리적 데이터베이스 요구사항(Logical database requirements)\n\n```mermaid\nerDiagram\n User {\n int id\n string accessToken\n date expiredAt\n }\n User ||--o{ Permission : has\n Permission {\n string name\n string path\n }\n```\n\n 단순하게 공유를 위한 User 구조만 있다.\n\n\n## 3.5. 설계 제약사항(Design constraints)\n\n2.1.1. 에서 언급했듯이 다음 브라우저에서 동작해야 한다.\n\n- Chrome 버전 61 이상\n- Firefox 버전 60 이상\n- Edge 버전 79 이상\n- Safari 버전 11 이상\n- Chrome for Android 버전 100 이상\n- Samsung internet 버전 8.2 이상\n\n그리고 모바일에서도 큰 불편함 없이 원활히 동작해야 한다.\n\n### 3.5.1. 표준 준수(Standards compliance)\n\n해당되지 않음.\n\n## 3.6. 소프트웨어 시스템 속성(Software system attributes)\n\n해당되지 않음.\n\n## 3.7. 상세 요구사항의 구성(Organizing the specific requirements)\n\n### 3.7.1. 객체(Objects)\n\n다음과 같은 UML을 그릴 수 있다.\n\n```mermaid\nclassDiagram\n class Document{\n - URL path\n - string[] tags\n \n + renderChunk()\n + remove()\n + addTag(name: string)\n + deleteTag(name: string)\n + async share(option: ShareOption): URL\n + renderNavigator()\n }\n class Chunk{\n - id_t id\n - Content data\n - bool focused\n - pos_t cursorPos\n\n + focus(index: number)\n + unfocus(index: number)\n + remove(index: number)\n + insertBefore()\n + draw()\n + drawPreview()\n + autoComplete(ctx: AutoCompleteContext)\n + swapWith(p : Chunk)\n + edit()\n }\n Document \"1\" <-- \"n\" Chunk : List\n class Fileview{\n + listDirectory(path: URL)\n + open(path: URL)\n + remove(path: URL)\n + create(path: URL)\n + upload(file: Uint8Array| FileStream)\n + download(path: URL)\n + export(path: URL, option: ExportOption)\n }\n Fileview <.. Document : create\n class StashList{\n Content[] stash\n int maxStash\n createFromServer()\n push(c: Content)\n pop()\n }\n class DnDManager{\n + dropTo(Fileview)\n + dropTo(Document)\n + dropTo(Stash)\n + dragFrom(Fileview)\n + dragFrom(Document)\n + dragFrom(Stash)\n }\n DnDManager <.. Fileview : param\n DnDManager <.. Document : param\n DnDManager <.. StashList : param\n class SearchManager{\n search(query,option)\n }\n SearchManager <.. Document : return\n```\n\n```mermaid\nclassDiagram\n class Management{\n login(auth:Auth)\n setServerConfigure(config: Configure)\n setLocale(lang: string)\n setTheme(theme: string)\n getLocale()\n getTheme()\n getServerConfigure()\n }\n class Extension{\n registerPlugin(plugin: Plugin)\n }\n```\n\nChunk는 문서를 이루는 기본적인 단위이다. 글의 문단이라고 생각 할 수 있다. Document는 그런 Chunk의 리스트이다. FileView는 파일에 접근하고 Document를 열 수 있는 파일 브라우저이다. StashList는 단순한 파일이나 텍스트 등을 임시로 저장하고 꺼내쓰는 컨테이너이다.\n\n### 3.7.2. 사용자 인터페이스 상세\n\n![interface](./interface.png)\n\n다음과 같이 컴포넌트가 배치될 것이다. 배치될 컴포넌트는 Treeview, Chunk, ChunkList, Appbar, GeneralDialogue, Drawer, ContextMenu, StashList가 있다.","number":[3],"sub_items":[],"path":"specific.md","source_path":"specific.md","parent_names":[]}},{"Chapter":{"name":"Supporting information","content":"# 추가 이력 (Supporting Information)\n\n## 4.1. 부록(Appendixes)\n\n내용 없음.\n\n## 4.2. 개발 환경(Development Environment)\n\n프론트엔드는 [Vite](https://vitejs-kr.github.io/)로 개발한다. 그리고 개발 언어로는 typescript를 사용한다. 그리고 react를 사용한다.\n\n백엔드는 Deno를 사용한다.\n\n## 4.3. 일정표(Schedule)\n\n<%\nconst getIssueByNumber = (n) => it.issues.filter(x=> x.number === n)[0];\n\nconst trId= (n)=>{\n const title = getIssueByNumber(n).title;\nreturn `(#${n}) ${title}`.replaceAll(/[^A-Za-z\\s0-9]/gi,\"\").toLocaleLowerCase().replaceAll(\" \",\"-\");\n}\nconst inTable = (arr) => {\n return arr.map(x=> `[#${x}](./specific.md#${trId(x)})`).join(', ')\n}\n%>\n<%\nconst timeTable = [\n [1,2,3,5,14,15,27],\n [4,6,7,8,11],\n [9,10,12,22,23],\n [13,16,17,19,20,21,30],\n [18,,24,25],\n [28,29]\n]\nconst Weeks = [\n'4.24~4.30', \n'5.1~5.7', \n'5.8~5.14', \n'5.15~5.21', \n'5.22~5.28', \n'5.29~6.4'\n]\n%>\n\n|주차|구현 기능|\n|----|--------|\n|<%= Weeks[0]%>|<%= inTable(timeTable[0]) %>|\n|<%= Weeks[1]%>|<%= inTable(timeTable[1]) %>|\n|<%= Weeks[2]%>|<%= inTable(timeTable[2]) %>|\n|<%= Weeks[3]%>|<%= inTable(timeTable[3]) %>|\n|<%= Weeks[4]%>|<%= inTable(timeTable[4]) %>|\n|<%= Weeks[5]%>|<%= inTable(timeTable[5]) %>|\n\n<% for(let weekIndex = 0; weekIndex < Weeks.length; weekIndex++) {%>\n### 주차 <%= Weeks[weekIndex]%>\n\n<%~ timeTable[weekIndex].map(n => {\n return `- [(#${n}) ${getIssueByNumber(n).title}](specific.md#${trId(n)})\\n`\n }).join('')\n%>\n\n<%}%>\n","number":[4],"sub_items":[],"path":"support.md","source_path":"support.md","parent_names":[]}},{"Chapter":{"name":"Architecture","content":"# 5. 설계\n\n## 5.1 UML\n\n### 5.1.1 Server Side UML\n\n```mermaid\nclassDiagram\nclass PermissionDescriptor {\n +canRead(path: string): boolean\n +canWrite(path: string): boolean\n +canCustom(path: string, options: any): boolean\n}\n<> PermissionDescriptor\nPermissionDescriptor <|.. PermissionImpl\nclass PermissionImpl {\n +basePath: string\n +writable: boolean\n +canRead(path: string): boolean\n +canWrite(path: string): boolean\n +canCustom(_path: string, _options: any): boolean\n}\nSessionStore o-- UserSession\nUserSession *-- PermissionDescriptor\nclass SessionStore~T~ {\n +sessions: Record\n +get(id: string): T\n +set(id: string, value: T): void\n +delete(id: string): void\n +saveToFile(path: string): Promise\n +loadFromFile(path: string): Promise\n}\nclass UserSession {\n +id: string\n +superuser: boolean\n +expiredAt: number\n +permissionSet: PermissionDescriptor\n}\n<> UserSession\n```\n```mermaid\nclassDiagram\nRouter <|.. FileServeRouter\nclass FileServeRouter {\n +fn: Handler\n +match(path: string, _ctx: MatchContext): any\n}\nclass MethodRouterBuilber {\n +handlers: MethodRouter\n +get(handler: Handler): this\n +post(handler: Handler): this\n +put(handler: Handler): this\n +delete(handler: Handler): this\n +build(): Handler\n}\nclass ResponseBuilder {\n +status: Status\n +headers: Record\n +body?: BodyInit\n +setStatus(status: Status): this\n +setHeader(key: string, value: string): this\n +setBody(body: BodyInit): this\n +redirect(location: string): this\n +build(): Response\n}\nclass MatchContext\n<> MatchContext\nclass Router~T~ {\n +match(path: string, ctx: MatchContext): T\n}\n<> Router\nRouter <|.. TreeRouter\nclass TreeRouter~T~ {\n -staticNode: Record>\n -simpleParamNode?: SimpleParamNode\n -regexParamNodes: Map>\n -fallbackNode?: Router\n -elem?: T\n -findRouter(path: string, ctx: MatchContext): [TreeRouter, string]\n +match(path: string, ctx?: MatchContext): T\n +register(path: string, elem: T): this\n +registerRouter(path: string, router: Router): void\n -setOrMerge(elem: RegElem): void\n -registerPath(path: string, elem: RegElem): void\n -singleRoute(p: string): SingleRouteOutput\n}\nMethodRouter <-- MethodRouterBuilber: Create\nRouter <|.. MethodRouter\nRouter <|.. FsRouter\nResponse <-- ResponseBuilder: Create\n```\n\n```mermaid\nclassDiagram\nclass ChunkMethodAction {\n +action: (doc: ActiveDocumentObject) => ChunkMethodHistory\n +checkConflict: (m: ChunkMethodHistory) => boolean\n +trySolveConflict: (m: ChunkMethodHistory) => boolean\n}\n<> ChunkMethodAction\nChunkMethodAction <|.. ChunkCreateAction\nclass ChunkCreateAction {\n +params: ChunkCreateMethod\n +action(doc: ActiveDocumentObject): ChunkCreateHistory\n +checkConflict(m: ChunkMethodHistory): boolean\n +trySolveConflict(m: ChunkMethodHistory): boolean\n}\nChunkMethodAction <|.. ChunkDeleteAction\nclass ChunkDeleteAction {\n +params: ChunkDeleteMethod\n +action(doc: ActiveDocumentObject): ChunkRemoveHistory\n +checkConflict(_m: ChunkMethodHistory): boolean\n +trySolveConflict(_m: ChunkMethodHistory): boolean\n}\nChunkMethodAction <|.. ChunkModifyAction\nclass ChunkModifyAction {\n +params: ChunkModifyMethod\n +action(doc: ActiveDocumentObject): ChunkModifyHistory\n +checkConflict(m: ChunkMethodHistory): boolean\n +trySolveConflict(_m: ChunkMethodHistory): boolean\n}\nChunkMethodAction <|.. ChunkMoveAction\nclass ChunkMoveAction {\n +params: ChunkMoveMethod\n +action(doc: ActiveDocumentObject): ChunkMoveHistory\n +checkConflict(_m: ChunkMethodHistory): boolean\n +trySolveConflict(_m: ChunkMethodHistory): boolean\n}\n```\n```mermaid\nclassDiagram\nDocumentObject <|.. FileDocumentObject\nclass FileDocumentObject {\n +docPath: string\n +chunks: Chunk[]\n +tags: string[]\n +updatedAt: number\n +tagsUpdatedAt: number\n +open(): Promise\n +parse(content: unknown[]): void\n +save(): Promise\n}\nclass Participant {\n +id: string\n +user: UserSession\n +send(data: string): void\n +addEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void\n +removeEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void\n +close(): void\n}\n<> Participant\nParticipant <|.. Connection\nclass Connection {\n +id: string\n +user: UserSession\n +socket: WebSocket\n +send(data: string): void\n +addEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void\n +removeEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void\n +close(code?: number, reason?: string): void\n}\nclass ParticipantList {\n +connections: Map\n +add(id: string, p: Participant): void\n +get(id: string): any\n +remove(id: string): void\n +unicast(id: string, message: string): void\n +broadcast(message: string): void\n}\nFileDocumentObject <|-- ActiveDocumentObject\nclass ActiveDocumentObject {\n +conns: Set\n +history: DocHistory[]\n +maxHistory: number\n +join(conn: Participant): void\n +leave(conn: Participant): void\n +updateDocHistory(method: ChunkMethodHistory): void\n +broadcastMethod(method: ChunkMethod, updatedAt: number, exclude?: Participant): void\n}\nclass DocumentStore {\n +documents: Inline\n +open(conn: Participant, docPath: string): Promise\n +close(conn: Participant, docPath: string): void\n +closeAll(conn: Participant): void\n}\nParticipantList o-- Participant\nDocumentStore o-- ActiveDocumentObject\n```\n```mermaid\nclassDiagram\nclass IFsWatcher{\n addEventListener(): void\n onNofity(e: FileWatchEvent): void\n}\n<> IFsWatcher\nclass FsWatcherImpl{\n onNotify(e: FileWatchEvent): void\n}\n\n```\n### 5.1.2 Client Side UML\n\n```mermaid\nclassDiagram\nError <|-- RPCErrorWrapper\nclass RPCErrorWrapper {\n +data?: unknown\n +code: RPCErrorCode\n +toJSON(): Inline\n}\nMessageEvent <|-- RPCNotificationEvent\nclass RPCNotificationEvent {\n +notification: ChunkNotification\n}\n```\n```mermaid\nclassDiagram\nclass ViewModelBase {\n +updateAsSource(path: string, updatedAt: number): void\n}\n<> ViewModelBase\nViewModelBase <|.. IViewModel\nclass IViewModel {\n +pageView: IPageViewModel\n}\n<> IViewModel\nIPageViewModel <|.. BlankPage\nclass BlankPage {\n +type: string\n +updateAsSource(_path: string, _updatedAt: number): void\n}\nIViewModel <|.. ViewModel\nclass ViewModel {\n +pageView: IPageViewModel\n +updateAsSource(path: string, updatedAt: number): void\n}\nViewModelBase <|.. IPageViewModel\nclass IPageViewModel {\n +type: string\n}\n<> IPageViewModel\nIPageViewModel <|.. IDocumentViewModel\nclass IDocumentViewModel {\n +updateOnNotification(notification: ChunkNotification): void\n}\n<> IDocumentViewModel\nclass ChunkListMutator {\n +add(i?: number | undefined, chunkContent?: ChunkContent | undefined): void\n +create(i?: number | undefined): void\n +addFromText(i: number, text: string): void\n +del(id: string): void\n +move(id: string, pos: number): void\n}\n<> ChunkListMutator\nclass ChunkListState {\n +chunks: Chunk[]\n +cloen(): ChunkListState\n}\nclass ChunkListStateMutator\n<> ChunkListStateMutator\nclass ChunkListHistory {\n +history: ChunkListHistoryElem[]\n +limit: number\n -applyLast(mutator: ChunkListStateMutator, updatedAt: number): void\n +current: ChunkListState\n +currentUpdatedAt: number\n +revoke(): void\n +apply(mutator: ChunkListStateMutator, updatedAt: number): boolean\n}\nclass ChunkMutator {\n +setType(t: ChunkContentType): void\n +setContent(s: string): void\n}\n<> ChunkMutator\nIDocumentViewModel <|.. DocumentViewModel\nclass DocumentViewModel {\n +type: \"document\"\n +docPath: string\n +chunks: Chunk[]\n +history: ChunkListHistory\n +buffer: Inline\n +seq: number\n +tags: string[]\n +updatedAt: number\n +tagsUpdatedAt: number\n +updateOnNotification(notification: ChunkNotification): void\n -updateMark(updatedAt: number): void\n +apply(mutator: ChunkListStateMutator, updatedAt: number, seq: number, refresh?: boolean): void\n +updateAsSource(_path: string, _updatedAt: number): void\n +useChunks(): [Chunk[], ChunkListMutator]\n +useTags(): [string[], (tags: string[]) => Promise]\n +useChunk(chunk_arg: Chunk): [Chunk, ChunkMutator]\n}\nIViewModel ..> \"1\" IPageViewModel\nViewModel ..> \"1\" IPageViewModel\nChunkListHistory ..> \"1\" ChunkListStateMutator\nChunkListHistory ..> \"1\" ChunkListState\nDocumentViewModel ..> \"1\" ChunkListHistory\nDocumentViewModel ..> \"1\" ChunkListStateMutator\nDocumentViewModel ..> \"1\" ChunkListMutator\nDocumentViewModel ..> \"1\" ChunkMutator\n```\n```mermaid\nclassDiagram\nclass ChunkListStateMutator\n<> ChunkListStateMutator\nclass ChunkListStateAddMutator\nclass ChunkListStateDeleteMutator\nclass ChunkListStateModifyMutator\nclass ChunkListStateMoveMutator\nChunkListStateMutator <|.. ChunkListStateAddMutator\nChunkListStateMutator <|.. ChunkListStateDeleteMutator\nChunkListStateMutator <|.. ChunkListStateModifyMutator\nChunkListStateMutator <|.. ChunkListStateMoveMutator\n```\n```mermaid\nclassDiagram\nclass IRPCMessageManager {\n +opened: boolean\n +close(): void\n +sendNotification(notification: ChunkNotification): void\n +addEventListener(name: \"notification\", listener: RPCMessageMessagerEventListener): void\n +addEventListener(name: string, listener: EventListenerOrEventListenerObject): void\n +removeEventListener(name: \"notification\", listener: RPCMessageMessagerEventListener): void\n +removeEventListener(name: string, listener: EventListenerOrEventListenerObject): void\n +invokeMethod(m: RPCMessageBody): Promise\n}\n<> IRPCMessageManager\nIRPCMessageManager <|.. RPCMessageManager\nclass RPCMessageManager {\n -callbackList: Map\n -curId: number\n -ws?: WebSocket | undefined\n +opened: boolean\n +open(url: string | URL, protocals?: string | undefined): Promise\n +close(): void\n -genId(): number\n +genHeader(): Inline\n +send(message: RPCMethod): Promise\n +sendNotification(message: ChunkNotification): void\n +addEventListener(type: \"notification\", callback: RPCMessageMessagerEventListener): void\n +removeEventListener(type: \"notification\", callback: RPCMessageMessagerEventListener): void\n +invokeMethod(m: RPCMessageBody): Promise\n}\nclass FsDirEntry {\n +name: string\n +isDirectory: boolean\n +isFile: boolean\n +isSymlink: boolean\n}\n<> FsDirEntry\nclass FsStatInfo {\n +isFile: boolean\n +isDirectory: boolean\n +isSymlink: boolean\n +size: number\n +mtime: Date | null\n +atime: Date | null\n +birthtime: Date | null\n}\n<> FsStatInfo\nFsStatInfo <|.. FsGetResult\nclass FsGetResult {\n +entries?: FsDirEntry[] | undefined\n}\n<> FsGetResult\nclass IFsEventMap {\n +modify: (this: IFsManager, event: MessageEvent) => void\n +create: (this: IFsManager, event: MessageEvent) => void\n +delete: (this: IFsManager, event: MessageEvent) => void\n}\n<> IFsEventMap\nclass IFsManager {\n +get(path: string): Promise\n +getStat(path: string): Promise\n +upload(filePath: string, data: BodyInit): Promise\n +delete(filePath: string): Promise\n +mkdir(path: string): Promise\n +addEventListener(name: string, listener: EventListenerOrEventListenerObject): void\n +removeEventListener(name: string, listener: EventListenerOrEventListenerObject): void\n +dispatchEvent(event: Event): boolean\n}\n<> IFsManager\nIFsManager <|.. FsManager\nclass FsManager {\n -manager: RPCMessageManager\n -prefix: string\n +get(path: string): Promise\n +getStat(filePath: string): Promise\n +upload(filePath: string, data: BodyInit): Promise\n +mkdir(filePath: string): Promise\n +delete(filePath: string): Promise\n}\nFsGetResult ..> \"*\" FsDirEntry\nIFsEventMap ..> \"1\" IFsManager\nIFsManager ..> \"1\" FsGetResult\nFsManager ..> \"1\" RPCMessageManager\nFsManager ..> \"1\" FsGetResult\n```\n\n## 5.2 의사코드\n\n의사코드는 다음과 같이 진행된다.\n\n### 서버 RPC 메세지 처리\n\n클라이언트는 RPC를 진행하기 위해 웹소켓을 연결합니다. 웹소켓의 주소 `/ws`에 \n도달하기 위해서 먼저 라우팅이 진행이 됩니다. 라우팅은 `TreeRouter` 클래스에서\n 진행됩니다.\n```\nInput: req Request\nOutput: Response\nfn Route(req){\n for node in HandlerNodes {\n if(req.url prefix is matching){\n node.Handler[matching](req)\n }\n }\n response with 404 Not Found\n}\n``` \n마침내 엔드포인트에 도달하게 되면 웹소켓을 얻어내고 새로운 연결을 등록합니다.\n```\nInput: req Request\nOutput: Response\nfn RPCHandleEndpoint(req){\n ws, res = upgradeWebSocket(req)\n user = getUserInfo(req.header)\n conn = new Connection(ws, user) \n registerParticipant(conn)\n res\n}\n```\n이제부터 메세지를 받을 수 있습니다.\n메세지가 오면 이 함수가 실행이 됩니다.\n```\nInput: message on send\nOutput: response\nfn Connection.handleMessage(msg: string){\n data = JSON.parse(msg)\n check format of data\n dispatch(data.method, data, this)\n}\n```\n\n디스패치가 성공적으로 이루어지면 RPC 작업를 처리하는 함수에 도착합니다.\n청크 작업을 예로 들겠습니다. 청크 작업에서는 권한을 확인하고\n명령의 충돌을 히스토리를 비교하며 다시 적용하며 해결합니다.\n그리고 요구된 작업을 처리하고 문서의 `updatedAt`을 업데이트하고\n문서 업데이트 사실을 이 문서를 보고 있던 참여자에게 전파합니다.\n```\nInput: conn Connection\nInput: m RPCChunkMethod\nOutput: response\n\nfn ChunkOperation(conn, m){\n doc = DocStore.getDocument(m.docPath);\n updatedAt = m.updatedAt;\n action = getAction(m);\n if !conn.userpermissionSet.canDo(Action) {\n response with PermissionError;\n }\n \n appliedList = doc.history.filter(x => x.updatedAt > updatedAt)\n for m in appliedList {\n if action.checkConflict(m) {\n resolvedAction = action.tryResolveConflict(m)\n if resolvedAction == Fail {\n response with ConflictError\n }\n action = resolvedAction\n }\n }\n \n res = action.act(doc);\n doc.updateHistory(res);\n \n subscribers = doc.getSubscribers();\n subscribers.broadcastNotification(m, exclude = conn);\n response with res\n}\n```\n\n문서 작업도 마찬가지로 이루어집니다.\n\n### 클라이언트의 메세지 처리 동기화\n\n클라이언트에서는 다음과 같은 일이 일어납니다.\n\n먼저 `notification`을 받습니다. 그러면 모든 `DocumentViewModel`에게 이벤트를 전달합니다.\n그리고 각각의 `DocumentViewModel`은 자기 문서에 일어난 일인지 확인하고 `ChunkListMutator`를 만들어서\n문서에 적용합니다.\n```\nInput: e RPCNotification\nInput: this document view model\n\nfn updateOnNotification(this,notification){\n { docPath, method, seq, updatedAt } = notification.params;\n if (docPath !== this.docPath) return;\n mutator = ChunkListMutatorFactory.createFrom(method);\n this.apply(mutator, updatedAt, seq);\n}\n```\n\n문서의 레디큐에 mutator를 집어넣고 `seq` 번호가 기다리는 것이면 실행하고 업데이트합니다.\n\n```\nInput: mutator ChunkListMutator\nInput: updatedAt Date\nInput: seq number\nInput: this Document View Model\n\n//readyQueue is priority queue.\nfn apply(this, mutator, updatedAt, seq){\n this.readyQueue.push({mutator, updatedAt, seq})\n\n if(this.readyQueue.lenght < some limit){\n while(!readyQueue.empty() &&\n this.readyQueue.top().seq === this.seq + 1)\n {\n mutator, updatedAt, seq = this.readyQueue.pop();\n mutator(this.chunks);\n this.seq = seq;\n this.updatedAt = updatedAt; \n }\n this.dispatch(new Event(\"chunksChange\"));\n }\n else {\n document.refresh();\n }\n}\n```\n\n### 다른 작업들\n\n```\nmodule chunk {\n type mode = Read | Write\n\n struct Chunk {\n id: string\n content: string\n type: string\n }\n\n newChunk() {\n { id = uuid()\n ; content = \"\"\n ; type = \"\"\n }\n }\n\n chunkViewer(chunk : Chunk, focusedChunk : State, deleteThis : () => void) : Component {\n var mode = Read\n\n var c = new Component(\n content { value = chunk.content }\n settypebutton\n editbutton\n deletebutton\n )\n\n when mode becomes Read { chunk.content = content }\n when mode becomes Write { focusedChunk = chunk.id }\n when focusedChunk is changed { mode = Read }\n\n when editbutton is clicked { mode = (mode = Read) ? Write : Read }\n when deletebutton is clicked { deleteThis() }\n when settypebutton is clicked { chunk.type = prompt() }\n\n return c\n }\n}\n```\n\n```\nmodule search {\n searchWord(chunks, word) {\n return doc.chunks.concat_map((s) => s.matchAll(word))\n }\n\n searchWordPrompt(chunks: Chunk.chunk list) {\n var word = prompt()\n var results = searchWord(chunks, word)\n\n var c = new Component(results)\n\n when result in results is selected {\n moveto(result.location)\n close()\n }\n }\n\n when Ctrl-F is pressed { searchWordPrompt() }\n}\n```\n\n```\nmodule document {\n struct Document {\n title: string\n path: Path\n tags: string set\n chunks: chunk.Chunk array\n }\n\n documentViewer(doc: document) : Component {\n var focusedChunk = null\n\n var c = new Component(\n taglist { value: tags }\n chunklist\n )\n\n delete(id) {\n i = doc.chunks.find((c) => c.id = id)\n doc.chunks.remove(i)\n }\n\n chunklist = doc.chunks.concat_map((c, i) =>\n [ divider(i), chuknViewer(c, focusedChunk, () => delete(c.id)) ])\n\n when divider(i) clicked { doc.chunks.insert(i, c) }\n when chunkViewer(c) is dropped on divider(i) { doc.chunks.move(c, i) }\n\n return c\n }\n}\n```\n\n```\nmodule filelist {\n fileList(dir : Directory, open: (File) => void) : Component {\n var c = new Component(\n filelist\n )\n\n filelist = dir.files().map((f) => button(f))\n\n when button(f) is clicked {\n open(f)\n }\n\n return c\n }\n}\n```\n\n```\nmodule settings {\n settings() : Component {\n var c = new Component(\n language = select(\"korean\", \"english\")\n theme = select(\"light\", \"dark\")\n )\n\n when language(l) is selected {\n global context.lang = l\n }\n\n when theme(t) is selected {\n global context.theme = t\n }\n\n return c\n }\n}\n\n```\nmodule frontend {\n main() : Component {\n var docv\n\n var open = (f) => {\n with doc = openfile(f) {\n docv = document.documentViewer(doc)\n }\n }\n\n var filelist = filelist.fileList(rootdir, open)\n\n var c = new Component(\n document = docv\n filelist = filelist\n )\n\n return c\n }\n}\n```\n","number":[5],"sub_items":[],"path":"architecture.md","source_path":"architecture.md","parent_names":[]}},{"Chapter":{"name":"Testing","content":"# Testing\r\n\r\n## 유닛 테스트\r\n\r\n유닛 테스트로 69.6%의 Line Coverage와 73.4%의 Function Coverage를 달성했다.\r\n다음과 같은 로그가 있다.\r\n\r\n```\r\nrunning 2 tests from ./src/auth/permission.test.ts\r\npermission.test ... ok (8ms)\r\npermission empty ... ok (16ms)\r\nrunning 4 tests from ./src/auth/session.test.ts\r\nSession ...\r\n set ... ok (9ms)\r\n delete ... ok (16ms)\r\nok (42ms)\r\nLogin Handler ...\r\n login with invalid format ... ok (15ms)\r\n login with invalid password ... ok (16ms)\r\n login ... ok (16ms)\r\n logout with no session ... ok (16ms)\r\n logout ... ok (16ms)\r\nok (96ms)\r\ngetSession ... ok (16ms)\r\ngetSession with invalid cookie ... ok (16ms)\r\nrunning 1 test from ./src/auth/user.test.ts\r\nuser.createAdminUser ... ok (15ms)\r\nrunning 4 tests from ./src/document/filedoc.test.ts\r\nreadDocFile ... ok (19ms)\r\nreadDocFile: not found ... ok (16ms)\r\nreadDocFile: invalid json ... ok (16ms)\r\nsaveDocFile ... ok (15ms)\r\nrunning 3 tests from ./src/router/methodHandle.test.ts\r\nmethodHandle: basic methods ... ok (8ms)\r\nmethodHandle: not found ... ok (16ms)\r\nmethodHandle: options ... ok (16ms)\r\nrunning 8 tests from ./src/router/route.test.ts\r\nroute: basic route ... ok (10ms)\r\nroute: double slash route ... ok (16ms)\r\nroute: double match ... ok (16ms)\r\nroute: test context ... ok (16ms)\r\nroute: test regex ... ok (16ms)\r\nroute: test not found ... ok (16ms)\r\nroute: encode_route ... ok (2ms)\r\nroute: router in router ... ok (13ms)\r\nrunning 4 tests from ./src/rpc/chunk.test.ts\r\nbasic chunk operation ...\r\n create chunk ... ok (19ms)\r\n delete chunk ... ok (15ms)\r\n modify chunk ... ok (15ms)\r\n move chunk ... ok (15ms)\r\n invalid chunk operation ... ok (17ms)\r\nok (98ms)\r\ntest chunk notification operation ... ok (15ms)\r\ntest chunk conflict ... ok (16ms)\r\ntest chunk conflict resolve with history ... ok (32ms)\r\nrunning 2 tests from ./src/rpc/doc.test.ts\r\nhandleDocumentMethod ... ok (4ms)\r\nhandleTagMethod ...\r\n setTag ... ok (13ms)\r\n getTag ... ok (15ms)\r\n conflict ... ok (15ms)\r\nok (61ms)\r\nrunning 3 tests from ./src/rpc/share.test.ts\r\nhandleShareGetInfo ... ok (18ms)\r\nhandleShareDocMethod ... ok (15ms)\r\nhandleShareMethod with no existing share token ... ok (16ms)\r\nrunning 1 test from ./src/server.test.ts\r\nserver rpc test ... ok (1s)\r\nrunning 3 tests from ./src/setting.test.ts\r\nsetting: basic ... ok (35ms)\r\nsetting: default value ... ok (7ms)\r\nsetting: defered register ... ok (16ms)\r\ntest result: ok. 35 passed (15 steps); 0 failed; 0 ignored; 0 measured; 0 filtered out (2s)\r\n```\r\n\r\n\r\n## 기능 테스트\r\n\r\n### Chunk\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
IDContentProcedureTest DataP/F
1Focus/Unfocus1. 청크를 클릭한다.P
2remove1. 청크를 삭제하는 버튼을 클릭한다.P
3-1render - markdown1. 마크다운 청크 렌더링을 확인한다. # 제목 P
3-2render - latex1. LaTex 청크 렌더링을 확인한다. sum^n_{n=0}n = \\frac{n(n+1)}2$$ P
3-3render - link1. Image 청크 렌더링을 확인한다.http://picsum.photosP
4previews1. Katex 청크의 미리보기를 본다. sum^n_{n=0}n = \\frac{n(n+1)}2$$ F
10autocomplete1. Ctrl+Space를 눌러 자동완성을 시도한다.F
11swap positions1. 청크의 위치를 바꾼다.P
27-1edit1. 청크를 수정한다.P
27-2edit chunk conflict1. 청크를 수정모드에 들어간다.F
\r\n\r\n### Document\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
IDContentProcedureTest DataP/F
5view Chunk1. 문서를 열어 청크가 렌더링되는지 본다.test.sydP
7add/delete tag1. 문서에 태그를 추가한다.
2. 문서에 태그를 삭제한다.
AP
8Drag And Drop Upload,1. 텍스트를 드래그한다.P
\r\n\r\n### File\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
IDContentProcedureTest DataP/F
14create/delete/rename file1. 파일을 만든다.test.txtP
15upload/download files1. 파일을 업로드한다.test.txtP
18export document1. export 버튼을 누른다.F
\r\n\r\n### Search\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
IDContentProcedureTest DataP/F
16Document Search1. 검색버튼을 눌러 검색을 한다.chunkF
\r\n\r\n### Stash\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
IDContentProcedureTest DataP/F
17render1. 스태시가 그려지는지 확인한다P
19add1. 청크를 추가한다P
20remove1. 청크를 삭제한다P
21Drag and Drop to Document1. 청크로부터 문서로 청크를 옮긴다.P
\r\n\r\n### Management\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
IDContentProcedureTest DataP/F
22Login1. 비밀번호를 입력한다.adminF
24Localization1. 다른언어를 지원하는지 언어를 바꿔 확인한다F
\r\n","number":[6],"sub_items":[],"path":"testing.md","source_path":"testing.md","parent_names":[]}}],"__non_exhaustive":null}] \ No newline at end of file +[{"root":"C:\\Users\\Monoid\\Desktop\\SRS\\SRS","config":{"book":{"authors":["monoid"],"language":"ko","multilingual":false,"src":"src","title":"Software Requirement Specification"},"output":{"html":{"additional-js":["mermaid.min.js","mermaid-init.js"],"live-reload-endpoint":"__livereload","site-url":"/"}},"preprocessor":{"etap":{"before":["mermaid"],"command":"deno run -A --no-check tools/preprop.ts"},"mermaid":{"command":"mdbook-mermaid"}}},"renderer":"html","mdbook_version":"0.4.18"},{"sections":[{"Chapter":{"name":"Index","content":"# 목차(Index)\r\n\r\n1. [Introduction](./intro.md)\r\n 1. [목적(Purpose)](./intro.md#11-목적purpose)\r\n 2. [범위(scope)](./intro.md#12-범위scope)\r\n 3. [용어 및 약어 정의(Definitions, acronyms and abbreviations)](./intro.md#13-용어-및-약어-정의definitions-acronyms-and-abbreviations)\r\n 4. [참고자료(References)](./intro.md#14-참고자료references)\r\n 5. [개요(Overview)](./intro.md#15-개요overview)\r\n2. [Overall Description](./overall.md)\r\n 1. [제품 관점(Product perspective)](./overall.md#21-제품-관점product-perspective)\r\n 1. [시스템 인터페이스(System interfaces)](./overall.md#211-시스템-인터페이스system-interfaces)\r\n 2. [사용자 인터페이스(User interfaces)](./overall.md#212-사용자-인터페이스user-interfaces)\r\n 3. [하드웨어 인터페이스(Hardware interfaces)](./overall.md#213-하드웨어-인터페이스hardware-interfaces)\r\n 4. [소프트웨어 인터페이스(Software interfaces)](./overall.md#214-소프트웨어-인터페이스software-interfaces)\r\n 5. [통신 인터페이스(Communications interfaces)](./overall.md#215-통신-인터페이스communications-interfaces)\r\n 6. [메모리 제약사항(Memory constraints)](./overall.md#216-메모리-제약사항memory-constraints)\r\n 7. [운영(Operations)](./overall.md#217-운영operations)\r\n 8. [사이트 적용 요건(Site adaption requirements)](./overall.md#218-사이트-적용-요건site-adaption-requirements)\r\n 2. [제품 기능(Product functions)](./overall.md#22-제품-기능product-functions)\r\n <%\r\n const table = it.table;\r\n let index = 1;\r\n for (const [c,issues] of table) {\r\n const name = `${c} Operation`;\r\n const href = `2.2.${index} ${c} Operation`\r\n %><%= `${index++}. [${name}](./overall.md#${it.toHeadId(href)})\\n ` %><%}%><%=\"\\n\"%>\r\n3. [Specific Requirement](./specific.md)\r\n 1. [외부 인터페이스 요구사항(External interface requirements)](./specific.md#31-외부-인터페이스-요구사항external-interface-requirements)\r\n 2. [기능 요구사항(Functional requirements)](./specific.md#32-기능-요구사항functional-requirements)\r\n <%= it.issues.map((i)=>`(#${i.number}) ${i.title}`).map(\r\n x=>`* [${x}](./specific.md#${it.toHeadId(x)})`\r\n ).join(\"\\n \") %><%= \"\\n\"%>\r\n 3. [성능 요구사항(Performance requirements)](./specific.md#33-성능-요구사항performance-requirements)\r\n 4. [논리적 데이터베이스 요구사항(Logical database requirements)](./specific.md#34-논리적-데이터베이스-요구사항logical-database-requirements)\r\n 5. [설계 제약사항(Design constraints)](./specific.md#35-설계-제약사항design-constraints)\r\n 1. [표준 준수(Standards compliance)](./specific.md#351-표준-준수standards-compliance)\r\n 6. [소프트웨어 시스템 속성(Software system attributes)](./specific.md#36-소프트웨어-시스템-속성software-system-attributes)\r\n 7. [상세 요구사항의 구성(Organizing the specific requirements)](./specific.md#37-상세-요구사항의-구성organizing-the-specific-requirements)\r\n 1. [객체(Objects)](./specific.md#371-객체objects)\r\n 2. [사용자 인터페이스 상세](./specific.md#372-사용자-인터페이스-상세)\r\n4. [Supporting information](./support.md)\r\n5. [Architecture](./architecture.md)\r\n6. [Testing](./testing.md)","number":null,"sub_items":[],"path":"index.md","source_path":"index.md","parent_names":[]}},{"Chapter":{"name":"Introduction","content":"# 1. 소개(Introduction)\n\n> Version : 1.0.1\n\n 본 문서는 전북대학교 컴퓨터공학과의 Floor 팀에서 Scrap Yard라는 어플리케이션을 설계 및 구현하기 위한 소프트웨어 요구사항 명세서(SRS)이다.\n\n## 1.1. 목적(Purpose)\n\n본 문서의 목적은 프로젝트의 관련된 모든 아이디어들을 정리하고 분석해서 나열하는 것이다. 또한 프로젝트를 더 잘 이해하기 위해 이 제품이 어떻게 사용될지 예측하고 분류하고, 나중에 개발될 요소를 설명하고, 고려 중이지만 폐기될 수 있는 요구사항들을 문서화한다.\n\n## 1.2. 범위(Scope)\n\n본 문서의 범위는 ScrapYard의 기능들과 그 환경이다.\n\nScrapYard는 문서 작성 밎 문서를 아카이빙 할 수 있는 웹 어플리케이션이다. 같이 제공되는 확장기능을 통해 북마크(즐겨찾기)를 구조적으로 보관할 수 있고 미리보기를 보여줄 수 있다.\n\n또한 문서를 다른 사람과 링크로 공유할 수 있다.\n\n개인정보의 관리를 자기 자신이 제어할 수 있도록 파일과 문서에 대한 메타데이터가 어떠한 외부 DB가 있는 것이 아닌 파일에서 사람이 읽을 수 있는 형태로 관리된다. \n\n## 1.3. 용어 및 약어 정의(Definitions, acronyms and abbreviations)\n\n|용어 및 약어|정의|\n|---|----|\n|ScrapYard|현재 개발하는 앱의 명칭|\n|DnD|드래그 앤 드롭의 약자|\n\n## 1.4. 참고자료(References)\n\n- [repo](https://github.com/vi117/scrap-yard)\n- [react](https://reactjs.org/)\n- [recoil](https://recoiljs.org/)\n- [MUI](https://mui.com/)\n- [dndkit](https://docs.dndkit.com/)\n- [markdown](https://commonmark.org/)\n\n## 1.5. 개요(Overview)\n\n 2장에서는 종합적인 요구사항을 서술하고, 3장에서는 기능 및 UI에 대해서 상세한 요구사항을 설명한다.\n","number":[1],"sub_items":[],"path":"intro.md","source_path":"intro.md","parent_names":[]}},{"Chapter":{"name":"Overall Description","content":"# 2. 전체 시스템 개요(Overall description)\n\n### 2.1. 제품 관점(Product perspective)\n\n### 2.1.1. 시스템 인터페이스(System interfaces)\n\n본 시스템은 Cross-platform 소프트웨어이다. 다음과 같은 브라우저가 원활히 실행될 수 있는 시스템에서 동작 할 수 있다.\n- Chrome 버전 61 이상\n- Firefox 버전 60 이상\n- Edge 버전 79 이상\n- Safari 버전 11 이상\n- Chrome for Android 버전 100 이상\n- Samsung internet 버전 8.2 이상\n\n### 2.1.2. 사용자 인터페이스(User interfaces)\n\n웹으로 동작하는 GUI이다. 키보드와 마우스, 터치 인터페이스로 동작할 수 있다. GUI의 디자인은 Material Design이나 Metro Design 같이 플랫한 디자인을 추구한다.\n\n### 2.1.3. 하드웨어 인터페이스(Hardware interfaces)\n\n해당되지 않음.\n\n### 2.1.4. 소프트웨어 인터페이스(Software interfaces)\n\n이 프로젝트의 결과물은 클립보드를 통해서 여러 타입의 데이터를 import/export한다.\n\n### 2.1.5. 통신 인터페이스(Communications interfaces)\n\n해당되지 않음.\n\n### 2.1.6. 메모리 제약사항(Memory constraints)\n\n서버는 2GB 메모리 환경에서 정상 작동해야 한다.\n\n### 2.1.7. 운영(Operations)\n\n해당되지 않음.\n\n### 2.1.8. 사이트 적용 요건(Site adaption requirements)\n\n해당되지 않음.\n\n## 2.2. 제품 기능(Product functions)\n\n본 프로젝트의 결과물은 다음과 같은 기능을 수행한다.\n\n<%\n const table = it.table;\n let index = 1;\n for (const [c,issues] of table) {\n%><%= `### 2.2.${index++} ${c} Operation\\n\\n` %><%\n let subIndex = 1;\n for (const i of issues) {\n%><%=`${subIndex++}. #${i.number} ${i.title}\\n` %><%\n }\n%>\n\n<%\n }\n%>\n\n## 2.3. 사용자 특성(User characteristics)\n\n사용자는 기본적인 GUI 조작을 할 줄 알며 인터넷 사용을 원활히 할 수 있고 기본적인 영어를 읽고 쓸 줄 알며, markdown을 작성할 수 있는 사용자로 한정한다. 일반적으로 13세 이상 65세 이하의 사람을 사용자로 가정한다.\n\n## 2.4. 제약사항(Constraints)\n\n- 이 프로젝트는 MIT License로 개발되고 있으므로 라이브러리의 라이센스도 신경을 쓴다.\n- XSS 공격에 안전해야 한다.\n\n## 2.5. 가정 및 의존성(Assumptions and dependencies)\n\n해당되지 않음.\n\n## 2.6. 단계별 요구사항(Apportioning of requirements)\n\n해당되지 않음.","number":[2],"sub_items":[],"path":"overall.md","source_path":"overall.md","parent_names":[]}},{"Chapter":{"name":"Specific Requirement","content":"# 3. 상세요구사항(Specific Requirements)\n\n## 3.1. 외부 인터페이스 요구사항(External interface requirements)\n\n해당되지 않음.\n## 3.2. 기능 요구사항(Functional requirements)\n\n<%~ it.issues.map(i => `### (#${i.number}) ${i.title}\\n${i.body.replaceAll(\"\\r\\n\",\"\\n\")}`).join(\"\\n\\n\") %>\n\n## 3.3. 성능 요구사항(Performance requirements)\n\n1. 최소 1000 RPS를 보장해야한다.\n2. 첫 로드후 로딩하면 0.5s 이내에 동작해야합니다\n3. 동시 편집 이용자를 5명까지는 허용해야 합니다.\n4. 적어도 400개의 파일을 관리할 수 있어야 합니다.\n\n## 3.4. 논리적 데이터베이스 요구사항(Logical database requirements)\n\n```mermaid\nerDiagram\n User {\n int id\n string accessToken\n date expiredAt\n }\n User ||--o{ Permission : has\n Permission {\n string name\n string path\n }\n```\n\n 단순하게 공유를 위한 User 구조만 있다.\n\n\n## 3.5. 설계 제약사항(Design constraints)\n\n2.1.1. 에서 언급했듯이 다음 브라우저에서 동작해야 한다.\n\n- Chrome 버전 61 이상\n- Firefox 버전 60 이상\n- Edge 버전 79 이상\n- Safari 버전 11 이상\n- Chrome for Android 버전 100 이상\n- Samsung internet 버전 8.2 이상\n\n그리고 모바일에서도 큰 불편함 없이 원활히 동작해야 한다.\n\n### 3.5.1. 표준 준수(Standards compliance)\n\n해당되지 않음.\n\n## 3.6. 소프트웨어 시스템 속성(Software system attributes)\n\n해당되지 않음.\n\n## 3.7. 상세 요구사항의 구성(Organizing the specific requirements)\n\n### 3.7.1. 객체(Objects)\n\n다음과 같은 UML을 그릴 수 있다.\n\n```mermaid\nclassDiagram\n class Document{\n - URL path\n - string[] tags\n \n + renderChunk()\n + remove()\n + addTag(name: string)\n + deleteTag(name: string)\n + async share(option: ShareOption): URL\n + renderNavigator()\n }\n class Chunk{\n - id_t id\n - Content data\n - bool focused\n - pos_t cursorPos\n\n + focus(index: number)\n + unfocus(index: number)\n + remove(index: number)\n + insertBefore()\n + draw()\n + drawPreview()\n + autoComplete(ctx: AutoCompleteContext)\n + swapWith(p : Chunk)\n + edit()\n }\n Document \"1\" <-- \"n\" Chunk : List\n class Fileview{\n + listDirectory(path: URL)\n + open(path: URL)\n + remove(path: URL)\n + create(path: URL)\n + upload(file: Uint8Array| FileStream)\n + download(path: URL)\n + export(path: URL, option: ExportOption)\n }\n Fileview <.. Document : create\n class StashList{\n Content[] stash\n int maxStash\n createFromServer()\n push(c: Content)\n pop()\n }\n class DnDManager{\n + dropTo(Fileview)\n + dropTo(Document)\n + dropTo(Stash)\n + dragFrom(Fileview)\n + dragFrom(Document)\n + dragFrom(Stash)\n }\n DnDManager <.. Fileview : param\n DnDManager <.. Document : param\n DnDManager <.. StashList : param\n class SearchManager{\n search(query,option)\n }\n SearchManager <.. Document : return\n```\n\n```mermaid\nclassDiagram\n class Management{\n login(auth:Auth)\n setServerConfigure(config: Configure)\n setLocale(lang: string)\n setTheme(theme: string)\n getLocale()\n getTheme()\n getServerConfigure()\n }\n class Extension{\n registerPlugin(plugin: Plugin)\n }\n```\n\nChunk는 문서를 이루는 기본적인 단위이다. 글의 문단이라고 생각 할 수 있다. Document는 그런 Chunk의 리스트이다. FileView는 파일에 접근하고 Document를 열 수 있는 파일 브라우저이다. StashList는 단순한 파일이나 텍스트 등을 임시로 저장하고 꺼내쓰는 컨테이너이다.\n\n### 3.7.2. 사용자 인터페이스 상세\n\n![interface](./interface.png)\n\n다음과 같이 컴포넌트가 배치될 것이다. 배치될 컴포넌트는 Treeview, Chunk, ChunkList, Appbar, GeneralDialogue, Drawer, ContextMenu, StashList가 있다.","number":[3],"sub_items":[],"path":"specific.md","source_path":"specific.md","parent_names":[]}},{"Chapter":{"name":"Supporting information","content":"# 추가 이력 (Supporting Information)\n\n## 4.1. 부록(Appendixes)\n\n내용 없음.\n\n## 4.2. 개발 환경(Development Environment)\n\n프론트엔드는 [Vite](https://vitejs-kr.github.io/)로 개발한다. 그리고 개발 언어로는 typescript를 사용한다. 그리고 react를 사용한다.\n\n백엔드는 Deno를 사용한다.\n\n## 4.3. 일정표(Schedule)\n\n<%\nconst getIssueByNumber = (n) => it.issues.filter(x=> x.number === n)[0];\n\nconst trId= (n)=>{\n const title = getIssueByNumber(n).title;\nreturn `(#${n}) ${title}`.replaceAll(/[^A-Za-z\\s0-9]/gi,\"\").toLocaleLowerCase().replaceAll(\" \",\"-\");\n}\nconst inTable = (arr) => {\n return arr.map(x=> `[#${x}](./specific.md#${trId(x)})`).join(', ')\n}\n%>\n<%\nconst timeTable = [\n [1,2,3,5,14,15,27],\n [4,6,7,8,11],\n [9,10,12,22,23],\n [13,16,17,19,20,21,30],\n [18,,24,25],\n [28,29]\n]\nconst Weeks = [\n'4.24~4.30', \n'5.1~5.7', \n'5.8~5.14', \n'5.15~5.21', \n'5.22~5.28', \n'5.29~6.4'\n]\n%>\n\n|주차|구현 기능|\n|----|--------|\n|<%= Weeks[0]%>|<%= inTable(timeTable[0]) %>|\n|<%= Weeks[1]%>|<%= inTable(timeTable[1]) %>|\n|<%= Weeks[2]%>|<%= inTable(timeTable[2]) %>|\n|<%= Weeks[3]%>|<%= inTable(timeTable[3]) %>|\n|<%= Weeks[4]%>|<%= inTable(timeTable[4]) %>|\n|<%= Weeks[5]%>|<%= inTable(timeTable[5]) %>|\n\n<% for(let weekIndex = 0; weekIndex < Weeks.length; weekIndex++) {%>\n### 주차 <%= Weeks[weekIndex]%>\n\n<%~ timeTable[weekIndex].map(n => {\n return `- [(#${n}) ${getIssueByNumber(n).title}](specific.md#${trId(n)})\\n`\n }).join('')\n%>\n\n<%}%>\n","number":[4],"sub_items":[],"path":"support.md","source_path":"support.md","parent_names":[]}},{"Chapter":{"name":"Architecture","content":"# 5. 설계\n\n## 5.1 UML\n\n### 5.1.1 Server Side UML\n\n```mermaid\nclassDiagram\nclass PermissionDescriptor {\n +canRead(path: string): boolean\n +canWrite(path: string): boolean\n +canCustom(path: string, options: any): boolean\n}\n<> PermissionDescriptor\nPermissionDescriptor <|.. PermissionImpl\nclass PermissionImpl {\n +basePath: string\n +writable: boolean\n +canRead(path: string): boolean\n +canWrite(path: string): boolean\n +canCustom(_path: string, _options: any): boolean\n}\nSessionStore o-- UserSession\nUserSession *-- PermissionDescriptor\nclass SessionStore~T~ {\n +sessions: Record\n +get(id: string): T\n +set(id: string, value: T): void\n +delete(id: string): void\n +saveToFile(path: string): Promise\n +loadFromFile(path: string): Promise\n}\nclass UserSession {\n +id: string\n +superuser: boolean\n +expiredAt: number\n +permissionSet: PermissionDescriptor\n}\n<> UserSession\n```\n```mermaid\nclassDiagram\nRouter <|.. FileServeRouter\nclass FileServeRouter {\n +fn: Handler\n +match(path: string, _ctx: MatchContext): any\n}\nclass MethodRouterBuilber {\n +handlers: MethodRouter\n +get(handler: Handler): this\n +post(handler: Handler): this\n +put(handler: Handler): this\n +delete(handler: Handler): this\n +build(): Handler\n}\nclass ResponseBuilder {\n +status: Status\n +headers: Record\n +body?: BodyInit\n +setStatus(status: Status): this\n +setHeader(key: string, value: string): this\n +setBody(body: BodyInit): this\n +redirect(location: string): this\n +build(): Response\n}\nclass MatchContext\n<> MatchContext\nclass Router~T~ {\n +match(path: string, ctx: MatchContext): T\n}\n<> Router\nRouter <|.. TreeRouter\nclass TreeRouter~T~ {\n -staticNode: Record>\n -simpleParamNode?: SimpleParamNode\n -regexParamNodes: Map>\n -fallbackNode?: Router\n -elem?: T\n -findRouter(path: string, ctx: MatchContext): [TreeRouter, string]\n +match(path: string, ctx?: MatchContext): T\n +register(path: string, elem: T): this\n +registerRouter(path: string, router: Router): void\n -setOrMerge(elem: RegElem): void\n -registerPath(path: string, elem: RegElem): void\n -singleRoute(p: string): SingleRouteOutput\n}\nMethodRouter <-- MethodRouterBuilber: Create\nRouter <|.. MethodRouter\nRouter <|.. FsRouter\nResponse <-- ResponseBuilder: Create\n```\n\n```mermaid\nclassDiagram\nclass ChunkMethodAction {\n +action: (doc: ActiveDocumentObject) => ChunkMethodHistory\n +checkConflict: (m: ChunkMethodHistory) => boolean\n +trySolveConflict: (m: ChunkMethodHistory) => boolean\n}\n<> ChunkMethodAction\nChunkMethodAction <|.. ChunkCreateAction\nclass ChunkCreateAction {\n +params: ChunkCreateMethod\n +action(doc: ActiveDocumentObject): ChunkCreateHistory\n +checkConflict(m: ChunkMethodHistory): boolean\n +trySolveConflict(m: ChunkMethodHistory): boolean\n}\nChunkMethodAction <|.. ChunkDeleteAction\nclass ChunkDeleteAction {\n +params: ChunkDeleteMethod\n +action(doc: ActiveDocumentObject): ChunkRemoveHistory\n +checkConflict(_m: ChunkMethodHistory): boolean\n +trySolveConflict(_m: ChunkMethodHistory): boolean\n}\nChunkMethodAction <|.. ChunkModifyAction\nclass ChunkModifyAction {\n +params: ChunkModifyMethod\n +action(doc: ActiveDocumentObject): ChunkModifyHistory\n +checkConflict(m: ChunkMethodHistory): boolean\n +trySolveConflict(_m: ChunkMethodHistory): boolean\n}\nChunkMethodAction <|.. ChunkMoveAction\nclass ChunkMoveAction {\n +params: ChunkMoveMethod\n +action(doc: ActiveDocumentObject): ChunkMoveHistory\n +checkConflict(_m: ChunkMethodHistory): boolean\n +trySolveConflict(_m: ChunkMethodHistory): boolean\n}\n```\n```mermaid\nclassDiagram\nDocumentObject <|.. FileDocumentObject\nclass FileDocumentObject {\n +docPath: string\n +chunks: Chunk[]\n +tags: string[]\n +updatedAt: number\n +tagsUpdatedAt: number\n +open(): Promise\n +parse(content: unknown[]): void\n +save(): Promise\n}\nclass Participant {\n +id: string\n +user: UserSession\n +send(data: string): void\n +addEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void\n +removeEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void\n +close(): void\n}\n<> Participant\nParticipant <|.. Connection\nclass Connection {\n +id: string\n +user: UserSession\n +socket: WebSocket\n +send(data: string): void\n +addEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void\n +removeEventListener(type: T, listener: (this: WebSocket, event: WebSocketEventMap[T]) => void): void\n +close(code?: number, reason?: string): void\n}\nclass ParticipantList {\n +connections: Map\n +add(id: string, p: Participant): void\n +get(id: string): any\n +remove(id: string): void\n +unicast(id: string, message: string): void\n +broadcast(message: string): void\n}\nFileDocumentObject <|-- ActiveDocumentObject\nclass ActiveDocumentObject {\n +conns: Set\n +history: DocHistory[]\n +maxHistory: number\n +join(conn: Participant): void\n +leave(conn: Participant): void\n +updateDocHistory(method: ChunkMethodHistory): void\n +broadcastMethod(method: ChunkMethod, updatedAt: number, exclude?: Participant): void\n}\nclass DocumentStore {\n +documents: Inline\n +open(conn: Participant, docPath: string): Promise\n +close(conn: Participant, docPath: string): void\n +closeAll(conn: Participant): void\n}\nParticipantList o-- Participant\nDocumentStore o-- ActiveDocumentObject\n```\n```mermaid\nclassDiagram\nclass IFsWatcher{\n addEventListener(): void\n onNofity(e: FileWatchEvent): void\n}\n<> IFsWatcher\nclass FsWatcherImpl{\n onNotify(e: FileWatchEvent): void\n}\n\n```\n### 5.1.2 Client Side UML\n\n```mermaid\nclassDiagram\nError <|-- RPCErrorWrapper\nclass RPCErrorWrapper {\n +data?: unknown\n +code: RPCErrorCode\n +toJSON(): Inline\n}\nMessageEvent <|-- RPCNotificationEvent\nclass RPCNotificationEvent {\n +notification: ChunkNotification\n}\n```\n```mermaid\nclassDiagram\nclass ViewModelBase {\n +updateAsSource(path: string, updatedAt: number): void\n}\n<> ViewModelBase\nViewModelBase <|.. IViewModel\nclass IViewModel {\n +pageView: IPageViewModel\n}\n<> IViewModel\nIPageViewModel <|.. BlankPage\nclass BlankPage {\n +type: string\n +updateAsSource(_path: string, _updatedAt: number): void\n}\nIViewModel <|.. ViewModel\nclass ViewModel {\n +pageView: IPageViewModel\n +updateAsSource(path: string, updatedAt: number): void\n}\nViewModelBase <|.. IPageViewModel\nclass IPageViewModel {\n +type: string\n}\n<> IPageViewModel\nIPageViewModel <|.. IDocumentViewModel\nclass IDocumentViewModel {\n +updateOnNotification(notification: ChunkNotification): void\n}\n<> IDocumentViewModel\nclass ChunkListMutator {\n +add(i?: number | undefined, chunkContent?: ChunkContent | undefined): void\n +create(i?: number | undefined): void\n +addFromText(i: number, text: string): void\n +del(id: string): void\n +move(id: string, pos: number): void\n}\n<> ChunkListMutator\nclass ChunkListState {\n +chunks: Chunk[]\n +cloen(): ChunkListState\n}\nclass ChunkListStateMutator\n<> ChunkListStateMutator\nclass ChunkListHistory {\n +history: ChunkListHistoryElem[]\n +limit: number\n -applyLast(mutator: ChunkListStateMutator, updatedAt: number): void\n +current: ChunkListState\n +currentUpdatedAt: number\n +revoke(): void\n +apply(mutator: ChunkListStateMutator, updatedAt: number): boolean\n}\nclass ChunkMutator {\n +setType(t: ChunkContentType): void\n +setContent(s: string): void\n}\n<> ChunkMutator\nIDocumentViewModel <|.. DocumentViewModel\nclass DocumentViewModel {\n +type: \"document\"\n +docPath: string\n +chunks: Chunk[]\n +history: ChunkListHistory\n +buffer: Inline\n +seq: number\n +tags: string[]\n +updatedAt: number\n +tagsUpdatedAt: number\n +updateOnNotification(notification: ChunkNotification): void\n -updateMark(updatedAt: number): void\n +apply(mutator: ChunkListStateMutator, updatedAt: number, seq: number, refresh?: boolean): void\n +updateAsSource(_path: string, _updatedAt: number): void\n +useChunks(): [Chunk[], ChunkListMutator]\n +useTags(): [string[], (tags: string[]) => Promise]\n +useChunk(chunk_arg: Chunk): [Chunk, ChunkMutator]\n}\nIViewModel ..> \"1\" IPageViewModel\nViewModel ..> \"1\" IPageViewModel\nChunkListHistory ..> \"1\" ChunkListStateMutator\nChunkListHistory ..> \"1\" ChunkListState\nDocumentViewModel ..> \"1\" ChunkListHistory\nDocumentViewModel ..> \"1\" ChunkListStateMutator\nDocumentViewModel ..> \"1\" ChunkListMutator\nDocumentViewModel ..> \"1\" ChunkMutator\n```\n```mermaid\nclassDiagram\nclass ChunkListStateMutator\n<> ChunkListStateMutator\nclass ChunkListStateAddMutator\nclass ChunkListStateDeleteMutator\nclass ChunkListStateModifyMutator\nclass ChunkListStateMoveMutator\nChunkListStateMutator <|.. ChunkListStateAddMutator\nChunkListStateMutator <|.. ChunkListStateDeleteMutator\nChunkListStateMutator <|.. ChunkListStateModifyMutator\nChunkListStateMutator <|.. ChunkListStateMoveMutator\n```\n```mermaid\nclassDiagram\nclass IRPCMessageManager {\n +opened: boolean\n +close(): void\n +sendNotification(notification: ChunkNotification): void\n +addEventListener(name: \"notification\", listener: RPCMessageMessagerEventListener): void\n +addEventListener(name: string, listener: EventListenerOrEventListenerObject): void\n +removeEventListener(name: \"notification\", listener: RPCMessageMessagerEventListener): void\n +removeEventListener(name: string, listener: EventListenerOrEventListenerObject): void\n +invokeMethod(m: RPCMessageBody): Promise\n}\n<> IRPCMessageManager\nIRPCMessageManager <|.. RPCMessageManager\nclass RPCMessageManager {\n -callbackList: Map\n -curId: number\n -ws?: WebSocket | undefined\n +opened: boolean\n +open(url: string | URL, protocals?: string | undefined): Promise\n +close(): void\n -genId(): number\n +genHeader(): Inline\n +send(message: RPCMethod): Promise\n +sendNotification(message: ChunkNotification): void\n +addEventListener(type: \"notification\", callback: RPCMessageMessagerEventListener): void\n +removeEventListener(type: \"notification\", callback: RPCMessageMessagerEventListener): void\n +invokeMethod(m: RPCMessageBody): Promise\n}\nclass FsDirEntry {\n +name: string\n +isDirectory: boolean\n +isFile: boolean\n +isSymlink: boolean\n}\n<> FsDirEntry\nclass FsStatInfo {\n +isFile: boolean\n +isDirectory: boolean\n +isSymlink: boolean\n +size: number\n +mtime: Date | null\n +atime: Date | null\n +birthtime: Date | null\n}\n<> FsStatInfo\nFsStatInfo <|.. FsGetResult\nclass FsGetResult {\n +entries?: FsDirEntry[] | undefined\n}\n<> FsGetResult\nclass IFsEventMap {\n +modify: (this: IFsManager, event: MessageEvent) => void\n +create: (this: IFsManager, event: MessageEvent) => void\n +delete: (this: IFsManager, event: MessageEvent) => void\n}\n<> IFsEventMap\nclass IFsManager {\n +get(path: string): Promise\n +getStat(path: string): Promise\n +upload(filePath: string, data: BodyInit): Promise\n +delete(filePath: string): Promise\n +mkdir(path: string): Promise\n +addEventListener(name: string, listener: EventListenerOrEventListenerObject): void\n +removeEventListener(name: string, listener: EventListenerOrEventListenerObject): void\n +dispatchEvent(event: Event): boolean\n}\n<> IFsManager\nIFsManager <|.. FsManager\nclass FsManager {\n -manager: RPCMessageManager\n -prefix: string\n +get(path: string): Promise\n +getStat(filePath: string): Promise\n +upload(filePath: string, data: BodyInit): Promise\n +mkdir(filePath: string): Promise\n +delete(filePath: string): Promise\n}\nFsGetResult ..> \"*\" FsDirEntry\nIFsEventMap ..> \"1\" IFsManager\nIFsManager ..> \"1\" FsGetResult\nFsManager ..> \"1\" RPCMessageManager\nFsManager ..> \"1\" FsGetResult\n```\n\n## 5.2 의사코드\n\n의사코드는 다음과 같이 진행된다.\n\n### 서버 RPC 메세지 처리\n\n클라이언트는 RPC를 진행하기 위해 웹소켓을 연결합니다. 웹소켓의 주소 `/ws`에 \n도달하기 위해서 먼저 라우팅이 진행이 됩니다. 라우팅은 `TreeRouter` 클래스에서\n 진행됩니다.\n```\nInput: req Request\nOutput: Response\nfn Route(req){\n for node in HandlerNodes {\n if(req.url prefix is matching){\n node.Handler[matching](req)\n }\n }\n response with 404 Not Found\n}\n``` \n마침내 엔드포인트에 도달하게 되면 웹소켓을 얻어내고 새로운 연결을 등록합니다.\n```\nInput: req Request\nOutput: Response\nfn RPCHandleEndpoint(req){\n ws, res = upgradeWebSocket(req)\n user = getUserInfo(req.header)\n conn = new Connection(ws, user) \n registerParticipant(conn)\n res\n}\n```\n이제부터 메세지를 받을 수 있습니다.\n메세지가 오면 이 함수가 실행이 됩니다.\n```\nInput: message on send\nOutput: response\nfn Connection.handleMessage(msg: string){\n data = JSON.parse(msg)\n check format of data\n dispatch(data.method, data, this)\n}\n```\n\n디스패치가 성공적으로 이루어지면 RPC 작업를 처리하는 함수에 도착합니다.\n청크 작업을 예로 들겠습니다. 청크 작업에서는 권한을 확인하고\n명령의 충돌을 히스토리를 비교하며 다시 적용하며 해결합니다.\n그리고 요구된 작업을 처리하고 문서의 `updatedAt`을 업데이트하고\n문서 업데이트 사실을 이 문서를 보고 있던 참여자에게 전파합니다.\n```\nInput: conn Connection\nInput: m RPCChunkMethod\nOutput: response\n\nfn ChunkOperation(conn, m){\n doc = DocStore.getDocument(m.docPath);\n updatedAt = m.updatedAt;\n action = getAction(m);\n if !conn.userpermissionSet.canDo(Action) {\n response with PermissionError;\n }\n \n appliedList = doc.history.filter(x => x.updatedAt > updatedAt)\n for m in appliedList {\n if action.checkConflict(m) {\n resolvedAction = action.tryResolveConflict(m)\n if resolvedAction == Fail {\n response with ConflictError\n }\n action = resolvedAction\n }\n }\n \n res = action.act(doc);\n doc.updateHistory(res);\n \n subscribers = doc.getSubscribers();\n subscribers.broadcastNotification(m, exclude = conn);\n response with res\n}\n```\n\n문서 작업도 마찬가지로 이루어집니다.\n\n### 클라이언트의 메세지 처리 동기화\n\n클라이언트에서는 다음과 같은 일이 일어납니다.\n\n먼저 `notification`을 받습니다. 그러면 모든 `DocumentViewModel`에게 이벤트를 전달합니다.\n그리고 각각의 `DocumentViewModel`은 자기 문서에 일어난 일인지 확인하고 `ChunkListMutator`를 만들어서\n문서에 적용합니다.\n```\nInput: e RPCNotification\nInput: this document view model\n\nfn updateOnNotification(this,notification){\n { docPath, method, seq, updatedAt } = notification.params;\n if (docPath !== this.docPath) return;\n mutator = ChunkListMutatorFactory.createFrom(method);\n this.apply(mutator, updatedAt, seq);\n}\n```\n\n문서의 레디큐에 mutator를 집어넣고 `seq` 번호가 기다리는 것이면 실행하고 업데이트합니다.\n\n```\nInput: mutator ChunkListMutator\nInput: updatedAt Date\nInput: seq number\nInput: this Document View Model\n\n//readyQueue is priority queue.\nfn apply(this, mutator, updatedAt, seq){\n this.readyQueue.push({mutator, updatedAt, seq})\n\n if(this.readyQueue.lenght < some limit){\n while(!readyQueue.empty() &&\n this.readyQueue.top().seq === this.seq + 1)\n {\n mutator, updatedAt, seq = this.readyQueue.pop();\n mutator(this.chunks);\n this.seq = seq;\n this.updatedAt = updatedAt; \n }\n this.dispatch(new Event(\"chunksChange\"));\n }\n else {\n document.refresh();\n }\n}\n```\n\n### 다른 작업들\n\n```\nmodule chunk {\n type mode = Read | Write\n\n struct Chunk {\n id: string\n content: string\n type: string\n }\n\n newChunk() {\n { id = uuid()\n ; content = \"\"\n ; type = \"\"\n }\n }\n\n chunkViewer(chunk : Chunk, focusedChunk : State, deleteThis : () => void) : Component {\n var mode = Read\n\n var c = new Component(\n content { value = chunk.content }\n settypebutton\n editbutton\n deletebutton\n )\n\n when mode becomes Read { chunk.content = content }\n when mode becomes Write { focusedChunk = chunk.id }\n when focusedChunk is changed { mode = Read }\n\n when editbutton is clicked { mode = (mode = Read) ? Write : Read }\n when deletebutton is clicked { deleteThis() }\n when settypebutton is clicked { chunk.type = prompt() }\n\n return c\n }\n}\n```\n\n```\nmodule search {\n searchWord(chunks, word) {\n return doc.chunks.concat_map((s) => s.matchAll(word))\n }\n\n searchWordPrompt(chunks: Chunk.chunk list) {\n var word = prompt()\n var results = searchWord(chunks, word)\n\n var c = new Component(results)\n\n when result in results is selected {\n moveto(result.location)\n close()\n }\n }\n\n when Ctrl-F is pressed { searchWordPrompt() }\n}\n```\n\n```\nmodule document {\n struct Document {\n title: string\n path: Path\n tags: string set\n chunks: chunk.Chunk array\n }\n\n documentViewer(doc: document) : Component {\n var focusedChunk = null\n\n var c = new Component(\n taglist { value: tags }\n chunklist\n )\n\n delete(id) {\n i = doc.chunks.find((c) => c.id = id)\n doc.chunks.remove(i)\n }\n\n chunklist = doc.chunks.concat_map((c, i) =>\n [ divider(i), chuknViewer(c, focusedChunk, () => delete(c.id)) ])\n\n when divider(i) clicked { doc.chunks.insert(i, c) }\n when chunkViewer(c) is dropped on divider(i) { doc.chunks.move(c, i) }\n\n return c\n }\n}\n```\n\n```\nmodule filelist {\n fileList(dir : Directory, open: (File) => void) : Component {\n var c = new Component(\n filelist\n )\n\n filelist = dir.files().map((f) => button(f))\n\n when button(f) is clicked {\n open(f)\n }\n\n return c\n }\n}\n```\n\n```\nmodule settings {\n settings() : Component {\n var c = new Component(\n language = select(\"korean\", \"english\")\n theme = select(\"light\", \"dark\")\n )\n\n when language(l) is selected {\n global context.lang = l\n }\n\n when theme(t) is selected {\n global context.theme = t\n }\n\n return c\n }\n}\n\n```\nmodule frontend {\n main() : Component {\n var docv\n\n var open = (f) => {\n with doc = openfile(f) {\n docv = document.documentViewer(doc)\n }\n }\n\n var filelist = filelist.fileList(rootdir, open)\n\n var c = new Component(\n document = docv\n filelist = filelist\n )\n\n return c\n }\n}\n```\n","number":[5],"sub_items":[],"path":"architecture.md","source_path":"architecture.md","parent_names":[]}},{"Chapter":{"name":"Testing","content":"# Testing\r\n\r\n## 유닛 테스트\r\n\r\n유닛 테스트로 69.6%의 Line Coverage와 73.4%의 Function Coverage를 달성했다.\r\n다음과 같은 로그가 있다.\r\n\r\n```\r\nrunning 2 tests from ./src/auth/permission.test.ts\r\npermission.test ... ok (8ms)\r\npermission empty ... ok (16ms)\r\nrunning 4 tests from ./src/auth/session.test.ts\r\nSession ...\r\n set ... ok (9ms)\r\n delete ... ok (16ms)\r\nok (42ms)\r\nLogin Handler ...\r\n login with invalid format ... ok (15ms)\r\n login with invalid password ... ok (16ms)\r\n login ... ok (16ms)\r\n logout with no session ... ok (16ms)\r\n logout ... ok (16ms)\r\nok (96ms)\r\ngetSession ... ok (16ms)\r\ngetSession with invalid cookie ... ok (16ms)\r\nrunning 1 test from ./src/auth/user.test.ts\r\nuser.createAdminUser ... ok (15ms)\r\nrunning 4 tests from ./src/document/filedoc.test.ts\r\nreadDocFile ... ok (19ms)\r\nreadDocFile: not found ... ok (16ms)\r\nreadDocFile: invalid json ... ok (16ms)\r\nsaveDocFile ... ok (15ms)\r\nrunning 3 tests from ./src/router/methodHandle.test.ts\r\nmethodHandle: basic methods ... ok (8ms)\r\nmethodHandle: not found ... ok (16ms)\r\nmethodHandle: options ... ok (16ms)\r\nrunning 8 tests from ./src/router/route.test.ts\r\nroute: basic route ... ok (10ms)\r\nroute: double slash route ... ok (16ms)\r\nroute: double match ... ok (16ms)\r\nroute: test context ... ok (16ms)\r\nroute: test regex ... ok (16ms)\r\nroute: test not found ... ok (16ms)\r\nroute: encode_route ... ok (2ms)\r\nroute: router in router ... ok (13ms)\r\nrunning 4 tests from ./src/rpc/chunk.test.ts\r\nbasic chunk operation ...\r\n create chunk ... ok (19ms)\r\n delete chunk ... ok (15ms)\r\n modify chunk ... ok (15ms)\r\n move chunk ... ok (15ms)\r\n invalid chunk operation ... ok (17ms)\r\nok (98ms)\r\ntest chunk notification operation ... ok (15ms)\r\ntest chunk conflict ... ok (16ms)\r\ntest chunk conflict resolve with history ... ok (32ms)\r\nrunning 2 tests from ./src/rpc/doc.test.ts\r\nhandleDocumentMethod ... ok (4ms)\r\nhandleTagMethod ...\r\n setTag ... ok (13ms)\r\n getTag ... ok (15ms)\r\n conflict ... ok (15ms)\r\nok (61ms)\r\nrunning 3 tests from ./src/rpc/share.test.ts\r\nhandleShareGetInfo ... ok (18ms)\r\nhandleShareDocMethod ... ok (15ms)\r\nhandleShareMethod with no existing share token ... ok (16ms)\r\nrunning 1 test from ./src/server.test.ts\r\nserver rpc test ... ok (1s)\r\nrunning 3 tests from ./src/setting.test.ts\r\nsetting: basic ... ok (35ms)\r\nsetting: default value ... ok (7ms)\r\nsetting: defered register ... ok (16ms)\r\ntest result: ok. 35 passed (15 steps); 0 failed; 0 ignored; 0 measured; 0 filtered out (2s)\r\n```\r\n\r\n\r\n## 기능 테스트\r\n\r\n### Chunk\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
IDContentProcedureTest DataP/F
1Focus/Unfocus1. 청크를 클릭한다.P
2remove1. 청크를 삭제하는 버튼을 클릭한다.P
3-1render - markdown1. 마크다운 청크 렌더링을 확인한다. # 제목 P
3-2render - latex1. LaTex 청크 렌더링을 확인한다. sum^n_{n=0}n = \\frac{n(n+1)}2$$ P
3-3render - link1. Image 청크 렌더링을 확인한다.http://picsum.photosP
4previews1. Katex 청크의 미리보기를 본다. sum^n_{n=0}n = \\frac{n(n+1)}2$$ F
10autocomplete1. Ctrl+Space를 눌러 자동완성을 시도한다.F
11swap positions1. 청크의 위치를 바꾼다.P
27-1edit1. 청크를 수정한다.P
27-2edit chunk conflict1. 청크를 수정모드에 들어간다.F
\r\n\r\n### Document\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
IDContentProcedureTest DataP/F
5view Chunk1. 문서를 열어 청크가 렌더링되는지 본다.test.sydP
7add/delete tag1. 문서에 태그를 추가한다.
2. 문서에 태그를 삭제한다.
AP
8Drag And Drop Upload,1. 텍스트를 드래그한다.P
\r\n\r\n### File\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
IDContentProcedureTest DataP/F
14create/delete/rename file1. 파일을 만든다.test.txtP
15upload/download files1. 파일을 업로드한다.test.txtP
18export document1. export 버튼을 누른다.F
\r\n\r\n### Search\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
IDContentProcedureTest DataP/F
16Document Search1. 검색버튼을 눌러 검색을 한다.chunkF
\r\n\r\n### Stash\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
IDContentProcedureTest DataP/F
17render1. 스태시가 그려지는지 확인한다P
19add1. 청크를 추가한다P
20remove1. 청크를 삭제한다P
21Drag and Drop to Document1. 청크로부터 문서로 청크를 옮긴다.P
\r\n\r\n### Management\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
IDContentProcedureTest DataP/F
22Login1. 비밀번호를 입력한다.adminF
24Localization1. 다른언어를 지원하는지 언어를 바꿔 확인한다F
\r\n","number":[6],"sub_items":[],"path":"testing.md","source_path":"testing.md","parent_names":[]}}],"__non_exhaustive":null}] \ No newline at end of file diff --git a/src/SUMMARY.md b/src/SUMMARY.md index fd1c59d..28a9a6b 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -1,5 +1,7 @@ # Summary +[Index](./index.md) + - [Introduction](./intro.md) - [Overall Description](./overall.md) - [Specific Requirement](./specific.md) diff --git a/src/index.md b/src/index.md new file mode 100644 index 0000000..b6b117b --- /dev/null +++ b/src/index.md @@ -0,0 +1,43 @@ +# 목차(Index) + +1. [Introduction](./intro.md) + 1. [목적(Purpose)](./intro.md#11-목적purpose) + 2. [범위(scope)](./intro.md#12-범위scope) + 3. [용어 및 약어 정의(Definitions, acronyms and abbreviations)](./intro.md#13-용어-및-약어-정의definitions-acronyms-and-abbreviations) + 4. [참고자료(References)](./intro.md#14-참고자료references) + 5. [개요(Overview)](./intro.md#15-개요overview) +2. [Overall Description](./overall.md) + 1. [제품 관점(Product perspective)](./overall.md#21-제품-관점product-perspective) + 1. [시스템 인터페이스(System interfaces)](./overall.md#211-시스템-인터페이스system-interfaces) + 2. [사용자 인터페이스(User interfaces)](./overall.md#212-사용자-인터페이스user-interfaces) + 3. [하드웨어 인터페이스(Hardware interfaces)](./overall.md#213-하드웨어-인터페이스hardware-interfaces) + 4. [소프트웨어 인터페이스(Software interfaces)](./overall.md#214-소프트웨어-인터페이스software-interfaces) + 5. [통신 인터페이스(Communications interfaces)](./overall.md#215-통신-인터페이스communications-interfaces) + 6. [메모리 제약사항(Memory constraints)](./overall.md#216-메모리-제약사항memory-constraints) + 7. [운영(Operations)](./overall.md#217-운영operations) + 8. [사이트 적용 요건(Site adaption requirements)](./overall.md#218-사이트-적용-요건site-adaption-requirements) + 2. [제품 기능(Product functions)](./overall.md#22-제품-기능product-functions) + <% + const table = it.table; + let index = 1; + for (const [c,issues] of table) { + const name = `${c} Operation`; + const href = `2.2.${index} ${c} Operation` + %><%= `${index++}. [${name}](./overall.md#${it.toHeadId(href)})\n ` %><%}%><%="\n"%> +3. [Specific Requirement](./specific.md) + 1. [외부 인터페이스 요구사항(External interface requirements)](./specific.md#31-외부-인터페이스-요구사항external-interface-requirements) + 2. [기능 요구사항(Functional requirements)](./specific.md#32-기능-요구사항functional-requirements) + <%= it.issues.map((i)=>`(#${i.number}) ${i.title}`).map( + x=>`* [${x}](./specific.md#${it.toHeadId(x)})` + ).join("\n ") %><%= "\n"%> + 3. [성능 요구사항(Performance requirements)](./specific.md#33-성능-요구사항performance-requirements) + 4. [논리적 데이터베이스 요구사항(Logical database requirements)](./specific.md#34-논리적-데이터베이스-요구사항logical-database-requirements) + 5. [설계 제약사항(Design constraints)](./specific.md#35-설계-제약사항design-constraints) + 1. [표준 준수(Standards compliance)](./specific.md#351-표준-준수standards-compliance) + 6. [소프트웨어 시스템 속성(Software system attributes)](./specific.md#36-소프트웨어-시스템-속성software-system-attributes) + 7. [상세 요구사항의 구성(Organizing the specific requirements)](./specific.md#37-상세-요구사항의-구성organizing-the-specific-requirements) + 1. [객체(Objects)](./specific.md#371-객체objects) + 2. [사용자 인터페이스 상세](./specific.md#372-사용자-인터페이스-상세) +4. [Supporting information](./support.md) +5. [Architecture](./architecture.md) +6. [Testing](./testing.md) \ No newline at end of file diff --git a/src/overall.md b/src/overall.md index b694a82..ebbff9d 100644 --- a/src/overall.md +++ b/src/overall.md @@ -45,17 +45,7 @@ 본 프로젝트의 결과물은 다음과 같은 기능을 수행한다. <% - const table = new Map(); - it.issues.forEach((x)=>{ - const category = x.title.split(":")[0]; - if(!category) return; - let c = table.get(category) - if(!c){ - c = []; - table.set(category,c); - } - c.push(x); - }) + const table = it.table; let index = 1; for (const [c,issues] of table) { %><%= `### 2.2.${index++} ${c} Operation\n\n` %><% diff --git a/tools/preprop.ts b/tools/preprop.ts index 22f3cfa..29224c3 100644 --- a/tools/preprop.ts +++ b/tools/preprop.ts @@ -62,6 +62,10 @@ async function getIssues(){ return issues; } +function toHeadId(name: string){ + return name.replaceAll(/[^A-Za-z\s0-9]/gi,"").toLocaleLowerCase().replaceAll(" ","-"); +} + async function main(args: string[]) { if (args.length > 1) { //log.info(`args: ${JSON.stringify(args)}`); @@ -70,6 +74,17 @@ async function main(args: string[]) { } } const issues = await getIssues(); + const table = new Map(); + issues.forEach((x)=>{ + const category = x.title.split(":")[0]; + if(!category) return; + let c = table.get(category) + if(!c){ + c = []; + table.set(category,c); + } + c.push(x); + }) log.info(`start`); const data = await readAll(Deno.stdin); @@ -78,7 +93,9 @@ async function main(args: string[]) { const [context, book] = JSON.parse(jsonText) as [any, Book]; book.sections.forEach(x=>{ x.Chapter.content = Eta.render(x.Chapter.content, { - issues: issues + issues: issues, + table: table, + toHeadId: toHeadId, }) as string; }) //Deno.stderr.writeSync(new TextEncoder().encode(`context: ${JSON.stringify(context)}\n`));