Skip to content

Commit 6ab4631

Browse files
committed
Merge branch 'dev'
2 parents 254bb6d + 2194e14 commit 6ab4631

8 files changed

Lines changed: 75 additions & 79 deletions

File tree

.github/workflows/deploy-vercel.yml

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,36 +29,8 @@ jobs:
2929
--scope "$VERCEL_ORG_ID" \
3030
--project "$VERCEL_PROJECT_ID"
3131
32-
- name: Deploy & Redeploy on Failure
33-
shell: bash
32+
- name: Deploy to Production
3433
run: |
35-
set +e
36-
37-
# 1) Initial production deploy (capture logs)
3834
npx vercel --prod --yes \
3935
--token "$VERCEL_TOKEN" \
40-
--scope "$VERCEL_ORG_ID" > deploy.log 2>&1
41-
EXIT_CODE=$?
42-
cat deploy.log
43-
44-
# Extract deployment URL
45-
DEPLOY_URL=$(grep -Eo 'https?://[A-Za-z0-9.-]+\.vercel\.app' deploy.log | head -n1)
46-
47-
# 2) Check if failure is due to @repo/types module error and redeploy if so
48-
if [ $EXIT_CODE -ne 0 ]; then
49-
if grep -q "Cannot find module '@repo/types'" deploy.log; then
50-
echo "❗️ Initial deployment failed due to @repo/types module error. Redeploying..."
51-
npx vercel redeploy "$DEPLOY_URL" \
52-
--token "$VERCEL_TOKEN" \
53-
--scope "$VERCEL_ORG_ID"
54-
REDO_EXIT=$?
55-
if [ $REDO_EXIT -ne 0 ]; then
56-
echo "::error::Redeploy failed (exit code=$REDO_EXIT)!"
57-
exit 1
58-
fi
59-
else
60-
echo "❌ Deployment failed but not due to @repo/types module error. Exiting..."
61-
echo "::error::Deployment failed with exit code $EXIT_CODE"
62-
exit 1
63-
fi
64-
fi
36+
--scope "$VERCEL_ORG_ID"

apps/extension/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"background": {
1414
"service_worker": "service-worker.js"
1515
},
16-
"host_permissions": ["https://linkyboard.nebula-ai.kr/*", "http://localhost:3000/*"],
16+
"host_permissions": ["https://api.linkyboard.com/*", "http://localhost:3000/*"],
1717
"icons": {
1818
"128": "public/icon128.png"
1919
}

apps/extension/src/pages/search-content.tsx

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,32 @@ import {
2323
useQuickSaveYoutubeContent,
2424
} from "../lib/tanstack/mutation/content";
2525

