Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/infra/MastodonHttpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,8 +422,10 @@ export class MastodonHttpClient implements MastodonApi {
throw new Error("리액션은 미스키 계정에서만 사용할 수 있습니다.");
}

async fetchNoteState(account: Account, noteId: string): Promise<{ isFavourited: boolean; isReblogged: boolean; bookmarked: boolean }> {
// 마스토돈은 이미 Status 객체에 즐겨찾기, 리블로그, 북마크 상태가 포함되어 있음
async fetchNoteState(
account: Account,
noteId: string
): Promise<{ isFavourited: boolean; isReblogged: boolean; bookmarked: boolean }> {
const response = await fetch(`${account.instanceUrl}/api/v1/statuses/${noteId}`, {
headers: {
"Authorization": `Bearer ${account.accessToken}`
Expand Down
50 changes: 13 additions & 37 deletions src/infra/MisskeyHttpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,12 +421,23 @@ export class MisskeyHttpClient implements MastodonApi {
}

async favourite(account: Account, statusId: string): Promise<Status> {
await this.postSimple(account, "/api/notes/favorites/create", { noteId: statusId });
try {
await this.postSimple(account, "/api/notes/reactions/create", {
noteId: statusId,
reaction: DEFAULT_REACTION
});
} catch {
await this.postSimple(account, "/api/notes/favorites/create", { noteId: statusId });
}
return this.fetchNote(account, statusId);
}

async unfavourite(account: Account, statusId: string): Promise<Status> {
await this.postSimple(account, "/api/notes/favorites/delete", { noteId: statusId });
try {
await this.postSimple(account, "/api/notes/reactions/delete", { noteId: statusId });
} catch {
await this.postSimple(account, "/api/notes/favorites/delete", { noteId: statusId });
}
return this.fetchNote(account, statusId);
}

Expand Down Expand Up @@ -515,41 +526,6 @@ export class MisskeyHttpClient implements MastodonApi {
return data.map((item) => mapMisskeyStatusWithInstance(item, account.instanceUrl));
}

async fetchNoteState(account: Account, noteId: string): Promise<{ isFavourited: boolean; isReblogged: boolean; bookmarked: boolean }> {
const response = await fetch(`${normalizeInstanceUrl(account.instanceUrl)}/api/notes/state`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(buildBody(account, { noteId }))
});
if (!response.ok) {
throw new Error("게시물 상태를 불러오지 못했습니다.");
}
const data = (await response.json()) as Record<string, unknown>;
return {
isFavourited: Boolean(data.isFavorited ?? data.isFavourited ?? false),
isReblogged: Boolean(data.isRenoted ?? false),
bookmarked: Boolean(data.bookmarked ?? false)
};
}

async bookmark(_account: Account, _statusId: string): Promise<Status> {
throw new Error("북마크는 마스토돈 계정에서만 사용할 수 있습니다.");
}

async unbookmark(_account: Account, _statusId: string): Promise<Status> {
throw new Error("북마크는 마스토돈 계정에서만 사용할 수 있습니다.");
}

async fetchBookmarks(_account: Account, _limit?: number, _maxId?: string): Promise<Status[]> {
throw new Error("북마크는 마스토돈 계정에서만 사용할 수 있습니다.");
}

async fetchThreadContext(account: Account, statusId: string): Promise<ThreadContext> {
return this.fetchConversation(account, statusId);
}

private async fetchNotifications(
account: Account,
limit: number,
Expand Down
4 changes: 0 additions & 4 deletions src/infra/UnifiedApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,6 @@ export class UnifiedApiClient implements MastodonApi {
return this.getClient(account).unreblog(account, statusId);
}

fetchNoteState(account: Account, noteId: string) {
return this.getClient(account).fetchNoteState(account, noteId);
}

async fetchThreadContext(account: Account, statusId: string): Promise<ThreadContext> {
return this.getClient(account).fetchThreadContext(account, statusId);
}
Expand Down
5 changes: 4 additions & 1 deletion src/services/MastodonApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,8 @@ export interface MastodonApi {
unblockAccount(account: Account, accountId: string): Promise<AccountRelationship>;
fetchAccountStatuses(account: Account, accountId: string, limit: number, maxId?: string): Promise<Status[]>;
fetchThreadContext(account: Account, statusId: string): Promise<ThreadContext>;
fetchNoteState(account: Account, noteId: string): Promise<{ isFavourited: boolean; isReblogged: boolean; bookmarked: boolean }>;
fetchNoteState(
account: Account,
noteId: string
): Promise<{ isFavourited: boolean; isReblogged: boolean; bookmarked: boolean }>;
}
2 changes: 1 addition & 1 deletion src/ui/components/ProfileModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ export const ProfileModal = ({
? await api.unbookmark(account, target.id)
: await api.bookmark(account, target.id);
updateItem(updated);
showToast(isBookmarking ? "북마크했습니다." : "북마크를 취소했습니다.", { tone: "success" });
showToast(isBookmarking ? "북마크했습니다." : "북마크를 취소했습니다.");
} catch (error) {
setItemsError(error instanceof Error ? error.message : "북마크 처리에 실패했습니다.");
}
Expand Down
35 changes: 12 additions & 23 deletions src/ui/components/TimelineItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -366,11 +366,11 @@ export const TimelineItem = ({
const handleMenuToggle = useCallback(async () => {
const willOpen = !menuOpen;
setMenuOpen(willOpen);

if (willOpen && account && api && account.platform === "misskey") {
// 초기 상태를 null로 설정하여 비활성화 상태로 표시
setFavouriteState(null);

try {
const state = await api.fetchNoteState(account, displayStatus.id);
setFavouriteState(state.isFavourited);
Expand Down Expand Up @@ -1375,32 +1375,24 @@ export const TimelineItem = ({
<>
<div className="overlay-backdrop" aria-hidden="true" />
<div ref={menuRef} className="section-menu-panel status-menu-panel" role="menu">
{/* 미스키: 즐겨찾기만 사용 */}
{account?.platform === "misskey" && (
<button
<button
type="button"
disabled={favouriteState === null}
onClick={() => {
// 메뉴 즉시 닫기
setMenuOpen(false);

// 즐겨찾기 처리 비동기 실행
(async () => {

void (async () => {
try {
// 현재 상태에 따라 적절한 API 호출
let updatedStatus: Status;
if (favouriteState) {
updatedStatus = await api.unfavourite(account!, displayStatus.id);
await api.unfavourite(account, displayStatus.id);
} else {
updatedStatus = await api.favourite(account!, displayStatus.id);
await api.favourite(account, displayStatus.id);
}

// API 호출 후 최신 상태 다시 확인
const state = await api.fetchNoteState(account!, displayStatus.id);

const state = await api.fetchNoteState(account, displayStatus.id);
const newFavouriteState = state.isFavourited;
setFavouriteState(newFavouriteState);

// 토스트 메시지 표시
showToast(
newFavouriteState ? "즐겨찾기에 추가했습니다." : "즐겨찾기에서 해제했습니다.",
{ tone: "success" }
Expand All @@ -1412,14 +1404,12 @@ export const TimelineItem = ({
})();
}}
>
{favouriteState === null ? "로딩..." : (favouriteState ? "즐겨찾기 취소" : "즐겨찾기")}
{favouriteState === null ? "로딩..." : favouriteState ? "즐겨찾기 취소" : "즐겨찾기"}
</button>
)}

{/* 마스토돈: 북마크만 사용 */}
{account?.platform === "mastodon" && (
<button
type="button"
<button
type="button"
onClick={() => {
onToggleBookmark(displayStatus);
setMenuOpen(false);
Expand All @@ -1428,7 +1418,6 @@ export const TimelineItem = ({
{displayStatus.bookmarked ? "북마크 취소" : "북마크"}
</button>
)}

<button type="button" onClick={handleOpenOrigin} disabled={!originUrl}>
원본 서버에서 보기
</button>
Expand Down