diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ffcfdbf7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# dependencies +/node_modules +/package-lock.json +/.pnp +.pnp.js +/url.js +# intellij configs +.idea/ + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + + diff --git a/README.md b/README.md index 82b85e81..961ff3e9 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,35 @@ ## 필수 프로젝트 +글 단위를 Document라고 합니다. Document는 Document 여러개를 포함할 수 있습니다. +- [x] 화면 좌측에 Root Documents를 불러오는 API를 통해 root Documents를 렌더링합니다. +- [x] Root Documents를 클릭하면 오른쪽 편집기 영역에 해당 Docuement의 Content를 렌더링 합니다. +- [x] 해당 Root Document에 하위 Document가 있는 경우 , 해당 Document 아래에 트리 형태로 렌더링 합니다. +- [x] Document Tree에서 각 Document 우측에는 + 버튼이 있습니다. 해당 버튼을 클릭하면 , 클릭한 Document의 하위 Document로 새 Document를 생성하고 편집화면으로 넘깁니다. +- [x] 편집기에는 기본적으로 저장 버튼이 없습니다. Document Save API를 이용해 지속적으로 서버에 저장되도록 합니다. +- [x] History API를 이용해 SPA형태로 만듭니다. +- [x] 루트 URL 접속 시엔 별다른 편집기 선택이 안된 상태입니다. +- [x] /documents/{documentId} 로 접속시 , 해당 Document content를 불러와 편집기에 로딩합니다. +- [x] Header 버튼 클릭하면 정상적으로 새로운 Document 생성되는데 초기값 null 들어가는 현상 +- [x] '제목없음' ,'내용없음' 초기에 grey color , input 들어오면 색 변경 +- [x] putMethod 404 Error +- [x] Editor content 부분 textarea 값 변경 안되는 현상 +- [x] Editor content 부분 입력할때 textarea 2 개행 이상부터 안보이는 현상 +- [x] 상위 Document 삭제하면 하위 Document 같이 삭제가 되야 하는데 상위 Document 밑으로 이동 되는 현상 +- [x] 상위 Document 삭제하면 최하위 Document 삭제 안되는 현상 +- [x] 예외처리 작업, '/documents'-> documentsUrl 변경 +- [x] Editor content 부분 textarea 렌더링 했을때 2 개행 이상부터 잘려서 안나오는 현상 +- [x] Editor content 일정 개행 이상 넘어가면 title 안보이는 현상 +- [x] 버튼 이미지 넣어서 관리 -> 현재 text로 들어가 있음 +- [x] 편집기 하단에는 Documents의 하위 Document 링크를 렌더링하도록 생성 +- [ ] onDelete 리팩토링 필요 +- [ ] 화면 깜빡거리는 현상 +- [ ] 디바운스 적용 + - 프로젝트 기한 - 프로젝트 수행 기간 : 2022년 11월 8일(화) ~ 2022년 11월 16일(수) - 멘티 코드 리뷰 기간 : 2022년 11월 17일(목) ~ 2022년 11월 20일(일) - 멘토 코드 리뷰 기간 : 2022년 11월 17일(목) ~ 2022년 11월 22일(화) - 코드 리뷰 반영 기간 : 2022년 11월 23일(수) ~ 2022년 11월 25일(금) - 내용 - - **Day 17 [프로젝트] 노션 클로닝 요구사항** 확인 부탁드립니다. + - **Day 17 [프로젝트] 노션 클로닝 요구사항** 확인 부탁드립니다. \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 00000000..809daa56 --- /dev/null +++ b/index.html @@ -0,0 +1,19 @@ + + + + + Title + + + + +
+ + + + diff --git a/main.js b/main.js new file mode 100644 index 00000000..6dd485e7 --- /dev/null +++ b/main.js @@ -0,0 +1,5 @@ +import App from './src/App.js'; + +const app = document.querySelector('.app'); + +new App({ target: app }); diff --git a/package.json b/package.json new file mode 100644 index 00000000..7c902b95 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "fedc3-4_project_notion_vanillajs", + "version": "1.0.0", + "description": "- 프로젝트 기한\r - 프로젝트 수행 기간 : 2022년 11월 8일(화) ~ 2022년 11월 16일(수)\r - 멘티 코드 리뷰 기간 : 2022년 11월 17일(목) ~ 2022년 11월 20일(일)\r - 멘토 코드 리뷰 기간 : 2022년 11월 17일(목) ~ 2022년 11월 22일(화)\r - 코드 리뷰 반영 기간 : 2022년 11월 23일(수) ~ 2022년 11월 25일(금)\r - 내용\r - **Day 17 [프로젝트] 노션 클로닝 요구사항** 확인 부탁드립니다.", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/prgrms-fe-devcourse/FEDC3-4_Project_Notion_VanillaJS.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/prgrms-fe-devcourse/FEDC3-4_Project_Notion_VanillaJS/issues" + }, + "homepage": "https://github.com/prgrms-fe-devcourse/FEDC3-4_Project_Notion_VanillaJS#readme", + "devDependencies": { + "prettier": "2.7.1" + }, + "prettier": { + "singleQuote": true, + "semi": true, + "useTabs": false, + "tabWidth": 2, + "trailingComma": "all", + "printWidth": 100, + "bracketSpacing": true, + "arrowParens": "always" + } +} diff --git a/src/App.js b/src/App.js new file mode 100644 index 00000000..5ebfd03f --- /dev/null +++ b/src/App.js @@ -0,0 +1,34 @@ +import Navbar from './Navbar/Navbar.js'; +import { documentsUrl } from './utils/util.js'; +import { initRouter } from './utils/route.js'; +import Editor from './Editor/Editor.js'; + +function App({ target }) { + const navbarPage = new Navbar({ target }); + const postEditPage = new Editor({ + target, + initialState: { + title: '', + content: '', + }, + }); + + this.route = async () => { + const { pathname } = location; + + if (pathname === '/') { + target.innerHTML = ''; + navbarPage.setState(); + } else if (pathname.indexOf(`${documentsUrl}/`) === 0) { + const [, , postId] = pathname.split('/'); + + await navbarPage.setState(); + await postEditPage.setState({ postId }); + } + }; + + this.route(); + initRouter(() => this.route()); +} + +export default App; diff --git a/src/Editor/Editor.js b/src/Editor/Editor.js new file mode 100644 index 00000000..1ef50c99 --- /dev/null +++ b/src/Editor/Editor.js @@ -0,0 +1,75 @@ +import { createElement } from '../utils/dom.js'; +import { isNew, isObject } from '../utils/errorHandler.js'; +import { request } from '../utils/api.js'; +import { putContentMethod, putTitleMethod } from '../utils/optionsMethod.js'; +import EditorTitle from './EditorTitle.js'; +import EditorContent from './EditorContent.js'; +import { documentsUrl } from '../utils/util.js'; +import { route } from '../utils/route.js'; +import EditorSubContent from './EditorSubContent.js'; +import { ERROR_MESSAGE } from '../utils/constant.js'; + +function Editor({ target, initialState }) { + isNew(new.target); + const page = createElement('section'); + page.className = 'content'; + + const editor = createElement('div'); + editor.className = 'editor'; + + this.state = initialState; + + const postTitle = new EditorTitle({ + div: editor, + initialState: initialState, + + onChangeTitle: async ({ id, title }) => { + setTimeout(await putTitleMethod(id, title), 2000); + }, + }); + + const postContent = new EditorContent({ + div: editor, + initialState: initialState, + + onChangeContent: async ({ id, content }) => { + setTimeout(await putContentMethod(id, content), 2000); + }, + }); + + const postSub = new EditorSubContent({ + div: editor, + initialState: initialState, + onClickSubDocument: async (id) => { + await changeUrl(id); + }, + }); + + this.setState = async (nextState) => { + const post = await request(`${documentsUrl}/${nextState.postId}`); + isObject(post); + postTitle.setState(post); + postContent.setState(post); + postSub.setState(post); + render(); + }; + + const render = () => { + page.appendChild(editor); + target.appendChild(page); + }; + + const changeUrl = async (id) => { + try { + const post = await request(`${documentsUrl}/${id}`); + route(`${documentsUrl}/${post.id}`); + } catch (e) { + alert(ERROR_MESSAGE.EMPTY); + route('/'); + } + }; + + render(); +} + +export default Editor; diff --git a/src/Editor/EditorContent.js b/src/Editor/EditorContent.js new file mode 100644 index 00000000..1b5a36e4 --- /dev/null +++ b/src/Editor/EditorContent.js @@ -0,0 +1,47 @@ +import { createElement } from '../utils/dom.js'; +import { isNew, isString } from '../utils/errorHandler.js'; + +function EditorContent({ div, initialState, onChangeContent }) { + isNew(new.target); + const editorContent = createElement('div'); + editorContent.className = 'editor-content'; + + this.state = initialState; + + this.setState = (nextState) => { + this.state = nextState; + const { content } = this.state; + + render(); + editorContent.querySelector('.content').value = content && content; + }; + + const render = () => { + const { content } = this.state; + editorContent.innerHTML = ``; + + div.appendChild(editorContent); + }; + + const onKeyUpContent = () => { + editorContent.addEventListener('keyup', (e) => { + const { name } = e.target; + const contentValue = e.target.value; + isString(contentValue); + const nextState = { + ...this.state, + [name]: contentValue, + }; + + onChangeContent(nextState); + }); + }; + + render(); + onKeyUpContent(); +} + +export default EditorContent; diff --git a/src/Editor/EditorSubContent.js b/src/Editor/EditorSubContent.js new file mode 100644 index 00000000..f37921ab --- /dev/null +++ b/src/Editor/EditorSubContent.js @@ -0,0 +1,49 @@ +import { createElement, targetClosest } from '../utils/dom.js'; + +function EditorSubContent({ div, initialState, onClickSubDocument }) { + const editorDocuments = createElement('div'); + editorDocuments.className = 'editor-sub-document'; + + this.state = initialState; + + this.setState = (nextState) => { + this.state = nextState; + render(); + }; + + const render = () => { + div.appendChild(editorDocuments); + + const { documents } = this.state; + editorDocuments.innerHTML = paintSubDocuments(documents); + }; + + const paintSubDocuments = (documents) => { + if (documents === undefined) { + return false; + } else { + return documents + .map( + ({ id, title }) => ` + +
+ + ${title ? title : '제목없음'}
+ `, + ) + .join(''); + } + }; + + const onClickEditorSubDocument = () => { + editorDocuments.addEventListener('click', (e) => { + const subPostId = targetClosest(e, 'div').id; + onClickSubDocument(subPostId); + }); + }; + + render(); + onClickEditorSubDocument(); +} + +export default EditorSubContent; diff --git a/src/Editor/EditorTitle.js b/src/Editor/EditorTitle.js new file mode 100644 index 00000000..44f58ef2 --- /dev/null +++ b/src/Editor/EditorTitle.js @@ -0,0 +1,44 @@ +import { createElement } from '../utils/dom.js'; +import { isNew, isString } from '../utils/errorHandler.js'; + +function EditorTitle({ div, initialState, onChangeTitle }) { + isNew(new.target); + const editorTitle = createElement('div'); + editorTitle.className = 'editor-title'; + this.state = initialState; + + div.appendChild(editorTitle); + + this.setState = (nextState) => { + this.state = nextState; + const { title } = this.state; + render(); + editorTitle.querySelector('.title').value = title && title; + }; + + const render = () => { + const { title } = this.state; + editorTitle.innerHTML = ``; + div.appendChild(editorTitle); + }; + + const onKeyupEditorTitle = () => { + editorTitle.addEventListener('keyup', (e) => { + const { name } = e.target; + const titleValue = e.target.value; + + isString(titleValue); + + const nextState = { + ...this.state, + [name]: titleValue, + }; + + onChangeTitle(nextState); + }); + }; + render(); + onKeyupEditorTitle(); +} + +export default EditorTitle; diff --git a/src/Navbar/Navbar.js b/src/Navbar/Navbar.js new file mode 100644 index 00000000..164ee965 --- /dev/null +++ b/src/Navbar/Navbar.js @@ -0,0 +1,87 @@ +import { isNew } from '../utils/errorHandler.js'; +import { createElement } from '../utils/dom.js'; +import { request } from '../utils/api.js'; +import { documentsUrl } from '../utils/util.js'; +import { addMethod, deleteMethod } from '../utils/optionsMethod.js'; +import { route } from '../utils/route.js'; +import NavbarDocumentList from './NavbarDocumentList.js'; +import NavbarHeader from './NavbarHeader.js'; + +function Navbar({ target }) { + isNew(new.target); + const nav = createElement('nav'); + nav.className = 'sidebar'; + + new NavbarHeader({ + target: nav, + initialState: { + text: 'Notion', + }, + }); + + const documentList = new NavbarDocumentList({ + target: nav, + initialState: [], + onDelete: async (id) => { + const findCurrentId = () => { + const rootDocument = [...documentList.state]; + + while (rootDocument.length > 0) { + const cur = rootDocument.shift(); + + if (parseInt(cur.id) === parseInt(id)) { + console.log(cur); + return cur; + } + + cur.documents.forEach((el) => { + rootDocument.push(el); + }); + } + }; + // 현재 하위 document 가 3개 이상이면 하위 document 값이 삭제가 안되고 있음 + // 하위 document 2개씩만 삭제되는 현상 + + const handlerDelete = async () => { + const currentDocument = findCurrentId(); + + let que = [currentDocument]; + + const queId = [currentDocument.id]; + + while (que.length > 0) { + const current = que.shift(); + current.documents.forEach((el) => { + que.push(el); + queId.push(el.id); + }); + + for (let i of queId) { + await deleteMethod(i); + que = []; + } + } + }; + + await handlerDelete(); + route('/'); + }, + + onAdd: async (id) => { + const newPost = await addMethod(id); + route(`${documentsUrl}/${newPost.id}`); + }, + }); + + this.setState = async () => { + const posts = await request(`${documentsUrl}`); + documentList.setState(posts); + this.render(); + }; + + this.render = async () => { + target.appendChild(nav); + }; +} + +export default Navbar; diff --git a/src/Navbar/NavbarDocumentList.js b/src/Navbar/NavbarDocumentList.js new file mode 100644 index 00000000..58133906 --- /dev/null +++ b/src/Navbar/NavbarDocumentList.js @@ -0,0 +1,72 @@ +import { createElement, targetClosest, targetContains } from '../utils/dom.js'; +import { isNew } from '../utils/errorHandler.js'; +import { route } from '../utils/route.js'; +import { addBtnElement, deleteBtnElement, documentsUrl } from '../utils/util.js'; + +function NavbarDocumentList({ target, initialState, onDelete, onAdd }) { + isNew(new.target); + const postList = createElement('div'); + + target.appendChild(postList); + + this.state = initialState; + + this.setState = (nextState) => { + this.state = nextState; + render(); + }; + + const render = () => { + postList.innerHTML = paintDocument(this.state); + onClickDocument(); + }; + + const paintDocument = (docuements) => { + return ` + + `; + }; + + const onClickDocument = () => { + const ul = postList.querySelector('.document-list'); + + ul.addEventListener('click', (e) => { + const postId = targetClosest(e, 'li').id; + + if (targetContains(e, deleteBtnElement)) { + onDelete(postId); + } else if (targetContains(e, addBtnElement)) { + onAdd(postId); + } else { + route(`${documentsUrl}/${postId}`); + } + }); + }; + render(); +} + +export default NavbarDocumentList; diff --git a/src/Navbar/NavbarHeader.js b/src/Navbar/NavbarHeader.js new file mode 100644 index 00000000..322f0024 --- /dev/null +++ b/src/Navbar/NavbarHeader.js @@ -0,0 +1,38 @@ +import { createElement, targetClosest } from '../utils/dom.js'; +import { isNew } from '../utils/errorHandler.js'; +import { route } from '../utils/route.js'; +import { documentsUrl } from '../utils/util.js'; +import { addHeaderMethod } from '../utils/optionsMethod.js'; + +function NavbarHeader({ target, initialState }) { + isNew(new.target); + + const header = createElement('div'); + header.className = 'header'; + this.state = initialState; + + const render = () => { + header.innerHTML = ` + ${this.state.text} + + + `; + target.appendChild(header); + }; + + const onClickHeader = () => { + header.addEventListener('click', async (e) => { + const homeBtn = targetClosest(e, '.home-btn'); + if (homeBtn) { + route('/'); + } else { + const createPost = await addHeaderMethod(documentsUrl); + route(`${documentsUrl}/${createPost.id}`); + } + }); + }; + render(); + onClickHeader(); +} + +export default NavbarHeader; diff --git a/src/style/index.css b/src/style/index.css new file mode 100644 index 00000000..480be69d --- /dev/null +++ b/src/style/index.css @@ -0,0 +1,5 @@ +@import 'ui/index.css'; +html { + visibility: visible; + opacity: 1; +} diff --git a/src/style/ui/index.css b/src/style/ui/index.css new file mode 100644 index 00000000..d8354840 --- /dev/null +++ b/src/style/ui/index.css @@ -0,0 +1,3 @@ +@import 'modules/navbar.css'; +@import 'modules/editor.css'; +@import 'modules/reset.css'; diff --git a/src/style/ui/modules/editor.css b/src/style/ui/modules/editor.css new file mode 100644 index 00000000..ae730cd4 --- /dev/null +++ b/src/style/ui/modules/editor.css @@ -0,0 +1,45 @@ +.app { + display: flex; + height: 100vh; +} + +.content { + width: 100%; + height: 70%; + border: none; + outline: none; + overflow: auto; + font-size: 20px; + display: flex; + justify-content: space-around; + margin-top: 5%; + /*-ms-overflow-style: none;*/ +} + +.content ::-webkit-scrollbar { + display: none; + height: 100em; +} + +.editor { + width: 60%; +} + +.title { + width: 100%; + display: block; + border: none; + outline: none; + font-size: 38px; +} + +.add { + width: 25px; +} + +.editor-content { + height: 40vh; +} + +.editor-sub-document { +} diff --git a/src/style/ui/modules/navbar.css b/src/style/ui/modules/navbar.css new file mode 100644 index 00000000..86eb5868 --- /dev/null +++ b/src/style/ui/modules/navbar.css @@ -0,0 +1,82 @@ +.header { + margin-bottom: 30px; +} + +.document-list { + padding-left: 10px; +} + +.list-style-type { + list-style-type: none; +} + +.sidebar { + width: 20%; + font-size: 15px; + line-height: 1rem; + background-color: rgb(245, 244, 241); + padding: 1em; +} + +.row-item { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 5px; + min-height: 27px; + color: #cccbc7; + cursor: pointer; + padding-left: 10px; +} + +.row-item:hover { + background: rgba(55, 53, 47, 0.08); + color: black; +} + +.item-text { + display: flex; + width: 75%; + align-items: center; + margin-left: 10px; +} + +.item-button { + display: flex; + flex-direction: row-reverse; + /*align-items: center;*/ + width: inherit; + /*padding-left: 20px;*/ +} + +button { + border: none; + outline: none; + padding: 0; + background: none; + margin-left: 30px; + cursor: pointer; +} + +button:hover { + border: solid 1px #aca99e; + border-radius: 5px; + color: black; +} + +.delete-btn { + font-size: 18px; +} + +.add-btn { + font-size: 18px; +} + +.home-btn { + position: absolute; + left: 13%; +} +.home-plus-btn { + position: absolute; + left: 3rem; +} diff --git a/src/style/ui/modules/reset.css b/src/style/ui/modules/reset.css new file mode 100644 index 00000000..e44f2d21 --- /dev/null +++ b/src/style/ui/modules/reset.css @@ -0,0 +1,136 @@ +/* Reset Browser */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} + +/* HTML5 display-role reset for browsers */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} +body { + line-height: 1; +} +ol, +ul { + list-style: none; +} +blockquote, +q { + quotes: none; +} +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} +input:focus { + outline: none; +} +a { + color: inherit; + text-decoration: none; +} diff --git a/src/utils/api.js b/src/utils/api.js new file mode 100644 index 00000000..754b0dba --- /dev/null +++ b/src/utils/api.js @@ -0,0 +1,19 @@ +import { API_END_POINT } from '../../url.js'; + +export const request = async (url, option = {}) => { + try { + const res = await fetch(`${API_END_POINT}${url}`, { + ...option, + headers: { + 'x-username': 'hwa', + 'Content-Type': 'application/json', + }, + }); + + if (res.ok) { + return await res.json(); + } + } catch (e) { + alert(e.message); + } +}; diff --git a/src/utils/constant.js b/src/utils/constant.js new file mode 100644 index 00000000..865b3b78 --- /dev/null +++ b/src/utils/constant.js @@ -0,0 +1,6 @@ +export const ERROR_MESSAGE = { + NEW: 'NEW 연산자가 없습니다.', + OBJECT: '객체 타입이 아닙니다.', + STRING: '문자열 타입이 아닙니다.', + EMPTY: '해당 문서를 찾을 수 없습니다', +}; diff --git a/src/utils/dom.js b/src/utils/dom.js new file mode 100644 index 00000000..337f5ea9 --- /dev/null +++ b/src/utils/dom.js @@ -0,0 +1,3 @@ +export const createElement = (e) => document.createElement(e); +export const targetClosest = (e, str) => e.target.closest(str); +export const targetContains = (e, str) => e.target.classList.contains(str); diff --git a/src/utils/errorHandler.js b/src/utils/errorHandler.js new file mode 100644 index 00000000..23fc757b --- /dev/null +++ b/src/utils/errorHandler.js @@ -0,0 +1,19 @@ +import { ERROR_MESSAGE } from './constant.js'; + +export const isNew = (target) => { + if (!target) { + throw new Error(ERROR_MESSAGE.NEW); + } +}; + +export const isString = (title) => { + if (typeof title !== 'string') { + throw new Error(ERROR_MESSAGE.STRING); + } +}; + +export const isObject = (post) => { + if (!post instanceof Object) { + throw new Error(ERROR_MESSAGE.OBJECT); + } +}; diff --git a/src/utils/optionsMethod.js b/src/utils/optionsMethod.js new file mode 100644 index 00000000..adc59edb --- /dev/null +++ b/src/utils/optionsMethod.js @@ -0,0 +1,46 @@ +import { request } from './api.js'; +import { documentsUrl } from './util.js'; + +export const putTitleMethod = (id, title) => { + return request(`${documentsUrl}/${id}`, { + method: 'PUT', + body: JSON.stringify({ + title: title, + }), + }); +}; + +export const putContentMethod = (id, content) => { + return request(`${documentsUrl}/${id}`, { + method: 'PUT', + body: JSON.stringify({ + content: content, + }), + }); +}; + +export const addMethod = (id) => { + return request(`${documentsUrl}`, { + method: 'POST', + body: JSON.stringify({ + title: '', + parent: id, + }), + }); +}; + +export const addHeaderMethod = (documentsUrl) => { + return request(`${documentsUrl}`, { + method: 'POST', + boyd: JSON.stringify({ + title: '', + parent: null, + }), + }); +}; + +export const deleteMethod = (id) => { + return request(`${documentsUrl}/${id}`, { + method: 'DELETE', + }); +}; diff --git a/src/utils/route.js b/src/utils/route.js new file mode 100644 index 00000000..2a92ccb7 --- /dev/null +++ b/src/utils/route.js @@ -0,0 +1,24 @@ +import { ROUTE_CHANGE_NAME } from './util.js'; + +export const initRouter = (onRoute) => { + window.addEventListener(ROUTE_CHANGE_NAME, (e) => { + const { nextUrl } = e.detail; + + if (nextUrl) { + history.pushState(null, null, nextUrl); + onRoute(); + } + }); +}; + +export const route = (nextUrl) => { + if (nextUrl) { + window.dispatchEvent( + new CustomEvent(ROUTE_CHANGE_NAME, { + detail: { + nextUrl, + }, + }), + ); + } +}; diff --git a/src/utils/util.js b/src/utils/util.js new file mode 100644 index 00000000..0415ff6e --- /dev/null +++ b/src/utils/util.js @@ -0,0 +1,4 @@ +export const documentsUrl = '/documents'; +export const ROUTE_CHANGE_NAME = 'route-change'; +export const deleteBtnElement = 'fa-minus-square'; +export const addBtnElement = 'fa-plus-square';