26-
const getHtmlContent = async () => {
26+
const createHtmlFile = (htmlContent: string) => {
27+
const htmlBlob = new Blob([htmlContent], { type: "text/html" });
28+
return new File([htmlBlob], "content.html", { type: "text/html" });
29+
};
30+
31+
// 현재 탭의 HTML과 썸네일 메타 정보를 읽어 저장에 필요한 파일과 데이터를 만든다.
32+
const getHtmlPayload = async () => {
2733
const html = await getHtmlText();
2834
const htmlContent = html.split("------MultipartBoundary--")[1];
2935

3036
if (!htmlContent) {
3137
errorToast("잘못된 페이지에요. 다시 시도해주세요.");
32-
return { htmlContent: "", thumbnail: "" };
38+
return null;
3339
}
3440

3541
const thumbnail = extractMetaContent(htmlContent, "og:image");
3642

37-
if (thumbnail === null) {
43+
if (!thumbnail) {
3844
errorToast("썸네일 추출 중 오류가 발생했어요. 다시 시도해주세요.");
39-
return { htmlContent: "", thumbnail: "" };
45+
return null;
4046
}
4147

42-
return { htmlContent, thumbnail };
48+
return {
49+
htmlFile: createHtmlFile(htmlContent),
50+
thumbnail,
51+
};
4352
};
4453

4554
function LogoutDialogContent() {
@@ -74,6 +83,7 @@ function LogoutDialogContent() {
7483
);
7584
}
7685

86+
// 현재 탭을 빠르게 저장하거나 요약 생성 플로우로 이어주는 북마크 저장 페이지.
7787
export default function SearchContent() {
7888
const { currentTab, isFindingExistPath } = useTabStore();
7989

@@ -92,7 +102,7 @@ export default function SearchContent() {
92102
isPending: isPendingDetailSaveYoutubeContent,
93103
} = useDetailSaveYoutubeContent();
94104

95-
const saveDisabledd =
105+
const isSaveDisabled =
96106
isFindingExistPath ||
97107
isPendingQuickSaveContent ||
98108
isPendingDetailSaveContent ||
@@ -108,12 +118,10 @@ export default function SearchContent() {
108118
url: currentTab.url,
109119
});
110120
} else {
111-
const { htmlContent, thumbnail } = await getHtmlContent();
112-
if (!htmlContent || !thumbnail) return;
113-
114-
const htmlBlob = new Blob([htmlContent], { type: "text/html" });
115-
const htmlFile = new File([htmlBlob], "content.html", { type: "text/html" });
121+
const payload = await getHtmlPayload();
122+
if (!payload) return;
116123

124+
const { htmlFile, thumbnail } = payload;
117125
const formData = new FormData();
118126

119127
formData.append("htmlFile", htmlFile);
@@ -139,12 +147,10 @@ export default function SearchContent() {
139147
},
140148
});
141149
} else {
142-
const { htmlContent, thumbnail } = await getHtmlContent();
143-
if (!htmlContent || !thumbnail) return;
144-
145-
const htmlBlob = new Blob([htmlContent], { type: "text/html" });
146-
const htmlFile = new File([htmlBlob], "content.html", { type: "text/html" });
150+
const payload = await getHtmlPayload();
151+
if (!payload) return;
147152

153+
const { htmlFile, thumbnail } = payload;
148154
const formData = new FormData();
149155
formData.append("htmlFile", htmlFile);
150156

@@ -212,7 +218,7 @@ export default function SearchContent() {
212218
{/* 저장만 하기 */}
213219
<Button
214220
onClick={onSaveOnly}
215-
disabled={saveDisabledd}
221+
disabled={isSaveDisabled}
216222
variant="outline"
217223
size="lg"
218224
className="w-full justify-start"
@@ -232,7 +238,7 @@ export default function SearchContent() {
232238
{/* 요약과 함께 저장하기 */}
233239
<Button
234240
onClick={onSaveWithSummary}
235-
disabled={saveDisabledd}
241+
disabled={isSaveDisabled}
236242
size="lg"
237243
className="w-full justify-start"
238244
aria-label="요약과 함께 저장하기"

apps/web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
},
1111
"dependencies": {
1212
"@hookform/resolvers": "^5.2.1",
13+
"@next/third-parties": "^15.5.3",
1314
"@radix-ui/react-accordion": "^1.2.11",
1415
"@radix-ui/react-label": "^2.1.7",
1516
"@radix-ui/react-separator": "^1.1.7",

apps/web/src/app/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export const metadata: Metadata = {
4747
description: "브라우저 통합 지식 관리 서비스로 흩어진 정보를 연결된 인사이트로 변환하세요.",
4848
type: "website",
4949
images: "/og-image.png",
50-
url: "https://nebula-ai.kr",
50+
url: "https://linkyboard.com",
5151
siteName: "LinkyBoard",
5252
locale: "ko_KR",
5353
},
@@ -58,7 +58,7 @@ export const metadata: Metadata = {
5858
images: "/og-image.png",
5959
},
6060
alternates: {
61-
canonical: "https://nebula-ai.kr",
61+
canonical: "https://linkyboard.com",
6262
},
6363
robots: {
6464
index: true,

apps/web/src/components/(with-side-bar)/library/content-list.tsx

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useState } from "react";
3+
import { useMemo, useState } from "react";
44

55
import { Button } from "@/components/ui/button";
66
import { CONTENT_TYPE_OPTIONS, ContentTypeOptions } from "@/constants/content";
@@ -31,6 +31,7 @@ const TYPE_OPTIONS = [
3131
},
3232
];
3333

34+
// 카테고리 내 콘텐츠를 태그/유형 필터와 함께 보여주는 리스트 컴포넌트다.
3435
export default function ContentList({ category }: ContentListProps) {
3536
const [selectedTags, setSelectedTags] = useState<string[]>([]);
3637
const [selectedType, setSelectedType] = useState<ContentTypeOptions>(CONTENT_TYPE_OPTIONS.ALL);
@@ -43,28 +44,23 @@ export default function ContentList({ category }: ContentListProps) {
4344
const { data, isLoading } = useGetCategoryContentById(categoryId);
4445
const contents: CategoryContentDTO[] = data || [];
4546

46-
// 모든 지식에서 태그 추출
47-
const allTags = [...new Set(contents.flatMap((content) => content.tags))];
48-
49-
// 태그 필터링된 지식들
50-
const filteredContents = (() => {
51-
if (selectedTags.length === 0 && selectedType === CONTENT_TYPE_OPTIONS.ALL) return contents;
52-
53-
if (selectedTags.length > 0) {
54-
return contents.filter((content) => selectedTags.every((tag) => content.tags.includes(tag)));
55-
}
56-
57-
if (selectedType !== CONTENT_TYPE_OPTIONS.ALL) {
58-
return contents.filter((content) => content.type !== selectedType);
59-
}
47+
const allTags = useMemo(
48+
() => [...new Set(contents.flatMap((content) => content.tags))],
49+
[contents]
50+
);
6051

61-
return contents.filter(
62-
(content) =>
63-
selectedTags.every((tag) => content.tags.includes(tag)) &&
64-
selectedType !== CONTENT_TYPE_OPTIONS.ALL &&
65-
content.type === selectedType
66-
);
67-
})();
52+
// 복합 필터 조건을 메모이제이션해 카드 목록 계산을 명확하게 유지한다.
53+
const filteredContents = useMemo(() => {
54+
return contents.filter((content) => {
55+
const matchesTags =
56+
selectedTags.length === 0 ||
57+
selectedTags.every((tag) => content.tags.includes(tag));
58+
const matchesType =
59+
selectedType === CONTENT_TYPE_OPTIONS.ALL || content.type === selectedType;
60+
61+
return matchesTags && matchesType;
62+
});
63+
}, [contents, selectedTags, selectedType]);
6864

6965
const isFilterApplied = selectedTags.length > 0 || selectedType !== CONTENT_TYPE_OPTIONS.ALL;
7066

apps/web/src/components/(with-side-bar)/library/content-sidebar.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { ContentDetailDTO } from "@/models/content";
2020
import { contentSchema, type ContentSchemaType } from "@/schemas/content";
2121
import { errorToast, infoToast } from "@/utils/toast";
2222
import { zodResolver } from "@hookform/resolvers/zod";
23+
import { YouTubeEmbed } from "@next/third-parties/google";
2324
import { Dialog, DialogTrigger } from "@repo/ui/components/dialog";
2425
import { useOutsideClick } from "@repo/ui/hooks/use-outside-click";
2526
import { cn } from "@repo/ui/utils/cn";
@@ -388,12 +389,12 @@ export default function ContentSidebar() {
388389
</div>
389390

390391
{isYoutube && (
391-
<iframe
392-
className="aspect-video"
393-
src={`https://www.youtube.com/embed/${youtubeId}`}
394-
title="YouTube video player"
395-
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen"
396-
/>
392+
<div className="aspect-video">
393+
<YouTubeEmbed
394+
videoid={youtubeId || ""}
395+
params="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen"
396+
/>
397+
</div>
397398
)}
398399

399400
<div>

pnpm-lock.yaml

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)