From ecc5dc4806bc4db3f412ae9ab3c9238ba5970b1c Mon Sep 17 00:00:00 2001 From: Shin Hyuk Jun Date: Thu, 30 May 2024 15:18:44 +0900 Subject: [PATCH 01/10] =?UTF-8?q?=EC=9E=A0=EA=B8=88=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 특정 상황 예외 처리 --- .../src/Component/Page/utils/yjs/BlockLock.js | 148 ++++++++++-------- 1 file changed, 83 insertions(+), 65 deletions(-) diff --git a/frontend/src/Component/Page/utils/yjs/BlockLock.js b/frontend/src/Component/Page/utils/yjs/BlockLock.js index a3a0cb37..df8a7526 100644 --- a/frontend/src/Component/Page/utils/yjs/BlockLock.js +++ b/frontend/src/Component/Page/utils/yjs/BlockLock.js @@ -70,7 +70,8 @@ const BlockLock = forwardRef(({ ydocRef, editorRef }, ref) => { function removeIdFromParagraph(uuid) { const hoverDiv = document.querySelector(".hoverDiv"); - const hoverDivcurrentTop = parseFloat(window.getComputedStyle(hoverDiv).top); const view = editorRef.current.view; + const hoverDivcurrentTop = parseFloat(window.getComputedStyle(hoverDiv).top); + const view = editorRef.current.view; const { state, dispatch } = view; const { tr } = state; @@ -300,77 +301,94 @@ const BlockLock = forwardRef(({ ydocRef, editorRef }, ref) => { }; const toggleLineLock = async (guid) => { - const locker = yLineLocks.get(guid.toString()); - const myLockedBlockId = yUserLocks.get(nickname); + try { + const locker = yLineLocks.get(guid.toString()); + const myLockedBlockId = yUserLocks.get(nickname); - isLoggedIn(); - toastr.remove(); + isLoggedIn(); + toastr.remove(); - if (locker && locker !== nickname && myLockedBlockId && myLockedBlockId !== guid.toString()) { - const result = await baseSwal.fire({ - title: "🔓", - html: `기존에 설정한 잠금을 해제 후 요청을 보내시겠습니까?`, - }); - - if (result.isConfirmed) { - removeIdFromParagraph(myLockedBlockId); - yLineLocks.delete(myLockedBlockId); - yUserLocks.delete(nickname); - } else { - return; + if (locker && locker !== nickname && myLockedBlockId && myLockedBlockId !== guid.toString()) { + const result = await baseSwal.fire({ + title: "🔓", + html: `기존에 설정한 잠금을 해제 후 요청을 보내시겠습니까?`, + }); + + if (result.isConfirmed) { + removeIdFromParagraph(myLockedBlockId); + yLineLocks.delete(myLockedBlockId); + yUserLocks.delete(nickname); + } else { + return; + } } - } - if (!locker && myLockedBlockId && myLockedBlockId !== guid.toString()) { - const result = await baseSwal.fire({ - title: "최대 1개의 블록 잠금이 허용됩니다.", - text: "이전에 설정한 잠금을 해제하시겠습니까?", - icon: "warning", - }); + if (!locker && myLockedBlockId && myLockedBlockId !== guid.toString()) { + const result = await baseSwal.fire({ + title: "최대 1개의 블록 잠금이 허용됩니다.", + text: "이전에 설정한 잠금을 해제하시겠습니까?", + icon: "warning", + }); + + if (result.isConfirmed) { + selfUnlockBlock(nickname, myLockedBlockId) + } else { + return; + } + } - if (result.isConfirmed) { - selfUnlockBlock(nickname, myLockedBlockId) + if (locker) { + if (locker === nickname) { + selfUnlockBlock(locker, myLockedBlockId) + toastr.info(`편집 잠금이 해제되었습니다.`); + return; + } + if (yRequestUnLock.has(locker) && !yUnLockInfo.has(locker)) { + const requestor = yRequestUnLock.get(locker)?.requestor + const message = requestor === nickname ? `이전 요청을 처리 중입니다...` : `${requestor} 이(가) 잠금 해제 요청 중입니다.`; + toastr.warning(message); + } else { + const beforeRequest = await checkRequestUnLockTimer(locker); + if(beforeRequest) { + const result = await baseSwal.fire({ + title: "✉️", + html: `${locker} 에게 블록 잠금 해제를 요청합니다. +
+ 요청 만료 시간(초)을 설정해주세요.`, + input: "range", + inputAttributes: { + min: "15", + max: "30", + step: "5" + }, + inputValue: 15 + }); + if (result.isConfirmed) { + if (!yUserLocks.has(locker) || guid?.toString() !== yUserLocks.get(locker)?.toString()) { + toastr.remove(); + toastr.warning(`블록 잠금 정보가 변경되었습니다.
다시 시도하세요.`); + } else if (yRequestUnLock.has(locker)) { + const requestor = yRequestUnLock.get(locker)?.requestor; + toastr.remove(); + toastr.error(`${requestor} 이(가) 이미 요청했습니다.`); + } else { + yRequestUnLock.set(locker, { requestor: nickname, expirationTime: result.value }); + } + } + } + } } else { - return; - } - } - - if (locker) { - if (locker === nickname) { - selfUnlockBlock(locker, myLockedBlockId) - toastr.info(`편집 잠금이 해제되었습니다.`); - return; + yLineLocks.set(guid.toString(), nickname); + yUserLocks.set(nickname, guid.toString()); + addIdToParagraph(guid.toString()); + toastr.success(`블록 편집 잠금이 설정되었습니다.`); } - if (yRequestUnLock.has(locker) && !yUnLockInfo.has(locker)) { - const requestor = yRequestUnLock.get(locker)?.requestor - const message = requestor === nickname ? `이전 요청을 처리 중입니다...` : `${requestor} 이(가) 잠금 해제 요청 중입니다.`; - toastr.warning(message); - } else { - const beforeRequest = await checkRequestUnLockTimer(locker); - if(beforeRequest) { - const result = await baseSwal.fire({ - title: "✉️", - html: `${locker} 에게 블록 잠금 해제를 요청합니다. -
- 요청 만료 시간(초)을 설정해주세요.`, - input: "range", - inputAttributes: { - min: "15", - max: "30", - step: "5" - }, - inputValue: 15 - }); - if (result.isConfirmed) { - yRequestUnLock.set(locker, { requestor: nickname, expirationTime: result.value }); - } - } - } - } else { - yLineLocks.set(guid.toString(), nickname); - yUserLocks.set(nickname, guid.toString()); - addIdToParagraph(guid.toString()); - toastr.success(`블록 편집 잠금이 설정되었습니다.`); + } catch (error) { + const hoverDiv = document.querySelector(".hoverDiv"); + hoverDiv.style.visibility = "hidden"; + toastr.remove(); + toastr.warning(`다시 시도하세요.`); + console.error(error); } }; From 2b41e9dbec72dffdf026ecd72e0217d17d662da1 Mon Sep 17 00:00:00 2001 From: Shin Hyuk Jun Date: Fri, 31 May 2024 12:47:35 +0900 Subject: [PATCH 02/10] =?UTF-8?q?=EC=A0=91=EC=86=8D=20=EC=8B=9C=EC=97=90?= =?UTF-8?q?=20=EB=82=B4=EC=9A=A9=EC=9D=B4=20=EB=B0=80=EB=A6=AC=EB=8A=94=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/Component/Page/Page.js | 9 ++++++--- .../Page/utils/editor/plugin/generateBlockIdPlugin.js | 11 ++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/frontend/src/Component/Page/Page.js b/frontend/src/Component/Page/Page.js index a687bbc0..ce7c4c55 100644 --- a/frontend/src/Component/Page/Page.js +++ b/frontend/src/Component/Page/Page.js @@ -52,6 +52,7 @@ function Page() { const editorRef = useRef(null); const ydocRef = useRef(new Y.Doc()); const ydocProviderRef = useRef(null); + const yDocInitialized = useRef(null); const blockLikeRef = useRef(null); const blockLockRef = useRef(null); @@ -270,6 +271,7 @@ function Page() { if (!ydocRef.current) return; ydocRef.current = new Y.Doc(); + yDocInitialized.current = false; ydocProviderRef.current = new WebsocketProvider( // "wss://demos.yjs.dev/ws", // yjs 데모 서버 주소 // "ws://localhost:4000", @@ -297,12 +299,13 @@ function Page() { } else { if (isSynced) { handleUserConnection(); - ydocProviderRef.current.connect(); - } + ydocProviderRef.current.connect(); + } } // setisloaded(true); // 딜레이 없음 setTimeout(() => { setisloaded(true); + yDocInitialized.current = true; }, 300); // 딜레이 있음 }); @@ -575,7 +578,7 @@ function Page() { yUndoPlugin(), hoverButtonPlugin(blockLikeRef, blockLockRef), inlinePlaceholderPlugin(), - generateBlockIdPlugin(), + generateBlockIdPlugin({ yDocInitialized }), imagePlugin({ ...imageSettings, resizeCallback: (el, updateCallback) => { diff --git a/frontend/src/Component/Page/utils/editor/plugin/generateBlockIdPlugin.js b/frontend/src/Component/Page/utils/editor/plugin/generateBlockIdPlugin.js index d91680f7..0cc0cb1c 100644 --- a/frontend/src/Component/Page/utils/editor/plugin/generateBlockIdPlugin.js +++ b/frontend/src/Component/Page/utils/editor/plugin/generateBlockIdPlugin.js @@ -1,14 +1,19 @@ import { Plugin } from "prosemirror-state"; import { v4 as uuidv4 } from "uuid"; -export const generateBlockIdPlugin = (guidGenerator = uuidv4) => { - return new Plugin({ +export const generateBlockIdPlugin = ({ yDocInitialized, guidGenerator = uuidv4 }) => { + return new Plugin({ appendTransaction: (transactions, prevState, nextState) => { + // Yjs 문서가 초기화되지 않은 경우 동작하지 않도록 함 + if (!yDocInitialized.current) { + return null; + } + const tr = nextState.tr; let modified = false; const generatedIds = new Set(); const userId = localStorage.getItem('userId'); - + if (transactions.some(transaction => transaction.docChanged)) { const { paragraph, image } = nextState.schema.nodes; let prevNode = null; // 이전 노드를 추적하기 위한 변수 From 9b54e9802c59531540e75c497cc811925d6bb706 Mon Sep 17 00:00:00 2001 From: Shin Hyuk Jun Date: Fri, 31 May 2024 12:48:03 +0900 Subject: [PATCH 03/10] =?UTF-8?q?=EB=B8=94=EB=A1=9D=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이미지 노드 --- .../Component/Page/utils/editor/plugin/hoverButtonPlugin.js | 4 ++-- frontend/src/Component/Page/utils/yjs/BlockLock.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/Component/Page/utils/editor/plugin/hoverButtonPlugin.js b/frontend/src/Component/Page/utils/editor/plugin/hoverButtonPlugin.js index f0ef035d..ffbbc5d5 100644 --- a/frontend/src/Component/Page/utils/editor/plugin/hoverButtonPlugin.js +++ b/frontend/src/Component/Page/utils/editor/plugin/hoverButtonPlugin.js @@ -133,10 +133,10 @@ export function hoverButtonPlugin(blockLikeRef, blockLockRef) { // 새 노드 삽입 const newNode = state.schema.nodes.paragraph.create(); - tr = state.doc.content.size === $clickPos.end($clickPos.depth) ? tr.insert(insertPos - 1, newNode) : tr.insert(insertPos, newNode); + tr = state.doc.content.size === $clickPos.end($clickPos.depth) && !isImageNode ? tr.insert(insertPos - 1, newNode) : tr.insert(insertPos, newNode); // 삽입된 노드 내부에 커서 위치시키기 - const newPos = state.doc.content.size === $clickPos.end($clickPos.depth) ? insertPos : insertPos + 1; // 노드 삽입 후 새로운 위치 조정 + const newPos = state.doc.content.size === $clickPos.end($clickPos.depth) && !isImageNode ? insertPos : insertPos + 1; // 노드 삽입 후 새로운 위치 조정 tr = tr.setSelection(Selection.near(tr.doc.resolve(newPos))); // 트랜잭션 적용 diff --git a/frontend/src/Component/Page/utils/yjs/BlockLock.js b/frontend/src/Component/Page/utils/yjs/BlockLock.js index df8a7526..2db14fdc 100644 --- a/frontend/src/Component/Page/utils/yjs/BlockLock.js +++ b/frontend/src/Component/Page/utils/yjs/BlockLock.js @@ -345,7 +345,7 @@ const BlockLock = forwardRef(({ ydocRef, editorRef }, ref) => { } if (yRequestUnLock.has(locker) && !yUnLockInfo.has(locker)) { const requestor = yRequestUnLock.get(locker)?.requestor - const message = requestor === nickname ? `이전 요청을 처리 중입니다...` : `${requestor} 이(가) 잠금 해제 요청 중입니다.`; + const message = requestor === nickname ? `이전 요청을 처리 중입니다...` : `${requestor} 이(가) 잠금 해제 요청 중입니다.`; toastr.warning(message); } else { const beforeRequest = await checkRequestUnLockTimer(locker); @@ -366,11 +366,11 @@ const BlockLock = forwardRef(({ ydocRef, editorRef }, ref) => { if (result.isConfirmed) { if (!yUserLocks.has(locker) || guid?.toString() !== yUserLocks.get(locker)?.toString()) { toastr.remove(); - toastr.warning(`블록 잠금 정보가 변경되었습니다.
다시 시도하세요.`); + toastr.warning(`다시 시도하세요.
사유: 블록 잠금 정보가 변경됨`); } else if (yRequestUnLock.has(locker)) { const requestor = yRequestUnLock.get(locker)?.requestor; toastr.remove(); - toastr.error(`${requestor} 이(가) 이미 요청했습니다.`); + toastr.warning(`${requestor} 이(가) 이미 요청했습니다.`); } else { yRequestUnLock.set(locker, { requestor: nickname, expirationTime: result.value }); } From f6cd650f8a25fede99aa013835a8368283466f1b Mon Sep 17 00:00:00 2001 From: Shin Hyuk Jun Date: Fri, 31 May 2024 13:09:12 +0900 Subject: [PATCH 04/10] =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EB=A7=8C=EB=A3=8C?= =?UTF-8?q?=20=EC=8B=9C=EA=B0=84=20=EC=9E=85=EB=A0=A5=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Component/Page/utils/yjs/BlockLock.js | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/frontend/src/Component/Page/utils/yjs/BlockLock.js b/frontend/src/Component/Page/utils/yjs/BlockLock.js index 2db14fdc..97193c5f 100644 --- a/frontend/src/Component/Page/utils/yjs/BlockLock.js +++ b/frontend/src/Component/Page/utils/yjs/BlockLock.js @@ -23,8 +23,8 @@ const BlockLock = forwardRef(({ ydocRef, editorRef }, ref) => { const baseSwal = Swal.mixin({ showCancelButton: true, - confirmButtonColor: "#3085d6", - cancelButtonColor: "#d33", + confirmButtonColor: "#28a745", + cancelButtonColor: "#6c757d", confirmButtonText: '확인', cancelButtonText: '취소' }); @@ -147,7 +147,7 @@ const BlockLock = forwardRef(({ ydocRef, editorRef }, ref) => { let timerInterval; // 시간 차이를 밀리초 단위로 계산 (1분 = 60,000밀리초) if (timeDifference < expirationTime) { - const result = await baseSwal.fire({ html: `${locker} 이(가) 해당 블록의 잠금 해제 요청을 거절했습니다.`, + const result = await baseSwal.fire({ html: `${locker} 이(가) 해당 블록의 잠금 해제 요청을 거절했습니다.`, footer: `추가적인 요청은 초 후에 가능합니다.`, icon: "error", timer: expirationTime - timeDifference, @@ -185,7 +185,7 @@ const BlockLock = forwardRef(({ ydocRef, editorRef }, ref) => { if (unlockRequestor && myLockedBlockId && unlockRequestor !== nickname) { let timerInterval; let forcedModalClose = false; - const expirationTime = yRequestUnLock.get(nickname)?.expirationTime * 1000; + const expirationTime = 60000; // 요청 만료 시간(1분) const result = await baseSwal.fire({ html: `${unlockRequestor} 이(가) 블록 잠금 해제를 요청하였습니다.
최근 설정한 블록 잠금을 해제하시겠습니까? @@ -352,16 +352,7 @@ const BlockLock = forwardRef(({ ydocRef, editorRef }, ref) => { if(beforeRequest) { const result = await baseSwal.fire({ title: "✉️", - html: `${locker} 에게 블록 잠금 해제를 요청합니다. -
- 요청 만료 시간(초)을 설정해주세요.`, - input: "range", - inputAttributes: { - min: "15", - max: "30", - step: "5" - }, - inputValue: 15 + html: `${locker} 에게 블록 잠금 해제를 요청합니다.`, }); if (result.isConfirmed) { if (!yUserLocks.has(locker) || guid?.toString() !== yUserLocks.get(locker)?.toString()) { @@ -372,7 +363,7 @@ const BlockLock = forwardRef(({ ydocRef, editorRef }, ref) => { toastr.remove(); toastr.warning(`${requestor} 이(가) 이미 요청했습니다.`); } else { - yRequestUnLock.set(locker, { requestor: nickname, expirationTime: result.value }); + yRequestUnLock.set(locker, { requestor: nickname }); } } } From 7966ff25afb328da567b16dbb82266ea035bf87b Mon Sep 17 00:00:00 2001 From: Shin Hyuk Jun Date: Fri, 31 May 2024 13:43:27 +0900 Subject: [PATCH 05/10] =?UTF-8?q?=EC=9E=A0=EA=B8=88=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 잠금 요청이 있었던 블록에서 잠금 해제 후 3초동안 잠금 설정 불가 --- frontend/src/Component/Page/utils/yjs/BlockLock.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/frontend/src/Component/Page/utils/yjs/BlockLock.js b/frontend/src/Component/Page/utils/yjs/BlockLock.js index 97193c5f..f472c6b1 100644 --- a/frontend/src/Component/Page/utils/yjs/BlockLock.js +++ b/frontend/src/Component/Page/utils/yjs/BlockLock.js @@ -18,6 +18,7 @@ const BlockLock = forwardRef(({ ydocRef, editorRef }, ref) => { const yRequestUnLock = ydocRef.current.getMap('yRequestUnLock'); const yConnectedUserList = ydocRef.current.getMap('connectedUsers'); const yUnLockInfo = ydocRef.current.getMap('yUnLockInfo'); + const yRecentUnLockBlock = ydocRef.current.getArray('yRecentUnLockBlock'); const yResultUnLock = ydocRef.current.getMap('yResultUnLock'); const yReceivedMessage = ydocRef.current.getMap(`${nickname}_message`); @@ -123,7 +124,10 @@ const BlockLock = forwardRef(({ ydocRef, editorRef }, ref) => { const selfUnlockBlock = (locker, myLockedBlockId) => { const unlockRequestor = yRequestUnLock.get(locker)?.requestor; - if (yResultUnLock.get(`${unlockRequestor}`)?.result === "deny") yResultUnLock.set(`${unlockRequestor}`, { responser: locker, result: "lateAccept", unlockedBlockID: myLockedBlockId }); + if (yResultUnLock.get(`${unlockRequestor}`)?.result === "deny") { + yResultUnLock.set(`${unlockRequestor}`, { responser: locker, result: "lateAccept", unlockedBlockID: myLockedBlockId }); + yRecentUnLockBlock.push([`${myLockedBlockId}`]); + } removeYjsMapUnLockData(nickname); removeIdFromParagraph(myLockedBlockId); yLineLocks.delete(myLockedBlockId); @@ -132,9 +136,12 @@ const BlockLock = forwardRef(({ ydocRef, editorRef }, ref) => { const removeYjsMapUnLockData = (locker) => { const unlockRequestor = yRequestUnLock.get(locker)?.requestor; + const unlockedBlockID = yResultUnLock.get(unlockRequestor)?.unlockedBlockID; + const yRecentUnLockBlockIndex = yRecentUnLockBlock.toArray().indexOf(unlockedBlockID?.toString()) if (yResultUnLock.has(`${unlockRequestor}`)) yResultUnLock.delete(`${unlockRequestor}`); if (yRequestUnLock.has(locker)) yRequestUnLock.delete(locker); if (yUnLockInfo.has(locker)) yUnLockInfo.delete(locker); + if (yRecentUnLockBlockIndex !== -1) yRecentUnLockBlock.delete(`${yRecentUnLockBlockIndex}`, 1); }; const checkRequestUnLockTimer = async (locker) => { @@ -186,7 +193,7 @@ const BlockLock = forwardRef(({ ydocRef, editorRef }, ref) => { let timerInterval; let forcedModalClose = false; const expirationTime = 60000; // 요청 만료 시간(1분) - const result = await baseSwal.fire({ html: `${unlockRequestor} 이(가) 블록 잠금 해제를 요청하였습니다. + const result = await baseSwal.fire({ html: `${unlockRequestor} 이(가) 블록 잠금 해제를 요청하였습니다.
최근 설정한 블록 잠금을 해제하시겠습니까?

@@ -245,6 +252,7 @@ const BlockLock = forwardRef(({ ydocRef, editorRef }, ref) => { yUserLocks.delete(nickname); removeIdFromParagraph(myLockedBlockId); toastr.info(`편집 잠금이 해제되었습니다.`); + yRecentUnLockBlock.push([`${myLockedBlockId}`]); yResultUnLock.set(`${unlockRequestor}`, { responser: nickname, result: "accept", unlockedBlockID: myLockedBlockId }); } else if (forcedModalClose === false) { const yReceivedMessage = ydocRef.current.getMap(`${unlockRequestor}_message`); @@ -308,6 +316,8 @@ const BlockLock = forwardRef(({ ydocRef, editorRef }, ref) => { isLoggedIn(); toastr.remove(); + if (yRecentUnLockBlock.toArray().indexOf(guid.toString()) !== -1) { toastr.warning(`잠시 후 시도하세요.
사유: 블록 잠금 정보가 남아있음`); return; } + if (locker && locker !== nickname && myLockedBlockId && myLockedBlockId !== guid.toString()) { const result = await baseSwal.fire({ title: "🔓", From 3e066a56d0eae8357849d019172329417ba44882 Mon Sep 17 00:00:00 2001 From: Shin Hyuk Jun Date: Fri, 31 May 2024 15:22:46 +0900 Subject: [PATCH 06/10] =?UTF-8?q?30=EC=B4=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/Component/Page/utils/yjs/BlockLock.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Component/Page/utils/yjs/BlockLock.js b/frontend/src/Component/Page/utils/yjs/BlockLock.js index f472c6b1..ace21971 100644 --- a/frontend/src/Component/Page/utils/yjs/BlockLock.js +++ b/frontend/src/Component/Page/utils/yjs/BlockLock.js @@ -192,7 +192,7 @@ const BlockLock = forwardRef(({ ydocRef, editorRef }, ref) => { if (unlockRequestor && myLockedBlockId && unlockRequestor !== nickname) { let timerInterval; let forcedModalClose = false; - const expirationTime = 60000; // 요청 만료 시간(1분) + const expirationTime = 30000; // 요청 만료 시간(30초) const result = await baseSwal.fire({ html: `${unlockRequestor} 이(가) 블록 잠금 해제를 요청하였습니다.
최근 설정한 블록 잠금을 해제하시겠습니까? From cf2127c914f44604e9bc8cc2a5bad39661246a6e Mon Sep 17 00:00:00 2001 From: Shin Hyuk Jun Date: Sat, 1 Jun 2024 14:59:05 +0900 Subject: [PATCH 07/10] =?UTF-8?q?Chart.js=20=EB=9D=BC=EC=9D=B4=EB=B8=8C?= =?UTF-8?q?=EB=9F=AC=EB=A6=AC=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기여도 --- frontend/package-lock.json | 36 ++++++++++++++++++++++++++++++++++++ frontend/package.json | 3 +++ 2 files changed, 39 insertions(+) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 08111cd6..3d96bcd1 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,6 +14,8 @@ "@fortawesome/free-regular-svg-icons": "6.5.1", "@fortawesome/free-solid-svg-icons": "6.5.1", "@fortawesome/react-fontawesome": "0.2.0", + "chart.js": "^4.4.3", + "chartjs-plugin-datalabels": "^2.2.0", "http-proxy-middleware": "^2.0.6", "jquery": "^3.7.1", "prosemirror-example-setup": "^1.2.2", @@ -25,6 +27,7 @@ "prosemirror-utils": "^1.2.1-0", "prosemirror-view": "^1.33.1", "react": "17.0.2", + "react-chartjs-2": "^5.2.0", "react-dom": "17.0.2", "react-router-dom": "6.21.3", "react-scripts": "5.0.1", @@ -3346,6 +3349,11 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -5721,6 +5729,25 @@ "node": ">=10" } }, + "node_modules/chart.js": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.3.tgz", + "integrity": "sha512-qK1gkGSRYcJzqrrzdR6a+I0vQ4/R+SoODXyAjscQ/4mzuNzySaMCd+hyVxitSY1+L2fjPD1Gbn+ibNqRmwQeLw==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/chartjs-plugin-datalabels": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.2.0.tgz", + "integrity": "sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw==", + "peerDependencies": { + "chart.js": ">=3.0.0" + } + }, "node_modules/check-types": { "version": "11.2.3", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz", @@ -14827,6 +14854,15 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, + "node_modules/react-chartjs-2": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz", + "integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==", + "peerDependencies": { + "chart.js": "^4.1.1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 38ec32bf..b6c6f006 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,6 +9,8 @@ "@fortawesome/free-regular-svg-icons": "6.5.1", "@fortawesome/free-solid-svg-icons": "6.5.1", "@fortawesome/react-fontawesome": "0.2.0", + "chart.js": "^4.4.3", + "chartjs-plugin-datalabels": "^2.2.0", "http-proxy-middleware": "^2.0.6", "jquery": "^3.7.1", "prosemirror-example-setup": "^1.2.2", @@ -20,6 +22,7 @@ "prosemirror-utils": "^1.2.1-0", "prosemirror-view": "^1.33.1", "react": "17.0.2", + "react-chartjs-2": "^5.2.0", "react-dom": "17.0.2", "react-router-dom": "6.21.3", "react-scripts": "5.0.1", From 355b39de4c1336e0b6ea39caa1435bf4339a3561 Mon Sep 17 00:00:00 2001 From: Shin Hyuk Jun Date: Sat, 1 Jun 2024 16:35:40 +0900 Subject: [PATCH 08/10] =?UTF-8?q?=EB=B8=94=EB=A1=9D=20=EC=9E=A0=EA=B8=88?= =?UTF-8?q?=20=EC=A0=95=EB=B3=B4=20=EC=82=AD=EC=A0=9C=20=EC=8B=9C=EC=A0=90?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/Component/Page/utils/yjs/BlockLock.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Component/Page/utils/yjs/BlockLock.js b/frontend/src/Component/Page/utils/yjs/BlockLock.js index ace21971..70397469 100644 --- a/frontend/src/Component/Page/utils/yjs/BlockLock.js +++ b/frontend/src/Component/Page/utils/yjs/BlockLock.js @@ -379,6 +379,7 @@ const BlockLock = forwardRef(({ ydocRef, editorRef }, ref) => { } } } else { + removeYjsMapUnLockData(nickname); yLineLocks.set(guid.toString(), nickname); yUserLocks.set(nickname, guid.toString()); addIdToParagraph(guid.toString()); @@ -414,7 +415,6 @@ const BlockLock = forwardRef(({ ydocRef, editorRef }, ref) => { } }); }; - removeYjsMapUnLockData(nickname); window.addEventListener('popstate', handlePopState); yRequestUnLock.observe(checkRequestUnLockWrapper); yResultUnLock.observe(checkResultUnLockWrapper); From abf7e8c445b9bd00133ecc9790d8a549c561bfb3 Mon Sep 17 00:00:00 2001 From: Shin Hyuk Jun Date: Sun, 2 Jun 2024 19:51:27 +0900 Subject: [PATCH 09/10] =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EC=83=81=ED=83=9C=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit + p태그에 id 부여하는 코드 리팩토링 --- .../utils/editor/plugin/hoverButtonPlugin.js | 4 ---- .../src/Component/Page/utils/yjs/BlockLike.js | 5 +++- .../src/Component/Page/utils/yjs/BlockLock.js | 23 +++++++++++-------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/frontend/src/Component/Page/utils/editor/plugin/hoverButtonPlugin.js b/frontend/src/Component/Page/utils/editor/plugin/hoverButtonPlugin.js index ffbbc5d5..1f0a35ee 100644 --- a/frontend/src/Component/Page/utils/editor/plugin/hoverButtonPlugin.js +++ b/frontend/src/Component/Page/utils/editor/plugin/hoverButtonPlugin.js @@ -74,10 +74,6 @@ export function hoverButtonPlugin(blockLikeRef, blockLockRef) { toastr.warning("내용이 없는 블록입니다."); } else { await blockLikeRef.current.toggleLike(guid, liker, writer); - if (liker !== writer) { - this.classList.toggle("hoverButton_like"); - this.classList.toggle("hoverButton_like_fullRedHeart"); - } } } else { console.log('No UUID found for this node.'); diff --git a/frontend/src/Component/Page/utils/yjs/BlockLike.js b/frontend/src/Component/Page/utils/yjs/BlockLike.js index 3d0aa2de..71a08687 100644 --- a/frontend/src/Component/Page/utils/yjs/BlockLike.js +++ b/frontend/src/Component/Page/utils/yjs/BlockLike.js @@ -8,10 +8,11 @@ const BlockLike = forwardRef(({ ydocRef }, ref) => { const pathSegments = location.pathname.split('/').filter(Boolean); const organizationId = pathSegments[1]; const noteId = pathSegments[2]; + const hoverButton_like = document.querySelector(".hoverButton_like"); const userId = localStorage.getItem('userId'); const yLikeList = ydocRef.current.getMap(`yLikeList_${userId}`); - + const toggleLike = async (blockId, lover, heartReceiver) => { if (lover !== heartReceiver) { const currentLikeState = yLikeList.get(blockId); @@ -34,8 +35,10 @@ const BlockLike = forwardRef(({ ydocRef }, ref) => { if (response.ok) { if (responseData.includes("좋아요 성공!")) { toastr.success(responseData); + hoverButton_like.classList.replace('hoverButton_like', 'hoverButton_like_fullRedHeart'); } else { toastr.info(responseData); + hoverButton_like.classList.replace('hoverButton_like_fullRedHeart', 'hoverButton_like'); } } else { toastr.error(responseData); diff --git a/frontend/src/Component/Page/utils/yjs/BlockLock.js b/frontend/src/Component/Page/utils/yjs/BlockLock.js index 70397469..387c549b 100644 --- a/frontend/src/Component/Page/utils/yjs/BlockLock.js +++ b/frontend/src/Component/Page/utils/yjs/BlockLock.js @@ -43,10 +43,14 @@ const BlockLock = forwardRef(({ ydocRef, editorRef }, ref) => { } }; + function updateHoverDivPosition(hoverDiv, change) { + const hoverDivcurrentTop = parseFloat(window.getComputedStyle(hoverDiv)?.top || "0"); + const newTop = window.matchMedia("(max-width: 768px)").matches ? hoverDivcurrentTop + change : hoverDivcurrentTop + (change * 2); + hoverDiv.style.top = `${newTop}px`; + } + function addIdToParagraph(uuid) { const hoverDiv = document.querySelector(".hoverDiv"); - const hoverDivcurrentTop = parseFloat(window.getComputedStyle(hoverDiv).top); - const view = editorRef.current.view; const { state, dispatch } = view; const { tr } = state; @@ -63,15 +67,14 @@ const BlockLock = forwardRef(({ ydocRef, editorRef }, ref) => { if (paragraphNode) { const { node, pos } = paragraphNode; const paragraphWithId = node.type.create({ ...node.attrs, id: 'locked' }, node.content, node.marks); - dispatch(tr.replaceWith(pos, pos + node.nodeSize, paragraphWithId)); - view.updateState(state.apply(tr)); - hoverDiv.style.top = window.matchMedia("(max-width: 768px)").matches ? `${hoverDivcurrentTop + 2}px` : `${hoverDivcurrentTop + 4}px`; + tr.replaceWith(pos, pos + node.nodeSize, paragraphWithId); + dispatch(tr); + updateHoverDivPosition(hoverDiv, 2); } } function removeIdFromParagraph(uuid) { const hoverDiv = document.querySelector(".hoverDiv"); - const hoverDivcurrentTop = parseFloat(window.getComputedStyle(hoverDiv).top); const view = editorRef.current.view; const { state, dispatch } = view; const { tr } = state; @@ -90,12 +93,12 @@ const BlockLock = forwardRef(({ ydocRef, editorRef }, ref) => { if (!node.isText && !node.isInline) { const { id, ...attrsWithoutId } = node.attrs; const newAttrs = { ...attrsWithoutId, id: "non-locked" }; - dispatch(tr.setNodeMarkup(pos, null, newAttrs)); - view.updateState(state.apply(tr)); - hoverDiv.style.top = window.matchMedia("(max-width: 768px)").matches ? `${hoverDivcurrentTop - 2}px` : `${hoverDivcurrentTop - 4}px`; + tr.setNodeMarkup(pos, null, newAttrs); + dispatch(tr); + updateHoverDivPosition(hoverDiv, -2); } } - } + } // 특정 UUID로 노드를 찾아 해당 노드의 위치로 커서를 이동시키는 함수 function moveCursorToNodeWithUUID(uuid) { From 99537c961bd700f323d1eba530f8093fc92950b9 Mon Sep 17 00:00:00 2001 From: Shin Hyuk Jun Date: Tue, 4 Jun 2024 12:40:47 +0900 Subject: [PATCH 10/10] =?UTF-8?q?routes=20=EC=B6=94=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/frontend/src/App.js b/frontend/src/App.js index 564af9eb..81934b38 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -6,6 +6,7 @@ import SignupPage from "./Component/Auth/SignupPage"; import NotePage from "./Component/Note/NotePage"; import UserProfileEdit from "./Component/Auth/UserProfileEdit"; import Page from "./Component/Page/Page"; +import Contribution from "./Component/Contribution/ContributionPage"; import EmailTokenHandler from "./Component/Utils/EmailTokenHandler"; import { BrowserRouter, Routes, Route } from "react-router-dom"; @@ -15,16 +16,14 @@ export default function App() { } /> - } /> } /> } /> + } /> } /> - } /> - } /> } /> } /> - } /> - } /> + } /> + } />